diff --git a/.github/DISCUSSION_TEMPLATE/translations.yml b/.github/DISCUSSION_TEMPLATE/translations.yml new file mode 100644 index 0000000000..16e304d998 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/translations.yml @@ -0,0 +1,45 @@ +labels: [lang-all] +body: + - type: markdown + attributes: + value: | + Thanks for your interest in helping translate the FastAPI docs! 🌍 + + Please follow these instructions carefully to propose a new language translation. 🙏 + + This structured process helps ensure translations can be properly maintained long-term. + - type: checkboxes + id: checks + attributes: + label: Initial Checks + description: Please confirm and check all the following options. + options: + - label: I checked that this language is not already being translated in FastAPI docs. + required: true + - label: I searched existing discussions to ensure no one else proposed this language. + required: true + - label: I am a native speaker of the language I want to help translate. + required: true + - type: input + id: language + attributes: + label: Target Language + description: What language do you want to translate the FastAPI docs into? + placeholder: e.g. Latin + validations: + required: true + - type: textarea + id: additional_info + attributes: + label: Additional Information + description: Any other relevant information about your translation proposal + - type: markdown + attributes: + value: | + Translations are automatized with AI and then reviewed by native speakers. 🤖 🙋 + + This allows us to keep them consistent and up-to-date. + + If there are several native speakers commenting on this discussion and + committing to help review new translations, the FastAPI team will review it + and potentially make it an official translation. 😎 diff --git a/.github/actions/notify-translations/Dockerfile b/.github/actions/notify-translations/Dockerfile deleted file mode 100644 index b68b4bb1a2..0000000000 --- a/.github/actions/notify-translations/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.9 - -RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0" - -COPY ./app /app - -CMD ["python", "/app/main.py"] diff --git a/.github/actions/notify-translations/action.yml b/.github/actions/notify-translations/action.yml deleted file mode 100644 index c3579977c5..0000000000 --- a/.github/actions/notify-translations/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Notify Translations" -description: "Notify in the issue for a translation when there's a new PR available" -author: "Sebastián Ramírez " -inputs: - token: - description: 'Token, to read the GitHub API. Can be passed in using {{ secrets.GITHUB_TOKEN }}' - required: true -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/people/Dockerfile b/.github/actions/people/Dockerfile deleted file mode 100644 index 1455106bde..0000000000 --- a/.github/actions/people/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.9 - -RUN pip install httpx PyGithub "pydantic==2.0.2" pydantic-settings "pyyaml>=5.3.1,<6.0.0" - -COPY ./app /app - -CMD ["python", "/app/main.py"] diff --git a/.github/actions/people/action.yml b/.github/actions/people/action.yml deleted file mode 100644 index 71745b8747..0000000000 --- a/.github/actions/people/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Generate FastAPI People" -description: "Generate the data for the FastAPI People page" -author: "Sebastián Ramírez " -inputs: - token: - description: 'User token, to read the GitHub API. Can be passed in using {{ secrets.FASTAPI_PEOPLE }}' - required: true -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/people/app/main.py b/.github/actions/people/app/main.py deleted file mode 100644 index b752d9d2b2..0000000000 --- a/.github/actions/people/app/main.py +++ /dev/null @@ -1,682 +0,0 @@ -import logging -import subprocess -import sys -from collections import Counter, defaultdict -from datetime import datetime, timedelta, timezone -from pathlib import Path -from typing import Any, Container, DefaultDict, Dict, List, Set, Union - -import httpx -import yaml -from github import Github -from pydantic import BaseModel, SecretStr -from pydantic_settings import BaseSettings - -github_graphql_url = "https://api.github.com/graphql" -questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0" - -discussions_query = """ -query Q($after: String, $category_id: ID) { - repository(name: "fastapi", owner: "fastapi") { - discussions(first: 100, after: $after, categoryId: $category_id) { - edges { - cursor - node { - number - author { - login - avatarUrl - url - } - title - createdAt - comments(first: 100) { - nodes { - createdAt - author { - login - avatarUrl - url - } - isAnswer - replies(first: 10) { - nodes { - createdAt - author { - login - avatarUrl - url - } - } - } - } - } - } - } - } - } -} -""" - - -prs_query = """ -query Q($after: String) { - repository(name: "fastapi", owner: "fastapi") { - pullRequests(first: 100, after: $after) { - edges { - cursor - node { - number - labels(first: 100) { - nodes { - name - } - } - author { - login - avatarUrl - url - } - title - createdAt - state - comments(first: 100) { - nodes { - createdAt - author { - login - avatarUrl - url - } - } - } - reviews(first:100) { - nodes { - author { - login - avatarUrl - url - } - state - } - } - } - } - } - } -} -""" - -sponsors_query = """ -query Q($after: String) { - user(login: "fastapi") { - sponsorshipsAsMaintainer(first: 100, after: $after) { - edges { - cursor - node { - sponsorEntity { - ... on Organization { - login - avatarUrl - url - } - ... on User { - login - avatarUrl - url - } - } - tier { - name - monthlyPriceInDollars - } - } - } - } - } -} -""" - - -class Author(BaseModel): - login: str - avatarUrl: str - url: str - - -# Discussions - - -class CommentsNode(BaseModel): - createdAt: datetime - author: Union[Author, None] = None - - -class Replies(BaseModel): - nodes: List[CommentsNode] - - -class DiscussionsCommentsNode(CommentsNode): - replies: Replies - - -class Comments(BaseModel): - nodes: List[CommentsNode] - - -class DiscussionsComments(BaseModel): - nodes: List[DiscussionsCommentsNode] - - -class DiscussionsNode(BaseModel): - number: int - author: Union[Author, None] = None - title: str - createdAt: datetime - comments: DiscussionsComments - - -class DiscussionsEdge(BaseModel): - cursor: str - node: DiscussionsNode - - -class Discussions(BaseModel): - edges: List[DiscussionsEdge] - - -class DiscussionsRepository(BaseModel): - discussions: Discussions - - -class DiscussionsResponseData(BaseModel): - repository: DiscussionsRepository - - -class DiscussionsResponse(BaseModel): - data: DiscussionsResponseData - - -# PRs - - -class LabelNode(BaseModel): - name: str - - -class Labels(BaseModel): - nodes: List[LabelNode] - - -class ReviewNode(BaseModel): - author: Union[Author, None] = None - state: str - - -class Reviews(BaseModel): - nodes: List[ReviewNode] - - -class PullRequestNode(BaseModel): - number: int - labels: Labels - author: Union[Author, None] = None - title: str - createdAt: datetime - state: str - comments: Comments - reviews: Reviews - - -class PullRequestEdge(BaseModel): - cursor: str - node: PullRequestNode - - -class PullRequests(BaseModel): - edges: List[PullRequestEdge] - - -class PRsRepository(BaseModel): - pullRequests: PullRequests - - -class PRsResponseData(BaseModel): - repository: PRsRepository - - -class PRsResponse(BaseModel): - data: PRsResponseData - - -# Sponsors - - -class SponsorEntity(BaseModel): - login: str - avatarUrl: str - url: str - - -class Tier(BaseModel): - name: str - monthlyPriceInDollars: float - - -class SponsorshipAsMaintainerNode(BaseModel): - sponsorEntity: SponsorEntity - tier: Tier - - -class SponsorshipAsMaintainerEdge(BaseModel): - cursor: str - node: SponsorshipAsMaintainerNode - - -class SponsorshipAsMaintainer(BaseModel): - edges: List[SponsorshipAsMaintainerEdge] - - -class SponsorsUser(BaseModel): - sponsorshipsAsMaintainer: SponsorshipAsMaintainer - - -class SponsorsResponseData(BaseModel): - user: SponsorsUser - - -class SponsorsResponse(BaseModel): - data: SponsorsResponseData - - -class Settings(BaseSettings): - input_token: SecretStr - github_repository: str - httpx_timeout: int = 30 - - -def get_graphql_response( - *, - settings: Settings, - query: str, - after: Union[str, None] = None, - category_id: Union[str, None] = None, -) -> Dict[str, Any]: - headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} - # category_id is only used by one query, but GraphQL allows unused variables, so - # keep it here for simplicity - variables = {"after": after, "category_id": category_id} - response = httpx.post( - github_graphql_url, - headers=headers, - timeout=settings.httpx_timeout, - json={"query": query, "variables": variables, "operationName": "Q"}, - ) - if response.status_code != 200: - logging.error( - f"Response was not 200, after: {after}, category_id: {category_id}" - ) - logging.error(response.text) - raise RuntimeError(response.text) - data = response.json() - if "errors" in data: - logging.error(f"Errors in response, after: {after}, category_id: {category_id}") - logging.error(data["errors"]) - logging.error(response.text) - raise RuntimeError(response.text) - return data - - -def get_graphql_question_discussion_edges( - *, - settings: Settings, - after: Union[str, None] = None, -): - data = get_graphql_response( - settings=settings, - query=discussions_query, - after=after, - category_id=questions_category_id, - ) - graphql_response = DiscussionsResponse.model_validate(data) - return graphql_response.data.repository.discussions.edges - - -def get_graphql_pr_edges(*, settings: Settings, after: Union[str, None] = None): - data = get_graphql_response(settings=settings, query=prs_query, after=after) - graphql_response = PRsResponse.model_validate(data) - return graphql_response.data.repository.pullRequests.edges - - -def get_graphql_sponsor_edges(*, settings: Settings, after: Union[str, None] = None): - data = get_graphql_response(settings=settings, query=sponsors_query, after=after) - graphql_response = SponsorsResponse.model_validate(data) - return graphql_response.data.user.sponsorshipsAsMaintainer.edges - - -class DiscussionExpertsResults(BaseModel): - commenters: Counter - last_month_commenters: Counter - three_months_commenters: Counter - six_months_commenters: Counter - one_year_commenters: Counter - authors: Dict[str, Author] - - -def get_discussion_nodes(settings: Settings) -> List[DiscussionsNode]: - discussion_nodes: List[DiscussionsNode] = [] - discussion_edges = get_graphql_question_discussion_edges(settings=settings) - - while discussion_edges: - for discussion_edge in discussion_edges: - discussion_nodes.append(discussion_edge.node) - last_edge = discussion_edges[-1] - discussion_edges = get_graphql_question_discussion_edges( - settings=settings, after=last_edge.cursor - ) - return discussion_nodes - - -def get_discussions_experts( - discussion_nodes: List[DiscussionsNode], -) -> DiscussionExpertsResults: - commenters = Counter() - last_month_commenters = Counter() - three_months_commenters = Counter() - six_months_commenters = Counter() - one_year_commenters = Counter() - authors: Dict[str, Author] = {} - - now = datetime.now(tz=timezone.utc) - one_month_ago = now - timedelta(days=30) - three_months_ago = now - timedelta(days=90) - six_months_ago = now - timedelta(days=180) - one_year_ago = now - timedelta(days=365) - - for discussion in discussion_nodes: - discussion_author_name = None - if discussion.author: - authors[discussion.author.login] = discussion.author - discussion_author_name = discussion.author.login - discussion_commentors: dict[str, datetime] = {} - for comment in discussion.comments.nodes: - if comment.author: - authors[comment.author.login] = comment.author - if comment.author.login != discussion_author_name: - author_time = discussion_commentors.get( - comment.author.login, comment.createdAt - ) - discussion_commentors[comment.author.login] = max( - author_time, comment.createdAt - ) - for reply in comment.replies.nodes: - if reply.author: - authors[reply.author.login] = reply.author - if reply.author.login != discussion_author_name: - author_time = discussion_commentors.get( - reply.author.login, reply.createdAt - ) - discussion_commentors[reply.author.login] = max( - author_time, reply.createdAt - ) - for author_name, author_time in discussion_commentors.items(): - commenters[author_name] += 1 - if author_time > one_month_ago: - last_month_commenters[author_name] += 1 - if author_time > three_months_ago: - three_months_commenters[author_name] += 1 - if author_time > six_months_ago: - six_months_commenters[author_name] += 1 - if author_time > one_year_ago: - one_year_commenters[author_name] += 1 - discussion_experts_results = DiscussionExpertsResults( - authors=authors, - commenters=commenters, - last_month_commenters=last_month_commenters, - three_months_commenters=three_months_commenters, - six_months_commenters=six_months_commenters, - one_year_commenters=one_year_commenters, - ) - return discussion_experts_results - - -def get_pr_nodes(settings: Settings) -> List[PullRequestNode]: - pr_nodes: List[PullRequestNode] = [] - pr_edges = get_graphql_pr_edges(settings=settings) - - while pr_edges: - for edge in pr_edges: - pr_nodes.append(edge.node) - last_edge = pr_edges[-1] - pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor) - return pr_nodes - - -class ContributorsResults(BaseModel): - contributors: Counter - commenters: Counter - reviewers: Counter - translation_reviewers: Counter - authors: Dict[str, Author] - - -def get_contributors(pr_nodes: List[PullRequestNode]) -> ContributorsResults: - contributors = Counter() - commenters = Counter() - reviewers = Counter() - translation_reviewers = Counter() - authors: Dict[str, Author] = {} - - for pr in pr_nodes: - author_name = None - if pr.author: - authors[pr.author.login] = pr.author - author_name = pr.author.login - pr_commentors: Set[str] = set() - pr_reviewers: Set[str] = set() - for comment in pr.comments.nodes: - if comment.author: - authors[comment.author.login] = comment.author - if comment.author.login == author_name: - continue - pr_commentors.add(comment.author.login) - for author_name in pr_commentors: - commenters[author_name] += 1 - for review in pr.reviews.nodes: - if review.author: - authors[review.author.login] = review.author - pr_reviewers.add(review.author.login) - for label in pr.labels.nodes: - if label.name == "lang-all": - translation_reviewers[review.author.login] += 1 - break - for reviewer in pr_reviewers: - reviewers[reviewer] += 1 - if pr.state == "MERGED" and pr.author: - contributors[pr.author.login] += 1 - return ContributorsResults( - contributors=contributors, - commenters=commenters, - reviewers=reviewers, - translation_reviewers=translation_reviewers, - authors=authors, - ) - - -def get_individual_sponsors(settings: Settings): - nodes: List[SponsorshipAsMaintainerNode] = [] - edges = get_graphql_sponsor_edges(settings=settings) - - while edges: - for edge in edges: - nodes.append(edge.node) - last_edge = edges[-1] - edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor) - - tiers: DefaultDict[float, Dict[str, SponsorEntity]] = defaultdict(dict) - for node in nodes: - tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = ( - node.sponsorEntity - ) - return tiers - - -def get_top_users( - *, - counter: Counter, - authors: Dict[str, Author], - skip_users: Container[str], - min_count: int = 2, -): - users = [] - for commenter, count in counter.most_common(50): - if commenter in skip_users: - continue - if count >= min_count: - author = authors[commenter] - users.append( - { - "login": commenter, - "count": count, - "avatarUrl": author.avatarUrl, - "url": author.url, - } - ) - return users - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - settings = Settings() - logging.info(f"Using config: {settings.model_dump_json()}") - g = Github(settings.input_token.get_secret_value()) - repo = g.get_repo(settings.github_repository) - discussion_nodes = get_discussion_nodes(settings=settings) - experts_results = get_discussions_experts(discussion_nodes=discussion_nodes) - pr_nodes = get_pr_nodes(settings=settings) - contributors_results = get_contributors(pr_nodes=pr_nodes) - authors = {**experts_results.authors, **contributors_results.authors} - maintainers_logins = {"tiangolo"} - bot_names = {"codecov", "github-actions", "pre-commit-ci", "dependabot"} - maintainers = [] - for login in maintainers_logins: - user = authors[login] - maintainers.append( - { - "login": login, - "answers": experts_results.commenters[login], - "prs": contributors_results.contributors[login], - "avatarUrl": user.avatarUrl, - "url": user.url, - } - ) - - skip_users = maintainers_logins | bot_names - experts = get_top_users( - counter=experts_results.commenters, - authors=authors, - skip_users=skip_users, - ) - last_month_experts = get_top_users( - counter=experts_results.last_month_commenters, - authors=authors, - skip_users=skip_users, - ) - three_months_experts = get_top_users( - counter=experts_results.three_months_commenters, - authors=authors, - skip_users=skip_users, - ) - six_months_experts = get_top_users( - counter=experts_results.six_months_commenters, - authors=authors, - skip_users=skip_users, - ) - one_year_experts = get_top_users( - counter=experts_results.one_year_commenters, - authors=authors, - skip_users=skip_users, - ) - top_contributors = get_top_users( - counter=contributors_results.contributors, - authors=authors, - skip_users=skip_users, - ) - top_reviewers = get_top_users( - counter=contributors_results.reviewers, - authors=authors, - skip_users=skip_users, - ) - top_translations_reviewers = get_top_users( - counter=contributors_results.translation_reviewers, - authors=authors, - skip_users=skip_users, - ) - - tiers = get_individual_sponsors(settings=settings) - keys = list(tiers.keys()) - keys.sort(reverse=True) - sponsors = [] - for key in keys: - sponsor_group = [] - for login, sponsor in tiers[key].items(): - sponsor_group.append( - {"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url} - ) - sponsors.append(sponsor_group) - - people = { - "maintainers": maintainers, - "experts": experts, - "last_month_experts": last_month_experts, - "three_months_experts": three_months_experts, - "six_months_experts": six_months_experts, - "one_year_experts": one_year_experts, - "top_contributors": top_contributors, - "top_reviewers": top_reviewers, - "top_translations_reviewers": top_translations_reviewers, - } - github_sponsors = { - "sponsors": sponsors, - } - # For local development - # people_path = Path("../../../../docs/en/data/people.yml") - people_path = Path("./docs/en/data/people.yml") - github_sponsors_path = Path("./docs/en/data/github_sponsors.yml") - people_old_content = people_path.read_text(encoding="utf-8") - github_sponsors_old_content = github_sponsors_path.read_text(encoding="utf-8") - new_people_content = yaml.dump( - people, sort_keys=False, width=200, allow_unicode=True - ) - new_github_sponsors_content = yaml.dump( - github_sponsors, sort_keys=False, width=200, allow_unicode=True - ) - if ( - people_old_content == new_people_content - and github_sponsors_old_content == new_github_sponsors_content - ): - logging.info("The FastAPI People data hasn't changed, finishing.") - sys.exit(0) - people_path.write_text(new_people_content, encoding="utf-8") - github_sponsors_path.write_text(new_github_sponsors_content, encoding="utf-8") - logging.info("Setting up GitHub Actions git user") - subprocess.run(["git", "config", "user.name", "github-actions"], check=True) - subprocess.run( - ["git", "config", "user.email", "github-actions@github.com"], check=True - ) - branch_name = "fastapi-people" - logging.info(f"Creating a new branch {branch_name}") - subprocess.run(["git", "checkout", "-b", branch_name], check=True) - logging.info("Adding updated file") - subprocess.run( - ["git", "add", str(people_path), str(github_sponsors_path)], check=True - ) - logging.info("Committing updated file") - message = "👥 Update FastAPI People" - result = subprocess.run(["git", "commit", "-m", message], check=True) - logging.info("Pushing branch") - subprocess.run(["git", "push", "origin", branch_name], check=True) - logging.info("Creating PR") - pr = repo.create_pull(title=message, body=message, base="master", head=branch_name) - logging.info(f"Created PR: {pr.number}") - logging.info("Finished") diff --git a/.github/labeler.yml b/.github/labeler.yml index 1d49a24116..c5b1f84f3d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -7,6 +7,8 @@ docs: - all-globs-to-all-files: - '!fastapi/**' - '!pyproject.toml' + - '!docs/en/data/sponsors.yml' + - '!docs/en/overrides/main.html' lang-all: - all: @@ -28,6 +30,8 @@ internal: - .pre-commit-config.yaml - pdm_build.py - requirements*.txt + - docs/en/data/sponsors.yml + - docs/en/overrides/main.html - all-globs-to-all-files: - '!docs/*/docs/**' - '!fastapi/**' diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index e46629e9b4..b3e53b91cd 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -7,6 +7,10 @@ on: types: - opened - synchronize + +env: + UV_SYSTEM_PYTHON: 1 + jobs: changes: runs-on: ubuntu-latest @@ -17,7 +21,7 @@ jobs: outputs: docs: ${{ steps.filter.outputs.docs }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # For pull requests it's not necessary to checkout the code but for the main branch it is - uses: dorny/paths-filter@v3 id: filter @@ -36,6 +40,7 @@ jobs: - mkdocs.no-insiders.yml - .github/workflows/build-docs.yml - .github/workflows/deploy-docs.yml + - scripts/mkdocs_hooks.py langs: needs: - changes @@ -43,23 +48,25 @@ jobs: outputs: langs: ${{ steps.show-langs.outputs.langs }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" - - uses: actions/cache@v4 - id: cache + - name: Setup uv + uses: astral-sh/setup-uv@v7 with: - path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt', 'requirements-docs-insiders.txt', 'requirements-docs-tests.txt') }}-v08 + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml - name: Install docs extras - if: steps.cache.outputs.cache-hit != 'true' - run: pip install -r requirements-docs.txt + run: uv 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.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true' - run: pip install -r requirements-docs-insiders.txt + if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) + run: uv pip install -r requirements-docs-insiders.txt env: TOKEN: ${{ secrets.FASTAPI_MKDOCS_MATERIAL_INSIDERS }} - name: Verify Docs @@ -83,22 +90,24 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" - - uses: actions/cache@v4 - id: cache + - name: Setup uv + uses: astral-sh/setup-uv@v7 with: - path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt', 'requirements-docs-insiders.txt', 'requirements-docs-tests.txt') }}-v08 + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml - name: Install docs extras - if: steps.cache.outputs.cache-hit != 'true' - run: pip install -r requirements-docs.txt + run: uv pip install -r requirements-docs.txt - name: Install Material for MkDocs Insiders - if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true' - run: pip install -r requirements-docs-insiders.txt + if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) + run: uv pip install -r requirements-docs-insiders.txt env: TOKEN: ${{ secrets.FASTAPI_MKDOCS_MATERIAL_INSIDERS }} - name: Update Languages @@ -113,6 +122,7 @@ jobs: with: name: docs-site-${{ matrix.lang }} path: ./site/** + include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why docs-all-green: # This job does nothing and is only used for the branch protection diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 0000000000..7d5449c6a8 --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,53 @@ +name: FastAPI People Contributors + +on: + schedule: + - cron: "0 3 1 * *" + workflow_dispatch: + inputs: + debug_enabled: + description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)" + required: false + default: "false" + +env: + UV_SYSTEM_PYTHON: 1 + +jobs: + job: + if: github.repository_owner == 'fastapi' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt + # Allow debugging with tmate + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} + with: + limit-access-to-actor: true + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} + - name: FastAPI People Contributors + run: python ./scripts/contributors.py + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index d2953f2841..0eb26cc4d4 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -12,6 +12,9 @@ permissions: pull-requests: write statuses: write +env: + UV_SYSTEM_PYTHON: 1 + jobs: deploy-docs: runs-on: ubuntu-latest @@ -20,31 +23,33 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" - - uses: actions/cache@v4 - id: cache + - name: Setup uv + uses: astral-sh/setup-uv@v7 with: - path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-github-actions-${{ env.pythonLocation }}-${{ hashFiles('requirements-github-actions.txt') }}-v01 + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml - name: Install GitHub Actions dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: pip install -r requirements-github-actions.txt + run: uv pip install -r requirements-github-actions.txt - name: Deploy Docs Status Pending run: python ./scripts/deploy_docs_status.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} RUN_ID: ${{ github.run_id }} - + STATE: "pending" - name: Clean site run: | rm -rf ./site mkdir ./site - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: path: ./site/ pattern: docs-site-* @@ -55,19 +60,27 @@ jobs: # hashFiles returns an empty string if there are no files if: hashFiles('./site/*') id: deploy - uses: cloudflare/pages-action@v1 + env: + PROJECT_NAME: fastapitiangolo + BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' && 'main' ) || ( github.event.workflow_run.head_sha ) }} + uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - projectName: fastapitiangolo - directory: './site' - gitHubToken: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' && 'main' ) || ( github.event.workflow_run.head_sha ) }} + command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }} + - name: Deploy Docs Status Error + if: failure() + run: python ./scripts/deploy_docs_status.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} + RUN_ID: ${{ github.run_id }} + STATE: "error" - name: Comment Deploy run: python ./scripts/deploy_docs_status.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DEPLOY_URL: ${{ steps.deploy.outputs.url }} + DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }} COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} RUN_ID: ${{ github.run_id }} - IS_DONE: "true" + STATE: "success" diff --git a/.github/workflows/detect-conflicts.yml b/.github/workflows/detect-conflicts.yml new file mode 100644 index 0000000000..aba329db85 --- /dev/null +++ b/.github/workflows/detect-conflicts.yml @@ -0,0 +1,19 @@ +name: "Conflict detector" +on: + push: + pull_request_target: + types: [synchronize] + +jobs: + main: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Check if PRs have merge conflicts + uses: eps1lon/actions-label-merge-conflict@v3 + with: + dirtyLabel: "conflicts" + repoToken: "${{ secrets.GITHUB_TOKEN }}" + commentOnDirty: "This pull request has a merge conflict that needs to be resolved." diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index d5b947a9c5..f40ec4dc47 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -2,7 +2,7 @@ name: Issue Manager on: schedule: - - cron: "10 3 * * *" + - cron: "13 22 * * *" issue_comment: types: - created @@ -16,6 +16,7 @@ on: permissions: issues: write + pull-requests: write jobs: issue-manager: @@ -26,7 +27,7 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: tiangolo/issue-manager@0.5.0 + - uses: tiangolo/issue-manager@0.6.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > @@ -35,8 +36,16 @@ jobs: "delay": 864000, "message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs." }, - "changes-requested": { + "waiting": { "delay": 2628000, - "message": "As this PR had requested changes to be applied but has been inactive for a while, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." + "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.", + "reminder": { + "before": "P3D", + "message": "Heads-up: this will be closed in 3 days unless there’s new activity." + } + }, + "invalid": { + "delay": 0, + "message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details." } } diff --git a/.github/workflows/label-approved.yml b/.github/workflows/label-approved.yml index 0470fb6064..e6ae3d9636 100644 --- a/.github/workflows/label-approved.yml +++ b/.github/workflows/label-approved.yml @@ -8,6 +8,9 @@ on: permissions: pull-requests: write +env: + UV_SYSTEM_PYTHON: 1 + jobs: label-approved: if: github.repository_owner == 'fastapi' @@ -17,10 +20,26 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: docker://tiangolo/label-approved:0.0.4 + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 with: - token: ${{ secrets.GITHUB_TOKEN }} - config: > + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install GitHub Actions dependencies + run: uv pip install -r requirements-github-actions.txt + - name: Label Approved + run: python ./scripts/label_approved.py + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONFIG: > { "approved-1": { diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index c3bb83f9a5..7aeb448e6f 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -16,7 +16,9 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@v6 + if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }} + - run: echo "Done adding labels" # Run this after labeler applied labels check-labels: needs: diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 27e062d090..2fa832fab5 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -24,7 +24,7 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # To allow latest-changes to commit to the main branch token: ${{ secrets.FASTAPI_LATEST_CHANGES }} @@ -34,8 +34,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: docker://tiangolo/latest-changes:0.3.0 - # - uses: tiangolo/latest-changes@main + - uses: tiangolo/latest-changes@0.4.0 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: docs/en/docs/release-notes.md diff --git a/.github/workflows/notify-translations.yml b/.github/workflows/notify-translations.yml index 4787f3ddd6..04beeb64e9 100644 --- a/.github/workflows/notify-translations.yml +++ b/.github/workflows/notify-translations.yml @@ -15,24 +15,45 @@ on: required: false default: 'false' -permissions: - discussions: write +env: + UV_SYSTEM_PYTHON: 1 jobs: - notify-translations: + job: runs-on: ubuntu-latest + permissions: + discussions: write steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: ./.github/actions/notify-translations - with: - token: ${{ secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Notify Translations + run: python ./scripts/notify_translations.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUMBER: ${{ github.event.inputs.number || null }} + DEBUG: ${{ github.event.inputs.debug_enabled || 'false' }} diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml index c60c63d1b8..f15b921371 100644 --- a/.github/workflows/people.yml +++ b/.github/workflows/people.yml @@ -6,29 +6,49 @@ on: workflow_dispatch: inputs: debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + description: Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate) required: false - default: 'false' + default: "false" + +env: + UV_SYSTEM_PYTHON: 1 jobs: - fastapi-people: + job: if: github.repository_owner == 'fastapi' runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 - # Ref: https://github.com/actions/runner/issues/2033 - - name: Fix git safe.directory in container - run: mkdir -p /home/runner/work/_temp/_github_home && printf "[safe]\n\tdirectory = /github/workspace" > /home/runner/work/_temp/_github_home/.gitconfig + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: ./.github/actions/people - with: - token: ${{ secrets.FASTAPI_PEOPLE }} + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} + - name: FastAPI People Experts + run: python ./scripts/people.py + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} + SLEEP_INTERVAL: ${{ vars.PEOPLE_SLEEP_INTERVAL }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 591df634b2..441eb45608 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,9 +20,9 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" # Issue ref: https://github.com/actions/setup-python/issues/436 @@ -35,7 +35,7 @@ jobs: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} run: python -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.9.0 + uses: pypa/gh-action-pypi-publish@v1.13.0 - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index d3a975df58..e42d797236 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -8,6 +8,9 @@ on: permissions: statuses: write +env: + UV_SYSTEM_PYTHON: 1 + jobs: smokeshow: if: ${{ github.event.workflow_run.conclusion == 'success' }} @@ -18,20 +21,36 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: '3.9' - - - run: pip install smokeshow - - - uses: actions/download-artifact@v4 + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - run: uv pip install -r requirements-github-actions.txt + - uses: actions/download-artifact@v5 with: name: coverage-html path: htmlcov github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - - - run: smokeshow upload htmlcov + # Try 5 times to upload coverage to smokeshow + - name: Upload coverage to Smokeshow + run: | + for i in 1 2 3 4 5; do + if smokeshow upload htmlcov; then + echo "Smokeshow upload success!" + break + fi + echo "Smokeshow upload error, sleep 1 sec and try again." + sleep 1 + done env: SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100 diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml new file mode 100644 index 0000000000..7d29469a52 --- /dev/null +++ b/.github/workflows/sponsors.yml @@ -0,0 +1,52 @@ +name: FastAPI People Sponsors + +on: + schedule: + - cron: "0 6 1 * *" + workflow_dispatch: + inputs: + debug_enabled: + description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)" + required: false + default: "false" + +env: + UV_SYSTEM_PYTHON: 1 + +jobs: + job: + if: github.repository_owner == 'fastapi' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt + # Allow debugging with tmate + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} + with: + limit-access-to-actor: true + - name: FastAPI People Sponsors + run: python ./scripts/sponsors.py + env: + SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }} + PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} diff --git a/.github/workflows/test-redistribute.yml b/.github/workflows/test-redistribute.yml index 693f0c6032..a44f0b6815 100644 --- a/.github/workflows/test-redistribute.yml +++ b/.github/workflows/test-redistribute.yml @@ -22,9 +22,9 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" - name: Install build dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0458f83ffe..cbf1a8567a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,9 @@ on: # cron every week on monday - cron: "0 0 * * 1" +env: + UV_SYSTEM_PYTHON: 1 + jobs: lint: runs-on: ubuntu-latest @@ -20,24 +23,23 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 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@v4 - id: cache + - name: Setup uv + uses: astral-sh/setup-uv@v7 with: - path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-pydantic-v2-${{ hashFiles('pyproject.toml', 'requirements-tests.txt', 'requirements-docs-tests.txt') }}-test-v08 + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: pip install -r requirements-tests.txt + run: uv pip install -r requirements-tests.txt - name: Install Pydantic v2 - run: pip install "pydantic>=2.0.2,<3.0.0" + run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0" - name: Lint run: bash scripts/lint.sh @@ -46,40 +48,48 @@ jobs: strategy: matrix: python-version: + - "3.14" + - "3.13" - "3.12" - "3.11" - "3.10" - "3.9" - "3.8" pydantic-version: ["pydantic-v1", "pydantic-v2"] + exclude: + - python-version: "3.14" + pydantic-version: "pydantic-v1" fail-fast: false steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - # Issue ref: https://github.com/actions/setup-python/issues/436 - # cache: "pip" - # cache-dependency-path: pyproject.toml - - uses: actions/cache@v4 - id: cache + - name: Setup uv + uses: astral-sh/setup-uv@v7 with: - path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ matrix.pydantic-version }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt', 'requirements-docs-tests.txt') }}-test-v08 + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: pip install -r requirements-tests.txt + run: uv pip install -r requirements-tests.txt - name: Install Pydantic v1 if: matrix.pydantic-version == 'pydantic-v1' - run: pip install "pydantic>=1.10.0,<2.0.0" + run: uv pip install "pydantic>=1.10.0,<2.0.0" - name: Install Pydantic v2 if: matrix.pydantic-version == 'pydantic-v2' - run: pip install "pydantic>=2.0.2,<3.0.0" + run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0" + # TODO: Remove this once Python 3.8 is no longer supported + - name: Install older AnyIO in Python 3.8 + if: matrix.python-version == '3.8' + run: uv pip install "anyio[trio]<4.0.0" - run: mkdir coverage - name: Test run: bash scripts/test.sh @@ -91,6 +101,7 @@ jobs: with: name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }} path: coverage + include-hidden-files: true coverage-combine: needs: [test] @@ -100,20 +111,26 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: '3.8' - # Issue ref: https://github.com/actions/setup-python/issues/436 - # cache: "pip" - # cache-dependency-path: pyproject.toml + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-tests.txt - name: Get coverage files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: coverage-* path: coverage merge-multiple: true - - run: pip install coverage[toml] - run: ls -la coverage - run: coverage combine coverage - run: coverage report @@ -123,6 +140,7 @@ jobs: with: name: coverage-html path: htmlcov + include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why check: # This job does nothing and is only used for the branch protection diff --git a/.github/workflows/topic-repos.yml b/.github/workflows/topic-repos.yml new file mode 100644 index 0000000000..22b37d59d7 --- /dev/null +++ b/.github/workflows/topic-repos.yml @@ -0,0 +1,40 @@ +name: Update Topic Repos + +on: + schedule: + - cron: "0 12 1 * *" + workflow_dispatch: + +env: + UV_SYSTEM_PYTHON: 1 + +jobs: + topic-repos: + if: github.repository_owner == 'fastapi' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install GitHub Actions dependencies + run: uv pip install -r requirements-github-actions.txt + - name: Update Topic Repos + run: python ./scripts/topic_repos.py + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml new file mode 100644 index 0000000000..a7fcf84df1 --- /dev/null +++ b/.github/workflows/translate.yml @@ -0,0 +1,77 @@ +name: Translate + +on: + workflow_dispatch: + inputs: + debug_enabled: + description: Run with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate) + required: false + default: "false" + command: + description: Command to run + type: choice + options: + - translate-page + - translate-lang + - update-outdated + - add-missing + - update-and-add + - remove-all-removable + language: + description: Language to translate to as a letter code (e.g. "es" for Spanish) + type: string + required: false + default: "" + en_path: + description: File path in English to translate (e.g. docs/en/docs/index.md) + type: string + required: false + default: "" + +env: + UV_SYSTEM_PYTHON: 1 + +jobs: + job: + if: github.repository_owner == 'fastapi' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt -r requirements-translations.txt + # Allow debugging with tmate + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} + with: + limit-access-to-actor: true + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_TRANSLATIONS }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + - name: FastAPI Translate + run: | + python ./scripts/translate.py ${{ github.event.inputs.command }} + python ./scripts/translate.py make-pr + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_TRANSLATIONS }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + LANGUAGE: ${{ github.event.inputs.language }} + EN_PATH: ${{ github.event.inputs.en_path }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7532f21b54..9c075f68ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ default_language_version: python: python3.10 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-toml @@ -14,7 +14,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.1 + rev: v0.13.3 hooks: - id: ruff args: diff --git a/README.md b/README.md index ec7a954978..4fd87298e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

- Test + Test Coverage @@ -46,24 +46,22 @@ The key features are: - - - - + - - - - - + - - - + + + + + + - + + + @@ -91,13 +89,13 @@ The key features are: "_I’m over the moon excited about **FastAPI**. It’s so fun!_" -

Brian Okken - Python Bytes podcast host (ref)
+
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)
+
Timothy Crosley - Hug creator (ref)
--- @@ -105,7 +103,7 @@ The key features are: "_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)
+
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
--- @@ -127,7 +125,7 @@ If you are building a CLI app to be FastAPI stands on the shoulders of giants: -* Starlette for the web parts. +* Starlette for the web parts. * Pydantic for the data parts. ## Installation @@ -150,7 +148,7 @@ $ pip install "fastapi[standard]" ### Create it -* Create a file `main.py` with: +Create a file `main.py` with: ```Python from typing import Union @@ -233,7 +231,7 @@ INFO: Application startup complete.
About the command fastapi dev main.py... -The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn. +The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn. By default, `fastapi dev` will start with auto-reload enabled for local development. @@ -460,7 +458,7 @@ FastAPI depends on Pydantic and Starlette. ### `standard` Dependencies -When you install FastAPI with `pip install "fastapi[standard]"` it comes the `standard` group of optional dependencies: +When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies: Used by Pydantic: @@ -472,15 +470,20 @@ Used by Starlette: * jinja2 - Required if you want to use the default template configuration. * python-multipart - Required if you want to support form "parsing", with `request.form()`. -Used by FastAPI / Starlette: +Used by FastAPI: -* uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving. -* `fastapi-cli` - to provide the `fastapi` command. +* uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving. +* `fastapi-cli[standard]` - to provide the `fastapi` command. + * This includes `fastapi-cloud-cli`, which allows you to deploy your FastAPI application to FastAPI Cloud. ### Without `standard` Dependencies If you don't want to include the `standard` optional dependencies, you can install with `pip install fastapi` instead of `pip install "fastapi[standard]"`. +### Without `fastapi-cloud-cli` + +If you want to install FastAPI with the standard dependencies but without the `fastapi-cloud-cli`, you can install with `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. + ### Additional Optional Dependencies There are some additional dependencies you might want to install. diff --git a/SECURITY.md b/SECURITY.md index db412cf2c8..87e87e0ca1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,7 +16,7 @@ You can learn more about [FastAPI versions and how to pin and upgrade them](http If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: security@tiangolo.com. Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue. -I (the author, [@tiangolo](https://twitter.com/tiangolo)) will review it thoroughly and get back to you. +I (the author, [@tiangolo](https://x.com/tiangolo)) will review it thoroughly and get back to you. ## Public Discussions diff --git a/docs/az/docs/index.md b/docs/az/docs/index.md deleted file mode 100644 index b5d7f8f92f..0000000000 --- a/docs/az/docs/index.md +++ /dev/null @@ -1,467 +0,0 @@ -

- FastAPI -

-

- FastAPI framework, yüksək məshuldarlı, öyrənməsi asan, çevik kodlama, istifadəyə hazırdır -

-

- - Test - - - Əhatə - - - Paket versiyası - - - Dəstəklənən Python versiyaları - -

- ---- - -**Sənədlər**: https://fastapi.tiangolo.com - -**Qaynaq Kodu**: https://github.com/fastapi/fastapi - ---- - -FastAPI Python ilə API yaratmaq üçün standart Python tip məsləhətlərinə əsaslanan, müasir, sürətli (yüksək performanslı) framework-dür. - -Əsas xüsusiyyətləri bunlardır: - -* **Sürətli**: Çox yüksək performans, **NodeJS** və **Go** səviyyəsində (Starlette və Pydantic-ə təşəkkürlər). [Ən sürətli Python frameworklərindən biridir](#performans). -* **Çevik kodlama**: Funksiyanallıqları inkişaf etdirmək sürətini təxminən 200%-dən 300%-ə qədər artırın. * -* **Daha az xəta**: İnsan (developer) tərəfindən törədilən səhvlərin təxminən 40% -ni azaldın. * -* **İntuitiv**: Əla redaktor dəstəyi. Hər yerdə otomatik tamamlama. Xətaları müəyyənləşdirməyə daha az vaxt sərf edəcəksiniz. -* **Asan**: İstifadəsi və öyrənilməsi asan olması üçün nəzərdə tutulmuşdur. Sənədləri oxumaq üçün daha az vaxt ayıracaqsınız. -* **Qısa**: Kod təkrarlanmasını minimuma endirin. Hər bir parametr tərifində birdən çox xüsusiyyət ilə və daha az səhvlə qarşılaşacaqsınız. -* **Güclü**: Avtomatik və interaktiv sənədlərlə birlikdə istifadəyə hazır kod əldə edə bilərsiniz. -* **Standartlara əsaslanan**: API-lar üçün açıq standartlara əsaslanır (və tam uyğun gəlir): OpenAPI (əvvəlki adı ilə Swagger) və JSON Schema. - -* Bu fikirlər daxili development komandasının hazırladıqları məhsulların sınaqlarına əsaslanır. - -## Sponsorlar - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%}` -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Digər sponsorlar - -## Rəylər - -"_[...] Son günlərdə **FastAPI**-ı çox istifadə edirəm. [...] Əslində onu komandamın bütün **Microsoftda ML sevislərində** istifadə etməyi planlayıram. Onların bəziləri **windows**-un əsas məhsuluna və bəzi **Office** məhsullarına inteqrasiya olunurlar._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_**FastAPI** kitabxanasını **Proqnozlar** əldə etmək üçün sorğulana bilən **REST** serverini yaratmaqda istifadə etdik._" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** **böhran idarəçiliyi** orkestrləşmə framework-nün açıq qaynaqlı buraxılışını elan etməkdən məmnundur: **Dispatch**! [**FastAPI** ilə quruldu]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_**FastAPI** üçün həyəcanlıyam. Çox əyləncəlidir!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Düzünü desəm, sizin qurduğunuz şey həqiqətən möhkəm və peşəkar görünür. Bir çox cəhətdən **Hug**-un olmasını istədiyim kimdir - kiminsə belə bir şey qurduğunu görmək həqiqətən ruhlandırıcıdır._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_Əgər REST API-lər yaratmaq üçün **müasir framework** öyrənmək istəyirsinizsə, **FastAPI**-a baxın [...] Sürətli, istifadəsi və öyrənməsi asandır. [...]_" - -"_**API** xidmətlərimizi **FastAPI**-a köçürdük [...] Sizin də bəyənəcəyinizi düşünürük._" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -"_Python ilə istifadəyə hazır API qurmaq istəyən hər kəsə **FastAPI**-ı tövsiyə edirəm. **Möhtəşəm şəkildə dizayn edilmiş**, **istifadəsi asan** və **yüksək dərəcədə genişlənə bilən**-dir, API əsaslı inkişaf strategiyamızın **əsas komponentinə** çevrilib və Virtual TAC Engineer kimi bir çox avtomatlaşdırma və servisləri idarə edir._" - -
Deon Pillsbury - Cisco (ref)
- ---- - -## **Typer**, CLI-ların FastAPI-ı - - - -Əgər siz veb API əvəzinə terminalda istifadə ediləcək CLI proqramı qurursunuzsa, **Typer**-a baxa bilərsiniz. - -**Typer** FastAPI-ın kiçik qardaşıdır. Və o, CLI-lərin **FastAPI**-ı olmaq üçün nəzərdə tutulub. ⌨️ 🚀 - -## Tələblər - -FastAPI nəhənglərin çiyinlərində dayanır: - -* Web tərəfi üçün Starlette. -* Data tərəfi üçün Pydantic. - -## Quraşdırma - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -Tətbiqimizi əlçatan etmək üçün bizə Uvicorn və ya Hypercorn kimi ASGI server lazımdır. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Nümunə - -### Kodu yaradaq - -* `main.py` adlı fayl yaradaq və ona aşağıdakı kodu yerləşdirək: - -```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} -``` - -
-Və ya async def... - -Əgər kodunuzda `async` və ya `await` vardırsa `async def` istifadə edə bilərik: - -```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} -``` - -**Qeyd**: - -Əgər bu mövzu haqqında məlumatınız yoxdursa `async` və `await` sənədindəki _"Tələsirsən?"_ bölməsinə baxa bilərsiniz. - -
- -### Kodu işə salaq - -Serveri aşağıdakı əmr ilə işə salaq: - -
- -```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 əmri haqqında... - -`uvicorn main:app` əmri aşağıdakılara instinad edir: - -* `main`: `main.py` faylı (yəni Python "modulu"). -* `app`: `main.py` faylında `app = FastAPI()` sətrində yaratdığımız `FastAPI` obyektidir. -* `--reload`: kod dəyişikliyindən sonra avtomatik olaraq serveri yenidən işə salır. Bu parametrdən yalnız development mərhələsində istifadə etməliyik. - -
- -### İndi yoxlayaq - -Bu linki brauzerimizdə açaq http://127.0.0.1:8000/items/5?q=somequery. - -Aşağıdakı kimi bir JSON cavabı görəcəksiniz: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -Siz artıq bir API yaratmısınız, hansı ki: - -* `/` və `/items/{item_id}` _yollarında_ HTTP sorğularını qəbul edir. -* Hər iki _yolda_ `GET` əməliyyatlarını (həmçinin HTTP _metodları_ kimi bilinir) aparır. -* `/items/{item_id}` _yolu_ `item_id` adlı `int` qiyməti almalı olan _yol parametrinə_ sahibdir. -* `/items/{item_id}` _yolunun_ `q` adlı yol parametri var və bu parametr istəyə bağlı olsa da, `str` qiymətini almalıdır. - -### İnteraktiv API Sənədləri - -İndi http://127.0.0.1:8000/docs ünvanına daxil olun. - -Avtomatik interaktiv API sənədlərini görəcəksiniz (Swagger UI tərəfindən təmin edilir): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternativ API sənədləri - -İndi isə http://127.0.0.1:8000/redoc ünvanına daxil olun. - -ReDoc tərəfindən təqdim edilən avtomatik sənədləri görəcəksiniz: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Nümunəni Yeniləyək - -İndi gəlin `main.py` faylını `PUT` sorğusu ilə birlikdə gövdə qəbul edəcək şəkildə dəyişdirək. - -Pydantic sayəsində standart Python tiplərindən istifadə edərək gövdəni müəyyən edək. - -```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} -``` -Server avtomatik olaraq yenidən işə salınmalı idi (çünki biz yuxarıda `uvicorn` əmri ilə `--reload` parametrindən istifadə etmişik). - -### İnteraktiv API sənədlərindəki dəyişikliyə baxaq - -Yenidən http://127.0.0.1:8000/docs ünvanına daxil olun. - -* İnteraktiv API sənədləri yeni gövdə də daxil olmaq ilə avtomatik olaraq yenilənəcək: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* "Try it out" düyməsini klikləyin, bu, parametrləri doldurmağa və API ilə birbaşa əlaqə saxlamağa imkan verir: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Sonra "Execute" düyməsini klikləyin, istifadəçi interfeysi API ilə əlaqə quracaq, parametrləri göndərəcək, nəticələri əldə edəcək və onları ekranda göstərəcək: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternativ API Sənədlərindəki Dəyişikliyə Baxaq - -İndi isə yenidən http://127.0.0.1:8000/redoc ünvanına daxil olun. - -* Alternativ sənədlər həm də yeni sorğu parametri və gövdəsini əks etdirəcək: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Xülasə - -Ümumiləşdirsək, parametrlər, gövdə və s. Biz məlumat növlərini **bir dəfə** funksiya parametrləri kimi təyin edirik. - -Bunu standart müasir Python tipləri ilə edirsiniz. - -Yeni sintaksis, müəyyən bir kitabxananın metodlarını və ya siniflərini və s. öyrənmək məcburiyyətində deyilsiniz. - -Sadəcə standart **Python**. - -Məsələn, `int` üçün: - -```Python -item_id: int -``` - -və ya daha mürəkkəb `Item` modeli üçün: - -```Python -item: Item -``` - -...və yalnız parametr tipini təyin etməklə bunları əldə edirsiniz: - -* Redaktor dəstəyi ilə: - * Avtomatik tamamlama. - * Tip yoxlanması. -* Məlumatların Təsdiqlənməsi: - * Məlumat etibarsız olduqda avtomatik olaraq aydın xətalar göstərir. - * Hətta çox dərin JSON obyektlərində belə doğrulama aparır. -* Daxil olan məlumatları çevirmək üçün aşağıdakı məlumat növlərindən istifadə edilir: - * JSON. - * Yol parametrləri. - * Sorğu parametrləri. - * Çərəzlər. - * Başlıqlaq. - * Formalar. - * Fayllar. -* Daxil olan məlumatları çevirmək üçün aşağıdakı məlumat növlərindən istifadə edilir (JSON olaraq): - * Python tiplərinin (`str`, `int`, `float`, `bool`, `list`, və s) çevrilməsi. - * `datetime` obyektləri. - * `UUID` obyektləri. - * Verilənlər bazası modelləri. - * və daha çoxu... -* 2 alternativ istifadəçi interfeysi daxil olmaqla avtomatik interaktiv API sənədlərini təmin edir: - * Swagger UI. - * ReDoc. - ---- - -Gəlin əvvəlki nümunəyə qayıdaq və **FastAPI**-nin nələr edəcəyinə nəzər salaq: - -* `GET` və `PUT` sorğuları üçün `item_id`-nin yolda olub-olmadığını yoxlayacaq. -* `item_id`-nin `GET` və `PUT` sorğuları üçün növünün `int` olduğunu yoxlayacaq. - * Əgər `int` deyilsə, səbəbini göstərən bir xəta mesajı göstərəcəkdir. -* məcburi olmayan `q` parametrinin `GET` (`http://127.0.0.1:8000/items/foo?q=somequery` burdakı kimi) sorğusu içərisində olub olmadığını yoxlayacaq. - * `q` parametrini `= None` ilə yaratdığımız üçün, məcburi olmayan parametr olacaq. - * Əgər `None` olmasaydı, bu məcburi parametr olardı (`PUT` metodunun gövdəsində olduğu kimi). -* `PUT` sorğusu üçün, `/items/{item_id}` gövdəsini JSON olaraq oxuyacaq: - * `name` adında məcburi bir parametr olub olmadığını və əgər varsa, tipinin `str` olub olmadığını yoxlayacaq. - * `price` adında məcburi bir parametr olub olmadığını və əgər varsa, tipinin `float` olub olmadığını yoxlayacaq. - * `is_offer` adında məcburi olmayan bir parametr olub olmadığını və əgər varsa, tipinin `float` olub olmadığını yoxlayacaq. - * Bütün bunlar ən dərin JSON obyektlərində belə işləyəcək. -* Məlumatların JSON-a və JSON-un Python obyektinə çevrilməsi avtomatik həyata keçiriləcək. -* Hər şeyi OpenAPI ilə uyğun olacaq şəkildə avtomatik olaraq sənədləşdirəcək və onları aşağıdakı kimi istifadə edə biləcək: - * İnteraktiv sənədləşmə sistemləri. - * Bir çox proqramlaşdırma dilləri üçün avtomatlaşdırılmış müştəri kodu yaratma sistemləri. -* 2 interaktiv sənədləşmə veb interfeysini birbaşa təmin edəcək. - ---- - -Yeni başlamışıq, amma siz artıq işin məntiqini başa düşmüsünüz. - -İndi aşağıdakı sətri dəyişdirməyə çalışın: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...bundan: - -```Python - ... "item_name": item.name ... -``` - -...buna: - -```Python - ... "item_price": item.price ... -``` - -...və redaktorun məlumat tiplərini bildiyini və avtomatik tamaladığını görəcəksiniz: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -Daha çox funksiyaya malik daha dolğun nümunə üçün Öyrədici - İstifadəçi Təlimatı səhifəsinə baxa bilərsiniz. - -**Spoiler xəbərdarlığı**: Öyrədici - istifadəçi təlimatına bunlar daxildir: - -* **Parametrlərin**, **başlıqlar**, çərəzlər, **forma sahələri** və **fayllar** olaraq müəyyən edilməsi. -* `maximum_length` və ya `regex` kimi **doğrulama məhdudiyyətlərinin** necə təyin ediləcəyi. -* Çox güclü və istifadəsi asan **Dependency Injection** sistemi. -* Təhlükəsizlik və autentifikasiya, **JWT tokenləri** ilə **OAuth2** dəstəyi və **HTTP Basic** autentifikasiyası. -* **çox dərin JSON modellərini** müəyyən etmək üçün daha irəli səviyyə (lakin eyni dərəcədə asan) üsullar (Pydantic sayəsində). -* Strawberry və digər kitabxanalar ilə **GraphQL** inteqrasiyası. -* Digər əlavə xüsusiyyətlər (Starlette sayəsində): - * **WebSockets** - * HTTPX və `pytest` sayəsində çox asan testlər - * **CORS** - * **Cookie Sessions** - * ...və daha çoxu. - -## Performans - -Müstəqil TechEmpower meyarları göstərir ki, Uvicorn üzərində işləyən **FastAPI** proqramları ən sürətli Python kitabxanalarından biridir, yalnız Starlette və Uvicorn-un özündən yavaşdır, ki FastAPI bunların üzərinə qurulmuş bir framework-dür. (*) - -Ətraflı məlumat üçün bu bölməyə nəzər salın Müqayisələr. - -## Məcburi Olmayan Tələblər - -Pydantic tərəfindən istifadə olunanlar: - -* email-validator - e-poçtun yoxlanılması üçün. -* pydantic-settings - parametrlərin idarə edilməsi üçün. -* pydantic-extra-types - Pydantic ilə istifadə edilə bilən əlavə tiplər üçün. - -Starlette tərəfindən istifadə olunanlar: - -* httpx - Əgər `TestClient` strukturundan istifadə edəcəksinizsə, tələb olunur. -* jinja2 - Standart şablon konfiqurasiyasından istifadə etmək istəyirsinizsə, tələb olunur. -* python-multipart - `request.form()` ilə forma "çevirmə" dəstəyindən istifadə etmək istəyirsinizsə, tələb olunur. -* itsdangerous - `SessionMiddleware` dəstəyi üçün tələb olunur. -* pyyaml - `SchemaGenerator` dəstəyi üçün tələb olunur (Çox güman ki, FastAPI istifadə edərkən buna ehtiyacınız olmayacaq). -* ujson - `UJSONResponse` istifadə etmək istəyirsinizsə, tələb olunur. - -Həm FastAPI, həm də Starlette tərəfindən istifadə olunur: - -* uvicorn - Yaratdığımız proqramı servis edəcək veb server kimi fəaliyyət göstərir. -* orjson - `ORJSONResponse` istifadə edəcəksinizsə tələb olunur. - -Bütün bunları `pip install fastapi[all]` ilə quraşdıra bilərsiniz. - -## Lisenziya - -Bu layihə MIT lisenziyasının şərtlərinə əsasən lisenziyalaşdırılıb. diff --git a/docs/az/docs/learn/index.md b/docs/az/docs/learn/index.md deleted file mode 100644 index cc32108bf1..0000000000 --- a/docs/az/docs/learn/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Öyrən - -Burada **FastAPI** öyrənmək üçün giriş bölmələri və dərsliklər yer alır. - -Siz bunu kitab, kurs, FastAPI öyrənmək üçün rəsmi və tövsiyə olunan üsul hesab edə bilərsiniz. 😎 diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/az/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/bn/docs/index.md b/docs/bn/docs/index.md deleted file mode 100644 index c882506ff8..0000000000 --- a/docs/bn/docs/index.md +++ /dev/null @@ -1,463 +0,0 @@ -

- FastAPI -

-

- FastAPI উচ্চক্ষমতা সম্পন্ন, সহজে শেখার এবং দ্রুত কোড করে প্রোডাকশনের জন্য ফ্রামওয়ার্ক। -

-

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

- ---- - -**নির্দেশিকা নথি**: https://fastapi.tiangolo.com - -**সোর্স কোড**: https://github.com/fastapi/fastapi - ---- - -FastAPI একটি আধুনিক, দ্রুত ( বেশি ক্ষমতা ) সম্পন্ন, Python 3.6+ দিয়ে API তৈরির জন্য স্ট্যান্ডার্ড পাইথন টাইপ ইঙ্গিত ভিত্তিক ওয়েব ফ্রেমওয়ার্ক। - -এর মূল বৈশিষ্ট্য গুলো হলঃ - -- **গতি**: এটি **NodeJS** এবং **Go** এর মত কার্যক্ষমতা সম্পন্ন (Starlette এবং Pydantic এর সাহায্যে)। [পাইথন এর দ্রুততম ফ্রেমওয়ার্ক গুলোর মধ্যে এটি একটি](#_11)। -- **দ্রুত কোড করা**:বৈশিষ্ট্য তৈরির গতি ২০০% থেকে ৩০০% বৃদ্ধি করে৷ \* -- **স্বল্প bugs**: মানুব (ডেভেলপার) সৃষ্ট ত্রুটির প্রায় ৪০% হ্রাস করে। \* -- **স্বজ্ঞাত**: দুর্দান্ত এডিটর সাহায্য Completion নামেও পরিচিত। দ্রুত ডিবাগ করা যায়। - -- **সহজ**: এটি এমন ভাবে সজানো হয়েছে যেন নির্দেশিকা নথি পড়ে সহজে শেখা এবং ব্যবহার করা যায়। -- **সংক্ষিপ্ত**: কোড পুনরাবৃত্তি কমানোর পাশাপাশি, bug কমায় এবং প্রতিটি প্যারামিটার ঘোষণা থেকে একাধিক ফিচার পাওয়া যায় । -- **জোরালো**: স্বয়ংক্রিয় ভাবে তৈরি ক্রিয়াশীল নির্দেশনা নথি (documentation) সহ উৎপাদন উপযোগি (Production-ready) কোড পাওয়া যায়। -- **মান-ভিত্তিক**: এর ভিত্তি OpenAPI (যা পুর্বে Swagger নামে পরিচিত ছিল) এবং JSON Schema এর আদর্শের মানের ওপর - -\* উৎপাদনমুখি এপ্লিকেশন বানানোর এক দল ডেভেলপার এর মতামত ভিত্তিক ফলাফল। - -## স্পনসর গণ - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -অন্যান্য স্পনসর গণ - -## মতামত সমূহ - -"_আমি আজকাল **FastAPI** ব্যবহার করছি। [...] আমরা ভাবছি মাইক্রোসফ্টে **ML সার্ভিস** এ সকল দলের জন্য এটি ব্যবহার করব। যার মধ্যে কিছু পণ্য **Windows** এ সংযোযন হয় এবং কিছু **Office** এর সাথে সংযোযন হচ্ছে।_" - -
কবির খান - মাইক্রোসফ্টে (ref)
- ---- - -"_আমরা **FastAPI** লাইব্রেরি গ্রহণ করেছি একটি **REST** সার্ভার তৈরি করতে, যা **ভবিষ্যদ্বাণী** পাওয়ার জন্য কুয়েরি করা যেতে পারে। [লুডউইগের জন্য]_" - -
পিয়েরো মোলিনো, ইয়ারোস্লাভ দুদিন, এবং সাই সুমন্থ মিরিয়ালা - উবার (ref)
- ---- - -"_**Netflix** আমাদের **ক্রাইসিস ম্যানেজমেন্ট** অর্কেস্ট্রেশন ফ্রেমওয়ার্ক: **ডিসপ্যাচ** এর ওপেন সোর্স রিলিজ ঘোষণা করতে পেরে আনন্দিত! [যাকিনা **FastAPI** দিয়ে নির্মিত]_" - -
কেভিন গ্লিসন, মার্ক ভিলানোভা, ফরেস্ট মনসেন - নেটফ্লিক্স (ref)
- ---- - -"_আমি **FastAPI** নিয়ে চাঁদের সমান উৎসাহিত। এটি খুবই মজার!_" - -
ব্রায়ান ওকেন - পাইথন বাইটস পডকাস্ট হোস্ট (ref)
- ---- - -"\_সত্যিই, আপনি যা তৈরি করেছেন তা খুব মজবুত এবং পরিপূর্ন৷ অনেক উপায়ে, আমি যা **Hug** এ করতে চেয়েছিলাম - তা কাউকে তৈরি করতে দেখে আমি সত্যিই অনুপ্রানিত৷\_" - -
টিমোথি ক্রসলে - Hug স্রষ্টা (ref)
- ---- - -"আপনি যদি REST API তৈরির জন্য একটি **আধুনিক ফ্রেমওয়ার্ক** শিখতে চান, তাহলে **FastAPI** দেখুন [...] এটি দ্রুত, ব্যবহার করা সহজ এবং শিখতেও সহজ [...]\_" - -"_আমরা আমাদের **APIs** [...] এর জন্য **FastAPI**- তে এসেছি [...] আমি মনে করি আপনিও এটি পছন্দ করবেন [...]_" - -
ইনেস মন্টানি - ম্যাথিউ হোনিবাল - Explosion AI প্রতিষ্ঠাতা - spaCy স্রষ্টা (ref) - (ref)
- ---- - -## **Typer**, CLI এর জন্য FastAPI - - - -আপনি যদি CLI অ্যাপ বানাতে চান, যা কিনা ওয়েব API এর পরিবর্তে টার্মিনালে ব্যবহার হবে, তাহলে দেখুন**Typer**. - -**টাইপার** হল FastAPI এর ছোট ভাইয়ের মত। এবং এটির উদ্দেশ্য ছিল **CLIs এর FastAPI** হওয়া। ⌨️ 🚀 - -## প্রয়োজনীয়তা গুলো - -Python 3.7+ - -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`: `app = FastAPI()` লাইন দিয়ে `main.py` এর ভিতরে তৈরি করা অবজেক্ট। -- `--reload`: কোড পরিবর্তনের পরে সার্ভার পুনরায় চালু করুন। এটি শুধুমাত্র ডেভেলপমেন্ট এর সময় ব্যবহার করুন। - -
- -### এটা চেক করুন - -আপনার ব্রাউজার খুলুন http://127.0.0.1:8000/items/5?q=somequery এ। - -আপনি JSON রেসপন্স দেখতে পাবেন: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -আপনি ইতিমধ্যে একটি API তৈরি করেছেন যা: - -- `/` এবং `/items/{item_id}` _paths_ এ HTTP অনুরোধ গ্রহণ করে। -- উভয় *path*ই `GET` অপারেশন নেয় ( যা HTTP _methods_ নামেও পরিচিত)। -- _path_ `/items/{item_id}`-এ একটি _path প্যারামিটার_ `item_id` আছে যা কিনা `int` হতে হবে। -- _path_ `/items/{item_id}`-এর একটি ঐচ্ছিক `str` _query প্যারামিটার_ `q` আছে। - -### ক্রিয়াশীল 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) - -## উদাহরণস্বরূপ আপগ্রেড - -এখন `main.py` ফাইলটি পরিবর্তন করুন যেন এটি `PUT` রিকুয়েস্ট থেকে বডি পেতে পারে। - -Python স্ট্যান্ডার্ড লাইব্রেরি, 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} -``` - -সার্ভারটি স্বয়ংক্রিয়ভাবে পুনরায় লোড হওয়া উচিত (কারণ আপনি উপরের `uvicorn` কমান্ডে `--reload` যোগ করেছেন)। - -### ক্রিয়াশীল API নির্দেশিকা নথি উন্নীতকরণ - -এখন http://127.0.0.1:8000/docs এডড্রেসে যান. - -- ক্রিয়াশীল API নির্দেশিকা নথিটি স্বয়ংক্রিয়ভাবে উন্নীত হযে যাবে, নতুন বডি সহ: - -![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) - -### বিকল্প API নির্দেশিকা নথি আপগ্রেড - -এবং এখন http://127.0.0.1:8000/redoc এ যান। - -- বিকল্প নির্দেশিকা নথিতেও নতুন কুয়েরি প্যারামিটার এবং বডি প্রতিফলিত হবে: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### সংক্ষিপ্তকরণ - -সংক্ষেপে, আপনি **শুধু একবার** প্যারামিটারের ধরন, বডি ইত্যাদি ফাংশন প্যারামিটার হিসেবে ঘোষণা করেন। - -আপনি সেটি আধুনিক পাইথনের সাথে করেন। - -আপনাকে নতুন করে নির্দিষ্ট কোন লাইব্রেরির বাক্য গঠন, ফাংশন বা ক্লাস কিছুই শিখতে হচ্ছে না। - -শুধুই আধুনিক **Python 3.6+** - -উদাহরণস্বরূপ, `int` এর জন্য: - -```Python -item_id: int -``` - -অথবা আরও জটিল `Item` মডেলের জন্য: - -```Python -item: Item -``` - -...এবং সেই একই ঘোষণার সাথে আপনি পাবেন: - -- এডিটর সাহায্য, যেমন - - সমাপ্তি। - - ধরণ যাচাই -- তথ্য যাচাইকরণ: - - ডেটা অবৈধ হলে স্বয়ংক্রিয় এবং পরিষ্কার ত্রুটির নির্দেশনা। - - এমনকি গভীরভাবে নেস্ট করা JSON অবজেক্টের জন্য বৈধতা। -- প্রেরিত তথ্য রূপান্তর: যা নেটওয়ার্ক থেকে পাইথনের তথ্য এবং ধরনে আসে, এবং সেখান থেকে পড়া: - - - JSON। - - পাথ প্যারামিটার। - - কুয়েরি প্যারামিটার। - - কুকিজ - - হেডার - - ফর্ম - - ফাইল - -- আউটপুট ডেটার রূপান্তর: পাইথন ডেটা এবং টাইপ থেকে নেটওয়ার্ক ডেটাতে রূপান্তর করা (JSON হিসাবে): - -পাইথন টাইপে রূপান্তর করুন (`str`, `int`, `float`, `bool`, `list`, ইত্যাদি)। - - `datetime` অবজেক্ট। - - `UUID` objeঅবজেক্টcts। - - ডাটাবেস মডেল। - - ...এবং আরো অনেক। -- স্বয়ংক্রিয় ক্রিয়াশীল API নির্দেশিকা নথি, 2টি বিকল্প ব্যবহারকারীর ইন্টারফেস সহ: - - সোয়াগার ইউ আই (Swagger UI)। - - রিডক (ReDoc)। - ---- - -পূর্ববর্তী কোড উদাহরণে ফিরে আসা যাক, **FastAPI** যা করবে: - -- `GET` এবং `PUT` অনুরোধের জন্য পথে `item_id` আছে কিনা তা যাচাই করবে। -- `GET` এবং `PUT` অনুরোধের জন্য `item_id` টাইপ `int` এর হতে হবে তা যাচাই করবে। - - যদি না হয় তবে ক্লায়েন্ট একটি উপযুক্ত, পরিষ্কার ত্রুটি দেখতে পাবেন। -- `GET` অনুরোধের জন্য একটি ঐচ্ছিক ক্যুয়েরি প্যারামিটার নামক `q` (যেমন `http://127.0.0.1:8000/items/foo?q=somequery`) আছে কি তা চেক করবে। - - যেহেতু `q` প্যারামিটারটি `= None` দিয়ে ঘোষণা করা হয়েছে, তাই এটি ঐচ্ছিক। - - `None` ছাড়া এটি প্রয়োজনীয় হতো (যেমন `PUT` এর ক্ষেত্রে হয়েছে)। -- `/items/{item_id}` এর জন্য `PUT` অনুরোধের বডি JSON হিসাবে পড়ুন: - - লক্ষ করুন, `name` একটি প্রয়োজনীয় অ্যাট্রিবিউট হিসাবে বিবেচনা করেছে এবং এটি `str` হতে হবে। - - লক্ষ করুন এখানে, `price` অ্যাট্রিবিউটটি আবশ্যক এবং এটি `float` হতে হবে। - - লক্ষ করুন `is_offer` একটি ঐচ্ছিক অ্যাট্রিবিউট এবং এটি `bool` হতে হবে যদি উপস্থিত থাকে। - - এই সবটি গভীরভাবে অবস্থানরত JSON অবজেক্টগুলিতেও কাজ করবে। -- স্বয়ংক্রিয়ভাবে JSON হতে এবং JSON থেকে কনভার্ট করুন। -- OpenAPI দিয়ে সবকিছু ডকুমেন্ট করুন, যা ব্যবহার করা যেতে পারে: - - ক্রিয়াশীল নির্দেশিকা নথি। - - অনেক ভাষার জন্য স্বয়ংক্রিয় ক্লায়েন্ট কোড তৈরির ব্যবস্থা। -- সরাসরি 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** এবং **JWT টোকেন** এবং **HTTP Basic** auth সহ নিরাপত্তা এবং অনুমোদনপ্রাপ্তি সম্পর্কিত বিষয়সমূহের উপর। -- **গভীরভাবে অবস্থানরত JSON মডেল** ঘোষণা করার জন্য আরও উন্নত (কিন্তু সমান সহজ) কৌশল (Pydantic কে ধন্যবাদ)। -- আরো অতিরিক্ত বৈশিষ্ট্য (স্টারলেটকে ধন্যবাদ) হিসাবে: - - **WebSockets** - - **GraphQL** - - HTTPX এবং `pytest` ভিত্তিক অত্যন্ত সহজ পরীক্ষা - - **CORS** - - **Cookie Sessions** - - ...এবং আরো। - -## কর্মক্ষমতা - -স্বাধীন TechEmpower Benchmarks দেখায় যে **FastAPI** অ্যাপ্লিকেশনগুলি Uvicorn-এর অধীনে চলমান দ্রুততমপাইথন ফ্রেমওয়ার্কগুলির মধ্যে একটি, শুধুমাত্র Starlette এবং Uvicorn-এর পর (FastAPI দ্বারা অভ্যন্তরীণভাবে ব্যবহৃত)। (\*) - -এটি সম্পর্কে আরও বুঝতে, দেখুন Benchmarks. - -## ঐচ্ছিক নির্ভরশীলতা - -Pydantic দ্বারা ব্যবহৃত: - -- email-validator - ইমেল যাচাইকরণের জন্য। - -স্টারলেট দ্বারা ব্যবহৃত: - -- httpx - আপনি যদি `TestClient` ব্যবহার করতে চান তাহলে আবশ্যক। -- jinja2 - আপনি যদি প্রদত্ত টেমপ্লেট রূপরেখা ব্যবহার করতে চান তাহলে প্রয়োজন। -- python-multipart - আপনি যদি ফর্ম সহায়তা করতে চান তাহলে প্রয়োজন "parsing", `request.form()` সহ। -- itsdangerous - `SessionMiddleware` সহায়তার জন্য প্রয়োজন। -- pyyaml - স্টারলেটের SchemaGenerator সাপোর্ট এর জন্য প্রয়োজন (আপনার সম্ভাবত FastAPI প্রয়োজন নেই)। -- graphene - `GraphQLApp` সহায়তার জন্য প্রয়োজন। - -FastAPI / Starlette দ্বারা ব্যবহৃত: - -- uvicorn - সার্ভারের জন্য যা আপনার অ্যাপ্লিকেশন লোড করে এবং পরিবেশন করে। -- orjson - আপনি `ORJSONResponse` ব্যবহার করতে চাইলে প্রয়োজন। -- ujson - আপনি `UJSONResponse` ব্যবহার করতে চাইলে প্রয়োজন। - -আপনি এই সব ইনস্টল করতে পারেন `pip install fastapi[all]` দিয়ে. - -## লাইসেন্স - -এই প্রজেক্ট MIT লাইসেন্স নীতিমালার অধীনে শর্তায়িত। diff --git a/docs/bn/docs/learn/index.md b/docs/bn/docs/learn/index.md deleted file mode 100644 index 4e4c62038c..0000000000 --- a/docs/bn/docs/learn/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# শিখুন - -এখানে **FastAPI** শিখার জন্য প্রাথমিক বিভাগগুলি এবং টিউটোরিয়ালগুলি রয়েছে। - -আপনি এটিকে একটি **বই**, একটি **কোর্স**, এবং FastAPI শিখার **অফিসিয়াল** এবং প্রস্তাবিত উপায় বিবেচনা করতে পারেন। 😎 diff --git a/docs/bn/docs/python-types.md b/docs/bn/docs/python-types.md deleted file mode 100644 index d5304a65e3..0000000000 --- a/docs/bn/docs/python-types.md +++ /dev/null @@ -1,596 +0,0 @@ -# পাইথন এর টাইপ্স পরিচিতি - -Python-এ ঐচ্ছিক "টাইপ হিন্ট" (যা "টাইপ অ্যানোটেশন" নামেও পরিচিত) এর জন্য সাপোর্ট রয়েছে। - -এই **"টাইপ হিন্ট"** বা অ্যানোটেশনগুলি এক ধরণের বিশেষ সিনট্যাক্স যা একটি ভেরিয়েবলের টাইপ ঘোষণা করতে দেয়। - -ভেরিয়েবলগুলির জন্য টাইপ ঘোষণা করলে, এডিটর এবং টুলগুলি আপনাকে আরও ভালো সাপোর্ট দিতে পারে। - -এটি পাইথন টাইপ হিন্ট সম্পর্কে একটি দ্রুত **টিউটোরিয়াল / রিফ্রেশার** মাত্র। এটি **FastAPI** এর সাথে ব্যবহার করার জন্য শুধুমাত্র ন্যূনতম প্রয়োজনীয়তা কভার করে... যা আসলে খুব একটা বেশি না। - -**FastAPI** এই টাইপ হিন্টগুলির উপর ভিত্তি করে নির্মিত, যা এটিকে অনেক সুবিধা এবং লাভ প্রদান করে। - -তবে, আপনি যদি কখনো **FastAPI** ব্যবহার নাও করেন, তবুও এগুলি সম্পর্কে একটু শেখা আপনার উপকারে আসবে। - -/// note - -যদি আপনি একজন Python বিশেষজ্ঞ হন, এবং টাইপ হিন্ট সম্পর্কে সবকিছু জানেন, তাহলে পরবর্তী অধ্যায়ে চলে যান। - -/// - -## প্রেরণা - -চলুন একটি সাধারণ উদাহরণ দিয়ে শুরু করি: - -```Python -{!../../../docs_src/python_types/tutorial001.py!} -``` - -এই প্রোগ্রামটি কল করলে আউটপুট হয়: - -``` -John Doe -``` - -ফাংশনটি নিম্নলিখিত কাজ করে: - -* `first_name` এবং `last_name` নেয়। -* প্রতিটির প্রথম অক্ষরকে `title()` ব্যবহার করে বড় হাতের অক্ষরে রূপান্তর করে। -* তাদেরকে মাঝখানে একটি স্পেস দিয়ে concatenate করে। - -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial001.py!} -``` - -### এটি সম্পাদনা করুন - -এটি একটি খুব সাধারণ প্রোগ্রাম। - -কিন্তু এখন কল্পনা করুন যে আপনি এটি শুরু থেকে লিখছিলেন। - -এক পর্যায়ে আপনি ফাংশনের সংজ্ঞা শুরু করেছিলেন, আপনার প্যারামিটারগুলি প্রস্তুত ছিল... - -কিন্তু তারপর আপনাকে "সেই method কল করতে হবে যা প্রথম অক্ষরকে বড় হাতের অক্ষরে রূপান্তর করে"। - -এটা কি `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` চাপেন এবং আপনি দেখতে পান: - - - -এর সাথে, আপনি অপশনগুলি দেখে, স্ক্রল করতে পারেন, যতক্ষণ না আপনি এমন একটি অপশন খুঁজে পান যা কিছু মনে পরিয়ে দেয়: - - - -## আরও প্রেরণা - -এই ফাংশনটি দেখুন, এটিতে ইতিমধ্যে টাইপ হিন্ট রয়েছে: - -```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`। এবং অভ্যন্তরীণ মানগুলোরও নিজেদের টাইপ থাকতে পারে। - -এই ধরনের টাইপগুলিকে বলা হয় "**জেনেরিক**" টাইপ এবং এগুলিকে তাদের অভ্যন্তরীণ টাইপগুলি সহ ঘোষণা করা সম্ভব। - -এই টাইপগুলি এবং অভ্যন্তরীণ টাইপগুলি ঘোষণা করতে, আপনি Python মডিউল `typing` ব্যবহার করতে পারেন। এটি বিশেষভাবে এই টাইপ হিন্টগুলি সমর্থন করার জন্য রয়েছে। - -#### Python এর নতুন সংস্করণ - -`typing` ব্যবহার করা সিনট্যাক্সটি Python 3.6 থেকে সর্বশেষ সংস্করণগুলি পর্যন্ত, অর্থাৎ Python 3.9, Python 3.10 ইত্যাদি সহ সকল সংস্করণের সাথে **সামঞ্জস্যপূর্ণ**। - -Python যত এগিয়ে যাচ্ছে, **নতুন সংস্করণগুলি** এই টাইপ অ্যানোটেশনগুলির জন্য তত উন্নত সাপোর্ট নিয়ে আসছে এবং অনেক ক্ষেত্রে আপনাকে টাইপ অ্যানোটেশন ঘোষণা করতে `typing` মডিউল ইম্পোর্ট এবং ব্যবহার করার প্রয়োজন হবে না। - -যদি আপনি আপনার প্রজেক্টের জন্য Python-এর আরও সাম্প্রতিক সংস্করণ নির্বাচন করতে পারেন, তাহলে আপনি সেই অতিরিক্ত সরলতা থেকে সুবিধা নিতে পারবেন। - -ডক্সে রয়েছে Python-এর প্রতিটি সংস্করণের সাথে সামঞ্জস্যপূর্ণ উদাহরণগুলি (যখন পার্থক্য আছে)। - -উদাহরণস্বরূপ, "**Python 3.6+**" মানে এটি Python 3.6 বা তার উপরে সামঞ্জস্যপূর্ণ (যার মধ্যে 3.7, 3.8, 3.9, 3.10, ইত্যাদি অন্তর্ভুক্ত)। এবং "**Python 3.9+**" মানে এটি Python 3.9 বা তার উপরে সামঞ্জস্যপূর্ণ (যার মধ্যে 3.10, ইত্যাদি অন্তর্ভুক্ত)। - -যদি আপনি Python-এর **সর্বশেষ সংস্করণগুলি ব্যবহার করতে পারেন**, তাহলে সর্বশেষ সংস্করণের জন্য উদাহরণগুলি ব্যবহার করুন, সেগুলি আপনাকে **সর্বোত্তম এবং সহজতম সিনট্যাক্স** প্রদান করবে, যেমন, "**Python 3.10+**"। - -#### লিস্ট - -উদাহরণস্বরূপ, একটি ভেরিয়েবলকে `str`-এর একটি `list` হিসেবে সংজ্ঞায়িত করা যাক। - -//// tab | Python 3.9+ - -ভেরিয়েবলটি ঘোষণা করুন, একই কোলন (`:`) সিনট্যাক্স ব্যবহার করে। - -টাইপ হিসেবে, `list` ব্যবহার করুন। - -যেহেতু লিস্ট এমন একটি টাইপ যা অভ্যন্তরীণ টাইপগুলি ধারণ করে, আপনি তাদের স্কোয়ার ব্রাকেটের ভিতরে ব্যবহার করুন: - -```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -`typing` থেকে `List` (বড় হাতের `L` দিয়ে) ইমপোর্ট করুন: - -``` Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial006.py!} -``` - -ভেরিয়েবলটি ঘোষণা করুন, একই কোলন (`:`) সিনট্যাক্স ব্যবহার করে। - -টাইপ হিসেবে, `typing` থেকে আপনার ইম্পোর্ট করা `List` ব্যবহার করুন। - -যেহেতু লিস্ট এমন একটি টাইপ যা অভ্যন্তরীণ টাইপগুলি ধারণ করে, আপনি তাদের স্কোয়ার ব্রাকেটের ভিতরে করুন: - -```Python hl_lines="4" -{!> ../../../docs_src/python_types/tutorial006.py!} -``` - -//// - -/// info - -স্কোয়ার ব্রাকেট এর ভিতরে ব্যবহৃত এইসব অভন্তরীন টাইপগুলোকে "ইন্টারনাল টাইপ" বলে। - -এই উদাহরণে, এটি হচ্ছে `List`(অথবা পাইথন ৩.৯ বা তার উপরের সংস্করণের ক্ষেত্রে `list`) এ পাস করা টাইপ প্যারামিটার। - -/// - -এর অর্থ হচ্ছে: "ভেরিয়েবল `items` একটি `list`, এবং এই লিস্টের প্রতিটি আইটেম একটি `str`।" - -/// tip - -যদি আপনি Python 3.9 বা তার উপরে ব্যবহার করেন, আপনার `typing` থেকে `List` আমদানি করতে হবে না, আপনি সাধারণ `list` ওই টাইপের পরিবর্তে ব্যবহার করতে পারেন। - -/// - -এর মাধ্যমে, আপনার এডিটর লিস্ট থেকে আইটেম প্রসেস করার সময় সাপোর্ট প্রদান করতে পারবে: - - - -টাইপগুলি ছাড়া, এটি করা প্রায় অসম্ভব। - -লক্ষ্য করুন যে ভেরিয়েবল `item` হল `items` লিস্টের একটি এলিমেন্ট। - -তবুও, এডিটর জানে যে এটি একটি `str`, এবং তার জন্য সাপোর্ট প্রদান করে। - -#### টাপল এবং সেট - -আপনি `tuple` এবং `set` ঘোষণা করার জন্য একই প্রক্রিয়া অনুসরণ করবেন: - -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial007.py!} -``` - -//// - -এর মানে হল: - -* ভেরিয়েবল `items_t` হল একটি `tuple` যা ৩টি আইটেম ধারণ করে, একটি `int`, অন্য একটি `int`, এবং একটি `str`। -* ভেরিয়েবল `items_s` হল একটি `set`, এবং এর প্রতিটি আইটেম হল `bytes` টাইপের। - -#### ডিক্ট - -একটি `dict` সংজ্ঞায়িত করতে, আপনি ২টি টাইপ প্যারামিটার কমা দ্বারা পৃথক করে দেবেন। - -প্রথম টাইপ প্যারামিটারটি হল `dict`-এর কীগুলির জন্য। - -দ্বিতীয় টাইপ প্যারামিটারটি হল `dict`-এর মানগুলির জন্য: - -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial008.py!} -``` - -//// - -এর মানে হল: - -* ভেরিয়েবল `prices` হল একটি `dict`: - * এই `dict`-এর কীগুলি হল `str` টাইপের (ধরা যাক, প্রতিটি আইটেমের নাম)। - * এই `dict`-এর মানগুলি হল `float` টাইপের (ধরা যাক, প্রতিটি আইটেমের দাম)। - -#### ইউনিয়ন - -আপনি একটি ভেরিয়েবলকে এমনভাবে ঘোষণা করতে পারেন যেন তা **একাধিক টাইপের** হয়, উদাহরণস্বরূপ, একটি `int` অথবা `str`। - -Python 3.6 এবং তার উপরের সংস্করণগুলিতে (Python 3.10 অন্তর্ভুক্ত) আপনি `typing` থেকে `Union` টাইপ ব্যবহার করতে পারেন এবং স্কোয়ার ব্র্যাকেটের মধ্যে গ্রহণযোগ্য টাইপগুলি রাখতে পারেন। - -Python 3.10-এ একটি **নতুন সিনট্যাক্স** আছে যেখানে আপনি সম্ভাব্য টাইপগুলিকে একটি ভার্টিকাল বার (`|`) দ্বারা পৃথক করতে পারেন। - -//// tab | Python 3.10+ - -```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial008b_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial008b.py!} -``` - -//// - -উভয় ক্ষেত্রেই এর মানে হল যে `item` হতে পারে একটি `int` অথবা `str`। - -#### সম্ভবত `None` - -আপনি এমনভাবে ঘোষণা করতে পারেন যে একটি মান হতে পারে এক টাইপের, যেমন `str`, আবার এটি `None`-ও হতে পারে। - -Python 3.6 এবং তার উপরের সংস্করণগুলিতে (Python 3.10 অনতর্ভুক্ত) আপনি `typing` মডিউল থেকে `Optional` ইমপোর্ট করে এটি ঘোষণা এবং ব্যবহার করতে পারেন। - -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009.py!} -``` - -`Optional[str]` ব্যবহার করা মানে হল শুধু `str` নয়, এটি হতে পারে `None`-ও, যা আপনার এডিটরকে সেই ত্রুটিগুলি শনাক্ত করতে সাহায্য করবে যেখানে আপনি ধরে নিচ্ছেন যে একটি মান সবসময় `str` হবে, অথচ এটি `None`-ও হতে পারেও। - -`Optional[Something]` মূলত `Union[Something, None]`-এর একটি শর্টকাট, এবং তারা সমতুল্য। - -এর মানে হল, Python 3.10-এ, আপনি টাইপগুলির ইউনিয়ন ঘোষণা করতে `Something | None` ব্যবহার করতে পারেন: - -//// tab | Python 3.10+ - -```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial009_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial009.py!} -``` - -//// - -//// tab | Python 3.8+ বিকল্প - -```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial009b.py!} -``` - -//// - -#### `Union` বা `Optional` ব্যবহার - -যদি আপনি Python 3.10-এর নীচের সংস্করণ ব্যবহার করেন, তবে এখানে আমার খুবই **ব্যক্তিগত** দৃষ্টিভঙ্গি থেকে একটি টিপস: - -* 🚨 `Optional[SomeType]` ব্যবহার এড়িয়ে চলুন। -* এর পরিবর্তে ✨ **`Union[SomeType, None]` ব্যবহার করুন** ✨। - -উভয়ই সমতুল্য এবং মূলে একই, কিন্তু আমি `Union`-এর পক্ষে সুপারিশ করব কারণ "**অপশনাল**" শব্দটি মনে হতে পারে যে মানটি ঐচ্ছিক,অথচ এটি আসলে মানে "এটি হতে পারে `None`", এমনকি যদি এটি ঐচ্ছিক না হয়েও আবশ্যিক হয়। - -আমি মনে করি `Union[SomeType, None]` এর অর্থ আরও স্পষ্টভাবে প্রকাশ করে। - -এটি কেবল শব্দ এবং নামের ব্যাপার। কিন্তু সেই শব্দগুলি আপনি এবং আপনার সহকর্মীরা কোড সম্পর্কে কীভাবে চিন্তা করেন তা প্রভাবিত করতে পারে। - -একটি উদাহরণ হিসেবে, এই ফাংশনটি নিন: - -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009c.py!} -``` - -`name` প্যারামিটারটি `Optional[str]` হিসেবে সংজ্ঞায়িত হয়েছে, কিন্তু এটি **অপশনাল নয়**, আপনি প্যারামিটার ছাড়া ফাংশনটি কল করতে পারবেন না: - -```Python -say_hi() # ওহ না, এটি একটি ত্রুটি নিক্ষেপ করবে! 😱 -``` - -`name` প্যারামিটারটি **এখনও আবশ্যিক** (নন-অপশনাল) কারণ এটির কোনো ডিফল্ট মান নেই। তবুও, `name` এর মান হিসেবে `None` গ্রহণযোগ্য: - -```Python -say_hi(name=None) # এটি কাজ করে, None বৈধ 🎉 -``` - -সুখবর হল, একবার আপনি Python 3.10 ব্যবহার করা শুরু করলে, আপনাকে এগুলোর ব্যাপারে আর চিন্তা করতে হবে না, যেহুতু আপনি | ব্যবহার করেই ইউনিয়ন ঘোষণা করতে পারবেন: - -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009c_py310.py!} -``` - -এবং তারপর আপনাকে নামগুলি যেমন `Optional` এবং `Union` নিয়ে আর চিন্তা করতে হবে না। 😎 - -#### জেনেরিক টাইপস - -স্কোয়ার ব্র্যাকেটে টাইপ প্যারামিটার নেওয়া এই টাইপগুলিকে **জেনেরিক টাইপ** বা **জেনেরিকস** বলা হয়, যেমন: - -//// tab | Python 3.10+ - -আপনি সেই একই বিল্টইন টাইপ জেনেরিক্স হিসেবে ব্যবহার করতে পারবেন(ভিতরে টাইপ সহ স্কয়ারে ব্রাকেট দিয়ে): - -* `list` -* `tuple` -* `set` -* `dict` - -এবং Python 3.8 এর মতোই, `typing` মডিউল থেকে: - -* `Union` -* `Optional` (Python 3.8 এর মতোই) -* ...এবং অন্যান্য। - -Python 3.10-এ, `Union` এবং `Optional` জেনেরিকস ব্যবহার করার বিকল্প হিসেবে, আপনি টাইপগুলির ইউনিয়ন ঘোষণা করতে ভার্টিকাল বার (`|`) ব্যবহার করতে পারেন, যা ওদের থেকে অনেক ভালো এবং সহজ। - -//// - -//// tab | Python 3.9+ - -আপনি সেই একই বিল্টইন টাইপ জেনেরিক্স হিসেবে ব্যবহার করতে পারবেন(ভিতরে টাইপ সহ স্কয়ারে ব্রাকেট দিয়ে): - -* `list` -* `tuple` -* `set` -* `dict` - -এবং Python 3.8 এর মতোই, `typing` মডিউল থেকে: - -* `Union` -* `Optional` -* ...এবং অন্যান্য। - -//// - -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `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!} -``` - -এবং তারপর, আবার, আপনি এডিটর সাপোর্ট পেয়ে যাবেন: - - - -লক্ষ্য করুন যে এর মানে হল "`one_person` হল ক্লাস `Person`-এর একটি **ইন্সট্যান্স**।" - -এর মানে এটি নয় যে "`one_person` হল **ক্লাস** যাকে বলা হয় `Person`।" - -## Pydantic মডেল - -[Pydantic](https://docs.pydantic.dev/) হল একটি Python লাইব্রেরি যা ডাটা ভ্যালিডেশন সম্পাদন করে। - -আপনি ডাটার "আকার" এট্রিবিউট সহ ক্লাস হিসেবে ঘোষণা করেন। - -এবং প্রতিটি এট্রিবিউট এর একটি টাইপ থাকে। - -তারপর আপনি যদি কিছু মান দিয়ে সেই ক্লাসের একটি ইন্সট্যান্স তৈরি করেন-- এটি মানগুলিকে ভ্যালিডেট করবে, প্রয়োজন অনুযায়ী তাদেরকে উপযুক্ত টাইপে রূপান্তর করবে এবং আপনাকে সমস্ত ডাটা সহ একটি অবজেক্ট প্রদান করবে। - -এবং আপনি সেই ফলাফল অবজেক্টের সাথে এডিটর সাপোর্ট পাবেন। - -অফিসিয়াল Pydantic ডক্স থেকে একটি উদাহরণ: - -//// tab | Python 3.10+ - -```Python -{!> ../../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../../docs_src/python_types/tutorial011.py!} -``` - -//// - -/// info - -[Pydantic সম্পর্কে আরও জানতে, এর ডকুমেন্টেশন দেখুন](https://docs.pydantic.dev/)। - -/// - -**FastAPI** মূলত Pydantic-এর উপর নির্মিত। - -আপনি এই সমস্ত কিছুর অনেক বাস্তবসম্মত উদাহরণ পাবেন [টিউটোরিয়াল - ইউজার গাইডে](https://fastapi.tiangolo.com/tutorial/)। - -/// tip - -যখন আপনি `Optional` বা `Union[Something, None]` ব্যবহার করেন এবং কোনো ডিফল্ট মান না থাকে, Pydantic-এর একটি বিশেষ আচরণ রয়েছে, আপনি Pydantic ডকুমেন্টেশনে [Required Optional fields](https://docs.pydantic.dev/latest/concepts/models/#required-optional-fields) সম্পর্কে আরও পড়তে পারেন। - -/// - -## মেটাডাটা অ্যানোটেশন সহ টাইপ হিন্টস - -Python-এ এমন একটি ফিচার আছে যা `Annotated` ব্যবহার করে এই টাইপ হিন্টগুলিতে **অতিরিক্ত মেটাডাটা** রাখতে দেয়। - -//// tab | Python 3.9+ - -Python 3.9-এ, `Annotated` স্ট্যান্ডার্ড লাইব্রেরিতে অন্তর্ভুক্ত, তাই আপনি এটি `typing` থেকে ইমপোর্ট করতে পারেন। - -```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -Python 3.9-এর নীচের সংস্করণগুলিতে, আপনি `Annotated`-কে `typing_extensions` থেকে ইমপোর্ট করেন। - -এটি **FastAPI** এর সাথে ইতিমদ্ধে ইনস্টল হয়ে থাকবে। - -```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial013.py!} -``` - -//// - -Python নিজে এই `Annotated` দিয়ে কিছুই করে না। এবং এডিটর এবং অন্যান্য টুলগুলির জন্য, টাইপটি এখনও `str`। - -কিন্তু আপনি এই `Annotated` এর মধ্যকার জায়গাটির মধ্যে **FastAPI**-এ কীভাবে আপনার অ্যাপ্লিকেশন আচরণ করুক তা সম্পর্কে অতিরিক্ত মেটাডাটা প্রদান করার জন্য ব্যবহার করতে পারেন। - -মনে রাখার গুরুত্বপূর্ণ বিষয় হল যে **প্রথম *টাইপ প্যারামিটার*** আপনি `Annotated`-এ পাস করেন সেটি হল **আসল টাইপ**। বাকি শুধুমাত্র অন্যান্য টুলগুলির জন্য মেটাডাটা। - -এখন আপনার কেবল জানা প্রয়োজন যে `Annotated` বিদ্যমান, এবং এটি স্ট্যান্ডার্ড Python। 😎 - -পরবর্তীতে আপনি দেখবেন এটি কতটা **শক্তিশালী** হতে পারে। - -/// tip - -এটি **স্ট্যান্ডার্ড Python** হওয়ার মানে হল আপনি আপনার এডিটরে, আপনি যে টুলগুলি কোড বিশ্লেষণ এবং রিফ্যাক্টর করার জন্য ব্যবহার করেন তাতে **সেরা সম্ভাব্য ডেভেলপার এক্সপেরিয়েন্স** পাবেন। ✨ - -এবং এছাড়াও আপনার কোড অন্যান্য অনেক Python টুল এবং লাইব্রেরিগুলির সাথে খুব সামঞ্জস্যপূর্ণ হবে। 🚀 - -/// - -## **FastAPI**-এ টাইপ হিন্টস - -**FastAPI** এই টাইপ হিন্টগুলি ব্যবহার করে বেশ কিছু জিনিস করে। - -**FastAPI**-এ আপনি টাইপ হিন্টগুলি সহ প্যারামিটার ঘোষণা করেন এবং আপনি পান: - -* **এডিটর সাপোর্ট**। -* **টাইপচেক**। - -...এবং **FastAPI** একই ঘোষণাগুলি ব্যবহার করে: - -* **রিকুইরেমেন্টস সংজ্ঞায়িত করে**: রিকোয়েস্ট পাথ প্যারামিটার, কুয়েরি প্যারামিটার, হেডার, বডি, ডিপেন্ডেন্সিস, ইত্যাদি থেকে। -* **ডেটা রূপান্তর করে**: রিকোয়েস্ট থেকে প্রয়োজনীয় টাইপে ডেটা। -* **ডেটা যাচাই করে**: প্রতিটি রিকোয়েস্ট থেকে আসা ডেটা: - * যখন ডেটা অবৈধ হয় তখন **স্বয়ংক্রিয় ত্রুটি** গ্রাহকের কাছে ফেরত পাঠানো। -* **API ডকুমেন্টেশন তৈরি করে**: OpenAPI ব্যবহার করে: - * যা স্বয়ংক্রিয় ইন্টার‌্যাক্টিভ ডকুমেন্টেশন ইউজার ইন্টারফেস দ্বারা ব্যবহৃত হয়। - -এই সব কিছু আপনার কাছে অস্পষ্ট মনে হতে পারে। চিন্তা করবেন না। আপনি [টিউটোরিয়াল - ইউজার গাইড](https://fastapi.tiangolo.com/tutorial/) এ এই সব কিছু প্র্যাকটিসে দেখতে পাবেন। - -গুরুত্বপূর্ণ বিষয় হল, আপনি যদি স্ট্যান্ডার্ড Python টাইপগুলি ব্যবহার করেন, তবে আরও বেশি ক্লাস, ডেকোরেটর ইত্যাদি যোগ না করেই একই স্থানে **FastAPI** আপনার অনেক কাজ করে দিবে। - -/// info - -যদি আপনি টিউটোরিয়ালের সমস্ত বিষয় পড়ে ফেলে থাকেন এবং টাইপ সম্পর্কে আরও জানতে চান, তবে একটি ভালো রিসোর্স হল [mypy এর "cheat sheet"](https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html)। এই "cheat sheet" এ আপনি Python টাইপ হিন্ট সম্পর্কে বেসিক থেকে উন্নত লেভেলের ধারণা পেতে পারেন, যা আপনার কোডে টাইপ সেফটি এবং স্পষ্টতা বাড়াতে সাহায্য করবে। - -/// diff --git a/docs/bn/mkdocs.yml b/docs/bn/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/bn/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/de/docs/_llm-test.md b/docs/de/docs/_llm-test.md new file mode 100644 index 0000000000..4a5e5392c5 --- /dev/null +++ b/docs/de/docs/_llm-test.md @@ -0,0 +1,503 @@ +# LLM-Testdatei { #llm-test-file } + +Dieses Dokument testet, ob das LLM, das die Dokumentation übersetzt, den `general_prompt` in `scripts/translate.py` und den sprachspezifischen Prompt in `docs/{language code}/llm-prompt.md` versteht. Der sprachspezifische Prompt wird an `general_prompt` angehängt. + +Hier hinzugefügte Tests werden von allen Erstellern sprachspezifischer Prompts gesehen. + +So verwenden: + +* Einen sprachspezifischen Prompt haben – `docs/{language code}/llm-prompt.md`. +* Eine frische Übersetzung dieses Dokuments in die gewünschte Zielsprache durchführen (siehe z. B. das Kommando `translate-page` der `translate.py`). Dadurch wird die Übersetzung unter `docs/{language code}/docs/_llm-test.md` erstellt. +* Prüfen Sie, ob in der Übersetzung alles in Ordnung ist. +* Verbessern Sie bei Bedarf Ihren sprachsspezifischen Prompt, den allgemeinen Prompt oder das englische Dokument. +* Beheben Sie anschließend manuell die verbleibenden Probleme in der Übersetzung, sodass es eine gute Übersetzung ist. +* Übersetzen Sie erneut, nachdem die gute Übersetzung vorliegt. Das ideale Ergebnis wäre, dass das LLM an der Übersetzung keine Änderungen mehr vornimmt. Das bedeutet, dass der allgemeine Prompt und Ihr sprachsspezifischer Prompt so gut sind, wie sie sein können (Es wird manchmal ein paar scheinbar zufällige Änderungen machen, der Grund ist, dass LLMs keine deterministischen Algorithmen sind). + +Die Tests: + +## Codeschnipsel { #code-snippets} + +//// tab | Test + +Dies ist ein Codeschnipsel: `foo`. Und dies ist ein weiteres Codeschnipsel: `bar`. Und noch eins: `baz quux`. + +//// + +//// tab | Info + +Der Inhalt von Codeschnipseln sollte unverändert bleiben. + +Siehe Abschnitt `### Content of code snippets` im allgemeinen Prompt in `scripts/translate.py`. + +//// + +## Anführungszeichen { #quotes } + +//// tab | Test + +Gestern schrieb mein Freund: „Wenn man unkorrekt korrekt schreibt, hat man es unkorrekt geschrieben“. Worauf ich antwortete: „Korrekt, aber ‚unkorrekt‘ ist unkorrekterweise nicht ‚„unkorrekt“‘“. + +/// note | Hinweis + +Das LLM wird dies wahrscheinlich falsch übersetzen. Interessant ist nur, ob es die korrigierte Übersetzung bei einer erneuten Übersetzung beibehält. + +/// + +//// + +//// tab | Info + +Der Prompt-Designer kann entscheiden, ob neutrale Anführungszeichen in typografische Anführungszeichen umgewandelt werden sollen. Es ist in Ordnung, sie unverändert zu lassen. + +Siehe zum Beispiel den Abschnitt `### Quotes` in `docs/de/llm-prompt.md`. + +//// + +## Anführungszeichen in Codeschnipseln { #quotes-in-code-snippets} + +//// tab | Test + +`pip install "foo[bar]"` + +Beispiele für Stringliterale in Codeschnipseln: `"this"`, `'that'`. + +Ein schwieriges Beispiel für Stringliterale in Codeschnipseln: `f"I like {'oranges' if orange else "apples"}"` + +Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | Info + +... Allerdings müssen Anführungszeichen in Codeschnipseln unverändert bleiben. + +//// + +## Codeblöcke { #code-blocks } + +//// tab | Test + +Ein Bash-Codebeispiel ... + +```bash +# Eine Begrüßung an das Universum ausgeben +echo "Hello universe" +``` + +... und ein Konsolen-Codebeispiel ... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +... und noch ein Konsolen-Codebeispiel ... + +```console +// Ein Verzeichnis „Code“ erstellen +$ mkdir code +// In dieses Verzeichnis wechseln +$ cd code +``` + +... und ein Python-Codebeispiel ... + +```Python +wont_work() # Das wird nicht funktionieren 😱 +works(foo="bar") # Das funktioniert 🎉 +``` + +... und das war's. + +//// + +//// tab | Info + +Code in Codeblöcken sollte nicht verändert werden, mit Ausnahme von Kommentaren. + +Siehe Abschnitt `### Content of code blocks` im allgemeinen Prompt in `scripts/translate.py`. + +//// + +## Tabs und farbige Boxen { #tabs-and-colored-boxes } + +//// tab | Test + +/// info | Info +Etwas Text +/// + +/// note | Hinweis +Etwas Text +/// + +/// note | Technische Details +Etwas Text +/// + +/// check | Testen +Etwas Text +/// + +/// tip | Tipp +Etwas Text +/// + +/// warning | Achtung +Etwas Text +/// + +/// danger | Gefahr +Etwas Text +/// + +//// + +//// tab | Info + +Tabs und `Info`/`Note`/`Warning`/usw. Blöcke sollten die Übersetzung ihres Titels nach einem vertikalen Strich (`|`) erhalten. + +Siehe die Abschnitte `### Special blocks` und `### Tab blocks` im allgemeinen Prompt in `scripts/translate.py`. + +//// + +## Web- und interne Links { #web-and-internal-links } + +//// tab | Test + +Der Linktext sollte übersetzt werden, die Linkadresse sollte unverändert bleiben: + +* [Link zur Überschrift oben](#code-snippets) +* [Interner Link](index.md#installation){.internal-link target=_blank} +* Externer Link +* Link zu einem Stil +* Link zu einem Skript +* Link zu einem Bild + +Der Linktext sollte übersetzt werden, die Linkadresse sollte auf die Übersetzung zeigen: + +* FastAPI-Link + +//// + +//// tab | Info + +Links sollten übersetzt werden, aber ihre Adresse soll unverändert bleiben. Eine Ausnahme sind absolute Links zu Seiten der FastAPI-Dokumentation. In diesem Fall sollte auf die Übersetzung verlinkt werden. + +Siehe Abschnitt `### Links` im allgemeinen Prompt in `scripts/translate.py`. + +//// + +## HTML „abbr“-Elemente { #html-abbr-elements } + +//// tab | Test + +Hier einige Dinge, die in HTML-„abbr“-Elemente gepackt sind (einige sind erfunden): + +### Das abbr gibt eine vollständige Phrase { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### Das abbr gibt eine Erklärung { #the-abbr-gives-an-explanation } + +* Cluster +* Deep Learning + +### Das abbr gibt eine vollständige Phrase und eine Erklärung { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | Info + +„title“-Attribute von „abbr“-Elementen werden nach bestimmten Anweisungen übersetzt. + +Übersetzungen können eigene „abbr“-Elemente hinzufügen, die das LLM nicht entfernen soll. Z. B. um englische Wörter zu erklären. + +Siehe Abschnitt `### HTML abbr elements` im allgemeinen Prompt in `scripts/translate.py`. + +//// + +## Überschriften { #headings } + +//// tab | Test + +### Eine Webapp entwickeln – ein Tutorial { #develop-a-webapp-a-tutorial } + +Hallo. + +### Typhinweise und -annotationen { #type-hints-and-annotations } + +Hallo wieder. + +### Super- und Subklassen { #super-and-subclasses } + +Hallo wieder. + +//// + +//// tab | Info + +Die einzige strenge Regel für Überschriften ist, dass das LLM den Hash-Teil in geschweiften Klammern unverändert lässt, damit Links nicht kaputtgehen. + +Siehe Abschnitt `### Headings` im allgemeinen Prompt in `scripts/translate.py`. + +Für einige sprachspezifische Anweisungen, siehe z. B. den Abschnitt `### Headings` in `docs/de/llm-prompt.md`. + +//// + +## In der Dokumentation verwendete Begriffe { #terms-used-in-the-docs } + +//// tab | Test + +* Sie +* Ihr + +* z. B. +* usw. + +* `foo` vom Typ `int` +* `bar` vom Typ `str` +* `baz` vom Typ `list` + +* das Tutorial – Benutzerhandbuch +* das Handbuch für fortgeschrittene Benutzer +* die SQLModel-Dokumentation +* die API-Dokumentation +* die automatische Dokumentation + +* Data Science +* Deep Learning +* Machine Learning +* Dependency Injection +* HTTP Basic-Authentifizierung +* HTTP Digest +* ISO-Format +* der JSON-Schema-Standard +* das JSON-Schema +* die Schema-Definition +* Password Flow +* Mobile + +* deprecatet +* designt +* ungültig +* on the fly +* Standard +* Default +* Groß-/Klein­schrei­bung ist relevant +* Groß-/Klein­schrei­bung ist nicht relevant + +* die Anwendung bereitstellen +* die Seite ausliefern + +* die App +* die Anwendung + +* der Request +* die Response +* die Error-Response + +* die Pfadoperation +* der Pfadoperation-Dekorator +* die Pfadoperation-Funktion + +* der Body +* der Requestbody +* der Responsebody +* der JSON-Body +* der Formularbody +* der Dateibody +* der Funktionskörper + +* der Parameter +* der Body-Parameter +* der Pfad-Parameter +* der Query-Parameter +* der Cookie-Parameter +* der Header-Parameter +* der Formular-Parameter +* der Funktionsparameter + +* das Event +* das Startup-Event +* das Hochfahren des Servers +* das Shutdown-Event +* das Lifespan-Event + +* der Handler +* der Eventhandler +* der Exceptionhandler +* handhaben + +* das Modell +* das Pydantic-Modell +* das Datenmodell +* das Datenbankmodell +* das Formularmodell +* das Modellobjekt + +* die Klasse +* die Basisklasse +* die Elternklasse +* die Subklasse +* die Kindklasse +* die Geschwisterklasse +* die Klassenmethode + +* der Header +* die Header +* der Autorisierungsheader +* der `Authorization`-Header +* der Forwarded-Header + +* das Dependency-Injection-System +* die Dependency +* das Dependable +* der Dependant + +* I/O-lastig +* CPU-lastig +* Nebenläufigkeit +* Parallelität +* Multiprocessing + +* die Umgebungsvariable +* die Umgebungsvariable +* der `PATH` +* die `PATH`-Umgebungsvariable + +* die Authentifizierung +* der Authentifizierungsanbieter +* die Autorisierung +* das Anmeldeformular +* der Autorisierungsanbieter +* der Benutzer authentisiert sich +* das System authentifiziert den Benutzer + +* Das CLI +* Das Kommandozeileninterface + +* der Server +* der Client + +* der Cloudanbieter +* der Clouddienst + +* die Entwicklung +* die Entwicklungsphasen + +* das Dict +* das Dictionary +* die Enumeration +* das Enum +* das Enum-Member + +* der Encoder +* der Decoder +* kodieren +* dekodieren + +* die Exception +* werfen + +* der Ausdruck +* die Anweisung + +* das Frontend +* das Backend + +* die GitHub-Diskussion +* das GitHub-Issue + +* die Leistung +* die Leistungsoptimierung + +* der Rückgabetyp +* der Rückgabewert + +* die Sicherheit +* das Sicherheitsschema + +* der Task +* der Hintergrundtask +* die Taskfunktion + +* das Template +* die Template-Engine + +* die Typannotation +* der Typhinweis + +* der Serverworker +* der Uvicorn-Worker +* der Gunicorn-Worker +* der Workerprozess +* die Workerklasse +* die Workload + +* das Deployment +* bereitstellen + +* das SDK +* das Software Development Kit + +* der `APIRouter` +* die `requirements.txt` +* das Bearer-Token +* der Breaking Change +* der Bug +* der Button +* das Callable +* der Code +* der Commit +* der Contextmanager +* die Coroutine +* die Datenbank-Session +* die Festplatte +* die Domain +* die Engine +* das Fake-X +* die HTTP-GET-Methode +* das Item +* die Bibliothek +* der Lifespan +* der Lock +* die Middleware +* die Mobile-Anwendung +* das Modul +* das Mounten +* das Netzwerk +* das Origin +* Die Überschreibung +* die Payload +* der Prozessor +* die Property +* der Proxy +* der Pull Request +* die Query +* der RAM +* der entfernte Rechner +* der Statuscode +* der String +* der Tag +* das Webframework +* die Wildcard +* zurückgeben +* validieren + +//// + +//// tab | Info + +Dies ist eine nicht vollständige und nicht normative Liste von (meist) technischen Begriffen, die in der Dokumentation vorkommen. Sie kann dem Prompt-Designer helfen herauszufinden, bei welchen Begriffen das LLM Unterstützung braucht. Zum Beispiel, wenn es eine gute Übersetzung immer wieder auf eine suboptimale Übersetzung zurücksetzt. Oder wenn es Probleme hat, einen Begriff in Ihrer Sprache zu konjugieren/deklinieren. + +Siehe z. B. den Abschnitt `### List of English terms and their preferred German translations` in `docs/de/llm-prompt.md`. + +//// diff --git a/docs/de/docs/about/index.md b/docs/de/docs/about/index.md index 4c309e02a5..5e9c6b6a0a 100644 --- a/docs/de/docs/about/index.md +++ b/docs/de/docs/about/index.md @@ -1,3 +1,3 @@ -# Über +# Über { #about } Über FastAPI, sein Design, seine Inspiration und mehr. 🤓 diff --git a/docs/de/docs/advanced/additional-responses.md b/docs/de/docs/advanced/additional-responses.md index 6f2c4b2dd8..218dd6c4fb 100644 --- a/docs/de/docs/advanced/additional-responses.md +++ b/docs/de/docs/advanced/additional-responses.md @@ -1,6 +1,6 @@ -# Zusätzliche Responses in OpenAPI +# Zusätzliche Responses in OpenAPI { #additional-responses-in-openapi } -/// warning | "Achtung" +/// warning | Achtung Dies ist ein eher fortgeschrittenes Thema. @@ -8,17 +8,17 @@ Wenn Sie mit **FastAPI** beginnen, benötigen Sie dies möglicherweise nicht. /// -Sie können zusätzliche Responses mit zusätzlichen Statuscodes, Medientypen, Beschreibungen, usw. deklarieren. +Sie können zusätzliche Responses mit zusätzlichen Statuscodes, Medientypen, Beschreibungen, usw. deklarieren. Diese zusätzlichen Responses werden in das OpenAPI-Schema aufgenommen, sodass sie auch in der API-Dokumentation erscheinen. Für diese zusätzlichen Responses müssen Sie jedoch sicherstellen, dass Sie eine `Response`, wie etwa `JSONResponse`, direkt zurückgeben, mit Ihrem Statuscode und Inhalt. -## Zusätzliche Response mit `model` +## Zusätzliche Response mit `model` { #additional-response-with-model } Sie können Ihren *Pfadoperation-Dekoratoren* einen Parameter `responses` übergeben. -Der nimmt ein `dict` entgegen, die Schlüssel sind Statuscodes für jede Response, wie etwa `200`, und die Werte sind andere `dict`s mit den Informationen für jede Response. +Der nimmt ein `dict` entgegen, die Schlüssel sind Statuscodes für jede Response, wie etwa `200`, und die Werte sind andere `dict`s mit den Informationen für jede Response. Jedes dieser Response-`dict`s kann einen Schlüssel `model` haben, welcher ein Pydantic-Modell enthält, genau wie `response_model`. @@ -26,17 +26,15 @@ Jedes dieser Response-`dict`s kann einen Schlüssel `model` haben, welcher ein P Um beispielsweise eine weitere Response mit dem Statuscode `404` und einem Pydantic-Modell `Message` zu deklarieren, können Sie schreiben: -```Python hl_lines="18 22" -{!../../../docs_src/additional_responses/tutorial001.py!} -``` +{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} -/// note | "Hinweis" +/// note | Hinweis Beachten Sie, dass Sie die `JSONResponse` direkt zurückgeben müssen. /// -/// info +/// info | Info Der `model`-Schlüssel ist nicht Teil von OpenAPI. @@ -171,23 +169,21 @@ Die Schemas werden von einer anderen Stelle innerhalb des OpenAPI-Schemas refere } ``` -## Zusätzliche Medientypen für die Haupt-Response +## Zusätzliche Medientypen für die Haupt-Response { #additional-media-types-for-the-main-response } Sie können denselben `responses`-Parameter verwenden, um verschiedene Medientypen für dieselbe Haupt-Response hinzuzufügen. Sie können beispielsweise einen zusätzlichen Medientyp `image/png` hinzufügen und damit deklarieren, dass Ihre *Pfadoperation* ein JSON-Objekt (mit dem Medientyp `application/json`) oder ein PNG-Bild zurückgeben kann: -```Python hl_lines="19-24 28" -{!../../../docs_src/additional_responses/tutorial002.py!} -``` +{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} -/// note | "Hinweis" +/// note | Hinweis Beachten Sie, dass Sie das Bild direkt mit einer `FileResponse` zurückgeben müssen. /// -/// info +/// info | Info Sofern Sie in Ihrem Parameter `responses` nicht explizit einen anderen Medientyp angeben, geht FastAPI davon aus, dass die Response denselben Medientyp wie die Haupt-Response-Klasse hat (Standardmäßig `application/json`). @@ -195,7 +191,7 @@ Wenn Sie jedoch eine benutzerdefinierte Response-Klasse mit `None` als Medientyp /// -## Informationen kombinieren +## Informationen kombinieren { #combining-information } Sie können auch Response-Informationen von mehreren Stellen kombinieren, einschließlich der Parameter `response_model`, `status_code` und `responses`. @@ -207,15 +203,13 @@ Sie können beispielsweise eine Response mit dem Statuscode `404` deklarieren, d Und eine Response mit dem Statuscode `200`, die Ihr `response_model` verwendet, aber ein benutzerdefiniertes Beispiel (`example`) enthält: -```Python hl_lines="20-31" -{!../../../docs_src/additional_responses/tutorial003.py!} -``` +{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} Es wird alles kombiniert und in Ihre OpenAPI eingebunden und in der API-Dokumentation angezeigt: -## Vordefinierte und benutzerdefinierte Responses kombinieren +## Vordefinierte und benutzerdefinierte Responses kombinieren { #combine-predefined-responses-and-custom-ones } Möglicherweise möchten Sie einige vordefinierte Responses haben, die für viele *Pfadoperationen* gelten, Sie möchten diese jedoch mit benutzerdefinierten Responses kombinieren, die für jede *Pfadoperation* erforderlich sind. @@ -243,13 +237,11 @@ Mit dieser Technik können Sie einige vordefinierte Responses in Ihren *Pfadoper Zum Beispiel: -```Python hl_lines="13-17 26" -{!../../../docs_src/additional_responses/tutorial004.py!} -``` +{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} -## Weitere Informationen zu OpenAPI-Responses +## Weitere Informationen zu OpenAPI-Responses { #more-information-about-openapi-responses } Um zu sehen, was genau Sie in die Responses aufnehmen können, können Sie die folgenden Abschnitte in der OpenAPI-Spezifikation überprüfen: -* OpenAPI Responses Object, enthält das `Response Object`. -* OpenAPI Response Object, Sie können alles davon direkt in jede Response innerhalb Ihres `responses`-Parameter einfügen. Einschließlich `description`, `headers`, `content` (darin deklarieren Sie verschiedene Medientypen und JSON-Schemas) und `links`. +* OpenAPI Responses Object, enthält das `Response Object`. +* OpenAPI Response Object, Sie können alles davon direkt in jede Response innerhalb Ihres `responses`-Parameter einfügen. Einschließlich `description`, `headers`, `content` (darin deklarieren Sie verschiedene Medientypen und JSON-Schemas) und `links`. diff --git a/docs/de/docs/advanced/additional-status-codes.md b/docs/de/docs/advanced/additional-status-codes.md index 672efee51c..f948e18629 100644 --- a/docs/de/docs/advanced/additional-status-codes.md +++ b/docs/de/docs/advanced/additional-status-codes.md @@ -1,72 +1,22 @@ -# Zusätzliche Statuscodes +# Zusätzliche Statuscodes { #additional-status-codes } -Standardmäßig liefert **FastAPI** die Rückgabewerte (Responses) als `JSONResponse` zurück und fügt den Inhalt der jeweiligen *Pfadoperation* in das `JSONResponse` Objekt ein. +Standardmäßig liefert **FastAPI** die Responses als `JSONResponse` zurück und fügt den Inhalt, den Sie aus Ihrer *Pfadoperation* zurückgeben, in diese `JSONResponse` ein. Es wird der Default-Statuscode oder derjenige verwendet, den Sie in Ihrer *Pfadoperation* festgelegt haben. -## Zusätzliche Statuscodes +## Zusätzliche Statuscodes { #additional-status-codes_1 } Wenn Sie neben dem Hauptstatuscode weitere Statuscodes zurückgeben möchten, können Sie dies tun, indem Sie direkt eine `Response` zurückgeben, wie etwa eine `JSONResponse`, und den zusätzlichen Statuscode direkt festlegen. Angenommen, Sie möchten eine *Pfadoperation* haben, die das Aktualisieren von Artikeln ermöglicht und bei Erfolg den HTTP-Statuscode 200 „OK“ zurückgibt. -Sie möchten aber auch, dass sie neue Artikel akzeptiert. Und wenn die Elemente vorher nicht vorhanden waren, werden diese Elemente erstellt und der HTTP-Statuscode 201 „Created“ zurückgegeben. +Sie möchten aber auch, dass sie neue Artikel akzeptiert. Und wenn die Artikel vorher nicht vorhanden waren, werden diese Artikel erstellt und der HTTP-Statuscode 201 „Created“ zurückgegeben. Um dies zu erreichen, importieren Sie `JSONResponse`, und geben Sie Ihren Inhalt direkt zurück, indem Sie den gewünschten `status_code` setzen: -//// tab | Python 3.10+ +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 26" -{!> ../../../docs_src/additional_status_codes/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="2 23" -{!> ../../../docs_src/additional_status_codes/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001.py!} -``` - -//// - -/// warning | "Achtung" +/// warning | Achtung Wenn Sie eine `Response` direkt zurückgeben, wie im obigen Beispiel, wird sie direkt zurückgegeben. @@ -76,15 +26,15 @@ Stellen Sie sicher, dass sie die gewünschten Daten enthält und dass die Werte /// -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette.responses import JSONResponse` verwenden. -**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, als Annehmlichkeit für Sie, den Entwickler. Die meisten verfügbaren Responses kommen aber direkt von Starlette. Das Gleiche gilt für `status`. +**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, als Annehmlichkeit für Sie, den Entwickler. Die meisten verfügbaren Responses kommen aber direkt von Starlette. Dasselbe gilt für `status`. /// -## OpenAPI- und API-Dokumentation +## OpenAPI- und API-Dokumentation { #openapi-and-api-docs } Wenn Sie zusätzliche Statuscodes und Responses direkt zurückgeben, werden diese nicht in das OpenAPI-Schema (die API-Dokumentation) aufgenommen, da FastAPI keine Möglichkeit hat, im Voraus zu wissen, was Sie zurückgeben werden. diff --git a/docs/de/docs/advanced/advanced-dependencies.md b/docs/de/docs/advanced/advanced-dependencies.md index f299708726..da5f28c7c2 100644 --- a/docs/de/docs/advanced/advanced-dependencies.md +++ b/docs/de/docs/advanced/advanced-dependencies.md @@ -1,6 +1,6 @@ -# Fortgeschrittene Abhängigkeiten +# Fortgeschrittene Abhängigkeiten { #advanced-dependencies } -## Parametrisierte Abhängigkeiten +## Parametrisierte Abhängigkeiten { #parameterized-dependencies } Alle Abhängigkeiten, die wir bisher gesehen haben, waren festgelegte Funktionen oder Klassen. @@ -10,119 +10,35 @@ Stellen wir uns vor, wir möchten eine Abhängigkeit haben, die prüft, ob ein Q Aber wir wollen diesen vordefinierten Inhalt per Parameter festlegen können. -## Eine „aufrufbare“ Instanz +## Eine „aufrufbare“ Instanz { #a-callable-instance } -In Python gibt es eine Möglichkeit, eine Instanz einer Klasse „aufrufbar“ („callable“) zu machen. +In Python gibt es eine Möglichkeit, eine Instanz einer Klasse „aufrufbar“ zu machen. Nicht die Klasse selbst (die bereits aufrufbar ist), sondern eine Instanz dieser Klasse. Dazu deklarieren wir eine Methode `__call__`: -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} In diesem Fall ist dieses `__call__` das, was **FastAPI** verwendet, um nach zusätzlichen Parametern und Unterabhängigkeiten zu suchen, und das ist es auch, was später aufgerufen wird, um einen Wert an den Parameter in Ihrer *Pfadoperation-Funktion* zu übergeben. -## Die Instanz parametrisieren +## Die Instanz parametrisieren { #parameterize-the-instance } -Und jetzt können wir `__init__` verwenden, um die Parameter der Instanz zu deklarieren, die wir zum `Parametrisieren` der Abhängigkeit verwenden können: +Und jetzt können wir `__init__` verwenden, um die Parameter der Instanz zu deklarieren, die wir zum „Parametrisieren“ der Abhängigkeit verwenden können: -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} In diesem Fall wird **FastAPI** `__init__` nie berühren oder sich darum kümmern, wir werden es direkt in unserem Code verwenden. -## Eine Instanz erstellen +## Eine Instanz erstellen { #create-an-instance } Wir könnten eine Instanz dieser Klasse erstellen mit: -//// tab | Python 3.9+ - -```Python hl_lines="18" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="16" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} Und auf diese Weise können wir unsere Abhängigkeit „parametrisieren“, die jetzt `"bar"` enthält, als das Attribut `checker.fixed_content`. -## Die Instanz als Abhängigkeit verwenden +## Die Instanz als Abhängigkeit verwenden { #use-the-instance-as-a-dependency } Dann könnten wir diesen `checker` in einem `Depends(checker)` anstelle von `Depends(FixedContentQueryChecker)` verwenden, da die Abhängigkeit die Instanz `checker` und nicht die Klasse selbst ist. @@ -134,37 +50,9 @@ checker(q="somequery") ... und übergibt, was immer das als Wert dieser Abhängigkeit in unserer *Pfadoperation-Funktion* zurückgibt, als den Parameter `fixed_content_included`: -//// tab | Python 3.9+ +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} -```Python hl_lines="22" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Das alles mag gekünstelt wirken. Und es ist möglicherweise noch nicht ganz klar, welchen Nutzen das hat. @@ -175,3 +63,91 @@ In den Kapiteln zum Thema Sicherheit gibt es Hilfsfunktionen, die auf die gleich Wenn Sie das hier alles verstanden haben, wissen Sie bereits, wie diese Sicherheits-Hilfswerkzeuge unter der Haube funktionieren. /// + +## Abhängigkeiten mit `yield`, `HTTPException`, `except` und Hintergrundtasks { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | Achtung + +Sie benötigen diese technischen Details höchstwahrscheinlich nicht. + +Diese Details sind hauptsächlich nützlich, wenn Sie eine FastAPI-Anwendung haben, die älter als 0.118.0 ist, und Sie auf Probleme mit Abhängigkeiten mit `yield` stoßen. + +/// + +Abhängigkeiten mit `yield` haben sich im Laufe der Zeit weiterentwickelt, um verschiedene Anwendungsfälle abzudecken und einige Probleme zu beheben, hier ist eine Zusammenfassung der Änderungen. + +### Abhängigkeiten mit `yield` und `StreamingResponse`, Technische Details { #dependencies-with-yield-and-streamingresponse-technical-details } + +Vor FastAPI 0.118.0 wurde bei Verwendung einer Abhängigkeit mit `yield` der Exit-Code nach der *Pfadoperation-Funktion* ausgeführt, aber unmittelbar bevor die Response gesendet wurde. + +Die Absicht war, Ressourcen nicht länger als nötig zu halten, während darauf gewartet wird, dass die Response durchs Netzwerk reist. + +Diese Änderung bedeutete auch, dass bei Rückgabe einer `StreamingResponse` der Exit-Code der Abhängigkeit mit `yield` bereits ausgeführt worden wäre. + +Wenn Sie beispielsweise eine Datenbanksession in einer Abhängigkeit mit `yield` hatten, konnte die `StreamingResponse` diese Session während des Streamens von Daten nicht verwenden, weil die Session im Exit-Code nach `yield` bereits geschlossen worden wäre. + +Dieses Verhalten wurde in 0.118.0 zurückgenommen, sodass der Exit-Code nach `yield` ausgeführt wird, nachdem die Response gesendet wurde. + +/// info | Info + +Wie Sie unten sehen werden, ähnelt dies sehr dem Verhalten vor Version 0.106.0, jedoch mit mehreren Verbesserungen und Bugfixes für Sonderfälle. + +/// + +#### Anwendungsfälle mit frühem Exit-Code { #use-cases-with-early-exit-code } + +Es gibt einige Anwendungsfälle mit spezifischen Bedingungen, die vom alten Verhalten profitieren könnten, den Exit-Code von Abhängigkeiten mit `yield` vor dem Senden der Response auszuführen. + +Stellen Sie sich zum Beispiel vor, Sie haben Code, der in einer Abhängigkeit mit `yield` eine Datenbanksession verwendet, nur um einen Benutzer zu verifizieren, die Datenbanksession wird aber in der *Pfadoperation-Funktion* nie wieder verwendet, sondern nur in der Abhängigkeit, und die Response benötigt lange, um gesendet zu werden, wie eine `StreamingResponse`, die Daten langsam sendet, aus irgendeinem Grund aber die Datenbank nicht verwendet. + +In diesem Fall würde die Datenbanksession gehalten, bis das Senden der Response abgeschlossen ist, aber wenn Sie sie nicht verwenden, wäre es nicht notwendig, sie zu halten. + +So könnte es aussehen: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +Der Exit-Code, das automatische Schließen der `Session` in: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +... würde ausgeführt, nachdem die Response das langsame Senden der Daten beendet: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +Da `generate_stream()` die Datenbanksession jedoch nicht verwendet, ist es nicht wirklich notwendig, die Session während des Sendens der Response offen zu halten. + +Wenn Sie diesen spezifischen Anwendungsfall mit SQLModel (oder SQLAlchemy) haben, könnten Sie die Session explizit schließen, nachdem Sie sie nicht mehr benötigen: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +Auf diese Weise würde die Session die Datenbankverbindung freigeben, sodass andere Requests sie verwenden könnten. + +Wenn Sie einen anderen Anwendungsfall haben, der ein frühes Beenden aus einer Abhängigkeit mit `yield` benötigt, erstellen Sie bitte eine GitHub-Diskussion-Frage mit Ihrem spezifischen Anwendungsfall und warum Sie von einem frühen Schließen für Abhängigkeiten mit `yield` profitieren würden. + +Wenn es überzeugende Anwendungsfälle für ein frühes Schließen bei Abhängigkeiten mit `yield` gibt, würde ich erwägen, eine neue Möglichkeit hinzuzufügen, um ein frühes Schließen optional zu aktivieren. + +### Abhängigkeiten mit `yield` und `except`, Technische Details { #dependencies-with-yield-and-except-technical-details } + +Vor FastAPI 0.110.0 war es so, dass wenn Sie eine Abhängigkeit mit `yield` verwendet und dann in dieser Abhängigkeit mit `except` eine Exception abgefangen haben und die Exception nicht erneut geworfen haben, die Exception automatisch an beliebige Exceptionhandler oder den Handler für interne Serverfehler weitergereicht/weitergeworfen wurde. + +Dies wurde in Version 0.110.0 geändert, um unbehandelten Speicherverbrauch durch weitergeleitete Exceptions ohne Handler (interne Serverfehler) zu beheben und um es mit dem Verhalten von normalem Python-Code konsistent zu machen. + +### Hintergrundtasks und Abhängigkeiten mit `yield`, Technische Details { #background-tasks-and-dependencies-with-yield-technical-details } + +Vor FastAPI 0.106.0 war das Werfen von Exceptions nach `yield` nicht möglich, der Exit-Code in Abhängigkeiten mit `yield` wurde ausgeführt, nachdem die Response gesendet wurde, sodass [Exceptionhandler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} bereits ausgeführt worden wären. + +Dies war so designt, hauptsächlich um die Verwendung derselben von Abhängigkeiten „geyieldeten“ Objekte in Hintergrundtasks zu ermöglichen, da der Exit-Code erst ausgeführt wurde, nachdem die Hintergrundtasks abgeschlossen waren. + +Dies wurde in FastAPI 0.106.0 geändert mit der Absicht, keine Ressourcen zu halten, während darauf gewartet wird, dass die Response durchs Netzwerk reist. + +/// tip | Tipp + +Zusätzlich ist ein Hintergrundtask normalerweise ein unabhängiger Logikblock, der separat gehandhabt werden sollte, mit eigenen Ressourcen (z. B. eigener Datenbankverbindung). + +So haben Sie wahrscheinlich saubereren Code. + +/// + +Wenn Sie sich bisher auf dieses Verhalten verlassen haben, sollten Sie jetzt die Ressourcen für Hintergrundtasks innerhalb des Hintergrundtasks selbst erstellen und intern nur Daten verwenden, die nicht von den Ressourcen von Abhängigkeiten mit `yield` abhängen. + +Anstatt beispielsweise dieselbe Datenbanksession zu verwenden, würden Sie innerhalb des Hintergrundtasks eine neue Datenbanksession erstellen und die Objekte aus der Datenbank mithilfe dieser neuen Session beziehen. Und anstatt das Objekt aus der Datenbank als Parameter an die Hintergrundtask-Funktion zu übergeben, würden Sie die ID dieses Objekts übergeben und das Objekt dann innerhalb der Hintergrundtask-Funktion erneut beziehen. diff --git a/docs/de/docs/advanced/async-tests.md b/docs/de/docs/advanced/async-tests.md index 9f0bd4aa2e..ad82452052 100644 --- a/docs/de/docs/advanced/async-tests.md +++ b/docs/de/docs/advanced/async-tests.md @@ -1,24 +1,24 @@ -# Asynchrone Tests +# Asynchrone Tests { #async-tests } -Sie haben bereits gesehen, wie Sie Ihre **FastAPI**-Anwendungen mit dem bereitgestellten `TestClient` testen. Bisher haben Sie nur gesehen, wie man synchrone Tests schreibt, ohne `async`hrone Funktionen zu verwenden. +Sie haben bereits gesehen, wie Sie Ihre **FastAPI**-Anwendungen mit dem bereitgestellten `TestClient` testen. Bisher haben Sie nur gesehen, wie man synchrone Tests schreibt, ohne `async`-Funktionen zu verwenden. -Die Möglichkeit, in Ihren Tests asynchrone Funktionen zu verwenden, könnte beispielsweise nützlich sein, wenn Sie Ihre Datenbank asynchron abfragen. Stellen Sie sich vor, Sie möchten das Senden von Requests an Ihre FastAPI-Anwendung testen und dann überprüfen, ob Ihr Backend die richtigen Daten erfolgreich in die Datenbank geschrieben hat, während Sie eine asynchrone Datenbankbibliothek verwenden. +Die Möglichkeit, in Ihren Tests asynchrone Funktionen zu verwenden, könnte beispielsweise nützlich sein, wenn Sie Ihre Datenbank asynchron abfragen. Stellen Sie sich vor, Sie möchten das Senden von Requests an Ihre FastAPI-Anwendung testen und dann überprüfen, ob Ihr Backend die richtigen Daten erfolgreich in die Datenbank geschrieben hat, während Sie eine asynchrone Datenbankbibliothek verwenden. Schauen wir uns an, wie wir das machen können. -## pytest.mark.anyio +## pytest.mark.anyio { #pytest-mark-anyio } Wenn wir in unseren Tests asynchrone Funktionen aufrufen möchten, müssen unsere Testfunktionen asynchron sein. AnyIO stellt hierfür ein nettes Plugin zur Verfügung, mit dem wir festlegen können, dass einige Testfunktionen asynchron aufgerufen werden sollen. -## HTTPX +## HTTPX { #httpx } -Auch wenn Ihre **FastAPI**-Anwendung normale `def`-Funktionen anstelle von `async def` verwendet, handelt es sich darunter immer noch um eine `async`hrone Anwendung. +Auch wenn Ihre **FastAPI**-Anwendung normale `def`-Funktionen anstelle von `async def` verwendet, handelt es sich darunter immer noch um eine `async`-Anwendung. -Der `TestClient` macht unter der Haube magisches, um die asynchrone FastAPI-Anwendung in Ihren normalen `def`-Testfunktionen, mithilfe von Standard-Pytest aufzurufen. Aber diese Magie funktioniert nicht mehr, wenn wir sie in asynchronen Funktionen verwenden. Durch die asynchrone Ausführung unserer Tests können wir den `TestClient` nicht mehr in unseren Testfunktionen verwenden. +Der `TestClient` betreibt unter der Haube etwas Magie, um die asynchrone FastAPI-Anwendung in Ihren normalen `def`-Testfunktionen, mithilfe von Standard-Pytest aufzurufen. Aber diese Magie funktioniert nicht mehr, wenn wir sie in asynchronen Funktionen verwenden. Durch die asynchrone Ausführung unserer Tests können wir den `TestClient` nicht mehr in unseren Testfunktionen verwenden. -Der `TestClient` basiert auf HTTPX und glücklicherweise können wir ihn direkt verwenden, um die API zu testen. +Der `TestClient` basiert auf HTTPX und glücklicherweise können wir es direkt verwenden, um die API zu testen. -## Beispiel +## Beispiel { #example } Betrachten wir als einfaches Beispiel eine Dateistruktur ähnlich der in [Größere Anwendungen](../tutorial/bigger-applications.md){.internal-link target=_blank} und [Testen](../tutorial/testing.md){.internal-link target=_blank}: @@ -32,17 +32,13 @@ Betrachten wir als einfaches Beispiel eine Dateistruktur ähnlich der in [Größ Die Datei `main.py` hätte als Inhalt: -```Python -{!../../../docs_src/async_tests/main.py!} -``` +{* ../../docs_src/async_tests/main.py *} Die Datei `test_main.py` hätte die Tests für `main.py`, das könnte jetzt so aussehen: -```Python -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py *} -## Es ausführen +## Es ausführen { #run-it } Sie können Ihre Tests wie gewohnt ausführen mit: @@ -56,15 +52,13 @@ $ pytest -## Details +## Im Detail { #in-detail } Der Marker `@pytest.mark.anyio` teilt pytest mit, dass diese Testfunktion asynchron aufgerufen werden soll: -```Python hl_lines="7" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[7] *} -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass die Testfunktion jetzt `async def` ist und nicht nur `def` wie zuvor, wenn Sie den `TestClient` verwenden. @@ -72,9 +66,7 @@ Beachten Sie, dass die Testfunktion jetzt `async def` ist und nicht nur `def` wi Dann können wir einen `AsyncClient` mit der App erstellen und mit `await` asynchrone Requests an ihn senden. -```Python hl_lines="9-10" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} Das ist das Äquivalent zu: @@ -84,23 +76,23 @@ response = client.get('/') ... welches wir verwendet haben, um unsere Requests mit dem `TestClient` zu machen. -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass wir async/await mit dem neuen `AsyncClient` verwenden – der Request ist asynchron. /// -/// warning | "Achtung" +/// warning | Achtung Falls Ihre Anwendung auf Lifespan-Events angewiesen ist, der `AsyncClient` löst diese Events nicht aus. Um sicherzustellen, dass sie ausgelöst werden, verwenden Sie `LifespanManager` von florimondmanca/asgi-lifespan. /// -## Andere asynchrone Funktionsaufrufe +## Andere asynchrone Funktionsaufrufe { #other-asynchronous-function-calls } -Da die Testfunktion jetzt asynchron ist, können Sie in Ihren Tests neben dem Senden von Requests an Ihre FastAPI-Anwendung jetzt auch andere `async`hrone Funktionen aufrufen (und `await`en), genau so, wie Sie diese an anderer Stelle in Ihrem Code aufrufen würden. +Da die Testfunktion jetzt asynchron ist, können Sie in Ihren Tests neben dem Senden von Requests an Ihre FastAPI-Anwendung jetzt auch andere `async`-Funktionen aufrufen (und `await`en), genau so, wie Sie diese an anderer Stelle in Ihrem Code aufrufen würden. -/// tip | "Tipp" +/// tip | Tipp Wenn Sie einen `RuntimeError: Task attached to a different loop` erhalten, wenn Sie asynchrone Funktionsaufrufe in Ihre Tests integrieren (z. B. bei Verwendung von MongoDBs MotorClient), dann denken Sie daran, Objekte zu instanziieren, die einen Event Loop nur innerhalb asynchroner Funktionen benötigen, z. B. einen `@app.on_event("startup")`-Callback. diff --git a/docs/de/docs/advanced/behind-a-proxy.md b/docs/de/docs/advanced/behind-a-proxy.md index 18f90ebde8..036916cbe2 100644 --- a/docs/de/docs/advanced/behind-a-proxy.md +++ b/docs/de/docs/advanced/behind-a-proxy.md @@ -1,34 +1,129 @@ -# Hinter einem Proxy +# Hinter einem Proxy { #behind-a-proxy } -In manchen Situationen müssen Sie möglicherweise einen **Proxy**-Server wie Traefik oder Nginx verwenden, mit einer Konfiguration, die ein zusätzliches Pfadpräfix hinzufügt, das von Ihrer Anwendung nicht gesehen wird. +In vielen Situationen würden Sie einen **Proxy** wie Traefik oder Nginx vor Ihrer FastAPI-App verwenden. -In diesen Fällen können Sie `root_path` verwenden, um Ihre Anwendung zu konfigurieren. +Diese Proxys könnten HTTPS-Zertifikate und andere Dinge handhaben. -Der `root_path` („Wurzelpfad“) ist ein Mechanismus, der von der ASGI-Spezifikation bereitgestellt wird (auf der FastAPI via Starlette aufbaut). +## Proxy-Forwarded-Header { #proxy-forwarded-headers } + +Ein **Proxy** vor Ihrer Anwendung würde normalerweise einige Header on-the-fly setzen, bevor er die Requests an den **Server** sendet, um den Server wissen zu lassen, dass der Request vom Proxy **weitergeleitet** wurde, einschließlich der ursprünglichen (öffentlichen) URL, inklusive der Domain, dass HTTPS verwendet wird, usw. + +Das **Server**-Programm (z. B. **Uvicorn** via **FastAPI CLI**) ist in der Lage, diese Header zu interpretieren und diese Information dann an Ihre Anwendung weiterzugeben. + +Aber aus Sicherheitsgründen, da der Server nicht weiß, dass er hinter einem vertrauenswürdigen Proxy läuft, wird er diese Header nicht interpretieren. + +/// note | Technische Details + +Die Proxy-Header sind: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### Proxy-Forwarded-Header aktivieren { #enable-proxy-forwarded-headers } + +Sie können FastAPI CLI mit der *CLI-Option* `--forwarded-allow-ips` starten und die IP-Adressen übergeben, denen vertraut werden soll, um diese Forwarded-Header zu lesen. + +Wenn Sie es auf `--forwarded-allow-ips="*"` setzen, würde es allen eingehenden IPs vertrauen. + +Wenn Ihr **Server** hinter einem vertrauenswürdigen **Proxy** sitzt und nur der Proxy mit ihm spricht, würde dies dazu führen, dass er die IP dieses **Proxys** akzeptiert, was auch immer sie ist. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Weiterleitungen mit HTTPS { #redirects-with-https } + +Angenommen, Sie definieren eine *Pfadoperation* `/items/`: + +{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} + +Wenn der Client versucht, zu `/items` zu gehen, würde er standardmäßig zu `/items/` umgeleitet. + +Aber bevor Sie die *CLI-Option* `--forwarded-allow-ips` setzen, könnte er zu `http://localhost:8000/items/` umleiten. + +Aber möglicherweise wird Ihre Anwendung unter `https://mysuperapp.com` gehostet, und die Weiterleitung sollte zu `https://mysuperapp.com/items/` erfolgen. + +Durch Setzen von `--proxy-headers` kann FastAPI jetzt an den richtigen Ort umleiten. 😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | Tipp + +Wenn Sie mehr über HTTPS erfahren möchten, lesen Sie den Leitfaden [Über HTTPS](../deployment/https.md){.internal-link target=_blank}. + +/// + +### Wie Proxy-Forwarded-Header funktionieren + +Hier ist eine visuelle Darstellung, wie der **Proxy** weitergeleitete Header zwischen dem Client und dem **Anwendungsserver** hinzufügt: + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Loadbalancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS-Request
Host: mysuperapp.com
Pfad: /items + + Note over Proxy: Proxy fügt Forwarded-Header hinzu + + Proxy->>Server: HTTP-Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Pfad: /items + + Note over Server: Server interpretiert die Header
(wenn --forwarded-allow-ips gesetzt ist) + + Server->>Proxy: HTTP-Response
mit correkten HTTPS-URLs + + Proxy->>Client: HTTPS-Response +``` + +Der **Proxy** fängt den ursprünglichen Client-Request ab und fügt die speziellen *Forwarded*-Header (`X-Forwarded-*`) hinzu, bevor er den Request an den **Anwendungsserver** weitergibt. + +Diese Header bewahren Informationen über den ursprünglichen Request, die sonst verloren gingen: + +* **X-Forwarded-For**: Die ursprüngliche IP-Adresse des Clients +* **X-Forwarded-Proto**: Das ursprüngliche Protokoll (`https`) +* **X-Forwarded-Host**: Der ursprüngliche Host (`mysuperapp.com`) + +Wenn **FastAPI CLI** mit `--forwarded-allow-ips` konfiguriert ist, vertraut es diesen Headern und verwendet sie, z. B. um die korrekten URLs in Weiterleitungen zu erzeugen. + +## Proxy mit einem abgetrennten Pfadpräfix { #proxy-with-a-stripped-path-prefix } + +Sie könnten einen Proxy haben, der Ihrer Anwendung ein Pfadpräfix hinzufügt. + +In diesen Fällen können Sie `root_path` verwenden, um Ihre Anwendung zu konfigurieren. + +Der `root_path` ist ein Mechanismus, der von der ASGI-Spezifikation bereitgestellt wird (auf der FastAPI via Starlette aufbaut). Der `root_path` wird verwendet, um diese speziellen Fälle zu handhaben. Und er wird auch intern beim Mounten von Unteranwendungen verwendet. -## Proxy mit einem abgetrennten Pfadpräfix - Ein Proxy mit einem abgetrennten Pfadpräfix bedeutet in diesem Fall, dass Sie einen Pfad unter `/app` in Ihrem Code deklarieren könnten, dann aber, eine Ebene darüber, den Proxy hinzufügen, der Ihre **FastAPI**-Anwendung unter einem Pfad wie `/api/v1` platziert. In diesem Fall würde der ursprüngliche Pfad `/app` tatsächlich unter `/api/v1/app` bereitgestellt. Auch wenn Ihr gesamter Code unter der Annahme geschrieben ist, dass es nur `/app` gibt. -```Python hl_lines="6" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} -Und der Proxy würde das **Pfadpräfix** on-the-fly **"entfernen**", bevor er die Anfrage an Uvicorn übermittelt, dafür sorgend, dass Ihre Anwendung davon überzeugt ist, dass sie unter `/app` bereitgestellt wird, sodass Sie nicht Ihren gesamten Code dahingehend aktualisieren müssen, das Präfix `/api/v1` zu verwenden. +Und der Proxy würde das **Pfadpräfix** on-the-fly **„entfernen“**, bevor er den Request an den Anwendungsserver (wahrscheinlich Uvicorn via FastAPI CLI) übermittelt, dafür sorgend, dass Ihre Anwendung davon überzeugt ist, dass sie unter `/app` bereitgestellt wird, sodass Sie nicht Ihren gesamten Code dahingehend aktualisieren müssen, das Präfix `/api/v1` zu verwenden. Bis hierher würde alles wie gewohnt funktionieren. Wenn Sie dann jedoch die Benutzeroberfläche der integrierten Dokumentation (das Frontend) öffnen, wird angenommen, dass sich das OpenAPI-Schema unter `/openapi.json` anstelle von `/api/v1/openapi.json` befindet. -Das Frontend (das im Browser läuft) würde also versuchen, `/openapi.json` zu erreichen und wäre nicht in der Lage, das OpenAPI-Schema abzurufen. +Also würde das Frontend (das im Browser läuft) versuchen, `/openapi.json` zu erreichen und wäre nicht in der Lage, das OpenAPI-Schema abzurufen. Da wir für unsere Anwendung einen Proxy mit dem Pfadpräfix `/api/v1` haben, muss das Frontend das OpenAPI-Schema unter `/api/v1/openapi.json` abrufen. @@ -43,7 +138,7 @@ browser --> proxy proxy --> server ``` -/// tip | "Tipp" +/// tip | Tipp Die IP `0.0.0.0` wird üblicherweise verwendet, um anzudeuten, dass das Programm alle auf diesem Computer/Server verfügbaren IPs abhört. @@ -66,16 +161,16 @@ Die Benutzeroberfläche der Dokumentation würde benötigen, dass das OpenAPI-Sc } ``` -In diesem Beispiel könnte der „Proxy“ etwa **Traefik** sein. Und der Server wäre so etwas wie **Uvicorn**, auf dem Ihre FastAPI-Anwendung ausgeführt wird. +In diesem Beispiel könnte der „Proxy“ etwa **Traefik** sein. Und der Server wäre etwas wie FastAPI CLI mit **Uvicorn**, auf dem Ihre FastAPI-Anwendung ausgeführt wird. -### Bereitstellung des `root_path` +### Bereitstellung des `root_path` { #providing-the-root-path } Um dies zu erreichen, können Sie die Kommandozeilenoption `--root-path` wie folgt verwenden:
```console -$ uvicorn main:app --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -84,7 +179,7 @@ $ uvicorn main:app --root-path /api/v1 Falls Sie Hypercorn verwenden, das hat auch die Option `--root-path`. -/// note | "Technische Details" +/// note | Technische Details Die ASGI-Spezifikation definiert einen `root_path` für diesen Anwendungsfall. @@ -92,29 +187,27 @@ Und die Kommandozeilenoption `--root-path` stellt diesen `root_path` bereit. /// -### Überprüfen des aktuellen `root_path` +### Testen des aktuellen `root_path` { #checking-the-current-root-path } -Sie können den aktuellen `root_path` abrufen, der von Ihrer Anwendung für jede Anfrage verwendet wird. Er ist Teil des `scope`-Dictionarys (das ist Teil der ASGI-Spezifikation). +Sie können den aktuellen `root_path` abrufen, der von Ihrer Anwendung für jeden Request verwendet wird. Er ist Teil des `scope`-Dictionarys (das ist Teil der ASGI-Spezifikation). Hier fügen wir ihn, nur zu Demonstrationszwecken, in die Nachricht ein. -```Python hl_lines="8" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} Wenn Sie Uvicorn dann starten mit:
```console -$ uvicorn main:app --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-wäre die Response etwa: +wäre die Response etwa: ```JSON { @@ -123,21 +216,19 @@ wäre die Response etwa: } ``` -### Festlegen des `root_path` in der FastAPI-Anwendung +### Festlegen des `root_path` in der FastAPI-Anwendung { #setting-the-root-path-in-the-fastapi-app } -Falls Sie keine Möglichkeit haben, eine Kommandozeilenoption wie `--root-path` oder ähnlich zu übergeben, können Sie als Alternative beim Erstellen Ihrer FastAPI-Anwendung den Parameter `root_path` setzen: +Falls Sie keine Möglichkeit haben, eine Kommandozeilenoption wie `--root-path` oder ähnlich zu übergeben, können Sie, alternativ dazu, beim Erstellen Ihrer FastAPI-Anwendung den Parameter `root_path` setzen: -```Python hl_lines="3" -{!../../../docs_src/behind_a_proxy/tutorial002.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} Die Übergabe des `root_path` an `FastAPI` wäre das Äquivalent zur Übergabe der `--root-path`-Kommandozeilenoption an Uvicorn oder Hypercorn. -### Über `root_path` +### Über `root_path` { #about-root-path } -Beachten Sie, dass der Server (Uvicorn) diesen `root_path` für nichts anderes außer die Weitergabe an die Anwendung verwendet. +Beachten Sie, dass der Server (Uvicorn) diesen `root_path` für nichts anderes verwendet als für die Weitergabe an die Anwendung. -Aber wenn Sie mit Ihrem Browser auf http://127.0.0.1:8000/app gehen, sehen Sie die normale Antwort: +Aber wenn Sie mit Ihrem Browser auf http://127.0.0.1:8000/app gehen, sehen Sie die normale Response: ```JSON { @@ -150,17 +241,17 @@ Es wird also nicht erwartet, dass unter `http://127.0.0.1:8000/api/v1/app` darau Uvicorn erwartet, dass der Proxy unter `http://127.0.0.1:8000/app` auf Uvicorn zugreift, und dann liegt es in der Verantwortung des Proxys, das zusätzliche `/api/v1`-Präfix darüber hinzuzufügen. -## Über Proxys mit einem abgetrennten Pfadpräfix +## Über Proxys mit einem abgetrennten Pfadpräfix { #about-proxies-with-a-stripped-path-prefix } -Bedenken Sie, dass ein Proxy mit abgetrennten Pfadpräfix nur eine von vielen Konfigurationsmöglichkeiten ist. +Bedenken Sie, dass ein Proxy mit abgetrenntem Pfadpräfix nur eine von vielen Konfigurationsmöglichkeiten ist. Wahrscheinlich wird in vielen Fällen die Standardeinstellung sein, dass der Proxy kein abgetrenntes Pfadpräfix hat. -In einem solchen Fall (ohne ein abgetrenntes Pfadpräfix) würde der Proxy auf etwas wie `https://myawesomeapp.com` lauschen, und wenn der Browser dann zu `https://myawesomeapp.com/api/v1/` wechselt, und Ihr Server (z. B. Uvicorn) auf `http://127.0.0.1:8000` lauscht, würde der Proxy (ohne ein abgetrenntes Pfadpräfix) über denselben Pfad auf Uvicorn zugreifen: `http://127.0.0.1:8000/api/v1/app`. +In einem solchen Fall (ohne ein abgetrenntes Pfadpräfix) würde der Proxy auf etwas wie `https://myawesomeapp.com` lauschen, und wenn der Browser dann zu `https://myawesomeapp.com/api/v1/app` wechselt, und Ihr Server (z. B. Uvicorn) auf `http://127.0.0.1:8000` lauscht, würde der Proxy (ohne ein abgetrenntes Pfadpräfix) über denselben Pfad auf Uvicorn zugreifen: `http://127.0.0.1:8000/api/v1/app`. -## Lokal testen mit Traefik +## Lokal testen mit Traefik { #testing-locally-with-traefik } -Sie können das Experiment mit einem abgetrennten Pfadpräfix ganz einfach lokal ausführen, indem Sie Traefik verwenden. +Sie können das Experiment mit einem abgetrennten Pfadpräfix einfach lokal ausführen, indem Sie Traefik verwenden. Laden Sie Traefik herunter, es ist eine einzelne Binärdatei, Sie können die komprimierte Datei extrahieren und sie direkt vom Terminal aus ausführen. @@ -178,7 +269,7 @@ Dann erstellen Sie eine Datei `traefik.toml` mit: Dadurch wird Traefik angewiesen, Port 9999 abzuhören und eine andere Datei `routes.toml` zu verwenden. -/// tip | "Tipp" +/// tip | Tipp Wir verwenden Port 9999 anstelle des Standard-HTTP-Ports 80, damit Sie ihn nicht mit Administratorrechten (`sudo`) ausführen müssen. @@ -211,7 +302,7 @@ Erstellen Sie nun die andere Datei `routes.toml`: Diese Datei konfiguriert Traefik, das Pfadpräfix `/api/v1` zu verwenden. -Und dann leitet Traefik seine Anfragen an Ihren Uvicorn weiter, der unter `http://127.0.0.1:8000` läuft. +Und dann leitet Traefik seine Requests an Ihren Uvicorn weiter, der unter `http://127.0.0.1:8000` läuft. Starten Sie nun Traefik: @@ -230,14 +321,14 @@ Und jetzt starten Sie Ihre Anwendung mit Uvicorn, indem Sie die Option `--root-p
```console -$ uvicorn main:app --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-### Die Responses betrachten +### Die Responses testen { #check-the-responses } Wenn Sie nun zur URL mit dem Port für Uvicorn gehen: http://127.0.0.1:8000/app, sehen Sie die normale Response: @@ -248,7 +339,7 @@ Wenn Sie nun zur URL mit dem Port für Uvicorn gehen: -/// tip | "Tipp" +/// tip | Tipp Die Dokumentationsoberfläche interagiert mit dem von Ihnen ausgewählten Server. /// -### Den automatischen Server von `root_path` deaktivieren +### Den automatischen Server von `root_path` deaktivieren { #disable-automatic-server-from-root-path } Wenn Sie nicht möchten, dass **FastAPI** einen automatischen Server inkludiert, welcher `root_path` verwendet, können Sie den Parameter `root_path_in_servers=False` verwenden: -```Python hl_lines="9" -{!../../../docs_src/behind_a_proxy/tutorial004.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} Dann wird er nicht in das OpenAPI-Schema aufgenommen. -## Mounten einer Unteranwendung +## Mounten einer Unteranwendung { #mounting-a-sub-application } Wenn Sie gleichzeitig eine Unteranwendung mounten (wie beschrieben in [Unteranwendungen – Mounts](sub-applications.md){.internal-link target=_blank}) und einen Proxy mit `root_path` verwenden wollen, können Sie das normal tun, wie Sie es erwarten würden. diff --git a/docs/de/docs/advanced/custom-response.md b/docs/de/docs/advanced/custom-response.md index 20d6a039a8..8714086e59 100644 --- a/docs/de/docs/advanced/custom-response.md +++ b/docs/de/docs/advanced/custom-response.md @@ -1,40 +1,38 @@ -# Benutzerdefinierte Response – HTML, Stream, Datei, andere +# Benutzerdefinierte Response – HTML, Stream, Datei, andere { #custom-response-html-stream-file-others } -Standardmäßig gibt **FastAPI** die Responses mittels `JSONResponse` zurück. +Standardmäßig gibt **FastAPI** die Responses mittels `JSONResponse` zurück. -Sie können das überschreiben, indem Sie direkt eine `Response` zurückgeben, wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link target=_blank} gezeigt. +Sie können dies überschreiben, indem Sie direkt eine `Response` zurückgeben, wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link target=_blank} gezeigt. -Wenn Sie jedoch direkt eine `Response` zurückgeben, werden die Daten nicht automatisch konvertiert und die Dokumentation wird nicht automatisch generiert (zum Beispiel wird der spezifische „Medientyp“, der im HTTP-Header `Content-Type` angegeben ist, nicht Teil der generierten OpenAPI). +Wenn Sie jedoch direkt eine `Response` (oder eine Unterklasse wie `JSONResponse`) zurückgeben, werden die Daten nicht automatisch konvertiert (selbst wenn Sie ein `response_model` deklariert haben), und die Dokumentation wird nicht automatisch generiert (zum Beispiel wird der spezifische „Medientyp“, der im HTTP-Header `Content-Type` angegeben ist, nicht Teil der generierten OpenAPI). -Sie können aber auch die `Response`, die Sie verwenden möchten, im *Pfadoperation-Dekorator* deklarieren. +Sie können jedoch auch die `Response`, die Sie verwenden möchten (z. B. jede `Response`-Unterklasse), im *Pfadoperation-Dekorator* mit dem `response_class`-Parameter deklarieren. Der Inhalt, den Sie von Ihrer *Pfadoperation-Funktion* zurückgeben, wird in diese `Response` eingefügt. Und wenn diese `Response` einen JSON-Medientyp (`application/json`) hat, wie es bei `JSONResponse` und `UJSONResponse` der Fall ist, werden die von Ihnen zurückgegebenen Daten automatisch mit jedem Pydantic `response_model` konvertiert (und gefiltert), das Sie im *Pfadoperation-Dekorator* deklariert haben. -/// note | "Hinweis" +/// note | Hinweis Wenn Sie eine Response-Klasse ohne Medientyp verwenden, erwartet FastAPI, dass Ihre Response keinen Inhalt hat, und dokumentiert daher das Format der Response nicht in deren generierter OpenAPI-Dokumentation. /// -## `ORJSONResponse` verwenden +## `ORJSONResponse` verwenden { #use-orjsonresponse } -Um beispielsweise noch etwas Leistung herauszuholen, können Sie `orjson` installieren und verwenden, und die Response als `ORJSONResponse` deklarieren. +Um beispielsweise noch etwas Leistung herauszuholen, können Sie `orjson` installieren und die Response als `ORJSONResponse` setzen. -Importieren Sie die `Response`-Klasse (-Unterklasse), die Sie verwenden möchten, und deklarieren Sie sie im *Pfadoperation-Dekorator*. +Importieren Sie die `Response`-Klasse (Unterklasse), die Sie verwenden möchten, und deklarieren Sie sie im *Pfadoperation-Dekorator*. -Bei umfangreichen Responses ist die direkte Rückgabe einer `Response` viel schneller als ein Dictionary zurückzugeben. +Bei umfangreichen Responses ist die direkte Rückgabe einer `Response` wesentlich schneller als ein Dictionary zurückzugeben. Das liegt daran, dass FastAPI standardmäßig jedes enthaltene Element überprüft und sicherstellt, dass es als JSON serialisierbar ist, und zwar unter Verwendung desselben [JSON-kompatiblen Encoders](../tutorial/encoder.md){.internal-link target=_blank}, der im Tutorial erläutert wurde. Dadurch können Sie **beliebige Objekte** zurückgeben, zum Beispiel Datenbankmodelle. Wenn Sie jedoch sicher sind, dass der von Ihnen zurückgegebene Inhalt **mit JSON serialisierbar** ist, können Sie ihn direkt an die Response-Klasse übergeben und die zusätzliche Arbeit vermeiden, die FastAPI hätte, indem es Ihren zurückgegebenen Inhalt durch den `jsonable_encoder` leitet, bevor es ihn an die Response-Klasse übergibt. -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001b.py!} -``` +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} -/// info +/// info | Info Der Parameter `response_class` wird auch verwendet, um den „Medientyp“ der Response zu definieren. @@ -44,24 +42,22 @@ Und er wird als solcher in OpenAPI dokumentiert. /// -/// tip | "Tipp" +/// tip | Tipp -Die `ORJSONResponse` ist derzeit nur in FastAPI verfügbar, nicht in Starlette. +Die `ORJSONResponse` ist nur in FastAPI verfügbar, nicht in Starlette. /// -## HTML-Response +## HTML-Response { #html-response } Um eine Response mit HTML direkt von **FastAPI** zurückzugeben, verwenden Sie `HTMLResponse`. * Importieren Sie `HTMLResponse`. * Übergeben Sie `HTMLResponse` als den Parameter `response_class` Ihres *Pfadoperation-Dekorators*. -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial002.py!} -``` +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} -/// info +/// info | Info Der Parameter `response_class` wird auch verwendet, um den „Medientyp“ der Response zu definieren. @@ -71,41 +67,37 @@ Und er wird als solcher in OpenAPI dokumentiert. /// -### Eine `Response` zurückgeben +### Eine `Response` zurückgeben { #return-a-response } Wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link target=_blank} gezeigt, können Sie die Response auch direkt in Ihrer *Pfadoperation* überschreiben, indem Sie diese zurückgeben. Das gleiche Beispiel von oben, das eine `HTMLResponse` zurückgibt, könnte so aussehen: -```Python hl_lines="2 7 19" -{!../../../docs_src/custom_response/tutorial003.py!} -``` +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} -/// warning | "Achtung" +/// warning | Achtung Eine `Response`, die direkt von Ihrer *Pfadoperation-Funktion* zurückgegeben wird, wird in OpenAPI nicht dokumentiert (zum Beispiel wird der `Content-Type` nicht dokumentiert) und ist in der automatischen interaktiven Dokumentation nicht sichtbar. /// -/// info +/// info | Info Natürlich stammen der eigentliche `Content-Type`-Header, der Statuscode, usw., aus dem `Response`-Objekt, das Sie zurückgegeben haben. /// -### In OpenAPI dokumentieren und `Response` überschreiben +### In OpenAPI dokumentieren und `Response` überschreiben { #document-in-openapi-and-override-response } Wenn Sie die Response innerhalb der Funktion überschreiben und gleichzeitig den „Medientyp“ in OpenAPI dokumentieren möchten, können Sie den `response_class`-Parameter verwenden UND ein `Response`-Objekt zurückgeben. -Die `response_class` wird dann nur zur Dokumentation der OpenAPI-Pfadoperation* verwendet, Ihre `Response` wird jedoch unverändert verwendet. +Die `response_class` wird dann nur zur Dokumentation der OpenAPI-*Pfadoperation* verwendet, Ihre `Response` wird jedoch unverändert verwendet. -#### Eine `HTMLResponse` direkt zurückgeben +#### Eine `HTMLResponse` direkt zurückgeben { #return-an-htmlresponse-directly } Es könnte zum Beispiel so etwas sein: -```Python hl_lines="7 21 23" -{!../../../docs_src/custom_response/tutorial004.py!} -``` +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} In diesem Beispiel generiert die Funktion `generate_html_response()` bereits eine `Response` und gibt sie zurück, anstatt das HTML in einem `str` zurückzugeben. @@ -115,13 +107,13 @@ Aber da Sie die `HTMLResponse` auch in der `response_class` übergeben haben, we -## Verfügbare Responses +## Verfügbare Responses { #available-responses } Hier sind einige der verfügbaren Responses. Bedenken Sie, dass Sie `Response` verwenden können, um alles andere zurückzugeben, oder sogar eine benutzerdefinierte Unterklasse zu erstellen. -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette.responses import HTMLResponse` verwenden. @@ -129,7 +121,7 @@ Sie können auch `from starlette.responses import HTMLResponse` verwenden. /// -### `Response` +### `Response` { #response } Die Hauptklasse `Response`, alle anderen Responses erben von ihr. @@ -144,70 +136,71 @@ Sie akzeptiert die folgenden Parameter: FastAPI (eigentlich Starlette) fügt automatisch einen Content-Length-Header ein. Außerdem wird es einen Content-Type-Header einfügen, der auf dem media_type basiert, und für Texttypen einen Zeichensatz (charset) anfügen. -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} -### `HTMLResponse` +### `HTMLResponse` { #htmlresponse } Nimmt Text oder Bytes entgegen und gibt eine HTML-Response zurück, wie Sie oben gelesen haben. -### `PlainTextResponse` +### `PlainTextResponse` { #plaintextresponse } Nimmt Text oder Bytes entgegen und gibt eine Plain-Text-Response zurück. -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial005.py!} -``` +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} -### `JSONResponse` +### `JSONResponse` { #jsonresponse } Nimmt einige Daten entgegen und gibt eine `application/json`-codierte Response zurück. Dies ist die Standard-Response, die in **FastAPI** verwendet wird, wie Sie oben gelesen haben. -### `ORJSONResponse` +### `ORJSONResponse` { #orjsonresponse } Eine schnelle alternative JSON-Response mit `orjson`, wie Sie oben gelesen haben. -### `UJSONResponse` +/// info | Info + +Dazu muss `orjson` installiert werden, z. B. mit `pip install orjson`. + +/// + +### `UJSONResponse` { #ujsonresponse } Eine alternative JSON-Response mit `ujson`. -/// warning | "Achtung" +/// info | Info + +Dazu muss `ujson` installiert werden, z. B. mit `pip install ujson`. + +/// + +/// warning | Achtung `ujson` ist bei der Behandlung einiger Sonderfälle weniger sorgfältig als Pythons eingebaute Implementierung. /// -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001.py!} -``` +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} -/// tip | "Tipp" +/// tip | Tipp Möglicherweise ist `ORJSONResponse` eine schnellere Alternative. /// -### `RedirectResponse` +### `RedirectResponse` { #redirectresponse } Gibt eine HTTP-Weiterleitung (HTTP-Redirect) zurück. Verwendet standardmäßig den Statuscode 307 – Temporäre Weiterleitung (Temporary Redirect). Sie können eine `RedirectResponse` direkt zurückgeben: -```Python hl_lines="2 9" -{!../../../docs_src/custom_response/tutorial006.py!} -``` +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} --- Oder Sie können sie im Parameter `response_class` verwenden: - -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial006b.py!} -``` +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} Wenn Sie das tun, können Sie die URL direkt von Ihrer *Pfadoperation*-Funktion zurückgeben. @@ -217,45 +210,39 @@ In diesem Fall ist der verwendete `status_code` der Standardcode für die `Redir Sie können den Parameter `status_code` auch in Kombination mit dem Parameter `response_class` verwenden: -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial006c.py!} -``` +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} -### `StreamingResponse` +### `StreamingResponse` { #streamingresponse } Nimmt einen asynchronen Generator oder einen normalen Generator/Iterator und streamt den Responsebody. -```Python hl_lines="2 14" -{!../../../docs_src/custom_response/tutorial007.py!} -``` +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} -#### Verwendung von `StreamingResponse` mit dateiähnlichen Objekten +#### Verwendung von `StreamingResponse` mit dateiartigen Objekten { #using-streamingresponse-with-file-like-objects } -Wenn Sie ein dateiähnliches (file-like) Objekt haben (z. B. das von `open()` zurückgegebene Objekt), können Sie eine Generatorfunktion erstellen, um über dieses dateiähnliche Objekt zu iterieren. +Wenn Sie ein dateiartiges (file-like) Objekt haben (z. B. das von `open()` zurückgegebene Objekt), können Sie eine Generatorfunktion erstellen, um über dieses dateiartige Objekt zu iterieren. Auf diese Weise müssen Sie nicht alles zuerst in den Arbeitsspeicher lesen und können diese Generatorfunktion an `StreamingResponse` übergeben und zurückgeben. Das umfasst viele Bibliotheken zur Interaktion mit Cloud-Speicher, Videoverarbeitung und anderen. -```{ .python .annotate hl_lines="2 10-12 14" } -{!../../../docs_src/custom_response/tutorial008.py!} -``` +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} 1. Das ist die Generatorfunktion. Es handelt sich um eine „Generatorfunktion“, da sie `yield`-Anweisungen enthält. -2. Durch die Verwendung eines `with`-Blocks stellen wir sicher, dass das dateiähnliche Objekt geschlossen wird, nachdem die Generatorfunktion fertig ist. Also, nachdem sie mit dem Senden der Response fertig ist. +2. Durch die Verwendung eines `with`-Blocks stellen wir sicher, dass das dateiartige Objekt geschlossen wird, nachdem die Generatorfunktion fertig ist. Also, nachdem sie mit dem Senden der Response fertig ist. 3. Dieses `yield from` weist die Funktion an, über das Ding namens `file_like` zu iterieren. Und dann für jeden iterierten Teil, diesen Teil so zurückzugeben, als wenn er aus dieser Generatorfunktion (`iterfile`) stammen würde. Es handelt sich also hier um eine Generatorfunktion, die die „generierende“ Arbeit intern auf etwas anderes überträgt. Auf diese Weise können wir das Ganze in einen `with`-Block einfügen und so sicherstellen, dass das dateiartige Objekt nach Abschluss geschlossen wird. -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass wir, da wir Standard-`open()` verwenden, welches `async` und `await` nicht unterstützt, hier die Pfadoperation mit normalen `def` deklarieren. /// -### `FileResponse` +### `FileResponse` { #fileresponse } Streamt eine Datei asynchron als Response. @@ -268,19 +255,15 @@ Nimmt zur Instanziierung einen anderen Satz von Argumenten entgegen als die ande Datei-Responses enthalten die entsprechenden `Content-Length`-, `Last-Modified`- und `ETag`-Header. -```Python hl_lines="2 10" -{!../../../docs_src/custom_response/tutorial009.py!} -``` +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} Sie können auch den Parameter `response_class` verwenden: -```Python hl_lines="2 8 10" -{!../../../docs_src/custom_response/tutorial009b.py!} -``` +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} In diesem Fall können Sie den Dateipfad direkt von Ihrer *Pfadoperation*-Funktion zurückgeben. -## Benutzerdefinierte Response-Klasse +## Benutzerdefinierte Response-Klasse { #custom-response-class } Sie können Ihre eigene benutzerdefinierte Response-Klasse erstellen, die von `Response` erbt und diese verwendet. @@ -290,9 +273,7 @@ Sie möchten etwa, dass Ihre Response eingerücktes und formatiertes JSON zurüc Sie könnten eine `CustomORJSONResponse` erstellen. Das Wichtigste, was Sie tun müssen, ist, eine `Response.render(content)`-Methode zu erstellen, die den Inhalt als `bytes` zurückgibt: -```Python hl_lines="9-14 17" -{!../../../docs_src/custom_response/tutorial009c.py!} -``` +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} Statt: @@ -310,7 +291,7 @@ Statt: Natürlich werden Sie wahrscheinlich viel bessere Möglichkeiten finden, Vorteil daraus zu ziehen, als JSON zu formatieren. 😉 -## Standard-Response-Klasse +## Standard-Response-Klasse { #default-response-class } Beim Erstellen einer **FastAPI**-Klasseninstanz oder eines `APIRouter`s können Sie angeben, welche Response-Klasse standardmäßig verwendet werden soll. @@ -318,16 +299,14 @@ Der Parameter, der das definiert, ist `default_response_class`. Im folgenden Beispiel verwendet **FastAPI** standardmäßig `ORJSONResponse` in allen *Pfadoperationen*, anstelle von `JSONResponse`. -```Python hl_lines="2 4" -{!../../../docs_src/custom_response/tutorial010.py!} -``` +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} -/// tip | "Tipp" +/// tip | Tipp Sie können dennoch weiterhin `response_class` in *Pfadoperationen* überschreiben, wie bisher. /// -## Zusätzliche Dokumentation +## Zusätzliche Dokumentation { #additional-documentation } Sie können auch den Medientyp und viele andere Details in OpenAPI mit `responses` deklarieren: [Zusätzliche Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/de/docs/advanced/dataclasses.md b/docs/de/docs/advanced/dataclasses.md index d5a6634852..12ea8e9eca 100644 --- a/docs/de/docs/advanced/dataclasses.md +++ b/docs/de/docs/advanced/dataclasses.md @@ -1,26 +1,24 @@ -# Verwendung von Datenklassen +# Verwendung von Datenklassen { #using-dataclasses } -FastAPI basiert auf **Pydantic** und ich habe Ihnen gezeigt, wie Sie Pydantic-Modelle verwenden können, um Requests und Responses zu deklarieren. +FastAPI basiert auf **Pydantic**, und ich habe Ihnen gezeigt, wie Sie Pydantic-Modelle verwenden können, um Requests und Responses zu deklarieren. Aber FastAPI unterstützt auf die gleiche Weise auch die Verwendung von `dataclasses`: -```Python hl_lines="1 7-12 19-20" -{!../../../docs_src/dataclasses/tutorial001.py!} -``` +{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} -Das ist dank **Pydantic** ebenfalls möglich, da es `dataclasses` intern unterstützt. +Das ist dank **Pydantic** ebenfalls möglich, da es `dataclasses` intern unterstützt. -Auch wenn im obige Code Pydantic nicht explizit vorkommt, verwendet FastAPI Pydantic, um diese Standard-Datenklassen in Pydantics eigene Variante von Datenklassen zu konvertieren. +Auch wenn im obigen Code Pydantic nicht explizit vorkommt, verwendet FastAPI Pydantic, um diese Standard-Datenklassen in Pydantics eigene Variante von Datenklassen zu konvertieren. Und natürlich wird das gleiche unterstützt: -* Validierung der Daten -* Serialisierung der Daten -* Dokumentation der Daten, usw. +* Datenvalidierung +* Datenserialisierung +* Datendokumentation, usw. Das funktioniert genauso wie mit Pydantic-Modellen. Und tatsächlich wird es unter der Haube mittels Pydantic auf die gleiche Weise bewerkstelligt. -/// info +/// info | Info Bedenken Sie, dass Datenklassen nicht alles können, was Pydantic-Modelle können. @@ -30,13 +28,11 @@ Wenn Sie jedoch eine Menge Datenklassen herumliegen haben, ist dies ein guter Tr /// -## Datenklassen als `response_model` +## Datenklassen in `response_model` { #dataclasses-in-response-model } Sie können `dataclasses` auch im Parameter `response_model` verwenden: -```Python hl_lines="1 7-13 19" -{!../../../docs_src/dataclasses/tutorial002.py!} -``` +{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} Die Datenklasse wird automatisch in eine Pydantic-Datenklasse konvertiert. @@ -44,7 +40,7 @@ Auf diese Weise wird deren Schema in der Benutzeroberfläche der API-Dokumentati -## Datenklassen in verschachtelten Datenstrukturen +## Datenklassen in verschachtelten Datenstrukturen { #dataclasses-in-nested-data-structures } Sie können `dataclasses` auch mit anderen Typannotationen kombinieren, um verschachtelte Datenstrukturen zu erstellen. @@ -52,9 +48,7 @@ In einigen Fällen müssen Sie möglicherweise immer noch Pydantics Version von In diesem Fall können Sie einfach die Standard-`dataclasses` durch `pydantic.dataclasses` ersetzen, was einen direkten Ersatz darstellt: -```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } -{!../../../docs_src/dataclasses/tutorial003.py!} -``` +{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} 1. Wir importieren `field` weiterhin von Standard-`dataclasses`. @@ -68,7 +62,7 @@ In diesem Fall können Sie einfach die Standard-`dataclasses` durch `pydantic.da In diesem Fall handelt es sich um eine Liste von `Item`-Datenklassen. -6. Hier geben wir ein Dictionary zurück, das `items` enthält, welches eine Liste von Datenklassen ist. +6. Hier geben wir ein Dictionary zurück, das `items` enthält, welches eine Liste von Datenklassen ist. FastAPI ist weiterhin in der Lage, die Daten nach JSON zu serialisieren. @@ -80,7 +74,7 @@ In diesem Fall können Sie einfach die Standard-`dataclasses` durch `pydantic.da Wie immer können Sie in FastAPI `def` und `async def` beliebig kombinieren. - Wenn Sie eine Auffrischung darüber benötigen, wann welche Anwendung sinnvoll ist, lesen Sie den Abschnitt „In Eile?“ in der Dokumentation zu [`async` und `await`](../async.md#in-eile){.internal-link target=_blank}. + Wenn Sie eine Auffrischung darüber benötigen, wann welche Anwendung sinnvoll ist, lesen Sie den Abschnitt „In Eile?“ in der Dokumentation zu [`async` und `await`](../async.md#in-a-hurry){.internal-link target=_blank}. 9. Diese *Pfadoperation-Funktion* gibt keine Datenklassen zurück (obwohl dies möglich wäre), sondern eine Liste von Dictionarys mit internen Daten. @@ -90,12 +84,12 @@ Sie können `dataclasses` mit anderen Typannotationen auf vielfältige Weise kom Weitere Einzelheiten finden Sie in den Bemerkungen im Quellcode oben. -## Mehr erfahren +## Mehr erfahren { #learn-more } Sie können `dataclasses` auch mit anderen Pydantic-Modellen kombinieren, von ihnen erben, sie in Ihre eigenen Modelle einbinden, usw. -Weitere Informationen finden Sie in der Pydantic-Dokumentation zu Datenklassen. +Weitere Informationen finden Sie in der Pydantic-Dokumentation zu Datenklassen. -## Version +## Version { #version } Dies ist verfügbar seit FastAPI-Version `0.67.0`. 🔖 diff --git a/docs/de/docs/advanced/events.md b/docs/de/docs/advanced/events.md index e898db49b2..f94526b4fc 100644 --- a/docs/de/docs/advanced/events.md +++ b/docs/de/docs/advanced/events.md @@ -1,14 +1,14 @@ -# Lifespan-Events +# Lifespan-Events { #lifespan-events } -Sie können Logik (Code) definieren, die ausgeführt werden soll, bevor die Anwendung **hochfährt**. Dies bedeutet, dass dieser Code **einmal** ausgeführt wird, **bevor** die Anwendung **beginnt, Requests entgegenzunehmen**. +Sie können Logik (Code) definieren, die ausgeführt werden soll, bevor die Anwendung **hochfährt**. Dies bedeutet, dass dieser Code **einmal** ausgeführt wird, **bevor** die Anwendung **beginnt, Requests entgegenzunehmen**. Auf die gleiche Weise können Sie Logik (Code) definieren, die ausgeführt werden soll, wenn die Anwendung **heruntergefahren** wird. In diesem Fall wird dieser Code **einmal** ausgeführt, **nachdem** möglicherweise **viele Requests** bearbeitet wurden. -Da dieser Code ausgeführt wird, bevor die Anwendung **beginnt**, Requests entgegenzunehmen, und unmittelbar, nachdem sie die Bearbeitung von Requests **abgeschlossen hat**, deckt er die gesamte **Lebensdauer – „Lifespan“** – der Anwendung ab (das Wort „Lifespan“ wird gleich wichtig sein 😉). +Da dieser Code ausgeführt wird, bevor die Anwendung **beginnt**, Requests entgegenzunehmen, und unmittelbar, nachdem sie die Bearbeitung von Requests **abgeschlossen hat**, deckt er den gesamten Anwendungs-**Lifespan** ab (das Wort „Lifespan“ wird gleich wichtig sein 😉). -Dies kann sehr nützlich sein, um **Ressourcen** einzurichten, die Sie in der gesamten Anwendung verwenden wollen und die von Requests **gemeinsam genutzt** werden und/oder die Sie anschließend **aufräumen** müssen. Zum Beispiel ein Pool von Datenbankverbindungen oder das Laden eines gemeinsam genutzten Modells für maschinelles Lernen. +Dies kann sehr nützlich sein, um **Ressourcen** einzurichten, die Sie in der gesamten App verwenden wollen und die von Requests **gemeinsam genutzt** werden und/oder die Sie anschließend **aufräumen** müssen. Zum Beispiel ein Pool von Datenbankverbindungen oder das Laden eines gemeinsam genutzten Modells für maschinelles Lernen. -## Anwendungsfall +## Anwendungsfall { #use-case } Beginnen wir mit einem Beispiel-**Anwendungsfall** und schauen uns dann an, wie wir ihn mit dieser Methode implementieren können. @@ -22,51 +22,45 @@ Sie könnten das auf der obersten Ebene des Moduls/der Datei machen, aber das w Das wollen wir besser machen: Laden wir das Modell, bevor die Requests bearbeitet werden, aber unmittelbar bevor die Anwendung beginnt, Requests zu empfangen, und nicht, während der Code geladen wird. -## Lifespan +## Lifespan { #lifespan } -Sie können diese Logik beim *Hochfahren* und *Herunterfahren* mithilfe des `lifespan`-Parameters der `FastAPI`-App und eines „Kontextmanagers“ definieren (ich zeige Ihnen gleich, was das ist). +Sie können diese Logik beim *Startup* und *Shutdown* mithilfe des `lifespan`-Parameters der `FastAPI`-App und eines „Kontextmanagers“ definieren (ich zeige Ihnen gleich, was das ist). Beginnen wir mit einem Beispiel und sehen es uns dann im Detail an. Wir erstellen eine asynchrone Funktion `lifespan()` mit `yield` wie folgt: -```Python hl_lines="16 19" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[16,19] *} -Hier simulieren wir das langsame *Hochfahren*, das Laden des Modells, indem wir die (Fake-)Modellfunktion vor dem `yield` in das Dictionary mit Modellen für maschinelles Lernen einfügen. Dieser Code wird ausgeführt, **bevor** die Anwendung **beginnt, Requests entgegenzunehmen**, während des *Hochfahrens*. +Hier simulieren wir den langsamen *Startup*, das Laden des Modells, indem wir die (Fake-)Modellfunktion vor dem `yield` in das Dictionary mit Modellen für maschinelles Lernen einfügen. Dieser Code wird ausgeführt, **bevor** die Anwendung **beginnt, Requests entgegenzunehmen**, während des *Startups*. -Und dann, direkt nach dem `yield`, entladen wir das Modell. Dieser Code wird unmittelbar vor dem *Herunterfahren* ausgeführt, **nachdem** die Anwendung **die Bearbeitung von Requests abgeschlossen hat**. Dadurch könnten beispielsweise Ressourcen wie Arbeitsspeicher oder eine GPU freigegeben werden. +Und dann, direkt nach dem `yield`, entladen wir das Modell. Dieser Code wird ausgeführt, **nachdem** die Anwendung **die Bearbeitung von Requests abgeschlossen hat**, direkt vor dem *Shutdown*. Dadurch könnten beispielsweise Ressourcen wie Arbeitsspeicher oder eine GPU freigegeben werden. -/// tip | "Tipp" +/// tip | Tipp -Das *Herunterfahren* würde erfolgen, wenn Sie die Anwendung **stoppen**. +Das `shutdown` würde erfolgen, wenn Sie die Anwendung **stoppen**. Möglicherweise müssen Sie eine neue Version starten, oder Sie haben es einfach satt, sie auszuführen. 🤷 /// -### Lifespan-Funktion +### Lifespan-Funktion { #lifespan-function } Das Erste, was auffällt, ist, dass wir eine asynchrone Funktion mit `yield` definieren. Das ist sehr ähnlich zu Abhängigkeiten mit `yield`. -```Python hl_lines="14-19" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[14:19] *} Der erste Teil der Funktion, vor dem `yield`, wird ausgeführt **bevor** die Anwendung startet. Und der Teil nach `yield` wird ausgeführt, **nachdem** die Anwendung beendet ist. -### Asynchroner Kontextmanager +### Asynchroner Kontextmanager { #async-context-manager } Wie Sie sehen, ist die Funktion mit einem `@asynccontextmanager` versehen. Dadurch wird die Funktion in einen sogenannten „**asynchronen Kontextmanager**“ umgewandelt. -```Python hl_lines="1 13" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[1,13] *} Ein **Kontextmanager** in Python ist etwas, das Sie in einer `with`-Anweisung verwenden können, zum Beispiel kann `open()` als Kontextmanager verwendet werden: @@ -88,57 +82,51 @@ In unserem obigen Codebeispiel verwenden wir ihn nicht direkt, sondern übergebe Der Parameter `lifespan` der `FastAPI`-App benötigt einen **asynchronen Kontextmanager**, wir können ihm also unseren neuen asynchronen Kontextmanager `lifespan` übergeben. -```Python hl_lines="22" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[22] *} -## Alternative Events (deprecated) +## Alternative Events (deprecatet) { #alternative-events-deprecated } -/// warning | "Achtung" +/// warning | Achtung -Der empfohlene Weg, das *Hochfahren* und *Herunterfahren* zu handhaben, ist die Verwendung des `lifespan`-Parameters der `FastAPI`-App, wie oben beschrieben. Wenn Sie einen `lifespan`-Parameter übergeben, werden die `startup`- und `shutdown`-Eventhandler nicht mehr aufgerufen. Es ist entweder alles `lifespan` oder alles Events, nicht beides. +Der empfohlene Weg, den *Startup* und *Shutdown* zu handhaben, ist die Verwendung des `lifespan`-Parameters der `FastAPI`-App, wie oben beschrieben. Wenn Sie einen `lifespan`-Parameter übergeben, werden die `startup`- und `shutdown`-Eventhandler nicht mehr aufgerufen. Es ist entweder alles `lifespan` oder alles Events, nicht beides. Sie können diesen Teil wahrscheinlich überspringen. /// -Es gibt eine alternative Möglichkeit, diese Logik zu definieren, sodass sie beim *Hochfahren* und beim *Herunterfahren* ausgeführt wird. +Es gibt eine alternative Möglichkeit, diese Logik zu definieren, sodass sie beim *Startup* und beim *Shutdown* ausgeführt wird. -Sie können Eventhandler (Funktionen) definieren, die ausgeführt werden sollen, bevor die Anwendung hochgefahren wird oder wenn die Anwendung heruntergefahren wird. +Sie können Eventhandler (Funktionen) definieren, die ausgeführt werden sollen, bevor die Anwendung hochgefahren wird oder wenn die Anwendung heruntergefahren wird. Diese Funktionen können mit `async def` oder normalem `def` deklariert werden. -### `startup`-Event +### `startup`-Event { #startup-event } Um eine Funktion hinzuzufügen, die vor dem Start der Anwendung ausgeführt werden soll, deklarieren Sie diese mit dem Event `startup`: -```Python hl_lines="8" -{!../../../docs_src/events/tutorial001.py!} -``` +{* ../../docs_src/events/tutorial001.py hl[8] *} In diesem Fall initialisiert die Eventhandler-Funktion `startup` die „Datenbank“ der Items (nur ein `dict`) mit einigen Werten. Sie können mehr als eine Eventhandler-Funktion hinzufügen. -Und Ihre Anwendung empfängt erst dann Anfragen, wenn alle `startup`-Eventhandler abgeschlossen sind. +Und Ihre Anwendung empfängt erst dann Requests, wenn alle `startup`-Eventhandler abgeschlossen sind. -### `shutdown`-Event +### `shutdown`-Event { #shutdown-event } -Um eine Funktion hinzuzufügen, die beim Herunterfahren der Anwendung ausgeführt werden soll, deklarieren Sie sie mit dem Event `shutdown`: +Um eine Funktion hinzuzufügen, die beim Shutdown der Anwendung ausgeführt werden soll, deklarieren Sie sie mit dem Event `shutdown`: -```Python hl_lines="6" -{!../../../docs_src/events/tutorial002.py!} -``` +{* ../../docs_src/events/tutorial002.py hl[6] *} Hier schreibt die `shutdown`-Eventhandler-Funktion eine Textzeile `"Application shutdown"` in eine Datei `log.txt`. -/// info +/// info | Info In der Funktion `open()` bedeutet `mode="a"` „append“ („anhängen“), sodass die Zeile nach dem, was sich in dieser Datei befindet, hinzugefügt wird, ohne den vorherigen Inhalt zu überschreiben. /// -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass wir in diesem Fall eine Standard-Python-Funktion `open()` verwenden, die mit einer Datei interagiert. @@ -150,28 +138,28 @@ Daher deklarieren wir die Eventhandler-Funktion mit Standard-`def` statt mit `as /// -### `startup` und `shutdown` zusammen +### `startup` und `shutdown` zusammen { #startup-and-shutdown-together } -Es besteht eine hohe Wahrscheinlichkeit, dass die Logik für Ihr *Hochfahren* und *Herunterfahren* miteinander verknüpft ist. Vielleicht möchten Sie etwas beginnen und es dann beenden, eine Ressource laden und sie dann freigeben usw. +Es besteht eine hohe Wahrscheinlichkeit, dass die Logik für Ihr *Startup* und *Shutdown* miteinander verknüpft ist. Vielleicht möchten Sie etwas beginnen und es dann beenden, eine Ressource laden und sie dann freigeben usw. Bei getrennten Funktionen, die keine gemeinsame Logik oder Variablen haben, ist dies schwieriger, da Sie Werte in globalen Variablen speichern oder ähnliche Tricks verwenden müssen. Aus diesem Grund wird jetzt empfohlen, stattdessen `lifespan` wie oben erläutert zu verwenden. -## Technische Details +## Technische Details { #technical-details } Nur ein technisches Detail für die neugierigen Nerds. 🤓 In der technischen ASGI-Spezifikation ist dies Teil des Lifespan Protokolls und definiert Events namens `startup` und `shutdown`. -/// info +/// info | Info -Weitere Informationen zu Starlettes `lifespan`-Handlern finden Sie in Starlettes Lifespan-Dokumentation. +Weitere Informationen zu Starlettes `lifespan`-Handlern finden Sie in Starlettes Lifespan-Dokumentation. Einschließlich, wie man Lifespan-Zustand handhabt, der in anderen Bereichen Ihres Codes verwendet werden kann. /// -## Unteranwendungen +## Unteranwendungen { #sub-applications } -🚨 Beachten Sie, dass diese Lifespan-Events (Hochfahren und Herunterfahren) nur für die Hauptanwendung ausgeführt werden, nicht für [Unteranwendungen – Mounts](sub-applications.md){.internal-link target=_blank}. +🚨 Beachten Sie, dass diese Lifespan-Events (Startup und Shutdown) nur für die Hauptanwendung ausgeführt werden, nicht für [Unteranwendungen – Mounts](sub-applications.md){.internal-link target=_blank}. diff --git a/docs/de/docs/advanced/generate-clients.md b/docs/de/docs/advanced/generate-clients.md index b8d66fdd70..d8836295b6 100644 --- a/docs/de/docs/advanced/generate-clients.md +++ b/docs/de/docs/advanced/generate-clients.md @@ -1,135 +1,86 @@ -# Clients generieren +# SDKs generieren { #generating-sdks } -Da **FastAPI** auf der OpenAPI-Spezifikation basiert, erhalten Sie automatische Kompatibilität mit vielen Tools, einschließlich der automatischen API-Dokumentation (bereitgestellt von Swagger UI). +Da **FastAPI** auf der **OpenAPI**-Spezifikation basiert, können dessen APIs in einem standardisierten Format beschrieben werden, das viele Tools verstehen. -Ein besonderer Vorteil, der nicht unbedingt offensichtlich ist, besteht darin, dass Sie für Ihre API **Clients generieren** können (manchmal auch **SDKs** genannt), für viele verschiedene **Programmiersprachen**. +Dies vereinfacht es, aktuelle **Dokumentation** und Client-Bibliotheken (**SDKs**) in verschiedenen Sprachen zu generieren sowie **Test-** oder **Automatisierungs-Workflows**, die mit Ihrem Code synchron bleiben. -## OpenAPI-Client-Generatoren +In diesem Leitfaden erfahren Sie, wie Sie ein **TypeScript-SDK** für Ihr FastAPI-Backend generieren. -Es gibt viele Tools zum Generieren von Clients aus **OpenAPI**. +## Open Source SDK-Generatoren { #open-source-sdk-generators } -Ein gängiges Tool ist OpenAPI Generator. +Eine vielseitige Möglichkeit ist der OpenAPI Generator, der **viele Programmiersprachen** unterstützt und SDKs aus Ihrer OpenAPI-Spezifikation generieren kann. -Wenn Sie ein **Frontend** erstellen, ist openapi-ts eine sehr interessante Alternative. +Für **TypeScript-Clients** ist Hey API eine speziell entwickelte Lösung, die ein optimiertes Erlebnis für das TypeScript-Ökosystem bietet. -## Client- und SDK-Generatoren – Sponsor +Weitere SDK-Generatoren finden Sie auf OpenAPI.Tools. -Es gibt auch einige **vom Unternehmen entwickelte** Client- und SDK-Generatoren, die auf OpenAPI (FastAPI) basieren. In einigen Fällen können diese Ihnen **weitere Funktionalität** zusätzlich zu qualitativ hochwertigen generierten SDKs/Clients bieten. +/// tip | Tipp -Einige von diesen ✨ [**sponsern FastAPI**](../help-fastapi.md#den-autor-sponsern){.internal-link target=_blank} ✨, das gewährleistet die kontinuierliche und gesunde **Entwicklung** von FastAPI und seinem **Ökosystem**. +FastAPI generiert automatisch **OpenAPI 3.1**-Spezifikationen, daher muss jedes von Ihnen verwendete Tool diese Version unterstützen. -Und es zeigt deren wahres Engagement für FastAPI und seine **Community** (Sie), da diese Ihnen nicht nur einen **guten Service** bieten möchten, sondern auch sicherstellen möchten, dass Sie über ein **gutes und gesundes Framework** verfügen, FastAPI. 🙇 +/// -Beispielsweise könnten Sie Speakeasy ausprobieren. +## SDK-Generatoren von FastAPI-Sponsoren { #sdk-generators-from-fastapi-sponsors } -Es gibt auch mehrere andere Unternehmen, welche ähnliche Dienste anbieten und die Sie online suchen und finden können. 🤓 +Dieser Abschnitt hebt **venture-unterstützte** und **firmengestützte** Lösungen hervor, die von Unternehmen entwickelt werden, welche FastAPI sponsern. Diese Produkte bieten **zusätzliche Funktionen** und **Integrationen** zusätzlich zu hochwertig generierten SDKs. -## Einen TypeScript-Frontend-Client generieren +Durch das ✨ [**Sponsoring von FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ helfen diese Unternehmen sicherzustellen, dass das Framework und sein **Ökosystem** gesund und **nachhaltig** bleiben. + +Ihr Sponsoring zeigt auch ein starkes Engagement für die FastAPI-**Community** (Sie), was bedeutet, dass sie nicht nur einen **großartigen Service** bieten möchten, sondern auch ein **robustes und florierendes Framework**, FastAPI, unterstützen möchten. 🙇 + +Zum Beispiel könnten Sie ausprobieren: + +* Speakeasy +* Stainless +* liblab + +Einige dieser Lösungen sind möglicherweise auch Open Source oder bieten kostenlose Tarife an, sodass Sie diese ohne finanzielle Verpflichtung ausprobieren können. Andere kommerzielle SDK-Generatoren sind online verfügbar und können dort gefunden werden. 🤓 + +## Ein TypeScript-SDK erstellen { #create-a-typescript-sdk } Beginnen wir mit einer einfachen FastAPI-Anwendung: -//// tab | Python 3.9+ +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} -```Python hl_lines="7-9 12-13 16-17 21" -{!> ../../../docs_src/generate_clients/tutorial001_py39.py!} -``` +Beachten Sie, dass die *Pfadoperationen* die Modelle definieren, die sie für die Request- und Response-Payload verwenden, indem sie die Modelle `Item` und `ResponseMessage` verwenden. -//// +### API-Dokumentation { #api-docs } -//// tab | Python 3.8+ - -```Python hl_lines="9-11 14-15 18 19 23" -{!> ../../../docs_src/generate_clients/tutorial001.py!} -``` - -//// - -Beachten Sie, dass die *Pfadoperationen* die Modelle definieren, welche diese für die Request- und Response-Payload verwenden, indem sie die Modelle `Item` und `ResponseMessage` verwenden. - -### API-Dokumentation - -Wenn Sie zur API-Dokumentation gehen, werden Sie sehen, dass diese die **Schemas** für die Daten enthält, welche in Requests gesendet und in Responses empfangen werden: +Wenn Sie zu `/docs` gehen, sehen Sie, dass es die **Schemas** für die Daten enthält, die in Requests gesendet und in Responses empfangen werden: -Sie können diese Schemas sehen, da sie mit den Modellen in der Anwendung deklariert wurden. +Sie können diese Schemas sehen, da sie mit den Modellen in der App deklariert wurden. -Diese Informationen sind im **OpenAPI-Schema** der Anwendung verfügbar und werden dann in der API-Dokumentation angezeigt (von Swagger UI). +Diese Informationen sind im **OpenAPI-Schema** der Anwendung verfügbar und werden in der API-Dokumentation angezeigt. -Und dieselben Informationen aus den Modellen, die in OpenAPI enthalten sind, können zum **Generieren des Client-Codes** verwendet werden. +Diese Informationen aus den Modellen, die in OpenAPI enthalten sind, können verwendet werden, um **den Client-Code zu generieren**. -### Einen TypeScript-Client generieren +### Hey API { #hey-api } -Nachdem wir nun die Anwendung mit den Modellen haben, können wir den Client-Code für das Frontend generieren. +Sobald wir eine FastAPI-App mit den Modellen haben, können wir Hey API verwenden, um einen TypeScript-Client zu generieren. Der schnellste Weg das zu tun, ist über npx. -#### `openapi-ts` installieren - -Sie können `openapi-ts` in Ihrem Frontend-Code installieren mit: - -
- -```console -$ npm install @hey-api/openapi-ts --save-dev - ----> 100% +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client ``` -
+Dies generiert ein TypeScript-SDK in `./src/client`. -#### Client-Code generieren +Sie können lernen, wie man `@hey-api/openapi-ts` installiert und über die erzeugte Ausgabe auf deren Website lesen. -Um den Client-Code zu generieren, können Sie das Kommandozeilentool `openapi-ts` verwenden, das soeben installiert wurde. +### Das SDK verwenden { #using-the-sdk } -Da es im lokalen Projekt installiert ist, könnten Sie diesen Befehl wahrscheinlich nicht direkt aufrufen, sondern würden ihn in Ihre Datei `package.json` einfügen. - -Diese könnte so aussehen: - -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} -``` - -Nachdem Sie das NPM-Skript `generate-client` dort stehen haben, können Sie es ausführen mit: - -
- -```console -$ npm run generate-client - -frontend-app@1.0.0 generate-client /home/user/code/frontend-app -> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios -``` - -
- -Dieser Befehl generiert Code in `./src/client` und verwendet intern `axios` (die Frontend-HTTP-Bibliothek). - -### Den Client-Code ausprobieren - -Jetzt können Sie den Client-Code importieren und verwenden. Er könnte wie folgt aussehen, beachten Sie, dass Sie automatische Codevervollständigung für die Methoden erhalten: +Jetzt können Sie den Client-Code importieren und verwenden. Er könnte wie folgt aussehen, beachten Sie, dass Sie eine automatische Vervollständigung für die Methoden erhalten: -Sie erhalten außerdem automatische Vervollständigung für die zu sendende Payload: +Sie werden auch eine automatische Vervollständigung für die zu sendende Payload erhalten: -/// tip | "Tipp" +/// tip | Tipp -Beachten Sie die automatische Vervollständigung für `name` und `price`, welche in der FastAPI-Anwendung im `Item`-Modell definiert wurden. +Beachten Sie die automatische Vervollständigung für `name` und `price`, die in der FastAPI-Anwendung im `Item`-Modell definiert wurden. /// @@ -141,31 +92,17 @@ Das Response-Objekt hat auch automatische Vervollständigung: -## FastAPI-Anwendung mit Tags +## FastAPI-Anwendung mit Tags { #fastapi-app-with-tags } -In vielen Fällen wird Ihre FastAPI-Anwendung größer sein und Sie werden wahrscheinlich Tags verwenden, um verschiedene Gruppen von *Pfadoperationen* zu separieren. +In vielen Fällen wird Ihre FastAPI-App größer sein und Sie werden wahrscheinlich Tags verwenden, um verschiedene Gruppen von *Pfadoperationen* zu separieren. -Beispielsweise könnten Sie einen Abschnitt für **Items (Artikel)** und einen weiteren Abschnitt für **Users (Benutzer)** haben, und diese könnten durch Tags getrennt sein: +Zum Beispiel könnten Sie einen Abschnitt für **Items (Artikel)** und einen weiteren Abschnitt für **Users (Benutzer)** haben, und diese könnten durch Tags getrennt sein: -//// tab | Python 3.9+ +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} -```Python hl_lines="21 26 34" -{!> ../../../docs_src/generate_clients/tutorial002_py39.py!} -``` +### Einen TypeScript-Client mit Tags generieren { #generate-a-typescript-client-with-tags } -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23 28 36" -{!> ../../../docs_src/generate_clients/tutorial002.py!} -``` - -//// - -### Einen TypeScript-Client mit Tags generieren - -Wenn Sie unter Verwendung von Tags einen Client für eine FastAPI-Anwendung generieren, wird normalerweise auch der Client-Code anhand der Tags getrennt. +Wenn Sie einen Client für eine FastAPI-App generieren, die Tags verwendet, wird normalerweise der Client-Code auch anhand der Tags getrennt. Auf diese Weise können Sie die Dinge für den Client-Code richtig ordnen und gruppieren: @@ -176,7 +113,7 @@ In diesem Fall haben Sie: * `ItemsService` * `UsersService` -### Client-Methodennamen +### Client-Methodennamen { #client-method-names } Im Moment sehen die generierten Methodennamen wie `createItemItemsPost` nicht sehr sauber aus: @@ -186,45 +123,31 @@ ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) ... das liegt daran, dass der Client-Generator für jede *Pfadoperation* die OpenAPI-interne **Operation-ID** verwendet. -OpenAPI erfordert, dass jede Operation-ID innerhalb aller *Pfadoperationen* eindeutig ist. Daher verwendet FastAPI den **Funktionsnamen**, den **Pfad** und die **HTTP-Methode/-Operation**, um diese Operation-ID zu generieren. Denn so kann sichergestellt werden, dass die Operation-IDs eindeutig sind. +OpenAPI erfordert, dass jede Operation-ID innerhalb aller *Pfadoperationen* einzigartig ist. Daher verwendet FastAPI den **Funktionsnamen**, den **Pfad** und die **HTTP-Methode/-Operation**, um diese Operation-ID zu generieren. Denn so kann sichergestellt werden, dass die Operation-IDs einzigartig sind. -Aber ich zeige Ihnen als nächstes, wie Sie das verbessern können. 🤓 +Aber ich zeige Ihnen als Nächstes, wie Sie das verbessern können. 🤓 -## Benutzerdefinierte Operation-IDs und bessere Methodennamen +## Benutzerdefinierte Operation-IDs und bessere Methodennamen { #custom-operation-ids-and-better-method-names } Sie können die Art und Weise, wie diese Operation-IDs **generiert** werden, **ändern**, um sie einfacher zu machen und **einfachere Methodennamen** in den Clients zu haben. -In diesem Fall müssen Sie auf andere Weise sicherstellen, dass jede Operation-ID **eindeutig** ist. +In diesem Fall müssen Sie auf andere Weise sicherstellen, dass jede Operation-ID **einzigartig** ist. -Sie könnten beispielsweise sicherstellen, dass jede *Pfadoperation* einen Tag hat, und dann die Operation-ID basierend auf dem **Tag** und dem **Namen** der *Pfadoperation* (dem Funktionsnamen) generieren. +Zum Beispiel könnten Sie sicherstellen, dass jede *Pfadoperation* einen Tag hat, und dann die Operation-ID basierend auf dem **Tag** und dem *Pfadoperation*-**Namen** (dem Funktionsnamen) generieren. -### Funktion zum Generieren einer eindeutigen ID erstellen +### Eine benutzerdefinierte Funktion zur Erzeugung einer eindeutigen ID erstellen { #custom-generate-unique-id-function } -FastAPI verwendet eine **eindeutige ID** für jede *Pfadoperation*, diese wird für die **Operation-ID** und auch für die Namen aller benötigten benutzerdefinierten Modelle für Requests oder Responses verwendet. +FastAPI verwendet eine **eindeutige ID** für jede *Pfadoperation*, die für die **Operation-ID** und auch für die Namen aller benötigten benutzerdefinierten Modelle für Requests oder Responses verwendet wird. -Sie können diese Funktion anpassen. Sie nimmt eine `APIRoute` und gibt einen String zurück. +Sie können diese Funktion anpassen. Sie nimmt ein `APIRoute` und gibt einen String zurück. -Hier verwendet sie beispielsweise den ersten Tag (Sie werden wahrscheinlich nur einen Tag haben) und den Namen der *Pfadoperation* (den Funktionsnamen). +Hier verwendet sie beispielsweise den ersten Tag (Sie werden wahrscheinlich nur einen Tag haben) und den *Pfadoperation*-Namen (den Funktionsnamen). -Anschließend können Sie diese benutzerdefinierte Funktion als Parameter `generate_unique_id_function` an **FastAPI** übergeben: +Anschließend können Sie diese benutzerdefinierte Funktion als `generate_unique_id_function`-Parameter an **FastAPI** übergeben: -//// tab | Python 3.9+ +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} -```Python hl_lines="6-7 10" -{!> ../../../docs_src/generate_clients/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8-9 12" -{!> ../../../docs_src/generate_clients/tutorial003.py!} -``` - -//// - -### Einen TypeScript-Client mit benutzerdefinierten Operation-IDs generieren +### Einen TypeScript-Client mit benutzerdefinierten Operation-IDs generieren { #generate-a-typescript-client-with-custom-operation-ids } Wenn Sie nun den Client erneut generieren, werden Sie feststellen, dass er über die verbesserten Methodennamen verfügt: @@ -232,74 +155,54 @@ Wenn Sie nun den Client erneut generieren, werden Sie feststellen, dass er über Wie Sie sehen, haben die Methodennamen jetzt den Tag und dann den Funktionsnamen, aber keine Informationen aus dem URL-Pfad und der HTTP-Operation. -### Vorab-Modifikation der OpenAPI-Spezifikation für den Client-Generator +### Die OpenAPI-Spezifikation für den Client-Generator vorab modifizieren { #preprocess-the-openapi-specification-for-the-client-generator } -Der generierte Code enthält immer noch etwas **verdoppelte Information**. +Der generierte Code enthält immer noch einige **verdoppelte Informationen**. -Wir wissen bereits, dass diese Methode mit den **Items** zusammenhängt, da sich dieses Wort in `ItemsService` befindet (vom Tag übernommen), aber wir haben auch immer noch den Tagnamen im Methodennamen vorangestellt. 😕 +Wir wissen bereits, dass diese Methode mit den **Items** zusammenhängt, weil dieses Wort in `ItemsService` enthalten ist (vom Tag übernommen), aber wir haben den Tag-Namen dennoch im Methodennamen vorangestellt. 😕 -Wir werden das wahrscheinlich weiterhin für OpenAPI im Allgemeinen beibehalten wollen, da dadurch sichergestellt wird, dass die Operation-IDs **eindeutig** sind. +Wir werden das wahrscheinlich weiterhin für OpenAPI allgemein beibehalten wollen, da dadurch sichergestellt wird, dass die Operation-IDs **einzigartig** sind. Aber für den generierten Client könnten wir die OpenAPI-Operation-IDs direkt vor der Generierung der Clients **modifizieren**, um diese Methodennamen schöner und **sauberer** zu machen. -Wir könnten das OpenAPI-JSON in eine Datei `openapi.json` herunterladen und dann mit einem Skript wie dem folgenden **den vorangestellten Tag entfernen**: +Wir könnten das OpenAPI-JSON in eine Datei `openapi.json` herunterladen und dann mit einem Skript wie dem folgenden **den präfixierten Tag entfernen**: -//// tab | Python - -```Python -{!> ../../../docs_src/generate_clients/tutorial004.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial004.py *} //// tab | Node.js ```Javascript -{!> ../../../docs_src/generate_clients/tutorial004.js!} +{!> ../../docs_src/generate_clients/tutorial004.js!} ``` //// Damit würden die Operation-IDs von Dingen wie `items-get_items` in `get_items` umbenannt, sodass der Client-Generator einfachere Methodennamen generieren kann. -### Einen TypeScript-Client mit der modifizierten OpenAPI generieren +### Einen TypeScript-Client mit der modifizierten OpenAPI generieren { #generate-a-typescript-client-with-the-preprocessed-openapi } -Da das Endergebnis nun in einer Datei `openapi.json` vorliegt, würden Sie die `package.json` ändern, um diese lokale Datei zu verwenden, zum Beispiel: +Da das Endergebnis nun in einer `openapi.json`-Datei vorliegt, müssen Sie Ihren Eingabeort aktualisieren: -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client ``` -Nach der Generierung des neuen Clients hätten Sie nun **saubere Methodennamen** mit allen **Autovervollständigungen**, **Inline-Fehlerberichten**, usw.: +Nach der Generierung des neuen Clients haben Sie jetzt **saubere Methodennamen**, mit allen **Autovervollständigungen**, **Inline-Fehlerberichten**, usw.: -## Vorteile +## Vorteile { #benefits } -Wenn Sie die automatisch generierten Clients verwenden, erhalten Sie **automatische Codevervollständigung** für: +Wenn Sie die automatisch generierten Clients verwenden, erhalten Sie **Autovervollständigung** für: * Methoden. * Request-Payloads im Body, Query-Parameter, usw. * Response-Payloads. -Außerdem erhalten Sie für alles **Inline-Fehlerberichte**. +Sie erhalten auch **Inline-Fehlerberichte** für alles. -Und wann immer Sie den Backend-Code aktualisieren und das Frontend **neu generieren**, stehen alle neuen *Pfadoperationen* als Methoden zur Verfügung, die alten werden entfernt und alle anderen Änderungen werden im generierten Code reflektiert. 🤓 +Und wann immer Sie den Backend-Code aktualisieren und **das Frontend neu generieren**, stehen alle neuen *Pfadoperationen* als Methoden zur Verfügung, die alten werden entfernt und alle anderen Änderungen werden im generierten Code reflektiert. 🤓 -Das bedeutet auch, dass, wenn sich etwas ändert, dies automatisch im Client-Code **reflektiert** wird. Und wenn Sie den Client **erstellen**, kommt es zu einer Fehlermeldung, wenn die verwendeten Daten **nicht übereinstimmen**. +Das bedeutet auch, dass, wenn sich etwas ändert, dies automatisch im Client-Code **reflektiert** wird. Und wenn Sie den Client **erstellen**, wird eine Fehlermeldung ausgegeben, wenn die verwendeten Daten **nicht übereinstimmen**. -Sie würden also sehr früh im Entwicklungszyklus **viele Fehler erkennen**, anstatt darauf warten zu müssen, dass die Fehler Ihren Endbenutzern in der Produktion angezeigt werden, und dann zu versuchen, zu debuggen, wo das Problem liegt. ✨ +Sie würden also **viele Fehler sehr früh** im Entwicklungszyklus erkennen, anstatt darauf warten zu müssen, dass die Fehler Ihren Endbenutzern in der Produktion angezeigt werden, und dann zu versuchen, zu debuggen, wo das Problem liegt. ✨ diff --git a/docs/de/docs/advanced/index.md b/docs/de/docs/advanced/index.md index 953ad317d6..98fc7bc2fa 100644 --- a/docs/de/docs/advanced/index.md +++ b/docs/de/docs/advanced/index.md @@ -1,12 +1,12 @@ -# Handbuch für fortgeschrittene Benutzer +# Handbuch für fortgeschrittene Benutzer { #advanced-user-guide } -## Zusatzfunktionen +## Zusatzfunktionen { #additional-features } Das Haupt-[Tutorial – Benutzerhandbuch](../tutorial/index.md){.internal-link target=_blank} sollte ausreichen, um Ihnen einen Überblick über alle Hauptfunktionen von **FastAPI** zu geben. In den nächsten Abschnitten sehen Sie weitere Optionen, Konfigurationen und zusätzliche Funktionen. -/// tip | "Tipp" +/// tip | Tipp Die nächsten Abschnitte sind **nicht unbedingt „fortgeschritten“**. @@ -14,23 +14,8 @@ Und es ist möglich, dass für Ihren Anwendungsfall die Lösung in einem davon l /// -## Lesen Sie zuerst das Tutorial +## Das Tutorial zuerst lesen { #read-the-tutorial-first } Sie können immer noch die meisten Funktionen in **FastAPI** mit den Kenntnissen aus dem Haupt-[Tutorial – Benutzerhandbuch](../tutorial/index.md){.internal-link target=_blank} nutzen. -Und in den nächsten Abschnitten wird davon ausgegangen, dass Sie es bereits gelesen haben und dass Sie diese Haupt-Ideen kennen. - -## Externe Kurse - -Obwohl das [Tutorial – Benutzerhandbuch](../tutorial/index.md){.internal-link target=_blank} und dieses **Handbuch für fortgeschrittene Benutzer** als geführtes Tutorial (wie ein Buch) geschrieben sind und für Sie ausreichen sollten, um **FastAPI zu lernen**, möchten Sie sie vielleicht durch zusätzliche Kurse ergänzen. - -Oder Sie belegen einfach lieber andere Kurse, weil diese besser zu Ihrem Lernstil passen. - -Einige Kursanbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#den-autor-sponsern){.internal-link target=_blank} ✨, dies gewährleistet die kontinuierliche und gesunde **Entwicklung** von FastAPI und seinem **Ökosystem**. - -Und es zeigt deren wahres Engagement für FastAPI und seine **Gemeinschaft** (Sie), da diese Ihnen nicht nur eine **gute Lernerfahrung** bieten möchten, sondern auch sicherstellen möchten, dass Sie über ein **gutes und gesundes Framework verfügen **, FastAPI. 🙇 - -Vielleicht möchten Sie ihre Kurse ausprobieren: - -* Talk Python Training -* Test-Driven Development +Und die nächsten Abschnitte setzen voraus, dass Sie es bereits gelesen haben und dass Sie diese Hauptideen kennen. diff --git a/docs/de/docs/advanced/middleware.md b/docs/de/docs/advanced/middleware.md index 8912225fbf..8396a626b6 100644 --- a/docs/de/docs/advanced/middleware.md +++ b/docs/de/docs/advanced/middleware.md @@ -1,4 +1,4 @@ -# Fortgeschrittene Middleware +# Fortgeschrittene Middleware { #advanced-middleware } Im Haupttutorial haben Sie gelesen, wie Sie Ihrer Anwendung [benutzerdefinierte Middleware](../tutorial/middleware.md){.internal-link target=_blank} hinzufügen können. @@ -6,15 +6,15 @@ Und dann auch, wie man [CORS mittels der `CORSMiddleware`](../tutorial/cors.md){ In diesem Abschnitt werden wir sehen, wie man andere Middlewares verwendet. -## ASGI-Middleware hinzufügen +## ASGI-Middleware hinzufügen { #adding-asgi-middlewares } -Da **FastAPI** auf Starlette basiert und die ASGI-Spezifikation implementiert, können Sie jede ASGI-Middleware verwenden. +Da **FastAPI** auf Starlette basiert und die ASGI-Spezifikation implementiert, können Sie jede ASGI-Middleware verwenden. Eine Middleware muss nicht speziell für FastAPI oder Starlette gemacht sein, um zu funktionieren, solange sie der ASGI-Spezifikation genügt. Im Allgemeinen handelt es sich bei ASGI-Middleware um Klassen, die als erstes Argument eine ASGI-Anwendung erwarten. -In der Dokumentation für ASGI-Middlewares von Drittanbietern wird Ihnen wahrscheinlich gesagt, etwa Folgendes zu tun: +In der Dokumentation für ASGI-Middlewares von Drittanbietern wird Ihnen wahrscheinlich gesagt, dass Sie etwa Folgendes tun sollen: ```Python from unicorn import UnicornMiddleware @@ -39,11 +39,11 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") `app.add_middleware()` empfängt eine Middleware-Klasse als erstes Argument und dann alle weiteren Argumente, die an die Middleware übergeben werden sollen. -## Integrierte Middleware +## Integrierte Middleware { #integrated-middlewares } **FastAPI** enthält mehrere Middlewares für gängige Anwendungsfälle. Wir werden als Nächstes sehen, wie man sie verwendet. -/// note | "Technische Details" +/// note | Technische Details Für die nächsten Beispiele könnten Sie auch `from starlette.middleware.something import SomethingMiddleware` verwenden. @@ -51,45 +51,41 @@ Für die nächsten Beispiele könnten Sie auch `from starlette.middleware.someth /// -## `HTTPSRedirectMiddleware` +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } -Erzwingt, dass alle eingehenden Requests entweder `https` oder `wss` sein müssen. +Erzwingt, dass alle eingehenden Requests entweder `https` oder `wss` sein müssen. Alle eingehenden Requests an `http` oder `ws` werden stattdessen an das sichere Schema umgeleitet. -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial001.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} -## `TrustedHostMiddleware` +## `TrustedHostMiddleware` { #trustedhostmiddleware } Erzwingt, dass alle eingehenden Requests einen korrekt gesetzten `Host`-Header haben, um sich vor HTTP-Host-Header-Angriffen zu schützen. -```Python hl_lines="2 6-8" -{!../../../docs_src/advanced_middleware/tutorial002.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} Die folgenden Argumente werden unterstützt: * `allowed_hosts` – Eine Liste von Domain-Namen, die als Hostnamen zulässig sein sollten. Wildcard-Domains wie `*.example.com` werden unterstützt, um Subdomains zu matchen. Um jeden Hostnamen zu erlauben, verwenden Sie entweder `allowed_hosts=["*"]` oder lassen Sie diese Middleware weg. +* `www_redirect` – Wenn auf True gesetzt, werden Requests an Nicht-www-Versionen der erlaubten Hosts zu deren www-Gegenstücken umgeleitet. Der Defaultwert ist `True`. -Wenn ein eingehender Request nicht korrekt validiert wird, wird eine „400“-Response gesendet. +Wenn ein eingehender Request nicht korrekt validiert wird, wird eine `400`-Response gesendet. -## `GZipMiddleware` +## `GZipMiddleware` { #gzipmiddleware } Verarbeitet GZip-Responses für alle Requests, die `"gzip"` im `Accept-Encoding`-Header enthalten. Diese Middleware verarbeitet sowohl Standard- als auch Streaming-Responses. -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial003.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} Die folgenden Argumente werden unterstützt: -* `minimum_size` – Antworten, die kleiner als diese Mindestgröße in Bytes sind, nicht per GZip komprimieren. Der Defaultwert ist `500`. +* `minimum_size` – Responses, die kleiner als diese Mindestgröße in Bytes sind, nicht per GZip komprimieren. Der Defaultwert ist `500`. +* `compresslevel` – Wird während der GZip-Kompression verwendet. Es ist ein Ganzzahlwert zwischen 1 und 9. Der Defaultwert ist `9`. Ein niedrigerer Wert resultiert in schnellerer Kompression, aber größeren Dateigrößen, während ein höherer Wert langsamere Kompression, aber kleinere Dateigrößen zur Folge hat. -## Andere Middlewares +## Andere Middlewares { #other-middlewares } Es gibt viele andere ASGI-Middlewares. @@ -98,4 +94,4 @@ Zum Beispiel: * Uvicorns `ProxyHeadersMiddleware` * MessagePack -Um mehr über weitere verfügbare Middlewares herauszufinden, besuchen Sie Starlettes Middleware-Dokumentation und die ASGI Awesome List. +Um mehr über weitere verfügbare Middlewares herauszufinden, besuchen Sie Starlettes Middleware-Dokumentation und die ASGI Awesome List. diff --git a/docs/de/docs/advanced/openapi-callbacks.md b/docs/de/docs/advanced/openapi-callbacks.md index d7b5bc885a..afc48bbb82 100644 --- a/docs/de/docs/advanced/openapi-callbacks.md +++ b/docs/de/docs/advanced/openapi-callbacks.md @@ -1,12 +1,12 @@ -# OpenAPI-Callbacks +# OpenAPI-Callbacks { #openapi-callbacks } -Sie könnten eine API mit einer *Pfadoperation* erstellen, die einen Request an eine *externe API* auslösen könnte, welche von jemand anderem erstellt wurde (wahrscheinlich derselbe Entwickler, der Ihre API *verwenden* würde). +Sie könnten eine API mit einer *Pfadoperation* erstellen, die einen Request an eine *externe API* auslösen könnte, welche von jemand anderem erstellt wurde (wahrscheinlich derselbe Entwickler, der Ihre API *verwenden* würde). -Der Vorgang, der stattfindet, wenn Ihre API-Anwendung die *externe API* aufruft, wird als „Callback“ („Rückruf“) bezeichnet. Denn die Software, die der externe Entwickler geschrieben hat, sendet einen Request an Ihre API und dann *ruft Ihre API zurück* (*calls back*) und sendet einen Request an eine *externe API* (die wahrscheinlich vom selben Entwickler erstellt wurde). +Der Vorgang, der stattfindet, wenn Ihre API-Anwendung die *externe API* aufruft, wird als „Callback“ bezeichnet. Denn die Software, die der externe Entwickler geschrieben hat, sendet einen Request an Ihre API und dann *ruft Ihre API zurück* (*calls back*) und sendet einen Request an eine *externe API* (die wahrscheinlich vom selben Entwickler erstellt wurde). -In diesem Fall möchten Sie möglicherweise dokumentieren, wie diese externe API aussehen *sollte*. Welche *Pfadoperation* sie haben sollte, welchen Body sie erwarten sollte, welche Response sie zurückgeben sollte, usw. +In diesem Fall möchten Sie möglicherweise dokumentieren, wie diese externe API aussehen *sollte*. Welche *Pfadoperation* sie haben sollte, welchen Body sie erwarten sollte, welche Response sie zurückgeben sollte, usw. -## Eine Anwendung mit Callbacks +## Eine Anwendung mit Callbacks { #an-app-with-callbacks } Sehen wir uns das alles anhand eines Beispiels an. @@ -16,14 +16,14 @@ Diese Rechnungen haben eine `id`, einen optionalen `title`, einen `customer` (Ku Der Benutzer Ihrer API (ein externer Entwickler) erstellt mit einem POST-Request eine Rechnung in Ihrer API. -Dann wird Ihre API (beispielsweise): +Dann wird Ihre API (stellen wir uns vor): * die Rechnung an einen Kunden des externen Entwicklers senden. * das Geld einsammeln. * eine Benachrichtigung an den API-Benutzer (den externen Entwickler) zurücksenden. * Dies erfolgt durch Senden eines POST-Requests (von *Ihrer API*) an eine *externe API*, die von diesem externen Entwickler bereitgestellt wird (das ist der „Callback“). -## Die normale **FastAPI**-Anwendung +## Die normale **FastAPI**-Anwendung { #the-normal-fastapi-app } Sehen wir uns zunächst an, wie die normale API-Anwendung aussehen würde, bevor wir den Callback hinzufügen. @@ -31,11 +31,9 @@ Sie verfügt über eine *Pfadoperation*, die einen `Invoice`-Body empfängt, und Dieser Teil ist ziemlich normal, der größte Teil des Codes ist Ihnen wahrscheinlich bereits bekannt: -```Python hl_lines="9-13 36-53" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} -/// tip | "Tipp" +/// tip | Tipp Der Query-Parameter `callback_url` verwendet einen Pydantic-Url-Typ. @@ -43,7 +41,7 @@ Der Query-Parameter `callback_url` verwendet einen Pydantic-OpenAPI-3-Ausdruck enthalten (mehr dazu weiter unten), wo er Variablen mit Parametern und Teilen des ursprünglichen Requests verwenden kann, der an *Ihre API* gesendet wurde. -### Der Callback-Pfadausdruck +### Der Callback-Pfadausdruck { #the-callback-path-expression } Der Callback-*Pfad* kann einen OpenAPI-3-Ausdruck enthalten, welcher Teile des ursprünglichen Requests enthalten kann, der an *Ihre API* gesendet wurde. @@ -163,31 +157,29 @@ und sie würde eine Response von dieser *externen API* mit einem JSON-Body wie d } ``` -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass die verwendete Callback-URL die URL enthält, die als Query-Parameter in `callback_url` (`https://www.external.org/events`) empfangen wurde, und auch die Rechnungs-`id` aus dem JSON-Body (`2expen51ve`). /// -### Den Callback-Router hinzufügen +### Den Callback-Router hinzufügen { #add-the-callback-router } An diesem Punkt haben Sie die benötigte(n) *Callback-Pfadoperation(en)* (diejenige(n), die der *externe Entwickler* in der *externen API* implementieren sollte) im Callback-Router, den Sie oben erstellt haben. Verwenden Sie nun den Parameter `callbacks` im *Pfadoperation-Dekorator Ihrer API*, um das Attribut `.routes` (das ist eigentlich nur eine `list`e von Routen/*Pfadoperationen*) dieses Callback-Routers zu übergeben: -```Python hl_lines="35" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass Sie nicht den Router selbst (`invoices_callback_router`) an `callback=` übergeben, sondern das Attribut `.routes`, wie in `invoices_callback_router.routes`. /// -### Es in der Dokumentation ansehen +### Es in der Dokumentation testen { #check-the-docs } -Jetzt können Sie Ihre Anwendung mit Uvicorn starten und auf http://127.0.0.1:8000/docs gehen. +Jetzt können Sie Ihre Anwendung starten und auf http://127.0.0.1:8000/docs gehen. Sie sehen Ihre Dokumentation, einschließlich eines Abschnitts „Callbacks“ für Ihre *Pfadoperation*, der zeigt, wie die *externe API* aussehen sollte: diff --git a/docs/de/docs/advanced/openapi-webhooks.md b/docs/de/docs/advanced/openapi-webhooks.md index fb0daa9086..a09a675ed6 100644 --- a/docs/de/docs/advanced/openapi-webhooks.md +++ b/docs/de/docs/advanced/openapi-webhooks.md @@ -1,56 +1,54 @@ -# OpenAPI-Webhooks +# OpenAPI Webhooks { #openapi-webhooks } -Es gibt Fälle, in denen Sie Ihren API-Benutzern mitteilen möchten, dass Ihre Anwendung mit einigen Daten *deren* Anwendung aufrufen (ein Request senden) könnte, normalerweise um über ein bestimmtes **Event** zu **benachrichtigen**. +Es gibt Fälle, in denen Sie Ihren API-**Benutzern** mitteilen möchten, dass Ihre App *deren* App mit einigen Daten aufrufen (einen Request senden) könnte, normalerweise um über ein bestimmtes **Event** zu **benachrichtigen**. -Das bedeutet, dass anstelle des normalen Prozesses, bei dem Benutzer Requests an Ihre API senden, **Ihre API** (oder Ihre Anwendung) **Requests an deren System** (an deren API, deren Anwendung) senden könnte. +Das bedeutet, dass anstelle des normalen Prozesses, bei dem Ihre Benutzer Requests an Ihre API senden, **Ihre API** (oder Ihre App) **Requests an deren System** (an deren API, deren App) senden könnte. -Das wird normalerweise als **Webhook** bezeichnet. +Das wird normalerweise als **Webhook** bezeichnet. -## Webhooks-Schritte +## Webhooks-Schritte { #webhooks-steps } Der Prozess besteht normalerweise darin, dass **Sie in Ihrem Code definieren**, welche Nachricht Sie senden möchten, den **Body des Requests**. -Sie definieren auch auf irgendeine Weise, zu welchen **Momenten** Ihre Anwendung diese Requests oder Events sendet. +Sie definieren auch auf irgendeine Weise, in welchen **Momenten** Ihre App diese Requests oder Events senden wird. -Und **Ihre Benutzer** definieren auf irgendeine Weise (zum Beispiel irgendwo in einem Web-Dashboard) die **URL**, an die Ihre Anwendung diese Requests senden soll. +Und **Ihre Benutzer** definieren auf irgendeine Weise (zum Beispiel irgendwo in einem Web-Dashboard) die **URL**, an die Ihre App diese Requests senden soll. Die gesamte **Logik** zur Registrierung der URLs für Webhooks und der Code zum tatsächlichen Senden dieser Requests liegt bei Ihnen. Sie schreiben es so, wie Sie möchten, in **Ihrem eigenen Code**. -## Webhooks mit **FastAPI** und OpenAPI dokumentieren +## Webhooks mit **FastAPI** und OpenAPI dokumentieren { #documenting-webhooks-with-fastapi-and-openapi } -Mit **FastAPI** können Sie mithilfe von OpenAPI die Namen dieser Webhooks, die Arten von HTTP-Operationen, die Ihre Anwendung senden kann (z. B. `POST`, `PUT`, usw.) und die Request**bodys** definieren, die Ihre Anwendung senden würde. +Mit **FastAPI**, mithilfe von OpenAPI, können Sie die Namen dieser Webhooks, die Arten von HTTP-Operationen, die Ihre App senden kann (z. B. `POST`, `PUT`, usw.) und die Request**bodys** definieren, die Ihre App senden würde. -Dies kann es Ihren Benutzern viel einfacher machen, **deren APIs zu implementieren**, um Ihre **Webhook**-Requests zu empfangen. Möglicherweise können diese sogar einen Teil des eigenem API-Codes automatisch generieren. +Dies kann es Ihren Benutzern viel einfacher machen, **deren APIs zu implementieren**, um Ihre **Webhook**-Requests zu empfangen. Möglicherweise können diese sogar einen Teil ihres eigenen API-Codes automatisch generieren. -/// info +/// info | Info Webhooks sind in OpenAPI 3.1.0 und höher verfügbar und werden von FastAPI `0.99.0` und höher unterstützt. /// -## Eine Anwendung mit Webhooks +## Eine App mit Webhooks { #an-app-with-webhooks } -Wenn Sie eine **FastAPI**-Anwendung erstellen, gibt es ein `webhooks`-Attribut, mit dem Sie *Webhooks* definieren können, genauso wie Sie *Pfadoperationen* definieren würden, zum Beispiel mit `@app.webhooks.post()`. +Wenn Sie eine **FastAPI**-Anwendung erstellen, gibt es ein `webhooks`-Attribut, das Sie verwenden können, um *Webhooks* zu definieren, genauso wie Sie *Pfadoperationen* definieren würden, zum Beispiel mit `@app.webhooks.post()`. -```Python hl_lines="9-13 36-53" -{!../../../docs_src/openapi_webhooks/tutorial001.py!} -``` +{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} Die von Ihnen definierten Webhooks landen im **OpenAPI**-Schema und der automatischen **Dokumentations-Oberfläche**. -/// info +/// info | Info -Das `app.webhooks`-Objekt ist eigentlich nur ein `APIRouter`, derselbe Typ, den Sie verwenden würden, wenn Sie Ihre Anwendung mit mehreren Dateien strukturieren. +Das `app.webhooks`-Objekt ist eigentlich nur ein `APIRouter`, derselbe Typ, den Sie verwenden würden, wenn Sie Ihre App mit mehreren Dateien strukturieren. /// -Beachten Sie, dass Sie bei Webhooks tatsächlich keinen *Pfad* (wie `/items/`) deklarieren, sondern dass der Text, den Sie dort übergeben, lediglich eine **Kennzeichnung** des Webhooks (der Name des Events) ist. Zum Beispiel ist in `@app.webhooks.post("new-subscription")` der Webhook-Name `new-subscription`. +Beachten Sie, dass Sie bei Webhooks tatsächlich keinen *Pfad* (wie `/items/`) deklarieren, der Text, den Sie dort übergeben, ist lediglich eine **Kennzeichnung** des Webhooks (der Name des Events). Zum Beispiel ist in `@app.webhooks.post("new-subscription")` der Webhook-Name `new-subscription`. -Das liegt daran, dass erwartet wird, dass **Ihre Benutzer** den tatsächlichen **URL-Pfad**, an dem diese den Webhook-Request empfangen möchten, auf andere Weise definieren (z. B. über ein Web-Dashboard). +Das liegt daran, dass erwartet wird, dass **Ihre Benutzer** den tatsächlichen **URL-Pfad**, an dem sie den Webhook-Request empfangen möchten, auf andere Weise definieren (z. B. über ein Web-Dashboard). -### Es in der Dokumentation ansehen +### Die Dokumentation testen { #check-the-docs } -Jetzt können Sie Ihre Anwendung mit Uvicorn starten und auf http://127.0.0.1:8000/docs gehen. +Jetzt können Sie Ihre App starten und auf http://127.0.0.1:8000/docs gehen. Sie werden sehen, dass Ihre Dokumentation die normalen *Pfadoperationen* und jetzt auch einige **Webhooks** enthält: diff --git a/docs/de/docs/advanced/path-operation-advanced-configuration.md b/docs/de/docs/advanced/path-operation-advanced-configuration.md index c9cb82fe3d..f5ec7c49ea 100644 --- a/docs/de/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/de/docs/advanced/path-operation-advanced-configuration.md @@ -1,8 +1,8 @@ -# Fortgeschrittene Konfiguration der Pfadoperation +# Fortgeschrittene Konfiguration der Pfadoperation { #path-operation-advanced-configuration } -## OpenAPI operationId +## OpenAPI operationId { #openapi-operationid } -/// warning | "Achtung" +/// warning | Achtung Wenn Sie kein „Experte“ für OpenAPI sind, brauchen Sie dies wahrscheinlich nicht. @@ -12,27 +12,23 @@ Mit dem Parameter `operation_id` können Sie die OpenAPI `operationId` festlegen Sie müssten sicherstellen, dass sie für jede Operation eindeutig ist. -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} -### Verwendung des Namens der *Pfadoperation-Funktion* als operationId +### Verwendung des Namens der *Pfadoperation-Funktion* als operationId { #using-the-path-operation-function-name-as-the-operationid } Wenn Sie die Funktionsnamen Ihrer API als `operationId`s verwenden möchten, können Sie über alle iterieren und die `operation_id` jeder *Pfadoperation* mit deren `APIRoute.name` überschreiben. Sie sollten dies tun, nachdem Sie alle Ihre *Pfadoperationen* hinzugefügt haben. -```Python hl_lines="2 12-21 24" -{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} -/// tip | "Tipp" +/// tip | Tipp Wenn Sie `app.openapi()` manuell aufrufen, sollten Sie vorher die `operationId`s aktualisiert haben. /// -/// warning | "Achtung" +/// warning | Achtung Wenn Sie dies tun, müssen Sie sicherstellen, dass jede Ihrer *Pfadoperation-Funktionen* einen eindeutigen Namen hat. @@ -40,15 +36,13 @@ Auch wenn diese sich in unterschiedlichen Modulen (Python-Dateien) befinden. /// -## Von OpenAPI ausschließen +## Von OpenAPI ausschließen { #exclude-from-openapi } Um eine *Pfadoperation* aus dem generierten OpenAPI-Schema (und damit aus den automatischen Dokumentationssystemen) auszuschließen, verwenden Sie den Parameter `include_in_schema` und setzen Sie ihn auf `False`: -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} -## Fortgeschrittene Beschreibung mittels Docstring +## Fortgeschrittene Beschreibung mittels Docstring { #advanced-description-from-docstring } Sie können die verwendeten Zeilen aus dem Docstring einer *Pfadoperation-Funktion* einschränken, die für OpenAPI verwendet werden. @@ -56,25 +50,23 @@ Das Hinzufügen eines `\f` (ein maskiertes „Form Feed“-Zeichen) führt dazu, Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (z. B. Sphinx) können den Rest verwenden. -```Python hl_lines="19-29" -{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} -## Zusätzliche Responses +## Zusätzliche Responses { #additional-responses } Sie haben wahrscheinlich gesehen, wie man das `response_model` und den `status_code` für eine *Pfadoperation* deklariert. -Das definiert die Metadaten der Haupt-Response einer *Pfadoperation*. +Das definiert die Metadaten der Haupt-Response einer *Pfadoperation*. Sie können auch zusätzliche Responses mit deren Modellen, Statuscodes usw. deklarieren. Es gibt hier in der Dokumentation ein ganzes Kapitel darüber, Sie können es unter [Zusätzliche Responses in OpenAPI](additional-responses.md){.internal-link target=_blank} lesen. -## OpenAPI-Extra +## OpenAPI-Extra { #openapi-extra } Wenn Sie in Ihrer Anwendung eine *Pfadoperation* deklarieren, generiert **FastAPI** automatisch die relevanten Metadaten dieser *Pfadoperation*, die in das OpenAPI-Schema aufgenommen werden sollen. -/// note | "Technische Details" +/// note | Technische Details In der OpenAPI-Spezifikation wird das Operationsobjekt genannt. @@ -86,9 +78,9 @@ Es enthält `tags`, `parameters`, `requestBody`, `responses`, usw. Dieses *Pfadoperation*-spezifische OpenAPI-Schema wird normalerweise automatisch von **FastAPI** generiert, Sie können es aber auch erweitern. -/// tip | "Tipp" +/// tip | Tipp -Dies ist ein Low-Level Erweiterungspunkt. +Dies ist ein Low-Level-Erweiterungspunkt. Wenn Sie nur zusätzliche Responses deklarieren müssen, können Sie dies bequemer mit [Zusätzliche Responses in OpenAPI](additional-responses.md){.internal-link target=_blank} tun. @@ -96,13 +88,11 @@ Wenn Sie nur zusätzliche Responses deklarieren müssen, können Sie dies bequem Sie können das OpenAPI-Schema für eine *Pfadoperation* erweitern, indem Sie den Parameter `openapi_extra` verwenden. -### OpenAPI-Erweiterungen +### OpenAPI-Erweiterungen { #openapi-extensions } -Dieses `openapi_extra` kann beispielsweise hilfreich sein, um OpenAPI-Erweiterungen zu deklarieren: +Dieses `openapi_extra` kann beispielsweise hilfreich sein, um [OpenAPI-Erweiterungen](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) zu deklarieren: -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial005.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} Wenn Sie die automatische API-Dokumentation öffnen, wird Ihre Erweiterung am Ende der spezifischen *Pfadoperation* angezeigt. @@ -139,49 +129,43 @@ Und wenn Sie die resultierende OpenAPI sehen (unter `/openapi.json` in Ihrer API } ``` -### Benutzerdefiniertes OpenAPI-*Pfadoperation*-Schema +### Benutzerdefiniertes OpenAPI-*Pfadoperation*-Schema { #custom-openapi-path-operation-schema } -Das Dictionary in `openapi_extra` wird mit dem automatisch generierten OpenAPI-Schema für die *Pfadoperation* zusammengeführt (mittels Deep Merge). +Das Dictionary in `openapi_extra` wird mit dem automatisch generierten OpenAPI-Schema für die *Pfadoperation* zusammengeführt (mittels Deep Merge). Sie können dem automatisch generierten Schema also zusätzliche Daten hinzufügen. -Sie könnten sich beispielsweise dafür entscheiden, den Request mit Ihrem eigenen Code zu lesen und zu validieren, ohne die automatischen Funktionen von FastAPI mit Pydantic zu verwenden, aber Sie könnten den Request trotzdem im OpenAPI-Schema definieren wollen. +Sie könnten sich beispielsweise dafür entscheiden, den Request mit Ihrem eigenen Code zu lesen und zu validieren, ohne die automatischen Funktionen von FastAPI mit Pydantic zu verwenden, aber Sie könnten den Request trotzdem im OpenAPI-Schema definieren wollen. Das könnte man mit `openapi_extra` machen: -```Python hl_lines="20-37 39-40" -{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} -In diesem Beispiel haben wir kein Pydantic-Modell deklariert. Tatsächlich wird der Requestbody nicht einmal als JSON geparst, sondern direkt als `bytes` gelesen und die Funktion `magic_data_reader ()` wäre dafür verantwortlich, ihn in irgendeiner Weise zu parsen. +In diesem Beispiel haben wir kein Pydantic-Modell deklariert. Tatsächlich wird der Requestbody nicht einmal als JSON geparst, sondern direkt als `bytes` gelesen und die Funktion `magic_data_reader()` wäre dafür verantwortlich, ihn in irgendeiner Weise zu parsen. Dennoch können wir das zu erwartende Schema für den Requestbody deklarieren. -### Benutzerdefinierter OpenAPI-Content-Type +### Benutzerdefinierter OpenAPI-Content-Type { #custom-openapi-content-type } Mit demselben Trick könnten Sie ein Pydantic-Modell verwenden, um das JSON-Schema zu definieren, das dann im benutzerdefinierten Abschnitt des OpenAPI-Schemas für die *Pfadoperation* enthalten ist. -Und Sie könnten dies auch tun, wenn der Datentyp in der Anfrage nicht JSON ist. +Und Sie könnten dies auch tun, wenn der Datentyp im Request nicht JSON ist. In der folgenden Anwendung verwenden wir beispielsweise weder die integrierte Funktionalität von FastAPI zum Extrahieren des JSON-Schemas aus Pydantic-Modellen noch die automatische Validierung für JSON. Tatsächlich deklarieren wir den Request-Content-Type als YAML und nicht als JSON: //// tab | Pydantic v2 -```Python hl_lines="17-22 24" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} //// //// tab | Pydantic v1 -```Python hl_lines="17-22 24" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} //// -/// info +/// info | Info In Pydantic Version 1 hieß die Methode zum Abrufen des JSON-Schemas für ein Modell `Item.schema()`, in Pydantic Version 2 heißt die Methode `Item.model_json_schema()`. @@ -195,27 +179,23 @@ Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann //// tab | Pydantic v2 -```Python hl_lines="26-33" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} //// //// tab | Pydantic v1 -```Python hl_lines="26-33" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} //// -/// info +/// info | Info In Pydantic Version 1 war die Methode zum Parsen und Validieren eines Objekts `Item.parse_obj()`, in Pydantic Version 2 heißt die Methode `Item.model_validate()`. /// -/// tip | "Tipp" +/// tip | Tipp Hier verwenden wir dasselbe Pydantic-Modell wieder. diff --git a/docs/de/docs/advanced/response-change-status-code.md b/docs/de/docs/advanced/response-change-status-code.md index bba908a3ee..b079e241d1 100644 --- a/docs/de/docs/advanced/response-change-status-code.md +++ b/docs/de/docs/advanced/response-change-status-code.md @@ -1,33 +1,31 @@ -# Response – Statuscode ändern +# Response – Statuscode ändern { #response-change-status-code } -Sie haben wahrscheinlich schon vorher gelesen, dass Sie einen Standard-[Response-Statuscode](../tutorial/response-status-code.md){.internal-link target=_blank} festlegen können. +Sie haben wahrscheinlich schon vorher gelesen, dass Sie einen Default-[Response-Statuscode](../tutorial/response-status-code.md){.internal-link target=_blank} festlegen können. -In manchen Fällen müssen Sie jedoch einen anderen als den Standard-Statuscode zurückgeben. +In manchen Fällen müssen Sie jedoch einen anderen als den Default-Statuscode zurückgeben. -## Anwendungsfall +## Anwendungsfall { #use-case } Stellen Sie sich zum Beispiel vor, Sie möchten standardmäßig den HTTP-Statuscode „OK“ `200` zurückgeben. -Wenn die Daten jedoch nicht vorhanden waren, möchten Sie diese erstellen und den HTTP-Statuscode „CREATED“ `201` zurückgeben. +Wenn die Daten jedoch nicht vorhanden sind, möchten Sie diese erstellen und den HTTP-Statuscode „CREATED“ `201` zurückgeben. Sie möchten aber dennoch in der Lage sein, die von Ihnen zurückgegebenen Daten mit einem `response_model` zu filtern und zu konvertieren. In diesen Fällen können Sie einen `Response`-Parameter verwenden. -## Einen `Response`-Parameter verwenden +## Einen `Response`-Parameter verwenden { #use-a-response-parameter } Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion* deklarieren (wie Sie es auch für Cookies und Header tun können). -Anschließend können Sie den `status_code` in diesem *vorübergehenden* Response-Objekt festlegen. +Anschließend können Sie den `status_code` in diesem *vorübergehenden* Response-Objekt festlegen. -```Python hl_lines="1 9 12" -{!../../../docs_src/response_change_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} -Und dann können Sie wie gewohnt jedes benötigte Objekt zurückgeben (ein `dict`, ein Datenbankmodell usw.). +Und dann können Sie jedes benötigte Objekt zurückgeben, wie Sie es normalerweise tun würden (ein `dict`, ein Datenbankmodell usw.). Und wenn Sie ein `response_model` deklariert haben, wird es weiterhin zum Filtern und Konvertieren des von Ihnen zurückgegebenen Objekts verwendet. **FastAPI** verwendet diese *vorübergehende* Response, um den Statuscode (auch Cookies und Header) zu extrahieren und fügt diese in die endgültige Response ein, die den von Ihnen zurückgegebenen Wert enthält, gefiltert nach einem beliebigen `response_model`. -Sie können den Parameter `Response` auch in Abhängigkeiten deklarieren und den Statuscode darin festlegen. Bedenken Sie jedoch, dass der gewinnt, welcher zuletzt gesetzt wird. +Sie können den Parameter `Response` auch in Abhängigkeiten deklarieren und den Statuscode darin festlegen. Bedenken Sie jedoch, dass der zuletzt gesetzte gewinnt. diff --git a/docs/de/docs/advanced/response-cookies.md b/docs/de/docs/advanced/response-cookies.md index 3d2043565b..02fe99c26a 100644 --- a/docs/de/docs/advanced/response-cookies.md +++ b/docs/de/docs/advanced/response-cookies.md @@ -1,14 +1,12 @@ -# Response-Cookies +# Response-Cookies { #response-cookies } -## Einen `Response`-Parameter verwenden +## Einen `Response`-Parameter verwenden { #use-a-response-parameter } Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion* deklarieren. -Und dann können Sie Cookies in diesem *vorübergehenden* Response-Objekt setzen. +Und dann können Sie Cookies in diesem *vorübergehenden* Response-Objekt setzen. -```Python hl_lines="1 8-9" -{!../../../docs_src/response_cookies/tutorial002.py!} -``` +{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.). @@ -18,7 +16,7 @@ Und wenn Sie ein `response_model` deklariert haben, wird es weiterhin zum Filter Sie können den `Response`-Parameter auch in Abhängigkeiten deklarieren und darin Cookies (und Header) setzen. -## Eine `Response` direkt zurückgeben +## Eine `Response` direkt zurückgeben { #return-a-response-directly } Sie können Cookies auch erstellen, wenn Sie eine `Response` direkt in Ihrem Code zurückgeben. @@ -26,11 +24,9 @@ Dazu können Sie eine Response erstellen, wie unter [Eine Response direkt zurüc Setzen Sie dann Cookies darin und geben Sie sie dann zurück: -```Python hl_lines="10-12" -{!../../../docs_src/response_cookies/tutorial001.py!} -``` +{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass, wenn Sie eine Response direkt zurückgeben, anstatt den `Response`-Parameter zu verwenden, FastAPI diese direkt zurückgibt. @@ -40,9 +36,9 @@ Und auch, dass Sie keine Daten senden, die durch ein `response_model` hätten ge /// -### Mehr Informationen +### Mehr Informationen { #more-info } -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette.responses import Response` oder `from starlette.responses import JSONResponse` verwenden. @@ -52,4 +48,4 @@ Und da die `Response` häufig zum Setzen von Headern und Cookies verwendet wird, /// -Um alle verfügbaren Parameter und Optionen anzuzeigen, sehen Sie sich deren Dokumentation in Starlette an. +Um alle verfügbaren Parameter und Optionen anzuzeigen, sehen Sie sich deren Dokumentation in Starlette an. diff --git a/docs/de/docs/advanced/response-directly.md b/docs/de/docs/advanced/response-directly.md index 377490b569..d995173730 100644 --- a/docs/de/docs/advanced/response-directly.md +++ b/docs/de/docs/advanced/response-directly.md @@ -1,20 +1,20 @@ -# Eine Response direkt zurückgeben +# Eine Response direkt zurückgeben { #return-a-response-directly } -Wenn Sie eine **FastAPI** *Pfadoperation* erstellen, können Sie normalerweise beliebige Daten davon zurückgeben: ein `dict`, eine `list`e, ein Pydantic-Modell, ein Datenbankmodell, usw. +Wenn Sie eine **FastAPI** *Pfadoperation* erstellen, können Sie normalerweise beliebige Daten davon zurückgeben: ein `dict`, eine `list`, ein Pydantic-Modell, ein Datenbankmodell, usw. Standardmäßig konvertiert **FastAPI** diesen Rückgabewert automatisch nach JSON, mithilfe des `jsonable_encoder`, der in [JSON-kompatibler Encoder](../tutorial/encoder.md){.internal-link target=_blank} erläutert wird. -Dann würde es hinter den Kulissen diese JSON-kompatiblen Daten (z. B. ein `dict`) in eine `JSONResponse` einfügen, die zum Senden der Response an den Client verwendet würde. +Dann würde es hinter den Kulissen diese JSON-kompatiblen Daten (z. B. ein `dict`) in eine `JSONResponse` einfügen, die zum Senden der Response an den Client verwendet wird. Sie können jedoch direkt eine `JSONResponse` von Ihren *Pfadoperationen* zurückgeben. Das kann beispielsweise nützlich sein, um benutzerdefinierte Header oder Cookies zurückzugeben. -## Eine `Response` zurückgeben +## Eine `Response` zurückgeben { #return-a-response } Tatsächlich können Sie jede `Response` oder jede Unterklasse davon zurückgeben. -/// tip | "Tipp" +/// tip | Tipp `JSONResponse` selbst ist eine Unterklasse von `Response`. @@ -26,7 +26,7 @@ Es wird keine Datenkonvertierung mit Pydantic-Modellen durchführen, es wird den Dadurch haben Sie viel Flexibilität. Sie können jeden Datentyp zurückgeben, jede Datendeklaration oder -validierung überschreiben, usw. -## Verwendung des `jsonable_encoder` in einer `Response` +## Verwendung des `jsonable_encoder` in einer `Response` { #using-the-jsonable-encoder-in-a-response } Da **FastAPI** keine Änderungen an einer von Ihnen zurückgegebenen `Response` vornimmt, müssen Sie sicherstellen, dass deren Inhalt dafür bereit ist. @@ -34,19 +34,17 @@ Sie können beispielsweise kein Pydantic-Modell in eine `JSONResponse` einfügen In diesen Fällen können Sie den `jsonable_encoder` verwenden, um Ihre Daten zu konvertieren, bevor Sie sie an eine Response übergeben: -```Python hl_lines="6-7 21-22" -{!../../../docs_src/response_directly/tutorial001.py!} -``` +{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} -/// note | "Technische Details" +/// note | Technische Details -Sie können auch `from starlette.responses import JSONResponse` verwenden. +Sie könnten auch `from starlette.responses import JSONResponse` verwenden. **FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, als Annehmlichkeit für Sie, den Entwickler. Die meisten verfügbaren Responses kommen aber direkt von Starlette. /// -## Eine benutzerdefinierte `Response` zurückgeben +## Eine benutzerdefinierte `Response` zurückgeben { #returning-a-custom-response } Das obige Beispiel zeigt alle Teile, die Sie benötigen, ist aber noch nicht sehr nützlich, da Sie das `item` einfach direkt hätten zurückgeben können, und **FastAPI** würde es für Sie in eine `JSONResponse` einfügen, es in ein `dict` konvertieren, usw. All das standardmäßig. @@ -56,11 +54,9 @@ Nehmen wir an, Sie möchten eine Response-Objekt festlegen. -```Python hl_lines="1 7-8" -{!../../../docs_src/response_headers/tutorial002.py!} -``` +{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.). @@ -18,17 +16,15 @@ Und wenn Sie ein `response_model` deklariert haben, wird es weiterhin zum Filter Sie können den Parameter `Response` auch in Abhängigkeiten deklarieren und darin Header (und Cookies) festlegen. -## Eine `Response` direkt zurückgeben +## Eine `Response` direkt zurückgeben { #return-a-response-directly } Sie können auch Header hinzufügen, wenn Sie eine `Response` direkt zurückgeben. Erstellen Sie eine Response wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link target=_blank} beschrieben und übergeben Sie die Header als zusätzlichen Parameter: -```Python hl_lines="10-12" -{!../../../docs_src/response_headers/tutorial001.py!} -``` +{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette.responses import Response` oder `from starlette.responses import JSONResponse` verwenden. @@ -38,8 +34,8 @@ Und da die `Response` häufig zum Setzen von Headern und Cookies verwendet wird, /// -## Benutzerdefinierte Header +## Benutzerdefinierte Header { #custom-headers } -Beachten Sie, dass benutzerdefinierte proprietäre Header mittels des Präfix 'X-' hinzugefügt werden können. +Beachten Sie, dass benutzerdefinierte proprietäre Header mittels des Präfix `X-` hinzugefügt werden können. -Wenn Sie jedoch benutzerdefinierte Header haben, die ein Client in einem Browser sehen können soll, müssen Sie diese zu Ihren CORS-Konfigurationen hinzufügen (weitere Informationen finden Sie unter [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), unter Verwendung des Parameters `expose_headers`, dokumentiert in Starlettes CORS-Dokumentation. +Wenn Sie jedoch benutzerdefinierte Header haben, die ein Client in einem Browser sehen können soll, müssen Sie diese zu Ihrer CORS-Konfiguration hinzufügen (weitere Informationen finden Sie unter [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), unter Verwendung des Parameters `expose_headers`, dokumentiert in Starlettes CORS-Dokumentation. diff --git a/docs/de/docs/advanced/security/http-basic-auth.md b/docs/de/docs/advanced/security/http-basic-auth.md index 3e7aeb8a03..f906ecd92b 100644 --- a/docs/de/docs/advanced/security/http-basic-auth.md +++ b/docs/de/docs/advanced/security/http-basic-auth.md @@ -1,4 +1,4 @@ -# HTTP Basic Auth +# HTTP Basic Auth { #http-basic-auth } Für die einfachsten Fälle können Sie HTTP Basic Auth verwenden. @@ -6,13 +6,13 @@ Bei HTTP Basic Auth erwartet die Anwendung einen Header, der einen Benutzernamen Wenn sie diesen nicht empfängt, gibt sie den HTTP-Error 401 „Unauthorized“ zurück. -Und gibt einen Header `WWW-Authenticate` mit dem Wert `Basic` und einem optionalen `realm`-Parameter („Bereich“) zurück. +Und gibt einen Header `WWW-Authenticate` mit dem Wert `Basic` und einem optionalen `realm`-Parameter zurück. Dadurch wird der Browser angewiesen, die integrierte Eingabeaufforderung für einen Benutzernamen und ein Passwort anzuzeigen. Wenn Sie dann den Benutzernamen und das Passwort eingeben, sendet der Browser diese automatisch im Header. -## Einfaches HTTP Basic Auth +## Einfaches HTTP Basic Auth { #simple-http-basic-auth } * Importieren Sie `HTTPBasic` und `HTTPBasicCredentials`. * Erstellen Sie mit `HTTPBasic` ein „`security`-Schema“. @@ -20,41 +20,13 @@ Wenn Sie dann den Benutzernamen und das Passwort eingeben, sendet der Browser di * Diese gibt ein Objekt vom Typ `HTTPBasicCredentials` zurück: * Es enthält den gesendeten `username` und das gesendete `password`. -//// tab | Python 3.9+ - -```Python hl_lines="4 8 12" -{!> ../../../docs_src/security/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="2 7 11" -{!> ../../../docs_src/security/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="2 6 10" -{!> ../../../docs_src/security/tutorial006.py!} -``` - -//// +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} Wenn Sie versuchen, die URL zum ersten Mal zu öffnen (oder in der Dokumentation auf den Button „Execute“ zu klicken), wird der Browser Sie nach Ihrem Benutzernamen und Passwort fragen: -## Den Benutzernamen überprüfen +## Den Benutzernamen überprüfen { #check-the-username } Hier ist ein vollständigeres Beispiel. @@ -68,35 +40,7 @@ Um dies zu lösen, konvertieren wir zunächst den `username` und das `password` Dann können wir `secrets.compare_digest()` verwenden, um sicherzustellen, dass `credentials.username` `"stanleyjobson"` und `credentials.password` `"swordfish"` ist. -//// tab | Python 3.9+ - -```Python hl_lines="1 12-24" -{!> ../../../docs_src/security/tutorial007_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 12-24" -{!> ../../../docs_src/security/tutorial007_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1 11-21" -{!> ../../../docs_src/security/tutorial007.py!} -``` - -//// +{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} Dies wäre das gleiche wie: @@ -108,13 +52,13 @@ if not (credentials.username == "stanleyjobson") or not (credentials.password == Aber durch die Verwendung von `secrets.compare_digest()` ist dieser Code sicher vor einer Art von Angriffen, die „Timing-Angriffe“ genannt werden. -### Timing-Angriffe +### Timing-Angriffe { #timing-attacks } Aber was ist ein „Timing-Angriff“? Stellen wir uns vor, dass einige Angreifer versuchen, den Benutzernamen und das Passwort zu erraten. -Und sie senden eine Anfrage mit dem Benutzernamen `johndoe` und dem Passwort `love123`. +Und sie senden einen Request mit dem Benutzernamen `johndoe` und dem Passwort `love123`. Dann würde der Python-Code in Ihrer Anwendung etwa so aussehen: @@ -134,21 +78,21 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": ... ``` -Python muss das gesamte `stanleyjobso` in `stanleyjobsox` und `stanleyjobson` vergleichen, bevor es erkennt, dass beide Zeichenfolgen nicht gleich sind. Daher wird es einige zusätzliche Mikrosekunden dauern, bis die Antwort „Incorrect username or password“ erfolgt. +Python muss das gesamte `stanleyjobso` in `stanleyjobsox` und `stanleyjobson` vergleichen, bevor es erkennt, dass beide Zeichenfolgen nicht gleich sind. Daher wird es einige zusätzliche Mikrosekunden dauern, bis die Response „Incorrect username or password“ erfolgt. -#### Die Zeit zum Antworten hilft den Angreifern +#### Die Zeit zum Antworten hilft den Angreifern { #the-time-to-answer-helps-the-attackers } -Wenn die Angreifer zu diesem Zeitpunkt feststellen, dass der Server einige Mikrosekunden länger braucht, um die Antwort „Incorrect username or password“ zu senden, wissen sie, dass sie _etwas_ richtig gemacht haben, einige der Anfangsbuchstaben waren richtig. +Wenn die Angreifer zu diesem Zeitpunkt feststellen, dass der Server einige Mikrosekunden länger braucht, um die Response „Incorrect username or password“ zu senden, wissen sie, dass sie _etwas_ richtig gemacht haben, einige der Anfangsbuchstaben waren richtig. Und dann können sie es noch einmal versuchen, wohl wissend, dass es wahrscheinlich eher etwas mit `stanleyjobsox` als mit `johndoe` zu tun hat. -#### Ein „professioneller“ Angriff +#### Ein „professioneller“ Angriff { #a-professional-attack } Natürlich würden die Angreifer das alles nicht von Hand versuchen, sondern ein Programm dafür schreiben, möglicherweise mit Tausenden oder Millionen Tests pro Sekunde. Und würden jeweils nur einen zusätzlichen richtigen Buchstaben erhalten. Aber so hätten die Angreifer in wenigen Minuten oder Stunden mit der „Hilfe“ unserer Anwendung den richtigen Benutzernamen und das richtige Passwort erraten, indem sie die Zeitspanne zur Hilfe nehmen, die diese zur Beantwortung benötigt. -#### Das Problem beheben mittels `secrets.compare_digest()` +#### Das Problem beheben mittels `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest } Aber in unserem Code verwenden wir tatsächlich `secrets.compare_digest()`. @@ -156,36 +100,8 @@ Damit wird, kurz gesagt, der Vergleich von `stanleyjobsox` mit `stanleyjobson` g So ist Ihr Anwendungscode, dank der Verwendung von `secrets.compare_digest()`, vor dieser ganzen Klasse von Sicherheitsangriffen geschützt. -### Den Error zurückgeben +### Den Error zurückgeben { #return-the-error } Nachdem Sie festgestellt haben, dass die Anmeldeinformationen falsch sind, geben Sie eine `HTTPException` mit dem Statuscode 401 zurück (derselbe, der auch zurückgegeben wird, wenn keine Anmeldeinformationen angegeben werden) und fügen den Header `WWW-Authenticate` hinzu, damit der Browser die Anmeldeaufforderung erneut anzeigt: -//// tab | Python 3.9+ - -```Python hl_lines="26-30" -{!> ../../../docs_src/security/tutorial007_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="26-30" -{!> ../../../docs_src/security/tutorial007_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="23-27" -{!> ../../../docs_src/security/tutorial007.py!} -``` - -//// +{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/de/docs/advanced/security/index.md b/docs/de/docs/advanced/security/index.md index 380e48bbf4..d7e6332314 100644 --- a/docs/de/docs/advanced/security/index.md +++ b/docs/de/docs/advanced/security/index.md @@ -1,10 +1,10 @@ -# Fortgeschrittene Sicherheit +# Fortgeschrittene Sicherheit { #advanced-security } -## Zusatzfunktionen +## Zusatzfunktionen { #additional-features } Neben den in [Tutorial – Benutzerhandbuch: Sicherheit](../../tutorial/security/index.md){.internal-link target=_blank} behandelten Funktionen gibt es noch einige zusätzliche Funktionen zur Handhabung der Sicherheit. -/// tip | "Tipp" +/// tip | Tipp Die nächsten Abschnitte sind **nicht unbedingt „fortgeschritten“**. @@ -12,8 +12,8 @@ Und es ist möglich, dass für Ihren Anwendungsfall die Lösung in einem davon l /// -## Lesen Sie zuerst das Tutorial +## Das Tutorial zuerst lesen { #read-the-tutorial-first } -In den nächsten Abschnitten wird davon ausgegangen, dass Sie das Haupt-[Tutorial – Benutzerhandbuch: Sicherheit](../../tutorial/security/index.md){.internal-link target=_blank} bereits gelesen haben. +Die nächsten Abschnitte setzen voraus, dass Sie das Haupt-[Tutorial – Benutzerhandbuch: Sicherheit](../../tutorial/security/index.md){.internal-link target=_blank} bereits gelesen haben. Sie basieren alle auf den gleichen Konzepten, ermöglichen jedoch einige zusätzliche Funktionalitäten. diff --git a/docs/de/docs/advanced/security/oauth2-scopes.md b/docs/de/docs/advanced/security/oauth2-scopes.md index f02707698e..b96715d5a4 100644 --- a/docs/de/docs/advanced/security/oauth2-scopes.md +++ b/docs/de/docs/advanced/security/oauth2-scopes.md @@ -1,16 +1,16 @@ -# OAuth2-Scopes +# OAuth2-Scopes { #oauth2-scopes } Sie können OAuth2-Scopes direkt in **FastAPI** verwenden, sie sind nahtlos integriert. Das ermöglicht es Ihnen, ein feingranuliertes Berechtigungssystem nach dem OAuth2-Standard in Ihre OpenAPI-Anwendung (und deren API-Dokumentation) zu integrieren. -OAuth2 mit Scopes ist der Mechanismus, der von vielen großen Authentifizierungsanbietern wie Facebook, Google, GitHub, Microsoft, Twitter usw. verwendet wird. Sie verwenden ihn, um Benutzern und Anwendungen spezifische Berechtigungen zu erteilen. +OAuth2 mit Scopes ist der Mechanismus, der von vielen großen Authentifizierungsanbietern wie Facebook, Google, GitHub, Microsoft, X (Twitter) usw. verwendet wird. Sie verwenden ihn, um Benutzern und Anwendungen spezifische Berechtigungen zu erteilen. -Jedes Mal, wenn Sie sich mit Facebook, Google, GitHub, Microsoft oder Twitter anmelden („log in with“), verwendet die entsprechende Anwendung OAuth2 mit Scopes. +Jedes Mal, wenn Sie sich mit Facebook, Google, GitHub, Microsoft oder X (Twitter) anmelden („log in with“), verwendet die entsprechende Anwendung OAuth2 mit Scopes. In diesem Abschnitt erfahren Sie, wie Sie Authentifizierung und Autorisierung mit demselben OAuth2, mit Scopes in Ihrer **FastAPI**-Anwendung verwalten. -/// warning | "Achtung" +/// warning | Achtung Dies ist ein mehr oder weniger fortgeschrittener Abschnitt. Wenn Sie gerade erst anfangen, können Sie ihn überspringen. @@ -26,7 +26,7 @@ Aber wenn Sie wissen, dass Sie es brauchen oder neugierig sind, lesen Sie weiter /// -## OAuth2-Scopes und OpenAPI +## OAuth2-Scopes und OpenAPI { #oauth2-scopes-and-openapi } Die OAuth2-Spezifikation definiert „Scopes“ als eine Liste von durch Leerzeichen getrennten Strings. @@ -46,7 +46,7 @@ Er wird normalerweise verwendet, um bestimmte Sicherheitsberechtigungen zu dekla * `instagram_basic` wird von Facebook / Instagram verwendet. * `https://www.googleapis.com/auth/drive` wird von Google verwendet. -/// info +/// info | Info In OAuth2 ist ein „Scope“ nur ein String, der eine bestimmte erforderliche Berechtigung deklariert. @@ -58,149 +58,21 @@ Für OAuth2 sind es einfach nur Strings. /// -## Gesamtübersicht +## Gesamtübersicht { #global-view } Sehen wir uns zunächst kurz die Teile an, die sich gegenüber den Beispielen im Haupt-**Tutorial – Benutzerhandbuch** für [OAuth2 mit Password (und Hashing), Bearer mit JWT-Tokens](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} ändern. Diesmal verwenden wir OAuth2-Scopes: -//// tab | 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!} -``` - -//// - -//// tab | 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!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="2 4 8 12 47 65 106 108-116 122-125 129-135 140 156" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="3 7 11 45 63 104 106-114 120-123 127-133 138 154" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 155" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 155" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} Sehen wir uns diese Änderungen nun Schritt für Schritt an. -## OAuth2-Sicherheitsschema +## OAuth2-Sicherheitsschema { #oauth2-security-scheme } Die erste Änderung ist, dass wir jetzt das OAuth2-Sicherheitsschema mit zwei verfügbaren Scopes deklarieren: `me` und `items`. -Der `scopes`-Parameter erhält ein `dict` mit jedem Scope als Schlüssel und dessen Beschreibung als Wert: +Der `scopes`-Parameter erhält ein `dict` mit jedem Scope als Schlüssel und dessen Beschreibung als Wert: -//// tab | Python 3.10+ - -```Python hl_lines="62-65" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="62-65" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="63-66" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="61-64" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="62-65" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="62-65" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} Da wir diese Scopes jetzt deklarieren, werden sie in der API-Dokumentation angezeigt, wenn Sie sich einloggen/autorisieren. @@ -210,15 +82,15 @@ Das ist derselbe Mechanismus, der verwendet wird, wenn Sie beim Anmelden mit Fac -## JWT-Token mit Scopes +## JWT-Token mit Scopes { #jwt-token-with-scopes } Ändern Sie nun die Token-*Pfadoperation*, um die angeforderten Scopes zurückzugeben. -Wir verwenden immer noch dasselbe `OAuth2PasswordRequestForm`. Es enthält eine Eigenschaft `scopes` mit einer `list`e von `str`s für jeden Scope, den es im Request erhalten hat. +Wir verwenden immer noch dasselbe `OAuth2PasswordRequestForm`. Es enthält eine Eigenschaft `scopes` mit einer `list`e von `str`s für jeden Scope, den es im Request erhalten hat. Und wir geben die Scopes als Teil des JWT-Tokens zurück. -/// danger | "Gefahr" +/// danger | Gefahr Der Einfachheit halber fügen wir hier die empfangenen Scopes direkt zum Token hinzu. @@ -226,73 +98,9 @@ Aus Sicherheitsgründen sollten Sie jedoch sicherstellen, dass Sie in Ihrer Anwe /// -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} -```Python hl_lines="155" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="155" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="156" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="154" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="155" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="155" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Scopes in *Pfadoperationen* und Abhängigkeiten deklarieren +## Scopes in *Pfadoperationen* und Abhängigkeiten deklarieren { #declare-scopes-in-path-operations-and-dependencies } Jetzt deklarieren wir, dass die *Pfadoperation* für `/users/me/items/` den Scope `items` erfordert. @@ -308,7 +116,7 @@ Und die Abhängigkeitsfunktion `get_current_active_user` kann auch Unterabhängi In diesem Fall erfordert sie den Scope `me` (sie könnte mehr als einen Scope erfordern). -/// note | "Hinweis" +/// note | Hinweis Sie müssen nicht unbedingt an verschiedenen Stellen verschiedene Scopes hinzufügen. @@ -316,73 +124,9 @@ Wir tun dies hier, um zu demonstrieren, wie **FastAPI** auf verschiedenen Ebenen /// -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} -```Python hl_lines="4 139 170" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 139 170" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 140 171" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="3 138 167" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="4 139 168" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="4 139 168" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -/// info | "Technische Details" +/// info | Technische Details `Security` ist tatsächlich eine Unterklasse von `Depends` und hat nur noch einen zusätzlichen Parameter, den wir später kennenlernen werden. @@ -392,7 +136,7 @@ Wenn Sie jedoch `Query`, `Path`, `Depends`, `Security` und andere von `fastapi` /// -## `SecurityScopes` verwenden +## `SecurityScopes` verwenden { #use-securityscopes } Aktualisieren Sie nun die Abhängigkeit `get_current_user`. @@ -406,73 +150,9 @@ Wir deklarieren auch einen speziellen Parameter vom Typ `SecurityScopes`, der au Diese `SecurityScopes`-Klasse ähnelt `Request` (`Request` wurde verwendet, um das Request-Objekt direkt zu erhalten). -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} -```Python hl_lines="8 105" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="8 105" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8 106" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7 104" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8 105" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8 105" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Die `scopes` verwenden +## Die `scopes` verwenden { #use-the-scopes } Der Parameter `security_scopes` wird vom Typ `SecurityScopes` sein. @@ -484,73 +164,9 @@ Wir erstellen eine `HTTPException`, die wir später an mehreren Stellen wiederve In diese Exception fügen wir (falls vorhanden) die erforderlichen Scopes als durch Leerzeichen getrennten String ein (unter Verwendung von `scope_str`). Wir fügen diesen String mit den Scopes in den Header `WWW-Authenticate` ein (das ist Teil der Spezifikation). -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} -```Python hl_lines="105 107-115" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="105 107-115" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="106 108-116" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="104 106-114" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="105 107-115" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="105 107-115" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Den `username` und das Format der Daten überprüfen +## Den `username` und das Format der Daten überprüfen { #verify-the-username-and-data-shape } Wir verifizieren, dass wir einen `username` erhalten, und extrahieren die Scopes. @@ -564,145 +180,17 @@ Anstelle beispielsweise eines `dict`s oder etwas anderem, was später in der Anw Wir verifizieren auch, dass wir einen Benutzer mit diesem Benutzernamen haben, und wenn nicht, lösen wir dieselbe Exception aus, die wir zuvor erstellt haben. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} -```Python hl_lines="46 116-127" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` +## Die `scopes` verifizieren { #verify-the-scopes } -//// - -//// tab | Python 3.9+ - -```Python hl_lines="46 116-127" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="47 117-128" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="45 115-126" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="46 116-127" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="46 116-127" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Die `scopes` verifizieren - -Wir überprüfen nun, ob das empfangenen Token alle Scopes enthält, die von dieser Abhängigkeit und deren Verwendern (einschließlich *Pfadoperationen*) gefordert werden. Andernfalls lösen wir eine `HTTPException` aus. +Wir überprüfen nun, ob das empfangene Token alle Scopes enthält, die von dieser Abhängigkeit und deren Verwendern (einschließlich *Pfadoperationen*) gefordert werden. Andernfalls lösen wir eine `HTTPException` aus. Hierzu verwenden wir `security_scopes.scopes`, das eine `list`e mit allen diesen Scopes als `str` enthält. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} -```Python hl_lines="128-134" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="128-134" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="129-135" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="127-133" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="128-134" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="128-134" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Abhängigkeitsbaum und Scopes +## Abhängigkeitsbaum und Scopes { #dependency-tree-and-scopes } Sehen wir uns diesen Abhängigkeitsbaum und die Scopes noch einmal an. @@ -727,7 +215,7 @@ So sieht die Hierarchie der Abhängigkeiten und Scopes aus: * `security_scopes.scopes` enthält `["me"]` für die *Pfadoperation* `read_users_me`, da das in der Abhängigkeit `get_current_active_user` deklariert ist. * `security_scopes.scopes` wird `[]` (nichts) für die *Pfadoperation* `read_system_status` enthalten, da diese keine `Security` mit `scopes` deklariert hat, und deren Abhängigkeit `get_current_user` ebenfalls keinerlei `scopes` deklariert. -/// tip | "Tipp" +/// tip | Tipp Das Wichtige und „Magische“ hier ist, dass `get_current_user` für jede *Pfadoperation* eine andere Liste von `scopes` hat, die überprüft werden. @@ -735,7 +223,7 @@ Alles hängt von den „Scopes“ ab, die in jeder *Pfadoperation* und jeder Abh /// -## Weitere Details zu `SecurityScopes`. +## Weitere Details zu `SecurityScopes` { #more-details-about-securityscopes } Sie können `SecurityScopes` an jeder Stelle und an mehreren Stellen verwenden, es muss sich nicht in der „Wurzel“-Abhängigkeit befinden. @@ -745,7 +233,7 @@ Da die `SecurityScopes` alle von den Verwendern der Abhängigkeiten deklarierten Diese werden für jede *Pfadoperation* unabhängig überprüft. -## Testen Sie es +## Es testen { #check-it } Wenn Sie die API-Dokumentation öffnen, können Sie sich authentisieren und angeben, welche Scopes Sie autorisieren möchten. @@ -757,7 +245,7 @@ Und wenn Sie den Scope `me`, aber nicht den Scope `items` auswählen, können Si Das würde einer Drittanbieteranwendung passieren, die versucht, auf eine dieser *Pfadoperationen* mit einem Token zuzugreifen, das von einem Benutzer bereitgestellt wurde, abhängig davon, wie viele Berechtigungen der Benutzer dieser Anwendung erteilt hat. -## Über Integrationen von Drittanbietern +## Über Integrationen von Drittanbietern { #about-third-party-integrations } In diesem Beispiel verwenden wir den OAuth2-Flow „Password“. @@ -771,7 +259,7 @@ Am häufigsten ist der „Implicit“-Flow. Am sichersten ist der „Code“-Flow, die Implementierung ist jedoch komplexer, da mehr Schritte erforderlich sind. Da er komplexer ist, schlagen viele Anbieter letztendlich den „Implicit“-Flow vor. -/// note | "Hinweis" +/// note | Hinweis Es ist üblich, dass jeder Authentifizierungsanbieter seine Flows anders benennt, um sie zu einem Teil seiner Marke zu machen. @@ -781,6 +269,6 @@ Aber am Ende implementieren sie denselben OAuth2-Standard. **FastAPI** enthält Werkzeuge für alle diese OAuth2-Authentifizierungs-Flows in `fastapi.security.oauth2`. -## `Security` in Dekorator-`dependencies` +## `Security` in Dekorator-`dependencies` { #security-in-decorator-dependencies } Auf die gleiche Weise können Sie eine `list`e von `Depends` im Parameter `dependencies` des Dekorators definieren (wie in [Abhängigkeiten in Pfadoperation-Dekoratoren](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} erläutert), Sie könnten auch dort `Security` mit `scopes` verwenden. diff --git a/docs/de/docs/advanced/settings.md b/docs/de/docs/advanced/settings.md index 3cd4c6c7d4..ccd7f373dd 100644 --- a/docs/de/docs/advanced/settings.md +++ b/docs/de/docs/advanced/settings.md @@ -1,4 +1,4 @@ -# Einstellungen und Umgebungsvariablen +# Einstellungen und Umgebungsvariablen { #settings-and-environment-variables } In vielen Fällen benötigt Ihre Anwendung möglicherweise einige externe Einstellungen oder Konfigurationen, zum Beispiel geheime Schlüssel, Datenbank-Anmeldeinformationen, Anmeldeinformationen für E-Mail-Dienste, usw. @@ -6,143 +6,25 @@ Die meisten dieser Einstellungen sind variabel (können sich ändern), wie z. B. Aus diesem Grund werden diese üblicherweise in Umgebungsvariablen bereitgestellt, die von der Anwendung gelesen werden. -## Umgebungsvariablen +/// tip | Tipp -/// tip | "Tipp" - -Wenn Sie bereits wissen, was „Umgebungsvariablen“ sind und wie man sie verwendet, können Sie gerne mit dem nächsten Abschnitt weiter unten fortfahren. +Um Umgebungsvariablen zu verstehen, können Sie [Umgebungsvariablen](../environment-variables.md){.internal-link target=_blank} lesen. /// -Eine Umgebungsvariable (auch bekannt als „env var“) ist eine Variable, die sich außerhalb des Python-Codes im Betriebssystem befindet und von Ihrem Python-Code (oder auch von anderen Programmen) gelesen werden kann. - -Sie können Umgebungsvariablen in der Shell erstellen und verwenden, ohne Python zu benötigen: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -// Sie könnten eine Umgebungsvariable MY_NAME erstellen mittels -$ export MY_NAME="Wade Wilson" - -// Dann könnten Sie diese mit anderen Programmen verwenden, etwa -$ echo "Hello $MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -// Erstelle eine Umgebungsvariable MY_NAME -$ $Env:MY_NAME = "Wade Wilson" - -// Verwende sie mit anderen Programmen, etwa -$ echo "Hello $Env:MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -### Umgebungsvariablen mit Python auslesen - -Sie können Umgebungsvariablen auch außerhalb von Python im Terminal (oder mit einer anderen Methode) erstellen und diese dann mit Python auslesen. - -Sie könnten zum Beispiel eine Datei `main.py` haben mit: - -```Python hl_lines="3" -import os - -name = os.getenv("MY_NAME", "World") -print(f"Hello {name} from Python") -``` - -/// tip | "Tipp" - -Das zweite Argument für `os.getenv()` ist der zurückzugebende Defaultwert. - -Wenn nicht angegeben, ist er standardmäßig `None`. Hier übergeben wir `"World"` als Defaultwert. - -/// - -Dann könnten Sie dieses Python-Programm aufrufen: - -
- -```console -// Hier legen wir die Umgebungsvariable noch nicht fest -$ python main.py - -// Da wir die Umgebungsvariable nicht festgelegt haben, erhalten wir den Standardwert - -Hello World from Python - -// Aber wenn wir zuerst eine Umgebungsvariable erstellen -$ export MY_NAME="Wade Wilson" - -// Und dann das Programm erneut aufrufen -$ python main.py - -// Kann es jetzt die Umgebungsvariable lesen - -Hello Wade Wilson from Python -``` - -
- -Da Umgebungsvariablen außerhalb des Codes festgelegt, aber vom Code gelesen werden können und nicht zusammen mit den übrigen Dateien gespeichert (an `git` committet) werden müssen, werden sie häufig für Konfigurationen oder Einstellungen verwendet. - -Sie können eine Umgebungsvariable auch nur für einen bestimmten Programmaufruf erstellen, die nur für dieses Programm und nur für dessen Dauer verfügbar ist. - -Erstellen Sie diese dazu direkt vor dem Programm selbst, in derselben Zeile: - -
- -```console -// Erstelle eine Umgebungsvariable MY_NAME inline für diesen Programmaufruf -$ MY_NAME="Wade Wilson" python main.py - -// main.py kann jetzt diese Umgebungsvariable lesen - -Hello Wade Wilson from Python - -// Die Umgebungsvariable existiert danach nicht mehr -$ python main.py - -Hello World from Python -``` - -
- -/// tip | "Tipp" - -Weitere Informationen dazu finden Sie unter The Twelve-Factor App: Config. - -/// - -### Typen und Validierung +## Typen und Validierung { #types-and-validation } Diese Umgebungsvariablen können nur Text-Zeichenketten verarbeiten, da sie außerhalb von Python liegen und mit anderen Programmen und dem Rest des Systems (und sogar mit verschiedenen Betriebssystemen wie Linux, Windows, macOS) kompatibel sein müssen. Das bedeutet, dass jeder in Python aus einer Umgebungsvariablen gelesene Wert ein `str` ist und jede Konvertierung in einen anderen Typ oder jede Validierung im Code erfolgen muss. -## Pydantic `Settings` +## Pydantic `Settings` { #pydantic-settings } Glücklicherweise bietet Pydantic ein großartiges Werkzeug zur Verarbeitung dieser Einstellungen, die von Umgebungsvariablen stammen, mit Pydantic: Settings Management. -### `pydantic-settings` installieren +### `pydantic-settings` installieren { #install-pydantic-settings } -Installieren Sie zunächst das Package `pydantic-settings`: +Stellen Sie zunächst sicher, dass Sie Ihre [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellt und aktiviert haben, und installieren Sie dann das Package `pydantic-settings`:
@@ -164,13 +46,13 @@ $ pip install "fastapi[all]"
-/// info +/// info | Info -In Pydantic v1 war das im Hauptpackage enthalten. Jetzt wird es als unabhängiges Package verteilt, sodass Sie wählen können, ob Sie es installieren möchten oder nicht, falls Sie die Funktionalität nicht benötigen. +In Pydantic v1 war es im Hauptpackage enthalten. Jetzt wird es als unabhängiges Package verteilt, sodass Sie wählen können, ob Sie es installieren möchten oder nicht, falls Sie die Funktionalität nicht benötigen. /// -### Das `Settings`-Objekt erstellen +### Das `Settings`-Objekt erstellen { #create-the-settings-object } Importieren Sie `BaseSettings` aus Pydantic und erstellen Sie eine Unterklasse, ganz ähnlich wie bei einem Pydantic-Modell. @@ -180,27 +62,23 @@ Sie können dieselben Validierungs-Funktionen und -Tools verwenden, die Sie für //// tab | Pydantic v2 -```Python hl_lines="2 5-8 11" -{!> ../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} //// //// tab | Pydantic v1 -/// info +/// info | Info In Pydantic v1 würden Sie `BaseSettings` direkt von `pydantic` statt von `pydantic_settings` importieren. /// -```Python hl_lines="2 5-8 11" -{!> ../../../docs_src/settings/tutorial001_pv1.py!} -``` +{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} //// -/// tip | "Tipp" +/// tip | Tipp Für ein schnelles Copy-and-paste verwenden Sie nicht dieses Beispiel, sondern das letzte unten. @@ -210,29 +88,27 @@ Wenn Sie dann eine Instanz dieser `Settings`-Klasse erstellen (in diesem Fall al Als Nächstes werden die Daten konvertiert und validiert. Wenn Sie also dieses `settings`-Objekt verwenden, verfügen Sie über Daten mit den von Ihnen deklarierten Typen (z. B. ist `items_per_user` ein `int`). -### `settings` verwenden +### `settings` verwenden { #use-the-settings } Dann können Sie das neue `settings`-Objekt in Ihrer Anwendung verwenden: -```Python hl_lines="18-20" -{!../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[18:20] *} -### Den Server ausführen +### Den Server ausführen { #run-the-server } Als Nächstes würden Sie den Server ausführen und die Konfigurationen als Umgebungsvariablen übergeben. Sie könnten beispielsweise `ADMIN_EMAIL` und `APP_NAME` festlegen mit:
```console -$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-/// tip | "Tipp" +/// tip | Tipp Um mehrere Umgebungsvariablen für einen einzelnen Befehl festzulegen, trennen Sie diese einfach durch ein Leerzeichen und fügen Sie alle vor dem Befehl ein. @@ -242,81 +118,47 @@ Und dann würde die Einstellung `admin_email` auf `"deadpool@example.com"` geset Der `app_name` wäre `"ChimichangApp"`. -Und `items_per_user` würde seinen Standardwert von `50` behalten. +Und `items_per_user` würde seinen Defaultwert von `50` behalten. -## Einstellungen in einem anderen Modul +## Einstellungen in einem anderen Modul { #settings-in-another-module } Sie könnten diese Einstellungen in eine andere Moduldatei einfügen, wie Sie in [Größere Anwendungen – mehrere Dateien](../tutorial/bigger-applications.md){.internal-link target=_blank} gesehen haben. Sie könnten beispielsweise eine Datei `config.py` haben mit: -```Python -{!../../../docs_src/settings/app01/config.py!} -``` +{* ../../docs_src/settings/app01/config.py *} Und dann verwenden Sie diese in einer Datei `main.py`: -```Python hl_lines="3 11-13" -{!../../../docs_src/settings/app01/main.py!} -``` +{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} -/// tip | "Tipp" +/// tip | Tipp Sie benötigen außerdem eine Datei `__init__.py`, wie in [Größere Anwendungen – mehrere Dateien](../tutorial/bigger-applications.md){.internal-link target=_blank} gesehen. /// -## Einstellungen in einer Abhängigkeit +## Einstellungen in einer Abhängigkeit { #settings-in-a-dependency } In manchen Fällen kann es nützlich sein, die Einstellungen mit einer Abhängigkeit bereitzustellen, anstatt ein globales Objekt `settings` zu haben, das überall verwendet wird. Dies könnte besonders beim Testen nützlich sein, da es sehr einfach ist, eine Abhängigkeit mit Ihren eigenen benutzerdefinierten Einstellungen zu überschreiben. -### Die Konfigurationsdatei +### Die Konfigurationsdatei { #the-config-file } Ausgehend vom vorherigen Beispiel könnte Ihre Datei `config.py` so aussehen: -```Python hl_lines="10" -{!../../../docs_src/settings/app02/config.py!} -``` +{* ../../docs_src/settings/app02/config.py hl[10] *} Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erstellen. -### Die Haupt-Anwendungsdatei +### Die Haupt-Anwendungsdatei { #the-main-app-file } Jetzt erstellen wir eine Abhängigkeit, die ein neues `config.Settings()` zurückgibt. -//// tab | Python 3.9+ +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="5 11-12" -{!> ../../../docs_src/settings/app02/main.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Wir werden das `@lru_cache` in Kürze besprechen. @@ -326,55 +168,25 @@ Im Moment nehmen Sie an, dass `get_settings()` eine normale Funktion ist. Und dann können wir das von der *Pfadoperation-Funktion* als Abhängigkeit einfordern und es überall dort verwenden, wo wir es brauchen. -//// tab | Python 3.9+ +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="16 18-20" -{!> ../../../docs_src/settings/app02/main.py!} -``` - -//// - -### Einstellungen und Tests +### Einstellungen und Tests { #settings-and-testing } Dann wäre es sehr einfach, beim Testen ein anderes Einstellungsobjekt bereitzustellen, indem man eine Abhängigkeitsüberschreibung für `get_settings` erstellt: -```Python hl_lines="9-10 13 21" -{!../../../docs_src/settings/app02/test_main.py!} -``` +{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} Bei der Abhängigkeitsüberschreibung legen wir einen neuen Wert für `admin_email` fest, wenn wir das neue `Settings`-Objekt erstellen, und geben dann dieses neue Objekt zurück. Dann können wir testen, ob das verwendet wird. -## Lesen einer `.env`-Datei +## Lesen einer `.env`-Datei { #reading-a-env-file } Wenn Sie viele Einstellungen haben, die sich möglicherweise oft ändern, vielleicht in verschiedenen Umgebungen, kann es nützlich sein, diese in eine Datei zu schreiben und sie dann daraus zu lesen, als wären sie Umgebungsvariablen. Diese Praxis ist so weit verbreitet, dass sie einen Namen hat. Diese Umgebungsvariablen werden üblicherweise in einer Datei `.env` abgelegt und die Datei wird „dotenv“ genannt. -/// tip | "Tipp" +/// tip | Tipp Eine Datei, die mit einem Punkt (`.`) beginnt, ist eine versteckte Datei in Unix-ähnlichen Systemen wie Linux und macOS. @@ -384,13 +196,13 @@ Aber eine dotenv-Datei muss nicht unbedingt genau diesen Dateinamen haben. Pydantic unterstützt das Lesen dieser Dateitypen mithilfe einer externen Bibliothek. Weitere Informationen finden Sie unter Pydantic Settings: Dotenv (.env) support. -/// tip | "Tipp" +/// tip | Tipp Damit das funktioniert, müssen Sie `pip install python-dotenv` ausführen. /// -### Die `.env`-Datei +### Die `.env`-Datei { #the-env-file } Sie könnten eine `.env`-Datei haben, mit: @@ -399,19 +211,17 @@ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" ``` -### Einstellungen aus `.env` lesen +### Einstellungen aus `.env` lesen { #read-settings-from-env } Und dann aktualisieren Sie Ihre `config.py` mit: //// tab | Pydantic v2 -```Python hl_lines="9" -{!> ../../../docs_src/settings/app03_an/config.py!} -``` +{* ../../docs_src/settings/app03_an/config.py hl[9] *} -/// tip | "Tipp" +/// tip | Tipp -Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter Pydantic: Configuration. +Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter Pydantic: Concepts: Configuration. /// @@ -419,11 +229,9 @@ Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet. //// tab | Pydantic v1 -```Python hl_lines="9-10" -{!> ../../../docs_src/settings/app03_an/config_pv1.py!} -``` +{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} -/// tip | "Tipp" +/// tip | Tipp Die Klasse `Config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter Pydantic Model Config. @@ -431,17 +239,17 @@ Die Klasse `Config` wird nur für die Pydantic-Konfiguration verwendet. Weitere //// -/// info +/// info | Info -In Pydantic Version 1 erfolgte die Konfiguration in einer internen Klasse `Config`, in Pydantic Version 2 erfolgt sie in einem Attribut `model_config`. Dieses Attribut akzeptiert ein `dict`. Um automatische Codevervollständigung und Inline-Fehlerberichte zu erhalten, können Sie `SettingsConfigDict` importieren und verwenden, um dieses `dict` zu definieren. +In Pydantic Version 1 erfolgte die Konfiguration in einer internen Klasse `Config`, in Pydantic Version 2 erfolgt sie in einem Attribut `model_config`. Dieses Attribut akzeptiert ein `dict`. Um automatische Codevervollständigung und Inline-Fehlerberichte zu erhalten, können Sie `SettingsConfigDict` importieren und verwenden, um dieses `dict` zu definieren. /// Hier definieren wir die Konfiguration `env_file` innerhalb Ihrer Pydantic-`Settings`-Klasse und setzen den Wert auf den Dateinamen mit der dotenv-Datei, die wir verwenden möchten. -### Die `Settings` nur einmal laden mittels `lru_cache` +### Die `Settings` nur einmal laden mittels `lru_cache` { #creating-the-settings-only-once-with-lru-cache } -Das Lesen einer Datei von der Festplatte ist normalerweise ein kostspieliger (langsamer) Vorgang, daher möchten Sie ihn wahrscheinlich nur einmal ausführen und dann dasselbe Einstellungsobjekt erneut verwenden, anstatt es für jeden Request zu lesen. +Das Lesen einer Datei von der Festplatte ist normalerweise ein kostspieliger (langsamer) Vorgang, daher möchten Sie ihn wahrscheinlich nur einmal ausführen und dann dasselbe Einstellungsobjekt erneut verwenden, anstatt es für jeden Request zu lesen. Aber jedes Mal, wenn wir ausführen: @@ -462,39 +270,11 @@ würden wir dieses Objekt für jeden Request erstellen und die `.env`-Datei für Da wir jedoch den `@lru_cache`-Dekorator oben verwenden, wird das `Settings`-Objekt nur einmal erstellt, nämlich beim ersten Aufruf. ✔️ -//// tab | Python 3.9+ - -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1 10" -{!> ../../../docs_src/settings/app03/main.py!} -``` - -//// +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} Dann wird bei allen nachfolgenden Aufrufen von `get_settings()`, in den Abhängigkeiten für darauffolgende Requests, dasselbe Objekt zurückgegeben, das beim ersten Aufruf zurückgegeben wurde, anstatt den Code von `get_settings()` erneut auszuführen und ein neues `Settings`-Objekt zu erstellen. -#### Technische Details zu `lru_cache` +#### Technische Details zu `lru_cache` { #lru-cache-technical-details } `@lru_cache` ändert die Funktion, die es dekoriert, dahingehend, denselben Wert zurückzugeben, der beim ersten Mal zurückgegeben wurde, anstatt ihn erneut zu berechnen und den Code der Funktion jedes Mal auszuführen. @@ -515,7 +295,7 @@ sequenceDiagram participant code as Code participant function as say_hi() -participant execute as Funktion ausgeführt +participant execute as Funktion ausführen rect rgba(0, 255, 0, .1) code ->> function: say_hi(name="Camila") @@ -557,7 +337,7 @@ Auf diese Weise verhält es sich fast so, als wäre es nur eine globale Variable `@lru_cache` ist Teil von `functools`, welches Teil von Pythons Standardbibliothek ist. Weitere Informationen dazu finden Sie in der Python Dokumentation für `@lru_cache`. -## Zusammenfassung +## Zusammenfassung { #recap } Mit Pydantic Settings können Sie die Einstellungen oder Konfigurationen für Ihre Anwendung verwalten und dabei die gesamte Leistungsfähigkeit der Pydantic-Modelle nutzen. diff --git a/docs/de/docs/advanced/sub-applications.md b/docs/de/docs/advanced/sub-applications.md index 7dfaaa0cde..d634aac23b 100644 --- a/docs/de/docs/advanced/sub-applications.md +++ b/docs/de/docs/advanced/sub-applications.md @@ -1,47 +1,41 @@ -# Unteranwendungen – Mounts +# Unteranwendungen – Mounts { #sub-applications-mounts } Wenn Sie zwei unabhängige FastAPI-Anwendungen mit deren eigenen unabhängigen OpenAPI und deren eigenen Dokumentationsoberflächen benötigen, können Sie eine Hauptanwendung haben und dann eine (oder mehrere) Unteranwendung(en) „mounten“. -## Mounten einer **FastAPI**-Anwendung +## Eine **FastAPI**-Anwendung mounten { #mounting-a-fastapi-application } „Mounten“ („Einhängen“) bedeutet das Hinzufügen einer völlig „unabhängigen“ Anwendung an einem bestimmten Pfad, die sich dann um die Handhabung aller unter diesem Pfad liegenden _Pfadoperationen_ kümmert, welche in dieser Unteranwendung deklariert sind. -### Hauptanwendung +### Hauptanwendung { #top-level-application } Erstellen Sie zunächst die Hauptanwendung **FastAPI** und deren *Pfadoperationen*: -```Python hl_lines="3 6-8" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} -### Unteranwendung +### Unteranwendung { #sub-application } Erstellen Sie dann Ihre Unteranwendung und deren *Pfadoperationen*. Diese Unteranwendung ist nur eine weitere Standard-FastAPI-Anwendung, aber diese wird „gemountet“: -```Python hl_lines="11 14-16" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} -### Die Unteranwendung mounten +### Die Unteranwendung mounten { #mount-the-sub-application } Mounten Sie in Ihrer Top-Level-Anwendung `app` die Unteranwendung `subapi`. In diesem Fall wird sie im Pfad `/subapi` gemountet: -```Python hl_lines="11 19" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} -### Es in der automatischen API-Dokumentation betrachten +### Die automatische API-Dokumentation testen { #check-the-automatic-api-docs } -Führen Sie nun `uvicorn` mit der Hauptanwendung aus. Wenn Ihre Datei `main.py` lautet, wäre das: +Führen Sie nun den `fastapi`-Befehl mit Ihrer Datei aus:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -62,7 +56,7 @@ Sie sehen die automatische API-Dokumentation für die Unteranwendung, welche nur Wenn Sie versuchen, mit einer der beiden Benutzeroberflächen zu interagieren, funktionieren diese ordnungsgemäß, da der Browser mit jeder spezifischen Anwendung oder Unteranwendung kommunizieren kann. -### Technische Details: `root_path` +### Technische Details: `root_path` { #technical-details-root-path } Wenn Sie eine Unteranwendung wie oben beschrieben mounten, kümmert sich FastAPI darum, den Mount-Pfad für die Unteranwendung zu kommunizieren, mithilfe eines Mechanismus aus der ASGI-Spezifikation namens `root_path`. diff --git a/docs/de/docs/advanced/templates.md b/docs/de/docs/advanced/templates.md index abc7624f1d..65c7998b8a 100644 --- a/docs/de/docs/advanced/templates.md +++ b/docs/de/docs/advanced/templates.md @@ -1,4 +1,4 @@ -# Templates +# Templates { #templates } Sie können jede gewünschte Template-Engine mit **FastAPI** verwenden. @@ -6,9 +6,9 @@ Eine häufige Wahl ist Jinja2, dasselbe, was auch von Flask und anderen Tools ve Es gibt Werkzeuge zur einfachen Konfiguration, die Sie direkt in Ihrer **FastAPI**-Anwendung verwenden können (bereitgestellt von Starlette). -## Abhängigkeiten installieren +## Abhängigkeiten installieren { #install-dependencies } -Installieren Sie `jinja2`: +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und `jinja2` installieren:
@@ -20,18 +20,16 @@ $ pip install jinja2
-## Verwendung von `Jinja2Templates` +## `Jinja2Templates` verwenden { #using-jinja2templates } * Importieren Sie `Jinja2Templates`. * Erstellen Sie ein `templates`-Objekt, das Sie später wiederverwenden können. -* Deklarieren Sie einen `Request`-Parameter in der *Pfadoperation*, welcher ein Template zurückgibt. -* Verwenden Sie die von Ihnen erstellten `templates`, um eine `TemplateResponse` zu rendern und zurückzugeben, übergeben Sie den Namen des Templates, das Requestobjekt und ein „Kontext“-Dictionary mit Schlüssel-Wert-Paaren, die innerhalb des Jinja2-Templates verwendet werden sollen. +* Deklarieren Sie einen `Request`-Parameter in der *Pfadoperation*, welcher ein Template zurückgibt. +* Verwenden Sie die von Ihnen erstellten `templates`, um eine `TemplateResponse` zu rendern und zurückzugeben, übergeben Sie den Namen des Templates, das Requestobjekt und ein „Kontext“-Dictionary mit Schlüssel-Wert-Paaren, die innerhalb des Jinja2-Templates verwendet werden sollen. -```Python hl_lines="4 11 15-18" -{!../../../docs_src/templates/tutorial001.py!} -``` +{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} -/// note | "Hinweis" +/// note | Hinweis Vor FastAPI 0.108.0 und Starlette 0.29.0 war `name` der erste Parameter. @@ -39,29 +37,29 @@ Außerdem wurde in früheren Versionen das `request`-Objekt als Teil der Schlüs /// -/// tip | "Tipp" +/// tip | Tipp -Durch die Deklaration von `response_class=HTMLResponse` kann die Dokumentationsoberfläche erkennen, dass die Response HTML sein wird. +Durch die Deklaration von `response_class=HTMLResponse` kann die Dokumentationsoberfläche erkennen, dass die Response HTML sein wird. /// -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette.templating import Jinja2Templates` verwenden. -**FastAPI** bietet dasselbe `starlette.templating` auch via `fastapi.templating` an, als Annehmlichkeit für Sie, den Entwickler. Es kommt aber direkt von Starlette. Das Gleiche gilt für `Request` und `StaticFiles`. +**FastAPI** bietet dasselbe `starlette.templating` auch via `fastapi.templating` an, als Annehmlichkeit für Sie, den Entwickler. Aber die meisten der verfügbaren Responses kommen direkt von Starlette. Das Gleiche gilt für `Request` und `StaticFiles`. /// -## Templates erstellen +## Templates erstellen { #writing-templates } Dann können Sie unter `templates/item.html` ein Template erstellen, mit z. B. folgendem Inhalt: ```jinja hl_lines="7" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` -### Template-Kontextwerte +### Template-Kontextwerte { #template-context-values } Im HTML, welches enthält: @@ -85,7 +83,7 @@ Mit beispielsweise einer ID `42` würde das wie folgt gerendert werden: Item ID: 42 ``` -### Template-`url_for`-Argumente +### Template-`url_for`-Argumente { #template-url-for-arguments } Sie können `url_for()` auch innerhalb des Templates verwenden, es nimmt als Argumente dieselben Argumente, die von Ihrer *Pfadoperation-Funktion* verwendet werden. @@ -107,22 +105,22 @@ Mit beispielsweise der ID `42` würde dies Folgendes ergeben: ``` -## Templates und statische Dateien +## Templates und statische Dateien { #templates-and-static-files } Sie können `url_for()` innerhalb des Templates auch beispielsweise mit den `StaticFiles` verwenden, die Sie mit `name="static"` gemountet haben. ```jinja hl_lines="4" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` In diesem Beispiel würde das zu einer CSS-Datei unter `static/styles.css` verlinken, mit folgendem Inhalt: ```CSS hl_lines="4" -{!../../../docs_src/templates/static/styles.css!} +{!../../docs_src/templates/static/styles.css!} ``` -Und da Sie `StaticFiles` verwenden, wird diese CSS-Datei automatisch von Ihrer **FastAPI**-Anwendung unter der URL `/static/styles.css` bereitgestellt. +Und da Sie `StaticFiles` verwenden, wird diese CSS-Datei automatisch von Ihrer **FastAPI**-Anwendung unter der URL `/static/styles.css` ausgeliefert. -## Mehr Details +## Mehr Details { #more-details } -Weitere Informationen, einschließlich, wie man Templates testet, finden Sie in der Starlette Dokumentation zu Templates. +Weitere Informationen, einschließlich, wie man Templates testet, finden Sie in Starlettes Dokumentation zu Templates. diff --git a/docs/de/docs/advanced/testing-dependencies.md b/docs/de/docs/advanced/testing-dependencies.md index f131d27cd2..4fc653f775 100644 --- a/docs/de/docs/advanced/testing-dependencies.md +++ b/docs/de/docs/advanced/testing-dependencies.md @@ -1,6 +1,6 @@ -# Testen mit Ersatz für Abhängigkeiten +# Testen mit Überschreibungen für Abhängigkeiten { #testing-dependencies-with-overrides } -## Abhängigkeiten beim Testen überschreiben +## Abhängigkeiten beim Testen überschreiben { #overriding-dependencies-during-testing } Es gibt einige Szenarien, in denen Sie beim Testen möglicherweise eine Abhängigkeit überschreiben möchten. @@ -8,79 +8,29 @@ Sie möchten nicht, dass die ursprüngliche Abhängigkeit ausgeführt wird (und Stattdessen möchten Sie eine andere Abhängigkeit bereitstellen, die nur während Tests (möglicherweise nur bei einigen bestimmten Tests) verwendet wird und einen Wert bereitstellt, der dort verwendet werden kann, wo der Wert der ursprünglichen Abhängigkeit verwendet wurde. -### Anwendungsfälle: Externer Service +### Anwendungsfälle: Externer Service { #use-cases-external-service } Ein Beispiel könnte sein, dass Sie einen externen Authentifizierungsanbieter haben, mit dem Sie sich verbinden müssen. Sie senden ihm ein Token und er gibt einen authentifizierten Benutzer zurück. -Dieser Anbieter berechnet Ihnen möglicherweise Gebühren pro Anfrage, und der Aufruf könnte etwas länger dauern, als wenn Sie einen vordefinierten Scheinbenutzer für Tests hätten. +Dieser Anbieter berechnet Ihnen möglicherweise Gebühren pro Request, und der Aufruf könnte etwas länger dauern, als wenn Sie einen vordefinierten Mock-Benutzer für Tests hätten. Sie möchten den externen Anbieter wahrscheinlich einmal testen, ihn aber nicht unbedingt bei jedem weiteren ausgeführten Test aufrufen. -In diesem Fall können Sie die Abhängigkeit, die diesen Anbieter aufruft, überschreiben und eine benutzerdefinierte Abhängigkeit verwenden, die einen Scheinbenutzer zurückgibt, nur für Ihre Tests. +In diesem Fall können Sie die Abhängigkeit, die diesen Anbieter aufruft, überschreiben und eine benutzerdefinierte Abhängigkeit verwenden, die einen Mock-Benutzer zurückgibt, nur für Ihre Tests. -### Verwenden Sie das Attribut `app.dependency_overrides`. +### Das Attribut `app.dependency_overrides` verwenden { #use-the-app-dependency-overrides-attribute } -Für diese Fälle verfügt Ihre **FastAPI**-Anwendung über das Attribut `app.dependency_overrides`, bei diesem handelt sich um ein einfaches `dict`. +Für diese Fälle verfügt Ihre **FastAPI**-Anwendung über das Attribut `app.dependency_overrides`, bei diesem handelt sich um ein einfaches `dict`. Um eine Abhängigkeit für das Testen zu überschreiben, geben Sie als Schlüssel die ursprüngliche Abhängigkeit (eine Funktion) und als Wert Ihre Überschreibung der Abhängigkeit (eine andere Funktion) ein. Und dann ruft **FastAPI** diese Überschreibung anstelle der ursprünglichen Abhängigkeit auf. -//// tab | Python 3.10+ +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} -```Python hl_lines="26-27 30" -{!> ../../../docs_src/dependency_testing/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="28-29 32" -{!> ../../../docs_src/dependency_testing/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="29-30 33" -{!> ../../../docs_src/dependency_testing/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="24-25 28" -{!> ../../../docs_src/dependency_testing/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="28-29 32" -{!> ../../../docs_src/dependency_testing/tutorial001.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Sie können eine Überschreibung für eine Abhängigkeit festlegen, die an einer beliebigen Stelle in Ihrer **FastAPI**-Anwendung verwendet wird. @@ -96,7 +46,8 @@ Anschließend können Sie Ihre Überschreibungen zurücksetzen (entfernen), inde app.dependency_overrides = {} ``` -/// tip | "Tipp" + +/// tip | Tipp Wenn Sie eine Abhängigkeit nur während einiger Tests überschreiben möchten, können Sie die Überschreibung zu Beginn des Tests (innerhalb der Testfunktion) festlegen und am Ende (am Ende der Testfunktion) zurücksetzen. diff --git a/docs/de/docs/advanced/testing-events.md b/docs/de/docs/advanced/testing-events.md index f500935485..569518c51d 100644 --- a/docs/de/docs/advanced/testing-events.md +++ b/docs/de/docs/advanced/testing-events.md @@ -1,7 +1,12 @@ -# Events testen: Hochfahren – Herunterfahren +# Events testen: Lifespan und Startup – Shutdown { #testing-events-lifespan-and-startup-shutdown } -Wenn Sie in Ihren Tests Ihre Event-Handler (`startup` und `shutdown`) ausführen wollen, können Sie den `TestClient` mit einer `with`-Anweisung verwenden: +Wenn Sie `lifespan` in Ihren Tests ausführen müssen, können Sie den `TestClient` mit einer `with`-Anweisung verwenden: -```Python hl_lines="9-12 20-24" -{!../../../docs_src/app_testing/tutorial003.py!} -``` +{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *} + + +Sie können mehr Details unter [„Lifespan in Tests ausführen in der offiziellen Starlette-Dokumentation.“](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) nachlesen. + +Für die deprecateten Events `startup` und `shutdown` können Sie den `TestClient` wie folgt verwenden: + +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/de/docs/advanced/testing-websockets.md b/docs/de/docs/advanced/testing-websockets.md index 4cbc45c17a..f25aa4fd04 100644 --- a/docs/de/docs/advanced/testing-websockets.md +++ b/docs/de/docs/advanced/testing-websockets.md @@ -1,15 +1,13 @@ -# WebSockets testen +# WebSockets testen { #testing-websockets } Sie können den schon bekannten `TestClient` zum Testen von WebSockets verwenden. Dazu verwenden Sie den `TestClient` in einer `with`-Anweisung, eine Verbindung zum WebSocket herstellend: -```Python hl_lines="27-31" -{!../../../docs_src/app_testing/tutorial002.py!} -``` +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} -/// note | "Hinweis" +/// note | Hinweis -Weitere Informationen finden Sie in der Starlette-Dokumentation zum Testen von WebSockets. +Weitere Informationen finden Sie in Starlettes Dokumentation zum Testen von WebSockets. /// diff --git a/docs/de/docs/advanced/using-request-directly.md b/docs/de/docs/advanced/using-request-directly.md index 1d575a7cb0..8ec6741d07 100644 --- a/docs/de/docs/advanced/using-request-directly.md +++ b/docs/de/docs/advanced/using-request-directly.md @@ -1,6 +1,6 @@ -# Den Request direkt verwenden +# Den Request direkt verwenden { #using-the-request-directly } -Bisher haben Sie die Teile des Requests, die Sie benötigen, mithilfe von deren Typen deklariert. +Bisher haben Sie die Teile des Requests, die Sie benötigen, mithilfe von deren Typen deklariert. Daten nehmend von: @@ -13,9 +13,9 @@ Und indem Sie das tun, validiert **FastAPI** diese Daten, konvertiert sie und ge Es gibt jedoch Situationen, in denen Sie möglicherweise direkt auf das `Request`-Objekt zugreifen müssen. -## Details zum `Request`-Objekt +## Details zum `Request`-Objekt { #details-about-the-request-object } -Da **FastAPI** unter der Haube eigentlich **Starlette** ist, mit einer Ebene von mehreren Tools darüber, können Sie Starlette's `Request`-Objekt direkt verwenden, wenn Sie es benötigen. +Da **FastAPI** unter der Haube eigentlich **Starlette** ist, mit einer Ebene von mehreren Tools darüber, können Sie Starlettes `Request`-Objekt direkt verwenden, wenn Sie es benötigen. Das bedeutet allerdings auch, dass, wenn Sie Daten direkt vom `Request`-Objekt nehmen (z. B. dessen Body lesen), diese von FastAPI nicht validiert, konvertiert oder dokumentiert werden (mit OpenAPI, für die automatische API-Benutzeroberfläche). @@ -23,19 +23,17 @@ Obwohl jeder andere normal deklarierte Parameter (z. B. der Body, mit einem Pyda Es gibt jedoch bestimmte Fälle, in denen es nützlich ist, auf das `Request`-Objekt zuzugreifen. -## Das `Request`-Objekt direkt verwenden +## Das `Request`-Objekt direkt verwenden { #use-the-request-object-directly } Angenommen, Sie möchten auf die IP-Adresse/den Host des Clients in Ihrer *Pfadoperation-Funktion* zugreifen. Dazu müssen Sie direkt auf den Request zugreifen. -```Python hl_lines="1 7-8" -{!../../../docs_src/using_request_directly/tutorial001.py!} -``` +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} Durch die Deklaration eines *Pfadoperation-Funktionsparameters*, dessen Typ der `Request` ist, weiß **FastAPI**, dass es den `Request` diesem Parameter übergeben soll. -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass wir in diesem Fall einen Pfad-Parameter zusätzlich zum Request-Parameter deklarieren. @@ -45,11 +43,11 @@ Auf die gleiche Weise können Sie wie gewohnt jeden anderen Parameter deklariere /// -## `Request`-Dokumentation +## `Request`-Dokumentation { #request-documentation } -Weitere Details zum `Request`-Objekt finden Sie in der offiziellen Starlette-Dokumentation. +Weitere Details zum `Request`-Objekt finden Sie in der offiziellen Starlette-Dokumentation. -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette.requests import Request` verwenden. diff --git a/docs/de/docs/advanced/websockets.md b/docs/de/docs/advanced/websockets.md index 6d772b6c9a..5f662770f0 100644 --- a/docs/de/docs/advanced/websockets.md +++ b/docs/de/docs/advanced/websockets.md @@ -1,10 +1,10 @@ -# WebSockets +# WebSockets { #websockets } Sie können WebSockets mit **FastAPI** verwenden. -## `WebSockets` installieren +## `websockets` installieren { #install-websockets } -Zuerst müssen Sie `WebSockets` installieren: +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und `websockets` installieren (eine Python-Bibliothek, die die Verwendung des „WebSocket“-Protokolls erleichtert):
@@ -16,9 +16,9 @@ $ pip install websockets
-## WebSockets-Client +## WebSockets-Client { #websockets-client } -### In Produktion +### In Produktion { #in-production } In Ihrem Produktionssystem haben Sie wahrscheinlich ein Frontend, das mit einem modernen Framework wie React, Vue.js oder Angular erstellt wurde. @@ -36,46 +36,40 @@ Das ist natürlich nicht optimal und man würde das nicht in der Produktion mach In der Produktion hätten Sie eine der oben genannten Optionen. -Aber es ist die einfachste Möglichkeit, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben: +Aber es ist der einfachste Weg, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben: -```Python hl_lines="2 6-38 41-43" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} -## Einen `websocket` erstellen +## Einen `websocket` erstellen { #create-a-websocket } Erstellen Sie in Ihrer **FastAPI**-Anwendung einen `websocket`: -```Python hl_lines="1 46-47" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} -/// note | "Technische Details" +/// note | Technische Details -Sie können auch `from starlette.websockets import WebSocket` verwenden. +Sie könnten auch `from starlette.websockets import WebSocket` verwenden. **FastAPI** stellt den gleichen `WebSocket` direkt zur Verfügung, als Annehmlichkeit für Sie, den Entwickler. Er kommt aber direkt von Starlette. /// -## Nachrichten erwarten und Nachrichten senden +## Nachrichten erwarten und Nachrichten senden { #await-for-messages-and-send-messages } In Ihrer WebSocket-Route können Sie Nachrichten `await`en und Nachrichten senden. -```Python hl_lines="48-52" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} Sie können Binär-, Text- und JSON-Daten empfangen und senden. -## Es ausprobieren +## Es ausprobieren { #try-it } Wenn Ihre Datei `main.py` heißt, führen Sie Ihre Anwendung so aus:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -102,7 +96,7 @@ Sie können viele Nachrichten senden (und empfangen): Und alle verwenden dieselbe WebSocket-Verbindung. -## Verwendung von `Depends` und anderen +## Verwendung von `Depends` und anderen { #using-depends-and-others } In WebSocket-Endpunkten können Sie Folgendes aus `fastapi` importieren und verwenden: @@ -115,59 +109,9 @@ In WebSocket-Endpunkten können Sie Folgendes aus `fastapi` importieren und verw Diese funktionieren auf die gleiche Weise wie für andere FastAPI-Endpunkte/*Pfadoperationen*: -//// tab | Python 3.10+ +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} -```Python hl_lines="68-69 82" -{!> ../../../docs_src/websockets/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="68-69 82" -{!> ../../../docs_src/websockets/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="69-70 83" -{!> ../../../docs_src/websockets/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="66-67 79" -{!> ../../../docs_src/websockets/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="68-69 81" -{!> ../../../docs_src/websockets/tutorial002.py!} -``` - -//// - -/// info +/// info | Info Da es sich um einen WebSocket handelt, macht es keinen Sinn, eine `HTTPException` auszulösen, stattdessen lösen wir eine `WebSocketException` aus. @@ -175,14 +119,14 @@ Sie können einen „Closing“-Code verwenden, aus den ```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -196,9 +140,9 @@ Dort können Sie einstellen: * Die „Item ID“, die im Pfad verwendet wird. * Das „Token“, das als Query-Parameter verwendet wird. -/// tip | "Tipp" +/// tip | Tipp -Beachten Sie, dass der Query-„Token“ von einer Abhängigkeit verarbeitet wird. +Beachten Sie, dass die Query `token` von einer Abhängigkeit verarbeitet wird. /// @@ -206,25 +150,11 @@ Damit können Sie den WebSocket verbinden und dann Nachrichten senden und empfan -## Verbindungsabbrüche und mehreren Clients handhaben +## Verbindungsabbrüche und mehrere Clients handhaben { #handling-disconnections-and-multiple-clients } Wenn eine WebSocket-Verbindung geschlossen wird, löst `await websocket.receive_text()` eine `WebSocketDisconnect`-Exception aus, die Sie dann wie in folgendem Beispiel abfangen und behandeln können. -//// tab | Python 3.9+ - -```Python hl_lines="79-81" -{!> ../../../docs_src/websockets/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="81-83" -{!> ../../../docs_src/websockets/tutorial003.py!} -``` - -//// +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} Zum Ausprobieren: @@ -238,7 +168,7 @@ Das wird die Ausnahme `WebSocketDisconnect` auslösen und alle anderen Clients e Client #1596980209979 left the chat ``` -/// tip | "Tipp" +/// tip | Tipp Die obige Anwendung ist ein minimales und einfaches Beispiel, das zeigt, wie Nachrichten verarbeitet und an mehrere WebSocket-Verbindungen gesendet werden. @@ -248,9 +178,9 @@ Wenn Sie etwas benötigen, das sich leicht in FastAPI integrieren lässt, aber r /// -## Mehr Informationen +## Mehr Informationen { #more-info } Weitere Informationen zu Optionen finden Sie in der Dokumentation von Starlette: -* Die `WebSocket`-Klasse. -* Klassen-basierte Handhabung von WebSockets. +* Die `WebSocket`-Klasse. +* Klassen-basierte Handhabung von WebSockets. diff --git a/docs/de/docs/advanced/wsgi.md b/docs/de/docs/advanced/wsgi.md index 19ff90a901..1de9739dde 100644 --- a/docs/de/docs/advanced/wsgi.md +++ b/docs/de/docs/advanced/wsgi.md @@ -1,10 +1,10 @@ -# WSGI inkludieren – Flask, Django und andere +# WSGI inkludieren – Flask, Django und andere { #including-wsgi-flask-django-others } Sie können WSGI-Anwendungen mounten, wie Sie es in [Unteranwendungen – Mounts](sub-applications.md){.internal-link target=_blank}, [Hinter einem Proxy](behind-a-proxy.md){.internal-link target=_blank} gesehen haben. Dazu können Sie die `WSGIMiddleware` verwenden und damit Ihre WSGI-Anwendung wrappen, zum Beispiel Flask, Django usw. -## `WSGIMiddleware` verwenden +## `WSGIMiddleware` verwenden { #using-wsgimiddleware } Sie müssen `WSGIMiddleware` importieren. @@ -12,17 +12,15 @@ Wrappen Sie dann die WSGI-Anwendung (z. B. Flask) mit der Middleware. Und dann mounten Sie das auf einem Pfad. -```Python hl_lines="2-3 23" -{!../../../docs_src/wsgi/tutorial001.py!} -``` +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} -## Es ansehen +## Es testen { #check-it } -Jetzt wird jede Anfrage unter dem Pfad `/v1/` von der Flask-Anwendung verarbeitet. +Jetzt wird jeder Request unter dem Pfad `/v1/` von der Flask-Anwendung verarbeitet. Und der Rest wird von **FastAPI** gehandhabt. -Wenn Sie das mit Uvicorn ausführen und auf http://localhost:8000/v1/ gehen, sehen Sie die Response von Flask: +Wenn Sie das ausführen und auf http://localhost:8000/v1/ gehen, sehen Sie die Response von Flask: ```txt Hello, World from Flask! diff --git a/docs/de/docs/alternatives.md b/docs/de/docs/alternatives.md index 286ef1723b..4dd127dbad 100644 --- a/docs/de/docs/alternatives.md +++ b/docs/de/docs/alternatives.md @@ -1,8 +1,8 @@ -# Alternativen, Inspiration und Vergleiche +# Alternativen, Inspiration und Vergleiche { #alternatives-inspiration-and-comparisons } -Was hat **FastAPI** inspiriert, ein Vergleich zu Alternativen, und was FastAPI von diesen gelernt hat. +Was hat **FastAPI** inspiriert, wie es sich im Vergleich zu Alternativen verhält und was es von ihnen gelernt hat. -## Einführung +## Einführung { #intro } **FastAPI** würde ohne die frühere Arbeit anderer nicht existieren. @@ -12,17 +12,17 @@ Ich habe die Schaffung eines neuen Frameworks viele Jahre lang vermieden. Zuerst Aber irgendwann gab es keine andere Möglichkeit, als etwas zu schaffen, das all diese Funktionen bereitstellte, die besten Ideen früherer Tools aufnahm und diese auf die bestmögliche Weise kombinierte, wobei Sprachfunktionen verwendet wurden, die vorher noch nicht einmal verfügbar waren (Python 3.6+ Typhinweise). -## Vorherige Tools +## Vorherige Tools { #previous-tools } -### Django +### Django { #django } Es ist das beliebteste Python-Framework und genießt großes Vertrauen. Es wird zum Aufbau von Systemen wie Instagram verwendet. -Ist relativ eng mit relationalen Datenbanken (wie MySQL oder PostgreSQL) gekoppelt, daher ist es nicht sehr einfach, eine NoSQL-Datenbank (wie Couchbase, MongoDB, Cassandra, usw.) als Hauptspeicherengine zu verwenden. +Es ist relativ eng mit relationalen Datenbanken (wie MySQL oder PostgreSQL) gekoppelt, daher ist es nicht sehr einfach, eine NoSQL-Datenbank (wie Couchbase, MongoDB, Cassandra, usw.) als Hauptspeicherengine zu verwenden. -Es wurde erstellt, um den HTML-Code im Backend zu generieren, nicht um APIs zu erstellen, die von einem modernen Frontend (wie React, Vue.js und Angular) oder von anderen Systemen (wie IoT-Geräten) verwendet werden, um mit ihm zu kommunizieren. +Es wurde erstellt, um den HTML-Code im Backend zu generieren, nicht um APIs zu erstellen, die von einem modernen Frontend (wie React, Vue.js und Angular) oder von anderen Systemen (wie IoT-Geräten) verwendet werden, um mit ihm zu kommunizieren. -### Django REST Framework +### Django REST Framework { #django-rest-framework } Das Django REST Framework wurde als flexibles Toolkit zum Erstellen von Web-APIs unter Verwendung von Django entwickelt, um dessen API-Möglichkeiten zu verbessern. @@ -30,19 +30,19 @@ Es wird von vielen Unternehmen verwendet, darunter Mozilla, Red Hat und Eventbri Es war eines der ersten Beispiele für **automatische API-Dokumentation**, und dies war insbesondere eine der ersten Ideen, welche „die Suche nach“ **FastAPI** inspirierten. -/// note | "Hinweis" +/// note | Hinweis Das Django REST Framework wurde von Tom Christie erstellt. Derselbe Schöpfer von Starlette und Uvicorn, auf denen **FastAPI** basiert. /// -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Eine automatische API-Dokumentationsoberfläche zu haben. /// -### Flask +### Flask { #flask } Flask ist ein „Mikroframework“, es enthält weder Datenbankintegration noch viele der Dinge, die standardmäßig in Django enthalten sind. @@ -56,7 +56,7 @@ Diese Entkopplung der Teile und die Tatsache, dass es sich um ein „Mikroframew Angesichts der Einfachheit von Flask schien es eine gute Ergänzung zum Erstellen von APIs zu sein. Als Nächstes musste ein „Django REST Framework“ für Flask gefunden werden. -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Ein Mikroframework zu sein. Es einfach zu machen, die benötigten Tools und Teile zu kombinieren. @@ -64,7 +64,7 @@ Ein Mikroframework zu sein. Es einfach zu machen, die benötigten Tools und Teil /// -### Requests +### Requests { #requests } **FastAPI** ist eigentlich keine Alternative zu **Requests**. Der Umfang der beiden ist sehr unterschiedlich. @@ -82,7 +82,7 @@ Aus diesem Grund heißt es auf der offiziellen Website: > Requests ist eines der am häufigsten heruntergeladenen Python-Packages aller Zeiten -Die Art und Weise, wie Sie es verwenden, ist sehr einfach. Um beispielsweise einen `GET`-Request zu machen, würden Sie schreiben: +Die Art und Weise, wie Sie es verwenden, ist sehr einfach. Um beispielsweise einen `GET`-Request zu machen, würden Sie schreiben: ```Python response = requests.get("http://example.com/some/url") @@ -98,7 +98,7 @@ def read_url(): Sehen Sie sich die Ähnlichkeiten in `requests.get(...)` und `@app.get(...)` an. -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** * Über eine einfache und intuitive API zu verfügen. * HTTP-Methodennamen (Operationen) direkt, auf einfache und intuitive Weise zu verwenden. @@ -106,7 +106,7 @@ Sehen Sie sich die Ähnlichkeiten in `requests.get(...)` und `@app.get(...)` an. /// -### Swagger / OpenAPI +### Swagger / OpenAPI { #swagger-openapi } Die Hauptfunktion, die ich vom Django REST Framework haben wollte, war die automatische API-Dokumentation. @@ -118,7 +118,7 @@ Irgendwann wurde Swagger an die Linux Foundation übergeben und in OpenAPI umben Aus diesem Grund spricht man bei Version 2.0 häufig von „Swagger“ und ab Version 3 von „OpenAPI“. -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Einen offenen Standard für API-Spezifikationen zu übernehmen und zu verwenden, anstelle eines benutzerdefinierten Schemas. @@ -131,13 +131,13 @@ Diese beiden wurden ausgewählt, weil sie ziemlich beliebt und stabil sind, aber /// -### Flask REST Frameworks +### Flask REST Frameworks { #flask-rest-frameworks } Es gibt mehrere Flask REST Frameworks, aber nachdem ich die Zeit und Arbeit investiert habe, sie zu untersuchen, habe ich festgestellt, dass viele nicht mehr unterstützt werden oder abgebrochen wurden und dass mehrere fortbestehende Probleme sie unpassend machten. -### Marshmallow +### Marshmallow { #marshmallow } -Eine der von API-Systemen benötigen Hauptfunktionen ist die Daten-„Serialisierung“, welche Daten aus dem Code (Python) entnimmt und in etwas umwandelt, was durch das Netzwerk gesendet werden kann. Beispielsweise das Konvertieren eines Objekts, welches Daten aus einer Datenbank enthält, in ein JSON-Objekt. Konvertieren von `datetime`-Objekten in Strings, usw. +Eine der von API-Systemen benötigten Hauptfunktionen ist die Daten-„Serialisierung“, welche Daten aus dem Code (Python) entnimmt und in etwas umwandelt, was durch das Netzwerk gesendet werden kann. Beispielsweise das Konvertieren eines Objekts, welches Daten aus einer Datenbank enthält, in ein JSON-Objekt. Konvertieren von `datetime`-Objekten in Strings, usw. Eine weitere wichtige Funktion, benötigt von APIs, ist die Datenvalidierung, welche sicherstellt, dass die Daten unter gegebenen Umständen gültig sind. Zum Beispiel, dass ein Feld ein `int` ist und kein zufälliger String. Das ist besonders nützlich für hereinkommende Daten. @@ -145,15 +145,15 @@ Ohne ein Datenvalidierungssystem müssten Sie alle Prüfungen manuell im Code du Für diese Funktionen wurde Marshmallow entwickelt. Es ist eine großartige Bibliothek und ich habe sie schon oft genutzt. -Aber sie wurde erstellt, bevor Typhinweise in Python existierten. Um also ein Schema zu definieren, müssen Sie bestimmte Werkzeuge und Klassen verwenden, die von Marshmallow bereitgestellt werden. +Aber sie wurde erstellt, bevor Typhinweise in Python existierten. Um also ein Schema zu definieren, müssen Sie bestimmte Werkzeuge und Klassen verwenden, die von Marshmallow bereitgestellt werden. -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Code zu verwenden, um „Schemas“ zu definieren, welche Datentypen und Validierung automatisch bereitstellen. /// -### Webargs +### Webargs { #webargs } Eine weitere wichtige Funktion, die von APIs benötigt wird, ist das Parsen von Daten aus eingehenden Requests. @@ -163,19 +163,19 @@ Es verwendet unter der Haube Marshmallow, um die Datenvalidierung durchzuführen Es ist ein großartiges Tool und ich habe es auch oft verwendet, bevor ich **FastAPI** hatte. -/// info +/// info | Info Webargs wurde von denselben Marshmallow-Entwicklern erstellt. /// -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Eingehende Requestdaten automatisch zu validieren. /// -### APISpec +### APISpec { #apispec } Marshmallow und Webargs bieten Validierung, Parsen und Serialisierung als Plugins. @@ -193,19 +193,19 @@ Aber dann haben wir wieder das Problem einer Mikrosyntax innerhalb eines Python- Der Texteditor kann dabei nicht viel helfen. Und wenn wir Parameter oder Marshmallow-Schemas ändern und vergessen, auch den YAML-Docstring zu ändern, wäre das generierte Schema veraltet. -/// info +/// info | Info APISpec wurde von denselben Marshmallow-Entwicklern erstellt. /// -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Den offenen Standard für APIs, OpenAPI, zu unterstützen. /// -### Flask-apispec +### Flask-apispec { #flask-apispec } Hierbei handelt es sich um ein Flask-Plugin, welches Webargs, Marshmallow und APISpec miteinander verbindet. @@ -225,19 +225,19 @@ Die Verwendung führte zur Entwicklung mehrerer Flask-Full-Stack-Generatoren. Di Und dieselben Full-Stack-Generatoren bildeten die Basis der [**FastAPI**-Projektgeneratoren](project-generation.md){.internal-link target=_blank}. -/// info +/// info | Info Flask-apispec wurde von denselben Marshmallow-Entwicklern erstellt. /// -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Das OpenAPI-Schema automatisch zu generieren, aus demselben Code, welcher die Serialisierung und Validierung definiert. /// -### NestJS (und Angular) +### NestJS (und Angular) { #nestjs-and-angular } Dies ist nicht einmal Python, NestJS ist ein von Angular inspiriertes JavaScript (TypeScript) NodeJS Framework. @@ -249,9 +249,9 @@ Da die Parameter mit TypeScript-Typen beschrieben werden (ähnlich den Python-Ty Da TypeScript-Daten jedoch nach der Kompilierung nach JavaScript nicht erhalten bleiben, können die Typen nicht gleichzeitig die Validierung, Serialisierung und Dokumentation definieren. Aus diesem Grund und aufgrund einiger Designentscheidungen ist es für die Validierung, Serialisierung und automatische Schemagenerierung erforderlich, an vielen Stellen Dekoratoren hinzuzufügen. Es wird also ziemlich ausführlich. -Es kann nicht sehr gut mit verschachtelten Modellen umgehen. Wenn es sich beim JSON-Body in der Anfrage also um ein JSON-Objekt mit inneren Feldern handelt, die wiederum verschachtelte JSON-Objekte sind, kann er nicht richtig dokumentiert und validiert werden. +Es kann nicht sehr gut mit verschachtelten Modellen umgehen. Wenn es sich beim JSON-Body im Request also um ein JSON-Objekt mit inneren Feldern handelt, die wiederum verschachtelte JSON-Objekte sind, kann er nicht richtig dokumentiert und validiert werden. -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Python-Typen zu verwenden, um eine hervorragende Editorunterstützung zu erhalten. @@ -259,11 +259,11 @@ Python-Typen zu verwenden, um eine hervorragende Editorunterstützung zu erhalte /// -### Sanic +### Sanic { #sanic } Es war eines der ersten extrem schnellen Python-Frameworks, welches auf `asyncio` basierte. Es wurde so gestaltet, dass es Flask sehr ähnlich ist. -/// note | "Technische Details" +/// note | Technische Details Es verwendete `uvloop` anstelle der standardmäßigen Python-`asyncio`-Schleife. Das hat es so schnell gemacht. @@ -271,7 +271,7 @@ Hat eindeutig Uvicorn und Starlette inspiriert, welche derzeit in offenen Benchm /// -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Einen Weg zu finden, eine hervorragende Performanz zu haben. @@ -279,15 +279,15 @@ Aus diesem Grund basiert **FastAPI** auf Starlette, da dieses das schnellste ver /// -### Falcon +### Falcon { #falcon } Falcon ist ein weiteres leistungsstarkes Python-Framework. Es ist minimalistisch konzipiert und dient als Grundlage für andere Frameworks wie Hug. -Es ist so konzipiert, dass es über Funktionen verfügt, welche zwei Parameter empfangen, einen „Request“ und eine „Response“. Dann „lesen“ Sie Teile des Requests und „schreiben“ Teile der Response. Aufgrund dieses Designs ist es nicht möglich, Request-Parameter und -Bodys mit Standard-Python-Typhinweisen als Funktionsparameter zu deklarieren. +Es ist so konzipiert, dass es über Funktionen verfügt, welche zwei Parameter empfangen, einen „Request“ und eine „Response“. Dann „lesen“ Sie Teile des Requests und „schreiben“ Teile der Response. Aufgrund dieses Designs ist es nicht möglich, Request-Parameter und -Bodys mit Standard-Python-Typhinweisen als Funktionsparameter zu deklarieren. Daher müssen Datenvalidierung, Serialisierung und Dokumentation im Code und nicht automatisch erfolgen. Oder sie müssen als Framework oberhalb von Falcon implementiert werden, so wie Hug. Dieselbe Unterscheidung findet auch in anderen Frameworks statt, die vom Design von Falcon inspiriert sind und ein Requestobjekt und ein Responseobjekt als Parameter haben. -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Wege zu finden, eine großartige Performanz zu erzielen. @@ -297,7 +297,7 @@ Obwohl er in FastAPI optional ist und hauptsächlich zum Festlegen von Headern, /// -### Molten +### Molten { #molten } Ich habe Molten in den ersten Phasen der Entwicklung von **FastAPI** entdeckt. Und es hat ganz ähnliche Ideen: @@ -313,7 +313,7 @@ Das Dependency Injection System erfordert eine Vorab-Registrierung der Abhängig Routen werden an einer einzigen Stelle deklariert, indem Funktionen verwendet werden, die an anderen Stellen deklariert wurden (anstatt Dekoratoren zu verwenden, welche direkt über der Funktion platziert werden können, welche den Endpunkt verarbeitet). Dies ähnelt eher der Vorgehensweise von Django als der Vorgehensweise von Flask (und Starlette). Es trennt im Code Dinge, die relativ eng miteinander gekoppelt sind. -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Zusätzliche Validierungen für Datentypen zu definieren, mithilfe des „Default“-Werts von Modellattributen. Dies verbessert die Editorunterstützung und war zuvor in Pydantic nicht verfügbar. @@ -321,7 +321,7 @@ Das hat tatsächlich dazu geführt, dass Teile von Pydantic aktualisiert wurden, /// -### Hug +### Hug { #hug } Hug war eines der ersten Frameworks, welches die Deklaration von API-Parametertypen mithilfe von Python-Typhinweisen implementierte. Das war eine großartige Idee, die andere Tools dazu inspirierte, dasselbe zu tun. @@ -335,13 +335,13 @@ Es verfügt über eine interessante, ungewöhnliche Funktion: Mit demselben Fram Da es auf dem bisherigen Standard für synchrone Python-Webframeworks (WSGI) basiert, kann es nicht mit Websockets und anderen Dingen umgehen, verfügt aber dennoch über eine hohe Performanz. -/// info +/// info | Info -Hug wurde von Timothy Crosley erstellt, dem gleichen Schöpfer von `isort`, einem großartigen Tool zum automatischen Sortieren von Importen in Python-Dateien. +Hug wurde von Timothy Crosley erstellt, demselben Schöpfer von `isort`, einem großartigen Tool zum automatischen Sortieren von Importen in Python-Dateien. /// -/// check | "Ideen, die **FastAPI** inspiriert haben" +/// check | Ideen, die **FastAPI** inspiriert haben Hug inspirierte Teile von APIStar und war eines der Tools, die ich am vielversprechendsten fand, neben APIStar. @@ -351,7 +351,7 @@ Hug inspirierte **FastAPI** dazu, einen `response`-Parameter in Funktionen zu de /// -### APIStar (≦ 0.5) +### APIStar (≦ 0.5) { #apistar-0-5 } Kurz bevor ich mich entschied, **FastAPI** zu erstellen, fand ich den **APIStar**-Server. Er hatte fast alles, was ich suchte, und ein tolles Design. @@ -375,7 +375,7 @@ Es handelte sich nicht länger um ein API-Webframework, da sich der Entwickler a Jetzt handelt es sich bei APIStar um eine Reihe von Tools zur Validierung von OpenAPI-Spezifikationen, nicht um ein Webframework. -/// info +/// info | Info APIStar wurde von Tom Christie erstellt. Derselbe, welcher Folgendes erstellt hat: @@ -385,7 +385,7 @@ APIStar wurde von Tom Christie erstellt. Derselbe, welcher Folgendes erstellt ha /// -/// check | "Inspirierte **FastAPI**" +/// check | Inspirierte **FastAPI** Zu existieren. @@ -399,9 +399,9 @@ Ich betrachte **FastAPI** als einen „spirituellen Nachfolger“ von APIStar, w /// -## Verwendet von **FastAPI** +## Verwendet von **FastAPI** { #used-by-fastapi } -### Pydantic +### Pydantic { #pydantic } Pydantic ist eine Bibliothek zum Definieren von Datenvalidierung, Serialisierung und Dokumentation (unter Verwendung von JSON Schema) basierend auf Python-Typhinweisen. @@ -409,7 +409,7 @@ Das macht es äußerst intuitiv. Es ist vergleichbar mit Marshmallow. Obwohl es in Benchmarks schneller als Marshmallow ist. Und da es auf den gleichen Python-Typhinweisen basiert, ist die Editorunterstützung großartig. -/// check | "**FastAPI** verwendet es, um" +/// check | **FastAPI** verwendet es, um Die gesamte Datenvalidierung, Datenserialisierung und automatische Modelldokumentation (basierend auf JSON Schema) zu erledigen. @@ -417,7 +417,7 @@ Die gesamte Datenvalidierung, Datenserialisierung und automatische Modelldokumen /// -### Starlette +### Starlette { #starlette } Starlette ist ein leichtgewichtiges ASGI-Framework/Toolkit, welches sich ideal für die Erstellung hochperformanter asynchroner Dienste eignet. @@ -428,9 +428,9 @@ Es bietet: * Eine sehr beeindruckende Leistung. * WebSocket-Unterstützung. * Hintergrundtasks im selben Prozess. -* Events für das Hoch- und Herunterfahren. +* Startup- und Shutdown-Events. * Testclient basierend auf HTTPX. -* CORS, GZip, statische Dateien, Streamende Responses. +* CORS, GZip, statische Dateien, Responses streamen. * Session- und Cookie-Unterstützung. * 100 % Testabdeckung. * 100 % Typannotierte Codebasis. @@ -444,7 +444,7 @@ Es bietet jedoch keine automatische Datenvalidierung, Serialisierung oder Dokume Das ist eines der wichtigsten Dinge, welche **FastAPI** hinzufügt, alles basierend auf Python-Typhinweisen (mit Pydantic). Das, plus, das Dependency Injection System, Sicherheitswerkzeuge, OpenAPI-Schemagenerierung, usw. -/// note | "Technische Details" +/// note | Technische Details ASGI ist ein neuer „Standard“, welcher von Mitgliedern des Django-Kernteams entwickelt wird. Es handelt sich immer noch nicht um einen „Python-Standard“ (ein PEP), obwohl sie gerade dabei sind, das zu tun. @@ -452,7 +452,7 @@ Dennoch wird es bereits von mehreren Tools als „Standard“ verwendet. Das ver /// -/// check | "**FastAPI** verwendet es, um" +/// check | **FastAPI** verwendet es, um Alle Kern-Webaspekte zu handhaben. Und fügt Funktionen obenauf. @@ -462,7 +462,7 @@ Alles, was Sie also mit Starlette machen können, können Sie direkt mit **FastA /// -### Uvicorn +### Uvicorn { #uvicorn } Uvicorn ist ein blitzschneller ASGI-Server, der auf uvloop und httptools basiert. @@ -470,16 +470,16 @@ Es handelt sich nicht um ein Webframework, sondern um einen Server. Beispielswei Es ist der empfohlene Server für Starlette und **FastAPI**. -/// check | "**FastAPI** empfiehlt es als" +/// check | **FastAPI** empfiehlt es als Hauptwebserver zum Ausführen von **FastAPI**-Anwendungen. -Sie können ihn mit Gunicorn kombinieren, um einen asynchronen Multiprozess-Server zu erhalten. +Sie können auch die Kommandozeilenoption `--workers` verwenden, um einen asynchronen Multiprozess-Server zu erhalten. Weitere Details finden Sie im Abschnitt [Deployment](deployment/index.md){.internal-link target=_blank}. /// -## Benchmarks und Geschwindigkeit +## Benchmarks und Geschwindigkeit { #benchmarks-and-speed } Um den Unterschied zwischen Uvicorn, Starlette und FastAPI zu verstehen, zu vergleichen und zu sehen, lesen Sie den Abschnitt über [Benchmarks](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/de/docs/async.md b/docs/de/docs/async.md index 74b6b69687..3dbd442e5f 100644 --- a/docs/de/docs/async.md +++ b/docs/de/docs/async.md @@ -1,8 +1,8 @@ -# Nebenläufigkeit und async / await +# Nebenläufigkeit und async / await { #concurrency-and-async-await } Details zur `async def`-Syntax für *Pfadoperation-Funktionen* und Hintergrundinformationen zu asynchronem Code, Nebenläufigkeit und Parallelität. -## In Eile? +## In Eile? { #in-a-hurry } TL;DR: @@ -12,7 +12,7 @@ Wenn Sie Bibliotheken von Dritten verwenden, die mit `await` aufgerufen werden m results = await some_library() ``` -Dann deklarieren Sie Ihre *Pfadoperation-Funktionen* mit `async def` wie in: +Dann deklarieren Sie Ihre *Pfadoperation-Funktionen* mit `async def`, wie in: ```Python hl_lines="2" @app.get('/') @@ -21,7 +21,7 @@ async def read_results(): return results ``` -/// note +/// note | Hinweis Sie können `await` nur innerhalb von Funktionen verwenden, die mit `async def` erstellt wurden. @@ -29,7 +29,7 @@ Sie können `await` nur innerhalb von Funktionen verwenden, die mit `async def` --- -Wenn Sie eine Bibliothek eines Dritten verwenden, die mit etwas kommuniziert (einer Datenbank, einer API, dem Dateisystem, usw.) und welche die Verwendung von `await` nicht unterstützt (dies ist derzeit bei den meisten Datenbankbibliotheken der Fall), dann deklarieren Sie Ihre *Pfadoperation-Funktionen* ganz normal nur mit `def`, etwa: +Wenn Sie eine Bibliothek eines Dritten verwenden, die mit etwas kommuniziert (einer Datenbank, einer API, dem Dateisystem, usw.) und welche die Verwendung von `await` nicht unterstützt (dies ist derzeit bei den meisten Datenbankbibliotheken der Fall), dann deklarieren Sie Ihre *Pfadoperation-Funktionen* ganz normal nur mit `def`, wie in: ```Python hl_lines="2" @app.get('/') @@ -40,7 +40,7 @@ def results(): --- -Wenn Ihre Anwendung (irgendwie) mit nichts anderem kommunizieren und auf dessen Antwort warten muss, verwenden Sie `async def`. +Wenn Ihre Anwendung (irgendwie) nicht mit etwas anderem kommunizieren und auf dessen Antwort warten muss, verwenden Sie `async def`, auch wenn Sie `await` im Inneren nicht verwenden müssen. --- @@ -52,11 +52,11 @@ Wenn Sie sich unsicher sind, verwenden Sie einfach `def`. Wie dem auch sei, in jedem der oben genannten Fälle wird FastAPI immer noch asynchron arbeiten und extrem schnell sein. -Wenn Sie jedoch den oben genannten Schritten folgen, können einige Performance-Optimierungen vorgenommen werden. +Wenn Sie jedoch den oben genannten Schritten folgen, können einige Performanz-Optimierungen vorgenommen werden. -## Technische Details +## Technische Details { #technical-details } -Moderne Versionen von Python unterstützen **„asynchronen Code“** unter Verwendung sogenannter **„Coroutinen“** mithilfe der Syntax **`async`** und **`await`**. +Moderne Versionen von Python unterstützen **„asynchronen Code“** unter Verwendung sogenannter **„Coroutinen“** mithilfe der Syntax **`async` und `await`**. Nehmen wir obigen Satz in den folgenden Abschnitten Schritt für Schritt unter die Lupe: @@ -64,13 +64,13 @@ Nehmen wir obigen Satz in den folgenden Abschnitten Schritt für Schritt unter d * **`async` und `await`** * **Coroutinen** -## Asynchroner Code +## Asynchroner Code { #asynchronous-code } -Asynchroner Code bedeutet lediglich, dass die Sprache 💬 eine Möglichkeit hat, dem Computersystem / Programm 🤖 mitzuteilen, dass es 🤖 an einem bestimmten Punkt im Code darauf warten muss, dass *etwas anderes* irgendwo anders fertig wird. Nehmen wir an, *etwas anderes* ist hier „Langsam-Datei“ 📝. +Asynchroner Code bedeutet lediglich, dass die Sprache 💬 eine Möglichkeit hat, dem Computer / Programm 🤖 mitzuteilen, dass es 🤖 an einem bestimmten Punkt im Code darauf warten muss, dass *etwas anderes* irgendwo anders fertig wird. Nehmen wir an, *etwas anderes* ist hier „Langsam-Datei“ 📝. Während der Zeit, die „Langsam-Datei“ 📝 benötigt, kann das System also andere Aufgaben erledigen. -Dann kommt das System / Programm 🤖 bei jeder Gelegenheit zurück, wenn es entweder wieder wartet, oder wann immer es 🤖 die ganze Arbeit erledigt hat, die zu diesem Zeitpunkt zu tun war. Und es 🤖 wird nachschauen, ob eine der Aufgaben, auf die es gewartet hat, fertig damit ist, zu tun, was sie tun sollte. +Dann kommt der Computer / das Programm 🤖 bei jeder Gelegenheit zurück, weil es entweder wieder wartet oder wann immer es 🤖 die ganze Arbeit erledigt hat, die zu diesem Zeitpunkt zu tun war. Und es 🤖 wird nachschauen, ob eine der Aufgaben, auf die es gewartet hat, fertig ist. Dann nimmt es 🤖 die erste erledigte Aufgabe (sagen wir, unsere „Langsam-Datei“ 📝) und bearbeitet sie weiter. @@ -87,13 +87,13 @@ Das „Warten auf etwas anderes“ bezieht sich normalerweise auf I/O-Operationen verbraucht wird, nennt man dies auch „I/O-lastige“ („I/O bound“) Operationen. -„Asynchron“, sagt man, weil das Computersystem / Programm nicht mit einer langsamen Aufgabe „synchronisiert“ werden muss und nicht auf den genauen Moment warten muss, in dem die Aufgabe beendet ist, ohne dabei etwas zu tun, um schließlich das Ergebnis der Aufgabe zu übernehmen und die Arbeit fortsetzen zu können. +„Asynchron“, sagt man, weil der Computer / das Programm nicht mit einer langsamen Aufgabe „synchronisiert“ werden muss und nicht auf den genauen Moment warten muss, in dem die Aufgabe beendet ist, ohne dabei etwas zu tun, um schließlich das Ergebnis der Aufgabe zu übernehmen und die Arbeit fortsetzen zu können. -Da es sich stattdessen um ein „asynchrones“ System handelt, kann die Aufgabe nach Abschluss ein wenig (einige Mikrosekunden) in der Schlange warten, bis das System / Programm seine anderen Dinge erledigt hat und zurückkommt, um die Ergebnisse entgegenzunehmen und mit ihnen weiterzuarbeiten. +Da es sich stattdessen um ein „asynchrones“ System handelt, kann die Aufgabe nach Abschluss ein wenig (einige Mikrosekunden) in der Schlange warten, bis der Computer / das Programm seine anderen Dinge erledigt hat und zurückkommt, um die Ergebnisse entgegenzunehmen und mit ihnen weiterzuarbeiten. -Für „synchron“ (im Gegensatz zu „asynchron“) wird auch oft der Begriff „sequentiell“ verwendet, da das System / Programm alle Schritte in einer Sequenz („der Reihe nach“) ausführt, bevor es zu einer anderen Aufgabe wechselt, auch wenn diese Schritte mit Warten verbunden sind. +Für „synchron“ (im Gegensatz zu „asynchron“) wird auch oft der Begriff „sequentiell“ verwendet, da der Computer / das Programm alle Schritte in einer Sequenz („der Reihe nach“) ausführt, bevor es zu einer anderen Aufgabe wechselt, auch wenn diese Schritte mit Warten verbunden sind. -### Nebenläufigkeit und Hamburger +### Nebenläufigkeit und Hamburger { #concurrency-and-burgers } Diese oben beschriebene Idee von **asynchronem** Code wird manchmal auch **„Nebenläufigkeit“** genannt. Sie unterscheidet sich von **„Parallelität“**. @@ -103,7 +103,7 @@ Aber die Details zwischen *Nebenläufigkeit* und *Parallelität* sind ziemlich u Um den Unterschied zu erkennen, stellen Sie sich die folgende Geschichte über Hamburger vor: -### Nebenläufige Hamburger +### Nebenläufige Hamburger { #concurrent-burgers } Sie gehen mit Ihrem Schwarm Fastfood holen, stehen in der Schlange, während der Kassierer die Bestellungen der Leute vor Ihnen entgegennimmt. 😍 @@ -139,7 +139,7 @@ Sie und Ihr Schwarm essen die Burger und haben eine schöne Zeit. ✨ -/// info +/// info | Info Die wunderschönen Illustrationen stammen von Ketrina Thompson. 🎨 @@ -147,7 +147,7 @@ Die wunderschönen Illustrationen stammen von Ketrina Thompson. 🎨 @@ -233,15 +233,15 @@ Und man muss lange in der Schlange warten 🕙 sonst kommt man nicht an die Reih Sie würden Ihren Schwarm 😍 wahrscheinlich nicht mitnehmen wollen, um Besorgungen bei der Bank zu erledigen 🏦. -### Hamburger Schlussfolgerung +### Hamburger Schlussfolgerung { #burger-conclusion } -In diesem Szenario „Fast Food Burger mit Ihrem Schwarm“ ist es viel sinnvoller, ein nebenläufiges System zu haben ⏸🔀⏯, da viel gewartet wird 🕙. +In diesem Szenario „Fastfood-Burger mit Ihrem Schwarm“ ist es viel sinnvoller, ein nebenläufiges System zu haben ⏸🔀⏯, da viel gewartet wird 🕙. Das ist auch bei den meisten Webanwendungen der Fall. -Viele, viele Benutzer, aber Ihr Server wartet 🕙 darauf, dass deren nicht so gute Internetverbindungen die Requests übermitteln. +Viele, viele Benutzer, aber Ihr Server wartet 🕙 darauf, dass deren nicht so gute Internetverbindungen die Requests übermitteln. -Und dann warten 🕙, bis die Responses zurückkommen. +Und dann wieder warten 🕙, bis die Responses zurückkommen. Dieses „Warten“ 🕙 wird in Mikrosekunden gemessen, aber zusammenfassend lässt sich sagen, dass am Ende eine Menge gewartet wird. @@ -253,7 +253,7 @@ Und das ist das gleiche Leistungsniveau, das Sie mit **FastAPI** erhalten. Und da Sie Parallelität und Asynchronität gleichzeitig haben können, erzielen Sie eine höhere Performanz als die meisten getesteten NodeJS-Frameworks und sind mit Go auf Augenhöhe, einer kompilierten Sprache, die näher an C liegt (alles dank Starlette). -### Ist Nebenläufigkeit besser als Parallelität? +### Ist Nebenläufigkeit besser als Parallelität? { #is-concurrency-better-than-parallelism } Nein! Das ist nicht die Moral der Geschichte. @@ -290,17 +290,17 @@ Zum Beispiel: * **Maschinelles Lernen**: Normalerweise sind viele „Matrix“- und „Vektor“-Multiplikationen erforderlich. Stellen Sie sich eine riesige Tabelle mit Zahlen vor, in der Sie alle Zahlen gleichzeitig multiplizieren. * **Deep Learning**: Dies ist ein Teilgebiet des maschinellen Lernens, daher gilt das Gleiche. Es ist nur so, dass es nicht eine einzige Tabelle mit Zahlen zum Multiplizieren gibt, sondern eine riesige Menge davon, und in vielen Fällen verwendet man einen speziellen Prozessor, um diese Modelle zu erstellen und / oder zu verwenden. -### Nebenläufigkeit + Parallelität: Web + maschinelles Lernen +### Nebenläufigkeit + Parallelität: Web + maschinelles Lernen { #concurrency-parallelism-web-machine-learning } Mit **FastAPI** können Sie die Vorteile der Nebenläufigkeit nutzen, die in der Webentwicklung weit verbreitet ist (derselbe Hauptvorteil von NodeJS). -Sie können aber auch die Vorteile von Parallelität und Multiprocessing (Mehrere Prozesse werden parallel ausgeführt) für **CPU-lastige** Workloads wie in Systemen für maschinelles Lernen nutzen. +Sie können aber auch die Vorteile von Parallelität und Multiprocessing (mehrere Prozesse werden parallel ausgeführt) für **CPU-lastige** Workloads wie in Systemen für maschinelles Lernen nutzen. Dies und die einfache Tatsache, dass Python die Hauptsprache für **Data Science**, maschinelles Lernen und insbesondere Deep Learning ist, machen FastAPI zu einem sehr passenden Werkzeug für Web-APIs und Anwendungen für Data Science / maschinelles Lernen (neben vielen anderen). Wie Sie diese Parallelität in der Produktion erreichen, erfahren Sie im Abschnitt über [Deployment](deployment/index.md){.internal-link target=_blank}. -## `async` und `await`. +## `async` und `await` { #async-and-await } Moderne Versionen von Python verfügen über eine sehr intuitive Möglichkeit, asynchronen Code zu schreiben. Dadurch sieht es wie normaler „sequentieller“ Code aus und übernimmt im richtigen Moment das „Warten“ für Sie. @@ -316,16 +316,16 @@ Damit `await` funktioniert, muss es sich in einer Funktion befinden, die diese A ```Python hl_lines="1" async def get_burgers(number: int): - # Mach Sie hier etwas Asynchrones, um die Burger zu erstellen + # Mache hier etwas Asynchrones, um die Burger zu erstellen return burgers ``` ... statt mit `def`: ```Python hl_lines="2" -# Die ist nicht asynchron +# Dies ist nicht asynchron def get_sequential_burgers(number: int): - # Mach Sie hier etwas Sequentielles, um die Burger zu erstellen + # Mache hier etwas Sequentielles, um die Burger zu erstellen return burgers ``` @@ -349,7 +349,7 @@ async def read_burgers(): return burgers ``` -### Weitere technische Details +### Weitere technische Details { #more-technical-details } Ihnen ist wahrscheinlich aufgefallen, dass `await` nur innerhalb von Funktionen verwendet werden kann, die mit `async def` definiert sind. @@ -361,15 +361,17 @@ Wenn Sie mit **FastAPI** arbeiten, müssen Sie sich darüber keine Sorgen machen Wenn Sie jedoch `async` / `await` ohne FastAPI verwenden möchten, können Sie dies auch tun. -### Schreiben Sie Ihren eigenen asynchronen Code +### Schreiben Sie Ihren eigenen asynchronen Code { #write-your-own-async-code } -Starlette (und **FastAPI**) basiert auf AnyIO, was bedeutet, es ist sowohl kompatibel mit der Python-Standardbibliothek asyncio, als auch mit Trio. +Starlette (und **FastAPI**) basieren auf AnyIO, was bedeutet, dass es sowohl kompatibel mit der Python-Standardbibliothek asyncio als auch mit Trio ist. -Insbesondere können Sie AnyIO direkt verwenden für Ihre fortgeschritten nebenläufigen und parallelen Anwendungsfälle, die fortgeschrittenere Muster in Ihrem eigenen Code erfordern. +Insbesondere können Sie AnyIO direkt verwenden für Ihre fortgeschrittenen nebenläufigen Anwendungsfälle, die fortgeschrittenere Muster in Ihrem eigenen Code erfordern. -Und selbst wenn Sie FastAPI nicht verwenden würden, könnten Sie auch Ihre eigenen asynchronen Anwendungen mit AnyIO so schreiben, dass sie hoch kompatibel sind und Sie dessen Vorteile nutzen können (z. B. *strukturierte Nebenläufigkeit*). +Und auch wenn Sie FastAPI nicht verwenden würden, könnten Sie Ihre eigenen asynchronen Anwendungen mit AnyIO schreiben, um hochkompatibel zu sein und dessen Vorteile zu nutzen (z. B. *strukturierte Nebenläufigkeit*). -### Andere Formen von asynchronem Code +Ich habe eine weitere Bibliothek auf Basis von AnyIO erstellt, als dünne Schicht obendrauf, um die Typannotationen etwas zu verbessern und bessere **Autovervollständigung**, **Inline-Fehler** usw. zu erhalten. Sie hat auch eine freundliche Einführung und ein Tutorial, um Ihnen zu helfen, **Ihren eigenen asynchronen Code zu verstehen** und zu schreiben: Asyncer. Sie ist insbesondere nützlich, wenn Sie **asynchronen Code mit regulärem** (blockierendem/synchronem) Code kombinieren müssen. + +### Andere Formen von asynchronem Code { #other-forms-of-asynchronous-code } Diese Art der Verwendung von `async` und `await` ist in der Sprache relativ neu. @@ -381,27 +383,27 @@ Davor war der Umgang mit asynchronem Code jedoch deutlich komplexer und schwieri In früheren Versionen von Python hätten Sie Threads oder Gevent verwenden können. Der Code ist jedoch viel komplexer zu verstehen, zu debuggen und nachzuvollziehen. -In früheren Versionen von NodeJS / Browser JavaScript hätten Sie „Callbacks“ verwendet. Was zur Callback-Hölle führt. +In früheren Versionen von NodeJS / Browser JavaScript hätten Sie „Callbacks“ verwendet. Was zur „Callback-Hölle“ führt. -## Coroutinen +## Coroutinen { #coroutines } **Coroutine** ist nur ein schicker Begriff für dasjenige, was von einer `async def`-Funktion zurückgegeben wird. Python weiß, dass es so etwas wie eine Funktion ist, die es starten kann und die irgendwann endet, aber auch dass sie pausiert ⏸ werden kann, wann immer darin ein `await` steht. Aber all diese Funktionalität der Verwendung von asynchronem Code mit `async` und `await` wird oft als Verwendung von „Coroutinen“ zusammengefasst. Es ist vergleichbar mit dem Hauptmerkmal von Go, den „Goroutinen“. -## Fazit +## Fazit { #conclusion } Sehen wir uns den gleichen Satz von oben noch mal an: -> Moderne Versionen von Python unterstützen **„asynchronen Code“** unter Verwendung sogenannter **„Coroutinen“** mithilfe der Syntax **`async`** und **`await`**. +> Moderne Versionen von Python unterstützen **„asynchronen Code“** unter Verwendung sogenannter **„Coroutinen“** mithilfe der Syntax **`async` und `await`**. Das sollte jetzt mehr Sinn ergeben. ✨ All das ist es, was FastAPI (via Starlette) befeuert und es eine so beeindruckende Performanz haben lässt. -## Sehr technische Details +## Sehr technische Details { #very-technical-details } -/// warning | "Achtung" +/// warning | Achtung Das folgende können Sie wahrscheinlich überspringen. @@ -411,23 +413,23 @@ Wenn Sie über gute technische Kenntnisse verfügen (Coroutinen, Threads, Blocki /// -### Pfadoperation-Funktionen +### Pfadoperation-Funktionen { #path-operation-functions } Wenn Sie eine *Pfadoperation-Funktion* mit normalem `def` anstelle von `async def` deklarieren, wird sie in einem externen Threadpool ausgeführt, der dann `await`et wird, anstatt direkt aufgerufen zu werden (da dies den Server blockieren würde). Wenn Sie von einem anderen asynchronen Framework kommen, das nicht auf die oben beschriebene Weise funktioniert, und Sie es gewohnt sind, triviale, nur-berechnende *Pfadoperation-Funktionen* mit einfachem `def` zu definieren, um einen geringfügigen Geschwindigkeitsgewinn (etwa 100 Nanosekunden) zu erzielen, beachten Sie bitte, dass der Effekt in **FastAPI** genau gegenteilig wäre. In solchen Fällen ist es besser, `async def` zu verwenden, es sei denn, Ihre *Pfadoperation-Funktionen* verwenden Code, der blockierende I/O-Operationen durchführt. -Dennoch besteht in beiden Fällen eine gute Chance, dass **FastAPI** [immer noch schneller](index.md#performanz){.internal-link target=_blank} als Ihr bisheriges Framework (oder zumindest damit vergleichbar) ist. +Dennoch besteht in beiden Fällen eine gute Chance, dass **FastAPI** [immer noch schneller](index.md#performance){.internal-link target=_blank} als Ihr bisheriges Framework (oder zumindest damit vergleichbar) ist. -### Abhängigkeiten +### Abhängigkeiten { #dependencies } -Das Gleiche gilt für [Abhängigkeiten](tutorial/dependencies/index.md){.internal-link target=_blank}. Wenn eine Abhängigkeit eine normale `def`-Funktion ist, anstelle einer `async def`-Funktion, dann wird sie im externen Threadpool ausgeführt. +Das Gleiche gilt für [Abhängigkeiten](tutorial/dependencies/index.md){.internal-link target=_blank}. Wenn eine Abhängigkeit eine normale `def`-Funktion anstelle einer `async def` ist, wird sie im externen Threadpool ausgeführt. -### Unterabhängigkeiten +### Unterabhängigkeiten { #sub-dependencies } -Sie können mehrere Abhängigkeiten und [Unterabhängigkeiten](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} haben, die einander bedingen (als Parameter der Funktionsdefinitionen), einige davon könnten erstellt werden mit `async def` und einige mit normalem `def`. Es würde immer noch funktionieren und diejenigen, die mit normalem `def` erstellt wurden, würden in einem externen Thread (vom Threadpool stammend) aufgerufen werden, anstatt `await`et zu werden. +Sie können mehrere Abhängigkeiten und [Unterabhängigkeiten](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} haben, die einander bedingen (als Parameter der Funktionsdefinitionen), einige davon könnten erstellt werden mit `async def` und einige mit normalem `def`. Es würde immer noch funktionieren, und diejenigen, die mit normalem `def` erstellt wurden, würden in einem externen Thread (vom Threadpool stammend) aufgerufen werden, anstatt `await`et zu werden. -### Andere Hilfsfunktionen +### Andere Hilfsfunktionen { #other-utility-functions } Jede andere Hilfsfunktion, die Sie direkt aufrufen, kann mit normalem `def` oder `async def` erstellt werden, und FastAPI beeinflusst nicht die Art und Weise, wie Sie sie aufrufen. @@ -439,4 +441,4 @@ Wenn Ihre Hilfsfunktion eine normale Funktion mit `def` ist, wird sie direkt auf Nochmal, es handelt sich hier um sehr technische Details, die Ihnen helfen, falls Sie danach gesucht haben. -Andernfalls liegen Sie richtig, wenn Sie sich an die Richtlinien aus dem obigen Abschnitt halten: In Eile?. +Andernfalls liegen Sie richtig, wenn Sie sich an die Richtlinien aus dem obigen Abschnitt halten: In Eile?. diff --git a/docs/de/docs/benchmarks.md b/docs/de/docs/benchmarks.md index 6efd56e830..09126c5d99 100644 --- a/docs/de/docs/benchmarks.md +++ b/docs/de/docs/benchmarks.md @@ -1,16 +1,16 @@ -# Benchmarks +# Benchmarks { #benchmarks } -Unabhängige TechEmpower-Benchmarks zeigen, **FastAPI**-Anwendungen, die unter Uvicorn ausgeführt werden, gehören zu den schnellsten existierenden Python-Frameworks, nur Starlette und Uvicorn selbst (intern von FastAPI verwendet) sind schneller. +Unabhängige TechEmpower-Benchmarks zeigen **FastAPI**-Anwendungen, die unter Uvicorn ausgeführt werden, als eines der schnellsten verfügbaren Python-Frameworks, nur unterhalb von Starlette und Uvicorn selbst (die intern von FastAPI verwendet werden). -Beim Ansehen von Benchmarks und Vergleichen sollten Sie jedoch Folgende Punkte beachten. +Aber bei der Betrachtung von Benchmarks und Vergleichen sollten Sie Folgendes beachten. -## Benchmarks und Geschwindigkeit +## Benchmarks und Geschwindigkeit { #benchmarks-and-speed } -Wenn Sie sich die Benchmarks ansehen, werden häufig mehrere Tools mit unterschiedlichen Eigenschaften als gleichwertig verglichen. +Wenn Sie die Benchmarks ansehen, ist es üblich, dass mehrere Tools unterschiedlichen Typs als gleichwertig verglichen werden. -Konkret geht es darum, Uvicorn, Starlette und FastAPI miteinander zu vergleichen (neben vielen anderen Tools). +Insbesondere dass Uvicorn, Starlette und FastAPI zusammen verglichen werden (neben vielen anderen Tools). -Je einfacher das Problem, welches durch das Tool gelöst wird, desto besser ist die Performanz. Und die meisten Benchmarks testen nicht die zusätzlichen Funktionen, welche das Tool bietet. +Je einfacher das Problem, das durch das Tool gelöst wird, desto besser wird die Performanz sein. Und die meisten Benchmarks testen nicht die zusätzlichen Funktionen, die das Tool bietet. Die Hierarchie ist wie folgt: @@ -19,16 +19,16 @@ Die Hierarchie ist wie folgt: * **FastAPI**: (verwendet Starlette) ein API-Mikroframework mit mehreren zusätzlichen Funktionen zum Erstellen von APIs, mit Datenvalidierung, usw. * **Uvicorn**: - * Bietet die beste Leistung, da außer dem Server selbst nicht viel zusätzlicher Code vorhanden ist. - * Sie würden eine Anwendung nicht direkt in Uvicorn schreiben. Das würde bedeuten, dass Ihr Code zumindest mehr oder weniger den gesamten von Starlette (oder **FastAPI**) bereitgestellten Code enthalten müsste. Und wenn Sie das täten, hätte Ihre endgültige Anwendung den gleichen Overhead wie die Verwendung eines Frameworks nebst Minimierung Ihres Anwendungscodes und der Fehler. + * Wird die beste Performanz haben, da außer dem Server selbst nicht viel zusätzlicher Code vorhanden ist. + * Sie würden eine Anwendung nicht direkt in Uvicorn schreiben. Das würde bedeuten, dass Ihr Code zumindest mehr oder weniger den gesamten von Starlette (oder **FastAPI**) bereitgestellten Code enthalten müsste. Und wenn Sie das täten, hätte Ihre endgültige Anwendung den gleichen Overhead wie bei der Verwendung eines Frameworks und der Minimierung Ihres Anwendungscodes und der Fehler. * Wenn Sie Uvicorn vergleichen, vergleichen Sie es mit Anwendungsservern wie Daphne, Hypercorn, uWSGI, usw. * **Starlette**: - * Wird nach Uvicorn die nächstbeste Performanz erbringen. Tatsächlich nutzt Starlette intern Uvicorn. Daher kann es wahrscheinlich nur „langsamer“ als Uvicorn sein, weil mehr Code ausgeführt wird. - * Aber es bietet Ihnen die Tools zum Erstellen einfacher Webanwendungen, mit Routing basierend auf Pfaden, usw. + * Wird nach Uvicorn die nächstbeste Performanz erbringen. Tatsächlich verwendet Starlette intern Uvicorn, um zu laufen. Daher kann es wahrscheinlich nur „langsamer“ als Uvicorn werden, weil mehr Code ausgeführt werden muss. + * Aber es bietet Ihnen die Werkzeuge, um einfache Webanwendungen zu erstellen, mit Routing basierend auf Pfaden, usw. * Wenn Sie Starlette vergleichen, vergleichen Sie es mit Webframeworks (oder Mikroframeworks) wie Sanic, Flask, Django, usw. * **FastAPI**: * So wie Starlette Uvicorn verwendet und nicht schneller als dieses sein kann, verwendet **FastAPI** Starlette, sodass es nicht schneller als dieses sein kann. - * FastAPI bietet zusätzlich zu Starlette weitere Funktionen. Funktionen, die Sie beim Erstellen von APIs fast immer benötigen, wie Datenvalidierung und Serialisierung. Und wenn Sie es verwenden, erhalten Sie kostenlos automatische Dokumentation (die automatische Dokumentation verursacht nicht einmal zusätzlichen Aufwand für laufende Anwendungen, sie wird beim Start generiert). - * Wenn Sie FastAPI nicht, und direkt Starlette (oder ein anderes Tool wie Sanic, Flask, Responder, usw.) verwenden würden, müssten Sie die gesamte Datenvalidierung und Serialisierung selbst implementieren. Ihre finale Anwendung hätte also immer noch den gleichen Overhead, als ob sie mit FastAPI erstellt worden wäre. Und in vielen Fällen ist diese Datenvalidierung und Serialisierung der größte Teil des in Anwendungen geschriebenen Codes. - * Durch die Verwendung von FastAPI sparen Sie also Entwicklungszeit, Fehler und Codezeilen und würden wahrscheinlich die gleiche Leistung (oder eine bessere) erzielen, die Sie hätten, wenn Sie es nicht verwenden würden (da Sie alles in Ihrem Code implementieren müssten). - * Wenn Sie FastAPI vergleichen, vergleichen Sie es mit einem Webanwendung-Framework (oder einer Reihe von Tools), welche Datenvalidierung, Serialisierung und Dokumentation bereitstellen, wie Flask-apispec, NestJS, Molten, usw. – Frameworks mit integrierter automatischer Datenvalidierung, Serialisierung und Dokumentation. + * FastAPI bietet zusätzliche Funktionen auf Basis von Starlette. Funktionen, die Sie beim Erstellen von APIs fast immer benötigen, wie Datenvalidierung und Serialisierung. Und wenn Sie es verwenden, erhalten Sie kostenlose automatische Dokumentation (die automatische Dokumentation verursacht nicht einmal zusätzlichen Overhead für laufende Anwendungen, sie wird beim Starten generiert). + * Wenn Sie FastAPI nicht verwenden und stattdessen Starlette direkt (oder ein anderes Tool wie Sanic, Flask, Responder, usw.) verwenden würden, müssten Sie die gesamte Datenvalidierung und Serialisierung selbst implementieren. Ihre finale Anwendung hätte also immer noch den gleichen Overhead, als ob sie mit FastAPI erstellt worden wäre. Und in vielen Fällen ist diese Datenvalidierung und Serialisierung der größte Teil des in Anwendungen geschriebenen Codes. + * Durch die Verwendung von FastAPI sparen Sie also Entwicklungszeit, Fehler und Codezeilen und würden wahrscheinlich die gleiche Performanz (oder eine bessere) erzielen, die Sie hätten, wenn Sie es nicht verwenden würden (da Sie alles in Ihrem Code implementieren müssten). + * Wenn Sie FastAPI vergleichen, vergleichen Sie es mit einem Webanwendungs-Framework (oder einer Reihe von Tools), das Datenvalidierung, Serialisierung und Dokumentation bereitstellt, wie Flask-apispec, NestJS, Molten, usw. – Frameworks mit integrierter automatischer Datenvalidierung, Serialisierung und Dokumentation. diff --git a/docs/de/docs/contributing.md b/docs/de/docs/contributing.md deleted file mode 100644 index 58567ad7f2..0000000000 --- a/docs/de/docs/contributing.md +++ /dev/null @@ -1,484 +0,0 @@ -# Entwicklung – Mitwirken - -Vielleicht möchten Sie sich zuerst die grundlegenden Möglichkeiten anschauen, [FastAPI zu helfen und Hilfe zu erhalten](help-fastapi.md){.internal-link target=_blank}. - -## Entwicklung - -Wenn Sie das fastapi Repository bereits geklont haben und tief in den Code eintauchen möchten, hier einen Leitfaden zum Einrichten Ihrer Umgebung. - -### Virtuelle Umgebung mit `venv` - -Sie können mit dem Python-Modul `venv` in einem Verzeichnis eine isolierte virtuelle lokale Umgebung erstellen. Machen wir das im geklonten Repository (da wo sich die `requirements.txt` befindet): - -
- -```console -$ python -m venv env -``` - -
- -Das erstellt ein Verzeichnis `./env/` mit den Python-Binärdateien und Sie können dann Packages in dieser lokalen Umgebung installieren. - -### Umgebung aktivieren - -Aktivieren Sie die neue Umgebung mit: - -//// tab | Linux, macOS - -
- -```console -$ source ./env/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .\env\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Oder, wenn Sie Bash für Windows verwenden (z. B. Git Bash): - -
- -```console -$ source ./env/Scripts/activate -``` - -
- -//// - -Um zu überprüfen, ob es funktioniert hat, geben Sie ein: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ Get-Command pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -Wenn die `pip` Binärdatei unter `env/bin/pip` angezeigt wird, hat es funktioniert. 🎉 - -Stellen Sie sicher, dass Sie über die neueste Version von pip in Ihrer lokalen Umgebung verfügen, um Fehler bei den nächsten Schritten zu vermeiden: - -
- -```console -$ python -m pip install --upgrade pip - ----> 100% -``` - -
- -/// tip | "Tipp" - -Aktivieren Sie jedes Mal, wenn Sie ein neues Package mit `pip` in dieser Umgebung installieren, die Umgebung erneut. - -Dadurch wird sichergestellt, dass Sie, wenn Sie ein von diesem Package installiertes Terminalprogramm verwenden, das Programm aus Ihrer lokalen Umgebung verwenden und kein anderes, das global installiert sein könnte. - -/// - -### Benötigtes mit pip installieren - -Nachdem Sie die Umgebung wie oben beschrieben aktiviert haben: - -
- -```console -$ pip install -r requirements.txt - ----> 100% -``` - -
- -Das installiert alle Abhängigkeiten und Ihr lokales FastAPI in Ihrer lokalen Umgebung. - -#### Das lokale FastAPI verwenden - -Wenn Sie eine Python-Datei erstellen, die FastAPI importiert und verwendet, und diese mit dem Python aus Ihrer lokalen Umgebung ausführen, wird Ihr geklonter lokaler FastAPI-Quellcode verwendet. - -Und wenn Sie diesen lokalen FastAPI-Quellcode aktualisieren und dann die Python-Datei erneut ausführen, wird die neue Version von FastAPI verwendet, die Sie gerade bearbeitet haben. - -Auf diese Weise müssen Sie Ihre lokale Version nicht „installieren“, um jede Änderung testen zu können. - -/// note | "Technische Details" - -Das geschieht nur, wenn Sie die Installation mit der enthaltenen `requirements.txt` durchführen, anstatt `pip install fastapi` direkt auszuführen. - -Das liegt daran, dass in der Datei `requirements.txt` die lokale Version von FastAPI mit der Option `-e` für die Installation im „editierbaren“ Modus markiert ist. - -/// - -### Den Code formatieren - -Es gibt ein Skript, das, wenn Sie es ausführen, Ihren gesamten Code formatiert und bereinigt: - -
- -```console -$ bash scripts/format.sh -``` - -
- -Es sortiert auch alle Ihre Importe automatisch. - -Damit es sie richtig sortiert, muss FastAPI lokal in Ihrer Umgebung installiert sein, mit dem Befehl vom obigen Abschnitt, welcher `-e` verwendet. - -## Dokumentation - -Stellen Sie zunächst sicher, dass Sie Ihre Umgebung wie oben beschrieben einrichten, was alles Benötigte installiert. - -### Dokumentation live - -Während der lokalen Entwicklung gibt es ein Skript, das die Site erstellt, auf Änderungen prüft und direkt neu lädt (Live Reload): - -
- -```console -$ python ./scripts/docs.py live - -[INFO] Serving on http://127.0.0.1:8008 -[INFO] Start watching changes -[INFO] Start detecting changes -``` - -
- -Das stellt die Dokumentation unter `http://127.0.0.1:8008` bereit. - -Auf diese Weise können Sie die Dokumentation/Quelldateien bearbeiten und die Änderungen live sehen. - -/// tip | "Tipp" - -Alternativ können Sie die Schritte des Skripts auch manuell ausführen. - -Gehen Sie in das Verzeichnis für die entsprechende Sprache. Das für die englischsprachige Hauptdokumentation befindet sich unter `docs/en/`: - -```console -$ cd docs/en/ -``` - -Führen Sie dann `mkdocs` in diesem Verzeichnis aus: - -```console -$ mkdocs serve --dev-addr 8008 -``` - -/// - -#### Typer-CLI (optional) - -Die Anleitung hier zeigt Ihnen, wie Sie das Skript unter `./scripts/docs.py` direkt mit dem `python` Programm verwenden. - -Sie können aber auch Typer CLI verwenden und erhalten dann Autovervollständigung für Kommandos in Ihrem Terminal, nach dem Sie dessen Vervollständigung installiert haben. - -Wenn Sie Typer CLI installieren, können Sie die Vervollständigung installieren mit: - -
- -```console -$ typer --install-completion - -zsh completion installed in /home/user/.bashrc. -Completion will take effect once you restart the terminal. -``` - -
- -### Dokumentationsstruktur - -Die Dokumentation verwendet MkDocs. - -Und es gibt zusätzliche Tools/Skripte für Übersetzungen, in `./scripts/docs.py`. - -/// tip | "Tipp" - -Sie müssen sich den Code in `./scripts/docs.py` nicht anschauen, verwenden Sie ihn einfach in der Kommandozeile. - -/// - -Die gesamte Dokumentation befindet sich im Markdown-Format im Verzeichnis `./docs/en/`. - -Viele der Tutorials enthalten Codeblöcke. - -In den meisten Fällen handelt es sich bei diesen Codeblöcken um vollständige Anwendungen, die unverändert ausgeführt werden können. - -Tatsächlich sind diese Codeblöcke nicht Teil des Markdowns, sondern Python-Dateien im Verzeichnis `./docs_src/`. - -Und diese Python-Dateien werden beim Generieren der Site in die Dokumentation eingefügt. - -### Dokumentation für Tests - -Tatsächlich arbeiten die meisten Tests mit den Beispielquelldateien in der Dokumentation. - -Dadurch wird sichergestellt, dass: - -* Die Dokumentation aktuell ist. -* Die Dokumentationsbeispiele ohne Änderung ausgeführt werden können. -* Die meisten Funktionalitäten durch die Dokumentation abgedeckt werden, sichergestellt durch die Testabdeckung. - -#### Gleichzeitig Apps und Dokumentation - -Wenn Sie die Beispiele ausführen, mit z. B.: - -
- -```console -$ uvicorn tutorial001:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -wird das, da Uvicorn standardmäßig den Port `8000` verwendet, mit der Dokumentation auf dem Port `8008` nicht in Konflikt geraten. - -### Übersetzungen - -Hilfe bei Übersetzungen wird SEHR geschätzt! Und es kann nicht getan werden, ohne die Hilfe der Gemeinschaft. 🌎 🚀 - -Hier sind die Schritte, die Ihnen bei Übersetzungen helfen. - -#### Tipps und Richtlinien - -* Schauen Sie nach aktuellen Pull Requests für Ihre Sprache. Sie können die Pull Requests nach dem Label für Ihre Sprache filtern. Für Spanisch lautet das Label beispielsweise `lang-es`. - -* Sehen Sie diese Pull Requests durch (Review), schlagen Sie Änderungen vor, oder segnen Sie sie ab (Approval). Bei den Sprachen, die ich nicht spreche, warte ich, bis mehrere andere die Übersetzung durchgesehen haben, bevor ich den Pull Request merge. - -/// tip | "Tipp" - -Sie können Kommentare mit Änderungsvorschlägen zu vorhandenen Pull Requests hinzufügen. - -Schauen Sie sich die Dokumentation an, wie man ein Review zu einem Pull Request hinzufügt, welches den PR absegnet oder Änderungen vorschlägt. - -/// - -* Überprüfen Sie, ob es eine GitHub-Diskussion gibt, die Übersetzungen für Ihre Sprache koordiniert. Sie können sie abonnieren, und wenn ein neuer Pull Request zum Review vorliegt, wird der Diskussion automatisch ein Kommentar hinzugefügt. - -* Wenn Sie Seiten übersetzen, fügen Sie einen einzelnen Pull Request pro übersetzter Seite hinzu. Dadurch wird es für andere viel einfacher, ihn zu durchzusehen. - -* Um den Zwei-Buchstaben-Code für die Sprache zu finden, die Sie übersetzen möchten, schauen Sie sich die Tabelle List of ISO 639-1 codes an. - -#### Vorhandene Sprache - -Angenommen, Sie möchten eine Seite für eine Sprache übersetzen, die bereits Übersetzungen für einige Seiten hat, beispielsweise für Spanisch. - -Im Spanischen lautet der Zwei-Buchstaben-Code `es`. Das Verzeichnis für spanische Übersetzungen befindet sich also unter `docs/es/`. - -/// tip | "Tipp" - -Die Haupt („offizielle“) Sprache ist Englisch und befindet sich unter `docs/en/`. - -/// - -Führen Sie nun den Live-Server für die Dokumentation auf Spanisch aus: - -
- -```console -// Verwenden Sie das Kommando „live“ und fügen Sie den Sprach-Code als Argument hinten an -$ python ./scripts/docs.py live es - -[INFO] Serving on http://127.0.0.1:8008 -[INFO] Start watching changes -[INFO] Start detecting changes -``` - -
- -/// tip | "Tipp" - -Alternativ können Sie die Schritte des Skripts auch manuell ausführen. - -Gehen Sie in das Sprachverzeichnis, für die spanischen Übersetzungen ist das `docs/es/`: - -```console -$ cd docs/es/ -``` - -Dann führen Sie in dem Verzeichnis `mkdocs` aus: - -```console -$ mkdocs serve --dev-addr 8008 -``` - -/// - -Jetzt können Sie auf http://127.0.0.1:8008 gehen und Ihre Änderungen live sehen. - -Sie werden sehen, dass jede Sprache alle Seiten hat. Einige Seiten sind jedoch nicht übersetzt und haben oben eine Info-Box, dass die Übersetzung noch fehlt. - -Nehmen wir nun an, Sie möchten eine Übersetzung für den Abschnitt [Features](features.md){.internal-link target=_blank} hinzufügen. - -* Kopieren Sie die Datei: - -``` -docs/en/docs/features.md -``` - -* Fügen Sie sie an genau derselben Stelle ein, jedoch für die Sprache, die Sie übersetzen möchten, z. B.: - -``` -docs/es/docs/features.md -``` - -/// tip | "Tipp" - -Beachten Sie, dass die einzige Änderung in Pfad und Dateiname der Sprachcode ist, von `en` zu `es`. - -/// - -Wenn Sie in Ihrem Browser nachsehen, werden Sie feststellen, dass die Dokumentation jetzt Ihren neuen Abschnitt anzeigt (die Info-Box oben ist verschwunden). 🎉 - -Jetzt können Sie alles übersetzen und beim Speichern sehen, wie es aussieht. - -#### Neue Sprache - -Nehmen wir an, Sie möchten Übersetzungen für eine Sprache hinzufügen, die noch nicht übersetzt ist, nicht einmal einige Seiten. - -Angenommen, Sie möchten Übersetzungen für Kreolisch hinzufügen, diese sind jedoch noch nicht in den Dokumenten enthalten. - -Wenn Sie den Link von oben überprüfen, lautet der Sprachcode für Kreolisch `ht`. - -Der nächste Schritt besteht darin, das Skript auszuführen, um ein neues Übersetzungsverzeichnis zu erstellen: - -
- -```console -// Verwenden Sie das Kommando new-lang und fügen Sie den Sprach-Code als Argument hinten an -$ python ./scripts/docs.py new-lang ht - -Successfully initialized: docs/ht -``` - -
- -Jetzt können Sie in Ihrem Code-Editor das neu erstellte Verzeichnis `docs/ht/` sehen. - -Obiges Kommando hat eine Datei `docs/ht/mkdocs.yml` mit einer Minimal-Konfiguration erstellt, die alles von der `en`-Version erbt: - -```yaml -INHERIT: ../en/mkdocs.yml -``` - -/// tip | "Tipp" - -Sie können diese Datei mit diesem Inhalt auch einfach manuell erstellen. - -/// - -Das Kommando hat auch eine Dummy-Datei `docs/ht/index.md` für die Hauptseite erstellt. Sie können mit der Übersetzung dieser Datei beginnen. - -Sie können nun mit den obigen Instruktionen für eine „vorhandene Sprache“ fortfahren. - -Fügen Sie dem ersten Pull Request beide Dateien `docs/ht/mkdocs.yml` und `docs/ht/index.md` bei. 🎉 - -#### Vorschau des Ergebnisses - -Wie bereits oben erwähnt, können Sie `./scripts/docs.py` mit dem Befehl `live` verwenden, um eine Vorschau der Ergebnisse anzuzeigen (oder `mkdocs serve`). - -Sobald Sie fertig sind, können Sie auch alles so testen, wie es online aussehen würde, einschließlich aller anderen Sprachen. - -Bauen Sie dazu zunächst die gesamte Dokumentation: - -
- -```console -// Verwenden Sie das Kommando „build-all“, das wird ein wenig dauern -$ python ./scripts/docs.py build-all - -Building docs for: en -Building docs for: es -Successfully built docs for: es -``` - -
- -Dadurch werden alle diese unabhängigen MkDocs-Sites für jede Sprache erstellt, kombiniert und das endgültige Resultat unter `./site/` gespeichert. - -Dieses können Sie dann mit dem Befehl `serve` bereitstellen: - -
- -```console -// Verwenden Sie das Kommando „serve“ nachdem Sie „build-all“ ausgeführt haben. -$ 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 -``` - -
- -#### Übersetzungsspezifische Tipps und Richtlinien - -* Übersetzen Sie nur die Markdown-Dokumente (`.md`). Übersetzen Sie nicht die Codebeispiele unter `./docs_src`. - -* In Codeblöcken innerhalb des Markdown-Dokuments, übersetzen Sie Kommentare (`# ein Kommentar`), aber lassen Sie den Rest unverändert. - -* Ändern Sie nichts, was in "``" (Inline-Code) eingeschlossen ist. - -* In Zeilen, die mit `===` oder `!!!` beginnen, übersetzen Sie nur den ` "... Text ..."`-Teil. Lassen Sie den Rest unverändert. - -* Sie können Info-Boxen wie `!!! warning` mit beispielsweise `!!! warning "Achtung"` übersetzen. Aber ändern Sie nicht das Wort direkt nach dem `!!!`, es bestimmt die Farbe der Info-Box. - -* Ändern Sie nicht die Pfade in Links zu Bildern, Codedateien, Markdown Dokumenten. - -* Wenn ein Markdown-Dokument übersetzt ist, ändern sich allerdings unter Umständen die `#hash-teile` in Links zu dessen Überschriften. Aktualisieren Sie diese Links, wenn möglich. - * Suchen Sie im übersetzten Dokument nach solchen Links mit dem Regex `#[^# ]`. - * Suchen Sie in allen bereits in ihre Sprache übersetzen Dokumenten nach `ihr-ubersetztes-dokument.md`. VS Code hat beispielsweise eine Option „Bearbeiten“ -> „In Dateien suchen“. - * Übersetzen Sie bei der Übersetzung eines Dokuments nicht „im Voraus“ `#hash-teile`, die zu Überschriften in noch nicht übersetzten Dokumenten verlinken. - -## Tests - -Es gibt ein Skript, das Sie lokal ausführen können, um den gesamten Code zu testen und Code Coverage Reporte in HTML zu generieren: - -
- -```console -$ bash scripts/test-cov-html.sh -``` - -
- -Dieses Kommando generiert ein Verzeichnis `./htmlcov/`. Wenn Sie die Datei `./htmlcov/index.html` in Ihrem Browser öffnen, können Sie interaktiv die Codebereiche erkunden, die von den Tests abgedeckt werden, und feststellen, ob Bereiche fehlen. diff --git a/docs/de/docs/deployment/cloud.md b/docs/de/docs/deployment/cloud.md index 2d70fe4e5a..ca1ba3b3bf 100644 --- a/docs/de/docs/deployment/cloud.md +++ b/docs/de/docs/deployment/cloud.md @@ -1,17 +1,16 @@ -# FastAPI-Deployment bei Cloud-Anbietern +# FastAPI bei Cloudanbietern bereitstellen { #deploy-fastapi-on-cloud-providers } -Sie können praktisch **jeden Cloud-Anbieter** für das Deployment Ihrer FastAPI-Anwendung verwenden. +Sie können praktisch **jeden Cloudanbieter** verwenden, um Ihre FastAPI-Anwendung bereitzustellen. -In den meisten Fällen verfügen die Haupt-Cloud-Anbieter über Anleitungen zum Deployment von FastAPI. +In den meisten Fällen bieten die großen Cloudanbieter Anleitungen zum Bereitstellen von FastAPI an. -## Cloud-Anbieter – Sponsoren +## Cloudanbieter – Sponsoren { #cloud-providers-sponsors } -Einige Cloud-Anbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#den-autor-sponsern){.internal-link target=_blank} ✨, dies gewährleistet die kontinuierliche und gesunde **Entwicklung** von FastAPI und seinem **Ökosystem**. +Einige Cloudanbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, dies stellt die kontinuierliche und gesunde **Entwicklung** von FastAPI und seinem **Ökosystem** sicher. -Und es zeigt deren wahres Engagement für FastAPI und seine **Community** (Sie), da diese Ihnen nicht nur einen **guten Service** bieten möchten, sondern auch sicherstellen möchten, dass Sie über ein **gutes und gesundes Framework** verfügen, FastAPI. 🙇 +Und es zeigt ihr wahres Engagement für FastAPI und seine **Community** (Sie), da sie Ihnen nicht nur einen **guten Service** bieten möchten, sondern auch sicherstellen möchten, dass Sie ein **gutes und gesundes Framework**, FastAPI, haben. 🙇 Vielleicht möchten Sie deren Dienste ausprobieren und deren Anleitungen folgen: -* Platform.sh -* Porter -* Coherence +* Render +* Railway diff --git a/docs/de/docs/deployment/concepts.md b/docs/de/docs/deployment/concepts.md index 3c1c0cfce7..ef0f458a7e 100644 --- a/docs/de/docs/deployment/concepts.md +++ b/docs/de/docs/deployment/concepts.md @@ -1,4 +1,4 @@ -# Deployment-Konzepte +# Deployment-Konzepte { #deployments-concepts } Bei dem Deployment – der Bereitstellung – einer **FastAPI**-Anwendung, oder eigentlich jeder Art von Web-API, gibt es mehrere Konzepte, die Sie wahrscheinlich interessieren, und mithilfe der Sie die **am besten geeignete** Methode zur **Bereitstellung Ihrer Anwendung** finden können. @@ -13,7 +13,7 @@ Einige wichtige Konzepte sind: Wir werden sehen, wie diese sich auf das **Deployment** auswirken. -Letztendlich besteht das ultimative Ziel darin, **Ihre API-Clients** auf **sichere** Weise zu bedienen, um **Unterbrechungen** zu vermeiden und die **Rechenressourcen** (z. B. entfernte Server/virtuelle Maschinen) so effizient wie möglich zu nutzen. 🚀 +Letztendlich besteht das ultimative Ziel darin, **Ihre API-Clients** auf **sichere** Weise zu versorgen, um **Unterbrechungen** zu vermeiden und die **Rechenressourcen** (z. B. entfernte Server/virtuelle Maschinen) so effizient wie möglich zu nutzen. 🚀 Ich erzähle Ihnen hier etwas mehr über diese **Konzepte**, was Ihnen hoffentlich die **Intuition** gibt, die Sie benötigen, um zu entscheiden, wie Sie Ihre API in sehr unterschiedlichen Umgebungen bereitstellen, möglicherweise sogar in **zukünftigen**, die jetzt noch nicht existieren. @@ -23,7 +23,7 @@ In den nächsten Kapiteln werde ich Ihnen mehr **konkrete Rezepte** für die Ber Aber schauen wir uns zunächst einmal diese grundlegenden **konzeptionellen Ideen** an. Diese Konzepte gelten auch für jede andere Art von Web-API. 💡 -## Sicherheit – HTTPS +## Sicherheit – HTTPS { #security-https } Im [vorherigen Kapitel über HTTPS](https.md){.internal-link target=_blank} haben wir erfahren, wie HTTPS Verschlüsselung für Ihre API bereitstellt. @@ -31,7 +31,7 @@ Wir haben auch gesehen, dass HTTPS normalerweise von einer Komponente **außerha Und es muss etwas geben, das für die **Erneuerung der HTTPS-Zertifikate** zuständig ist, es könnte sich um dieselbe Komponente handeln oder um etwas anderes. -### Beispieltools für HTTPS +### Beispieltools für HTTPS { #example-tools-for-https } Einige der Tools, die Sie als TLS-Terminierungsproxy verwenden können, sind: @@ -55,11 +55,11 @@ In den nächsten Kapiteln zeige ich Ihnen einige konkrete Beispiele. Die nächsten zu berücksichtigenden Konzepte drehen sich dann um das Programm, das Ihre eigentliche API ausführt (z. B. Uvicorn). -## Programm und Prozess +## Programm und Prozess { #program-and-process } Wir werden viel über den laufenden „**Prozess**“ sprechen, daher ist es nützlich, Klarheit darüber zu haben, was das bedeutet und was der Unterschied zum Wort „**Programm**“ ist. -### Was ist ein Programm? +### Was ist ein Programm { #what-is-a-program } Das Wort **Programm** wird häufig zur Beschreibung vieler Dinge verwendet: @@ -67,7 +67,7 @@ Das Wort **Programm** wird häufig zur Beschreibung vieler Dinge verwendet: * Die **Datei**, die vom Betriebssystem **ausgeführt** werden kann, zum Beispiel: `python`, `python.exe` oder `uvicorn`. * Ein bestimmtes Programm, während es auf dem Betriebssystem **läuft**, die CPU nutzt und Dinge im Arbeitsspeicher ablegt. Dies wird auch als **Prozess** bezeichnet. -### Was ist ein Prozess? +### Was ist ein Prozess { #what-is-a-process } Das Wort **Prozess** wird normalerweise spezifischer verwendet und bezieht sich nur auf das, was im Betriebssystem ausgeführt wird (wie im letzten Punkt oben): @@ -88,13 +88,13 @@ Und Sie werden beispielsweise wahrscheinlich feststellen, dass mehrere Prozesse Nachdem wir nun den Unterschied zwischen den Begriffen **Prozess** und **Programm** kennen, sprechen wir weiter über das Deployment. -## Beim Hochfahren ausführen +## Beim Hochfahren ausführen { #running-on-startup } Wenn Sie eine Web-API erstellen, möchten Sie in den meisten Fällen, dass diese **immer läuft**, ununterbrochen, damit Ihre Clients immer darauf zugreifen können. Es sei denn natürlich, Sie haben einen bestimmten Grund, warum Sie möchten, dass diese nur in bestimmten Situationen ausgeführt wird. Meistens möchten Sie jedoch, dass sie ständig ausgeführt wird und **verfügbar** ist. -### Auf einem entfernten Server +### Auf einem entfernten Server { #in-a-remote-server } -Wenn Sie einen entfernten Server (einen Cloud-Server, eine virtuelle Maschine, usw.) einrichten, können Sie am einfachsten Uvicorn (oder ähnliches) manuell ausführen, genau wie bei der lokalen Entwicklung. +Wenn Sie einen entfernten Server (einen Cloud-Server, eine virtuelle Maschine, usw.) einrichten, können Sie am einfachsten `fastapi run` (welches Uvicorn verwendet) oder etwas Ähnliches manuell ausführen, genau wie bei der lokalen Entwicklung. Und es wird funktionieren und **während der Entwicklung** nützlich sein. @@ -102,15 +102,15 @@ Wenn Ihre Verbindung zum Server jedoch unterbrochen wird, wird der **laufende Pr Und wenn der Server neu gestartet wird (z. B. nach Updates oder Migrationen vom Cloud-Anbieter), werden Sie das wahrscheinlich **nicht bemerken**. Und deshalb wissen Sie nicht einmal, dass Sie den Prozess manuell neu starten müssen. Ihre API bleibt also einfach tot. 😱 -### Beim Hochfahren automatisch ausführen +### Beim Hochfahren automatisch ausführen { #run-automatically-on-startup } Im Allgemeinen möchten Sie wahrscheinlich, dass das Serverprogramm (z. B. Uvicorn) beim Hochfahren des Servers automatisch gestartet wird und kein **menschliches Eingreifen** erforderlich ist, sodass immer ein Prozess mit Ihrer API ausgeführt wird (z. B. Uvicorn, welches Ihre FastAPI-Anwendung ausführt). -### Separates Programm +### Separates Programm { #separate-program } Um dies zu erreichen, haben Sie normalerweise ein **separates Programm**, welches sicherstellt, dass Ihre Anwendung beim Hochfahren ausgeführt wird. Und in vielen Fällen würde es auch sicherstellen, dass auch andere Komponenten oder Anwendungen ausgeführt werden, beispielsweise eine Datenbank. -### Beispieltools zur Ausführung beim Hochfahren +### Beispieltools zur Ausführung beim Hochfahren { #example-tools-to-run-at-startup } Einige Beispiele für Tools, die diese Aufgabe übernehmen können, sind: @@ -125,33 +125,33 @@ Einige Beispiele für Tools, die diese Aufgabe übernehmen können, sind: In den nächsten Kapiteln werde ich Ihnen konkretere Beispiele geben. -## Neustart +## Neustart { #restarts } Ähnlich wie Sie sicherstellen möchten, dass Ihre Anwendung beim Hochfahren ausgeführt wird, möchten Sie wahrscheinlich auch sicherstellen, dass diese nach Fehlern **neu gestartet** wird. -### Wir machen Fehler +### Wir machen Fehler { #we-make-mistakes } Wir, als Menschen, machen ständig **Fehler**. Software hat fast *immer* **Bugs**, die an verschiedenen Stellen versteckt sind. 🐛 Und wir als Entwickler verbessern den Code ständig, wenn wir diese Bugs finden und neue Funktionen implementieren (und möglicherweise auch neue Bugs hinzufügen 😅). -### Kleine Fehler automatisch handhaben +### Kleine Fehler automatisch handhaben { #small-errors-automatically-handled } -Wenn beim Erstellen von Web-APIs mit FastAPI ein Fehler in unserem Code auftritt, wird FastAPI ihn normalerweise dem einzelnen Request zurückgeben, der den Fehler ausgelöst hat. 🛡 +Wenn beim Erstellen von Web-APIs mit FastAPI ein Fehler in unserem Code auftritt, wird FastAPI ihn normalerweise dem einzelnen Request zurückgeben, der den Fehler ausgelöst hat. 🛡 Der Client erhält für diesen Request einen **500 Internal Server Error**, aber die Anwendung arbeitet bei den nächsten Requests weiter, anstatt einfach komplett abzustürzen. -### Größere Fehler – Abstürze +### Größere Fehler – Abstürze { #bigger-errors-crashes } Dennoch kann es vorkommen, dass wir Code schreiben, der **die gesamte Anwendung zum Absturz bringt** und so zum Absturz von Uvicorn und Python führt. 💥 Und dennoch möchten Sie wahrscheinlich nicht, dass die Anwendung tot bleibt, weil an einer Stelle ein Fehler aufgetreten ist. Sie möchten wahrscheinlich, dass sie zumindest für die *Pfadoperationen*, die nicht fehlerhaft sind, **weiterläuft**. -### Neustart nach Absturz +### Neustart nach Absturz { #restart-after-crash } Aber in den Fällen mit wirklich schwerwiegenden Fehlern, die den laufenden **Prozess** zum Absturz bringen, benötigen Sie eine externe Komponente, die den Prozess **neu startet**, zumindest ein paar Mal ... -/// tip | "Tipp" +/// tip | Tipp ... Obwohl es wahrscheinlich keinen Sinn macht, sie immer wieder neu zu starten, wenn die gesamte Anwendung einfach **sofort abstürzt**. Aber in diesen Fällen werden Sie es wahrscheinlich während der Entwicklung oder zumindest direkt nach dem Deployment bemerken. @@ -161,7 +161,7 @@ Konzentrieren wir uns also auf die Hauptfälle, in denen die Anwendung in bestim Sie möchten wahrscheinlich, dass eine **externe Komponente** für den Neustart Ihrer Anwendung verantwortlich ist, da zu diesem Zeitpunkt dieselbe Anwendung mit Uvicorn und Python bereits abgestürzt ist und es daher nichts im selben Code derselben Anwendung gibt, was etwas dagegen tun kann. -### Beispieltools zum automatischen Neustart +### Beispieltools zum automatischen Neustart { #example-tools-to-restart-automatically } In den meisten Fällen wird dasselbe Tool, das zum **Ausführen des Programms beim Hochfahren** verwendet wird, auch für automatische **Neustarts** verwendet. @@ -176,19 +176,19 @@ Dies könnte zum Beispiel erledigt werden durch: * Intern von einem Cloud-Anbieter im Rahmen seiner Dienste * Andere ... -## Replikation – Prozesse und Arbeitsspeicher +## Replikation – Prozesse und Arbeitsspeicher { #replication-processes-and-memory } -Wenn Sie eine FastAPI-Anwendung verwenden und ein Serverprogramm wie Uvicorn verwenden, kann **ein einzelner Prozess** mehrere Clients gleichzeitig bedienen. +Wenn Sie eine FastAPI-Anwendung verwenden und ein Serverprogramm wie den `fastapi`-Befehl, der Uvicorn ausführt, kann **ein einzelner Prozess** an mehrere Clients gleichzeitig ausliefern. -In vielen Fällen möchten Sie jedoch mehrere Prozesse gleichzeitig ausführen. +In vielen Fällen möchten Sie jedoch mehrere Workerprozesse gleichzeitig ausführen. -### Mehrere Prozesse – Worker +### Mehrere Prozesse – Worker { #multiple-processes-workers } Wenn Sie mehr Clients haben, als ein einzelner Prozess verarbeiten kann (z. B. wenn die virtuelle Maschine nicht sehr groß ist) und die CPU des Servers **mehrere Kerne** hat, dann könnten **mehrere Prozesse** gleichzeitig mit derselben Anwendung laufen und alle Requests unter sich verteilen. -Wenn Sie mit **mehreren Prozessen** dasselbe API-Programm ausführen, werden diese üblicherweise als **Worker** bezeichnet. +Wenn Sie mit **mehreren Prozessen** dasselbe API-Programm ausführen, werden diese üblicherweise als **Worker** bezeichnet. -### Workerprozesse und Ports +### Workerprozesse und Ports { #worker-processes-and-ports } Erinnern Sie sich aus der Dokumentation [Über HTTPS](https.md){.internal-link target=_blank}, dass nur ein Prozess auf einer Kombination aus Port und IP-Adresse auf einem Server lauschen kann? @@ -196,35 +196,35 @@ Das ist immer noch wahr. Um also **mehrere Prozesse** gleichzeitig zu haben, muss es einen **einzelnen Prozess geben, der einen Port überwacht**, welcher dann die Kommunikation auf irgendeine Weise an jeden Workerprozess überträgt. -### Arbeitsspeicher pro Prozess +### Arbeitsspeicher pro Prozess { #memory-per-process } Wenn das Programm nun Dinge in den Arbeitsspeicher lädt, zum Beispiel ein Modell für maschinelles Lernen in einer Variablen oder den Inhalt einer großen Datei in einer Variablen, verbraucht das alles **einen Teil des Arbeitsspeichers (RAM – Random Access Memory)** des Servers. Und mehrere Prozesse teilen sich normalerweise keinen Speicher. Das bedeutet, dass jeder laufende Prozess seine eigenen Dinge, eigenen Variablen und eigenen Speicher hat. Und wenn Sie in Ihrem Code viel Speicher verbrauchen, verbraucht **jeder Prozess** die gleiche Menge Speicher. -### Serverspeicher +### Serverspeicher { #server-memory } Wenn Ihr Code beispielsweise ein Machine-Learning-Modell mit **1 GB Größe** lädt und Sie einen Prozess mit Ihrer API ausführen, verbraucht dieser mindestens 1 GB RAM. Und wenn Sie **4 Prozesse** (4 Worker) starten, verbraucht jeder 1 GB RAM. Insgesamt verbraucht Ihre API also **4 GB RAM**. Und wenn Ihr entfernter Server oder Ihre virtuelle Maschine nur über 3 GB RAM verfügt, führt der Versuch, mehr als 4 GB RAM zu laden, zu Problemen. 🚨 -### Mehrere Prozesse – Ein Beispiel +### Mehrere Prozesse – Ein Beispiel { #multiple-processes-an-example } Im folgenden Beispiel gibt es einen **Manager-Prozess**, welcher zwei **Workerprozesse** startet und steuert. Dieser Manager-Prozess wäre wahrscheinlich derjenige, welcher der IP am **Port** lauscht. Und er würde die gesamte Kommunikation an die Workerprozesse weiterleiten. -Diese Workerprozesse würden Ihre Anwendung ausführen, sie würden die Hauptberechnungen durchführen, um einen **Request** entgegenzunehmen und eine **Response** zurückzugeben, und sie würden alles, was Sie in Variablen einfügen, in den RAM laden. +Diese Workerprozesse würden Ihre Anwendung ausführen, sie würden die Hauptberechnungen durchführen, um einen **Request** entgegenzunehmen und eine **Response** zurückzugeben, und sie würden alles, was Sie in Variablen einfügen, in den RAM laden. - + -Und natürlich würden auf derselben Maschine neben Ihrer Anwendung wahrscheinlich auch **andere Prozesse** laufen. +Und natürlich würden auf derselben Maschine neben Ihrer Anwendung wahrscheinlich **andere Prozesse** laufen. Ein interessantes Detail ist dabei, dass der Prozentsatz der von jedem Prozess verwendeten **CPU** im Laufe der Zeit stark **variieren** kann, der **Arbeitsspeicher (RAM)** jedoch normalerweise mehr oder weniger **stabil** bleibt. Wenn Sie eine API haben, die jedes Mal eine vergleichbare Menge an Berechnungen durchführt, und Sie viele Clients haben, dann wird die **CPU-Auslastung** wahrscheinlich *ebenfalls stabil sein* (anstatt ständig schnell zu steigen und zu fallen). -### Beispiele für Replikation-Tools und -Strategien +### Beispiele für Replikation-Tools und -Strategien { #examples-of-replication-tools-and-strategies } Es gibt mehrere Ansätze, um dies zu erreichen, und ich werde Ihnen in den nächsten Kapiteln mehr über bestimmte Strategien erzählen, beispielsweise wenn es um Docker und Container geht. @@ -232,16 +232,14 @@ Die wichtigste zu berücksichtigende Einschränkung besteht darin, dass es eine Hier sind einige mögliche Kombinationen und Strategien: -* **Gunicorn**, welches **Uvicorn-Worker** managt - * Gunicorn wäre der **Prozessmanager**, der die **IP** und den **Port** überwacht, die Replikation würde durch **mehrere Uvicorn-Workerprozesse** erfolgen -* **Uvicorn**, welches **Uvicorn-Worker** managt +* **Uvicorn** mit `--workers` * Ein Uvicorn-**Prozessmanager** würde der **IP** am **Port** lauschen, und er würde **mehrere Uvicorn-Workerprozesse** starten. * **Kubernetes** und andere verteilte **Containersysteme** * Etwas in der **Kubernetes**-Ebene würde die **IP** und den **Port** abhören. Die Replikation hätte **mehrere Container**, in jedem wird jeweils **ein Uvicorn-Prozess** ausgeführt. * **Cloud-Dienste**, welche das für Sie erledigen * Der Cloud-Dienst wird wahrscheinlich **die Replikation für Sie übernehmen**. Er würde Sie möglicherweise **einen auszuführenden Prozess** oder ein **zu verwendendes Container-Image** definieren lassen, in jedem Fall wäre es höchstwahrscheinlich **ein einzelner Uvicorn-Prozess**, und der Cloud-Dienst wäre auch verantwortlich für die Replikation. -/// tip | "Tipp" +/// tip | Tipp Machen Sie sich keine Sorgen, wenn einige dieser Punkte zu **Containern**, Docker oder Kubernetes noch nicht viel Sinn ergeben. @@ -249,7 +247,7 @@ Ich werde Ihnen in einem zukünftigen Kapitel mehr über Container-Images, Docke /// -## Schritte vor dem Start +## Schritte vor dem Start { #previous-steps-before-starting } Es gibt viele Fälle, in denen Sie, **bevor Sie Ihre Anwendung starten**, einige Schritte ausführen möchten. @@ -263,7 +261,7 @@ Und Sie müssen sicherstellen, dass es sich um einen einzelnen Prozess handelt, Natürlich gibt es Fälle, in denen es kein Problem darstellt, die Vorab-Schritte mehrmals auszuführen. In diesem Fall ist die Handhabung viel einfacher. -/// tip | "Tipp" +/// tip | Tipp Bedenken Sie außerdem, dass Sie, abhängig von Ihrer Einrichtung, in manchen Fällen **gar keine Vorab-Schritte** benötigen, bevor Sie die Anwendung starten. @@ -271,7 +269,7 @@ In diesem Fall müssen Sie sich darüber keine Sorgen machen. 🤷 /// -### Beispiele für Strategien für Vorab-Schritte +### Beispiele für Strategien für Vorab-Schritte { #examples-of-previous-steps-strategies } Es hängt **stark** davon ab, wie Sie **Ihr System bereitstellen**, und hängt wahrscheinlich mit der Art und Weise zusammen, wie Sie Programme starten, Neustarts durchführen, usw. @@ -281,13 +279,13 @@ Hier sind einige mögliche Ideen: * Ein Bash-Skript, das die Vorab-Schritte ausführt und dann Ihre Anwendung startet * Sie benötigen immer noch eine Möglichkeit, *dieses* Bash-Skript zu starten/neu zu starten, Fehler zu erkennen, usw. -/// tip | "Tipp" +/// tip | Tipp Konkretere Beispiele hierfür mit Containern gebe ich Ihnen in einem späteren Kapitel: [FastAPI in Containern – Docker](docker.md){.internal-link target=_blank}. /// -## Ressourcennutzung +## Ressourcennutzung { #resource-utilization } Ihr(e) Server ist (sind) eine **Ressource**, welche Sie mit Ihren Programmen, der Rechenzeit auf den CPUs und dem verfügbaren RAM-Speicher verbrauchen oder **nutzen** können. @@ -303,11 +301,11 @@ In diesem Fall wäre es besser, **einen zusätzlichen Server** zu besorgen und e Es besteht auch die Möglichkeit, dass es aus irgendeinem Grund zu **Spitzen** in der Nutzung Ihrer API kommt. Vielleicht ist diese viral gegangen, oder vielleicht haben andere Dienste oder Bots damit begonnen, sie zu nutzen. Und vielleicht möchten Sie in solchen Fällen über zusätzliche Ressourcen verfügen, um auf der sicheren Seite zu sein. -Sie können eine **beliebige Zahl** festlegen, um beispielsweise eine Ressourcenauslastung zwischen **50 % und 90 %** anzustreben. Der Punkt ist, dass dies wahrscheinlich die wichtigen Dinge sind, die Sie messen und verwenden sollten, um Ihre Deployments zu optimieren. +Sie können eine **beliebige Zahl** festlegen, um beispielsweise eine Ressourcenauslastung zwischen **50 % und 90 %** anzustreben. Der Punkt ist, dass dies wahrscheinlich die wichtigen Dinge sind, die Sie messen und verwenden sollten, um Ihre Deployments zu optimieren. Sie können einfache Tools wie `htop` verwenden, um die in Ihrem Server verwendete CPU und den RAM oder die von jedem Prozess verwendete Menge anzuzeigen. Oder Sie können komplexere Überwachungstools verwenden, die möglicherweise auf mehrere Server usw. verteilt sind. -## Zusammenfassung +## Zusammenfassung { #recap } Sie haben hier einige der wichtigsten Konzepte gelesen, die Sie wahrscheinlich berücksichtigen müssen, wenn Sie entscheiden, wie Sie Ihre Anwendung bereitstellen: diff --git a/docs/de/docs/deployment/docker.md b/docs/de/docs/deployment/docker.md index c11dc41275..52ac999132 100644 --- a/docs/de/docs/deployment/docker.md +++ b/docs/de/docs/deployment/docker.md @@ -1,16 +1,16 @@ -# FastAPI in Containern – Docker +# FastAPI in Containern – Docker { #fastapi-in-containers-docker } Beim Deployment von FastAPI-Anwendungen besteht ein gängiger Ansatz darin, ein **Linux-Containerimage** zu erstellen. Normalerweise erfolgt dies mit **Docker**. Sie können dieses Containerimage dann auf eine von mehreren möglichen Arten bereitstellen. Die Verwendung von Linux-Containern bietet mehrere Vorteile, darunter **Sicherheit**, **Replizierbarkeit**, **Einfachheit** und andere. -/// tip | "Tipp" +/// tip | Tipp -Sie haben es eilig und kennen sich bereits aus? Springen Sie zum [`Dockerfile` unten 👇](#ein-docker-image-fur-fastapi-erstellen). +Sie haben es eilig und kennen sich bereits aus? Springen Sie zum [`Dockerfile` unten 👇](#build-a-docker-image-for-fastapi). /// -
+
Dockerfile-Vorschau 👀 ```Dockerfile @@ -24,15 +24,15 @@ 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"] +CMD ["fastapi", "run", "app/main.py", "--port", "80"] # Wenn Sie hinter einem Proxy wie Nginx oder Traefik sind, fügen Sie --proxy-headers hinzu -# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] +# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"] ```
-## Was ist ein Container? +## Was ist ein Container { #what-is-a-container } Container (hauptsächlich Linux-Container) sind eine sehr **leichtgewichtige** Möglichkeit, Anwendungen einschließlich aller ihrer Abhängigkeiten und erforderlichen Dateien zu verpacken und sie gleichzeitig von anderen Containern (anderen Anwendungen oder Komponenten) im selben System isoliert zu halten. @@ -42,7 +42,7 @@ Auf diese Weise verbrauchen Container **wenig Ressourcen**, eine Menge vergleich Container verfügen außerdem über ihre eigenen **isoliert** laufenden Prozesse (üblicherweise nur einen Prozess), über ihr eigenes Dateisystem und ihr eigenes Netzwerk, was die Bereitstellung, Sicherheit, Entwicklung usw. vereinfacht. -## Was ist ein Containerimage? +## Was ist ein Containerimage { #what-is-a-container-image } Ein **Container** wird von einem **Containerimage** ausgeführt. @@ -50,17 +50,17 @@ Ein Containerimage ist eine **statische** Version aller Dateien, Umgebungsvariab Im Gegensatz zu einem „**Containerimage**“, bei dem es sich um den gespeicherten statischen Inhalt handelt, bezieht sich ein „**Container**“ normalerweise auf die laufende Instanz, das Ding, das **ausgeführt** wird. -Wenn der **Container** gestartet und ausgeführt wird (gestartet von einem **Containerimage**), kann er Dateien, Umgebungsvariablen usw. erstellen oder ändern. Diese Änderungen sind nur in diesem Container vorhanden, nicht im zugrunde liegenden bestehen Containerimage (werden nicht auf der Festplatte gespeichert). +Wenn der **Container** gestartet und ausgeführt wird (gestartet von einem **Containerimage**), kann er Dateien, Umgebungsvariablen usw. erstellen oder ändern. Diese Änderungen sind nur in diesem Container vorhanden, nicht im zugrunde liegenden Containerimage (werden nicht auf der Festplatte gespeichert). Ein Containerimage ist vergleichbar mit der **Programmdatei** und ihrem Inhalt, z. B. `python` und eine Datei `main.py`. Und der **Container** selbst (im Gegensatz zum **Containerimage**) ist die tatsächlich laufende Instanz des Images, vergleichbar mit einem **Prozess**. Tatsächlich läuft ein Container nur, wenn er einen **laufenden Prozess** hat (und normalerweise ist es nur ein einzelner Prozess). Der Container stoppt, wenn kein Prozess darin ausgeführt wird. -## Containerimages +## Containerimages { #container-images } Docker ist eines der wichtigsten Tools zum Erstellen und Verwalten von **Containerimages** und **Containern**. -Und es gibt einen öffentlichen Docker Hub mit vorgefertigten **offiziellen Containerimages** für viele Tools, Umgebungen, Datenbanken und Anwendungen. +Und es gibt einen öffentlichen Docker Hub mit vorgefertigten **offiziellen Containerimages** für viele Tools, Umgebungen, Datenbanken und Anwendungen. Beispielsweise gibt es ein offizielles Python-Image. @@ -79,7 +79,7 @@ Sie würden also **mehrere Container** mit unterschiedlichen Dingen ausführen, In alle Containerverwaltungssysteme (wie Docker oder Kubernetes) sind diese Netzwerkfunktionen integriert. -## Container und Prozesse +## Container und Prozesse { #containers-and-processes } Ein **Containerimage** enthält normalerweise in seinen Metadaten das Standardprogramm oder den Standardbefehl, der ausgeführt werden soll, wenn der **Container** gestartet wird, sowie die Parameter, die an dieses Programm übergeben werden sollen. Sehr ähnlich zu dem, was wäre, wenn es über die Befehlszeile gestartet werden würde. @@ -91,7 +91,7 @@ Ein Container hat normalerweise einen **einzelnen Prozess**, aber es ist auch m Es ist jedoch nicht möglich, einen laufenden Container, ohne **mindestens einen laufenden Prozess** zu haben. Wenn der Hauptprozess stoppt, stoppt der Container. -## Ein Docker-Image für FastAPI erstellen +## Ein Docker-Image für FastAPI erstellen { #build-a-docker-image-for-fastapi } Okay, wollen wir jetzt etwas bauen! 🚀 @@ -103,7 +103,7 @@ Das ist, was Sie in **den meisten Fällen** tun möchten, zum Beispiel: * Beim Betrieb auf einem **Raspberry Pi** * Bei Verwendung eines Cloud-Dienstes, der ein Containerimage für Sie ausführt, usw. -### Paketanforderungen +### Paketanforderungen { #package-requirements } Normalerweise befinden sich die **Paketanforderungen** für Ihre Anwendung in einer Datei. @@ -116,9 +116,8 @@ Sie würden natürlich die gleichen Ideen verwenden, die Sie in [Über FastAPI-V Ihre `requirements.txt` könnte beispielsweise so aussehen: ``` -fastapi>=0.68.0,<0.69.0 -pydantic>=1.8.0,<2.0.0 -uvicorn>=0.15.0,<0.16.0 +fastapi[standard]>=0.113.0,<0.114.0 +pydantic>=2.7.0,<3.0.0 ``` Und normalerweise würden Sie diese Paketabhängigkeiten mit `pip` installieren, zum Beispiel: @@ -128,20 +127,18 @@ Und normalerweise würden Sie diese Paketabhängigkeiten mit `pip` installieren, ```console $ pip install -r requirements.txt ---> 100% -Successfully installed fastapi pydantic uvicorn +Successfully installed fastapi pydantic ```
-/// info +/// info | Info Es gibt andere Formate und Tools zum Definieren und Installieren von Paketabhängigkeiten. -Ich zeige Ihnen später in einem Abschnitt unten ein Beispiel unter Verwendung von Poetry. 👇 - /// -### Den **FastAPI**-Code erstellen +### Den **FastAPI**-Code erstellen { #create-the-fastapi-code } * Erstellen Sie ein `app`-Verzeichnis und betreten Sie es. * Erstellen Sie eine leere Datei `__init__.py`. @@ -165,35 +162,35 @@ def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -### Dockerfile +### Dockerfile { #dockerfile } Erstellen Sie nun im selben Projektverzeichnis eine Datei `Dockerfile` mit: ```{ .dockerfile .annotate } -# (1) +# (1)! FROM python:3.9 -# (2) +# (2)! WORKDIR /code -# (3) +# (3)! COPY ./requirements.txt /code/requirements.txt -# (4) +# (4)! RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -# (5) +# (5)! COPY ./app /code/app -# (6) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +# (6)! +CMD ["fastapi", "run", "app/main.py", "--port", "80"] ``` 1. Beginne mit dem offiziellen Python-Basisimage. 2. Setze das aktuelle Arbeitsverzeichnis auf `/code`. - Hier plazieren wir die Datei `requirements.txt` und das Verzeichnis `app`. + Hier platzieren wir die Datei `requirements.txt` und das Verzeichnis `app`. 3. Kopiere die Datei mit den Paketanforderungen in das Verzeichnis `/code`. @@ -223,20 +220,50 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] Daher ist es wichtig, dies **nahe dem Ende** des `Dockerfile`s zu platzieren, um die Erstellungszeiten des Containerimages zu optimieren. -6. Lege den **Befehl** fest, um den `uvicorn`-Server zu starten. +6. Lege den **Befehl** fest, um `fastapi run` zu nutzen, welches Uvicorn darunter verwendet. `CMD` nimmt eine Liste von Zeichenfolgen entgegen. Jede dieser Zeichenfolgen entspricht dem, was Sie durch Leerzeichen getrennt in die Befehlszeile eingeben würden. Dieser Befehl wird aus dem **aktuellen Arbeitsverzeichnis** ausgeführt, dem gleichen `/code`-Verzeichnis, das Sie oben mit `WORKDIR /code` festgelegt haben. - Da das Programm unter `/code` gestartet wird und sich darin das Verzeichnis `./app` mit Ihrem Code befindet, kann **Uvicorn** `app` sehen und aus `app.main` **importieren**. - -/// tip | "Tipp" +/// tip | Tipp Lernen Sie, was jede Zeile bewirkt, indem Sie auf die Zahlenblasen im Code klicken. 👆 /// +/// warning | Achtung + +Stellen Sie sicher, dass Sie **immer** die **exec form** der Anweisung `CMD` verwenden, wie unten erläutert. + +/// + +#### `CMD` – Exec Form verwenden { #use-cmd-exec-form } + +Die `CMD` Docker-Anweisung kann in zwei Formen geschrieben werden: + +✅ **Exec** form: + +```Dockerfile +# ✅ Tun Sie das +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +⛔️ **Shell** form: + +```Dockerfile +# ⛔️ Tun Sie das nicht +CMD fastapi run app/main.py --port 80 +``` + +Achten Sie darauf, stets die **exec** form zu verwenden, um sicherzustellen, dass FastAPI ordnungsgemäß heruntergefahren wird und [Lifespan-Events](../advanced/events.md){.internal-link target=_blank} ausgelöst werden. + +Sie können mehr darüber in der Docker-Dokumentation für Shell und Exec Form lesen. + +Dies kann insbesondere bei der Verwendung von `docker compose` deutlich spürbar sein. Sehen Sie sich diesen Abschnitt in der Docker Compose-FAQ für technische Details an: Warum benötigen meine Dienste 10 Sekunden, um neu erstellt oder gestoppt zu werden?. + +#### Verzeichnisstruktur { #directory-structure } + Sie sollten jetzt eine Verzeichnisstruktur wie diese haben: ``` @@ -248,15 +275,15 @@ Sie sollten jetzt eine Verzeichnisstruktur wie diese haben: └── requirements.txt ``` -#### Hinter einem TLS-Terminierungsproxy +#### Hinter einem TLS-Terminierungsproxy { #behind-a-tls-termination-proxy } -Wenn Sie Ihren Container hinter einem TLS-Terminierungsproxy (Load Balancer) wie Nginx oder Traefik ausführen, fügen Sie die Option `--proxy-headers` hinzu. Das sagt Uvicorn, den von diesem Proxy gesendeten Headern zu vertrauen und dass die Anwendung hinter HTTPS ausgeführt wird, usw. +Wenn Sie Ihren Container hinter einem TLS-Terminierungsproxy (Load Balancer) wie Nginx oder Traefik ausführen, fügen Sie die Option `--proxy-headers` hinzu. Das sagt Uvicorn (durch das FastAPI CLI), den von diesem Proxy gesendeten Headern zu vertrauen und dass die Anwendung hinter HTTPS ausgeführt wird, usw. ```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] ``` -#### Docker-Cache +#### Docker-Cache { #docker-cache } In diesem `Dockerfile` gibt es einen wichtigen Trick: Wir kopieren zuerst die **Datei nur mit den Abhängigkeiten**, nicht den Rest des Codes. Lassen Sie mich Ihnen erklären, warum. @@ -288,7 +315,7 @@ Dann, gegen Ende des `Dockerfile`s, kopieren wir den gesamten Code. Da sich der COPY ./app /code/app ``` -### Das Docker-Image erstellen +### Das Docker-Image erstellen { #build-the-docker-image } Nachdem nun alle Dateien vorhanden sind, erstellen wir das Containerimage. @@ -305,7 +332,7 @@ $ docker build -t myimage .
-/// tip | "Tipp" +/// tip | Tipp Beachten Sie das `.` am Ende, es entspricht `./` und teilt Docker mit, welches Verzeichnis zum Erstellen des Containerimages verwendet werden soll. @@ -313,7 +340,7 @@ In diesem Fall handelt es sich um dasselbe aktuelle Verzeichnis (`.`). /// -### Den Docker-Container starten +### Den Docker-Container starten { #start-the-docker-container } * Führen Sie einen Container basierend auf Ihrem Image aus: @@ -325,7 +352,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage
-## Es überprüfen +## Es testen { #check-it } Sie sollten es in der URL Ihres Docker-Containers überprüfen können, zum Beispiel: http://192.168.99.100/items/5?q=somequery oder http://127.0.0.1/items/5?q=somequery (oder gleichwertig, unter Verwendung Ihres Docker-Hosts). @@ -335,7 +362,7 @@ Sie werden etwas sehen wie: {"item_id": 5, "q": "somequery"} ``` -## Interaktive API-Dokumentation +## Interaktive API-Dokumentation { #interactive-api-docs } Jetzt können Sie auf http://192.168.99.100/docs oder http://127.0.0.1/docs gehen (oder ähnlich, unter Verwendung Ihres Docker-Hosts). @@ -343,7 +370,7 @@ Sie sehen die automatische interaktive API-Dokumentation (bereitgestellt von http://192.168.99.100/redoc oder http://127.0.0.1/redoc gehen (oder ähnlich, unter Verwendung Ihres Docker-Hosts). @@ -351,7 +378,7 @@ Sie sehen die alternative automatische Dokumentation (bereitgestellt von Traefik, welcher **HTTPS** und **automatischen** Erwerb von **Zertifikaten** handhabt. -/// tip | "Tipp" +/// tip | Tipp Traefik verfügt über Integrationen mit Docker, Kubernetes und anderen, sodass Sie damit ganz einfach HTTPS für Ihre Container einrichten und konfigurieren können. @@ -417,7 +444,7 @@ Traefik verfügt über Integrationen mit Docker, Kubernetes und anderen, sodass Alternativ könnte HTTPS von einem Cloud-Anbieter als einer seiner Dienste gehandhabt werden (während die Anwendung weiterhin in einem Container ausgeführt wird). -## Beim Hochfahren ausführen und Neustarts +## Beim Hochfahren ausführen und Neustarts { #running-on-startup-and-restarts } Normalerweise gibt es ein anderes Tool, das für das **Starten und Ausführen** Ihres Containers zuständig ist. @@ -427,21 +454,21 @@ In den meisten (oder allen) Fällen gibt es eine einfache Option, um die Ausfüh Ohne die Verwendung von Containern kann es umständlich und schwierig sein, Anwendungen beim Hochfahren auszuführen und neu zu starten. Bei der **Arbeit mit Containern** ist diese Funktionalität jedoch in den meisten Fällen standardmäßig enthalten. ✨ -## Replikation – Anzahl der Prozesse +## Replikation – Anzahl der Prozesse { #replication-number-of-processes } -Wenn Sie einen Cluster von Maschinen mit **Kubernetes**, Docker Swarm Mode, Nomad verwenden, oder einem anderen, ähnlich komplexen System zur Verwaltung verteilter Container auf mehreren Maschinen, möchten Sie wahrscheinlich die **Replikation auf Cluster-Ebene abwickeln**, anstatt in jedem Container einen **Prozessmanager** (wie Gunicorn mit Workern) zu verwenden. +Wenn Sie einen Cluster von Maschinen mit **Kubernetes**, Docker Swarm Mode, Nomad verwenden, oder einem anderen, ähnlich komplexen System zur Verwaltung verteilter Container auf mehreren Maschinen, möchten Sie wahrscheinlich die **Replikation auf Cluster-Ebene abwickeln**, anstatt in jedem Container einen **Prozessmanager** (wie Uvicorn mit Workern) zu verwenden. -Diese verteilten Containerverwaltungssysteme wie Kubernetes verfügen normalerweise über eine integrierte Möglichkeit, die **Replikation von Containern** zu handhaben und gleichzeitig **Load Balancing** für die eingehenden Requests zu unterstützen. Alles auf **Cluster-Ebene**. +Diese verteilten Containerverwaltungssysteme wie Kubernetes verfügen normalerweise über eine integrierte Möglichkeit, die **Replikation von Containern** zu handhaben und gleichzeitig **Load Balancing** für die eingehenden Requests zu unterstützen. Alles auf **Cluster-Ebene**. -In diesen Fällen möchten Sie wahrscheinlich ein **Docker-Image von Grund auf** erstellen, wie [oben erklärt](#dockerfile), Ihre Abhängigkeiten installieren und **einen einzelnen Uvicorn-Prozess** ausführen, anstatt etwas wie Gunicorn mit Uvicorn-Workern auszuführen. +In diesen Fällen möchten Sie wahrscheinlich ein **Docker-Image von Grund auf** erstellen, wie [oben erklärt](#dockerfile), Ihre Abhängigkeiten installieren und **einen einzelnen Uvicorn-Prozess** ausführen, anstatt mehrere Uvicorn-Worker zu verwenden. -### Load Balancer +### Load Balancer { #load-balancer } Bei der Verwendung von Containern ist normalerweise eine Komponente vorhanden, **die am Hauptport lauscht**. Es könnte sich um einen anderen Container handeln, der auch ein **TLS-Terminierungsproxy** ist, um **HTTPS** zu verarbeiten, oder ein ähnliches Tool. -Da diese Komponente die **Last** an Requests aufnehmen und diese (hoffentlich) **ausgewogen** auf die Worker verteilen würde, wird sie üblicherweise auch **Load Balancer** – Lastverteiler – genannt. +Da diese Komponente die **Last** an Requests aufnehmen und diese (hoffentlich) **ausgewogen** auf die Worker verteilen würde, wird sie üblicherweise auch **Load Balancer** genannt. -/// tip | "Tipp" +/// tip | Tipp Die gleiche **TLS-Terminierungsproxy**-Komponente, die für HTTPS verwendet wird, wäre wahrscheinlich auch ein **Load Balancer**. @@ -449,7 +476,7 @@ Die gleiche **TLS-Terminierungsproxy**-Komponente, die für HTTPS verwendet wird Und wenn Sie mit Containern arbeiten, verfügt das gleiche System, mit dem Sie diese starten und verwalten, bereits über interne Tools, um die **Netzwerkkommunikation** (z. B. HTTP-Requests) von diesem **Load Balancer** (das könnte auch ein **TLS-Terminierungsproxy** sein) zu den Containern mit Ihrer Anwendung weiterzuleiten. -### Ein Load Balancer – mehrere Workercontainer +### Ein Load Balancer – mehrere Workercontainer { #one-load-balancer-multiple-worker-containers } Bei der Arbeit mit **Kubernetes** oder ähnlichen verteilten Containerverwaltungssystemen würde die Verwendung ihrer internen Netzwerkmechanismen es dem einzelnen **Load Balancer**, der den Haupt-**Port** überwacht, ermöglichen, Kommunikation (Requests) an möglicherweise **mehrere Container** weiterzuleiten, in denen Ihre Anwendung ausgeführt wird. @@ -459,42 +486,49 @@ Und das verteilte Containersystem mit dem **Load Balancer** würde **die Request Und normalerweise wäre dieser **Load Balancer** in der Lage, Requests zu verarbeiten, die an *andere* Anwendungen in Ihrem Cluster gerichtet sind (z. B. eine andere Domain oder unter einem anderen URL-Pfad-Präfix), und würde diese Kommunikation an die richtigen Container weiterleiten für *diese andere* Anwendung, die in Ihrem Cluster ausgeführt wird. -### Ein Prozess pro Container +### Ein Prozess pro Container { #one-process-per-container } -In einem solchen Szenario möchten Sie wahrscheinlich **einen einzelnen (Uvicorn-)Prozess pro Container** haben, da Sie die Replikation bereits auf Cluster ebene durchführen würden. +In einem solchen Szenario möchten Sie wahrscheinlich **einen einzelnen (Uvicorn-)Prozess pro Container** haben, da Sie die Replikation bereits auf Cluster-Ebene durchführen würden. -In diesem Fall möchten Sie also **nicht** einen Prozessmanager wie Gunicorn mit Uvicorn-Workern oder Uvicorn mit seinen eigenen Uvicorn-Workern haben. Sie möchten nur einen **einzelnen Uvicorn-Prozess** pro Container haben (wahrscheinlich aber mehrere Container). +In diesem Fall möchten Sie also **nicht** mehrere Worker im Container haben, z. B. mit der `--workers` Befehlszeilenoption. Sie möchten nur einen **einzelnen Uvicorn-Prozess** pro Container haben (wahrscheinlich aber mehrere Container). -Ein weiterer Prozessmanager im Container (wie es bei Gunicorn oder Uvicorn der Fall wäre, welche Uvicorn-Worker verwalten) würde nur **unnötige Komplexität** hinzufügen, um welche Sie sich höchstwahrscheinlich bereits mit Ihrem Clustersystem kümmern. +Ein weiterer Prozessmanager im Container (wie es bei mehreren Workern der Fall wäre) würde nur **unnötige Komplexität** hinzufügen, um welche Sie sich höchstwahrscheinlich bereits mit Ihrem Clustersystem kümmern. -### Container mit mehreren Prozessen und Sonderfälle +### Container mit mehreren Prozessen und Sonderfälle { #containers-with-multiple-processes-and-special-cases } -Natürlich gibt es **Sonderfälle**, in denen Sie **einen Container** mit einem **Gunicorn-Prozessmanager** haben möchten, welcher mehrere **Uvicorn-Workerprozesse** darin startet. +Natürlich gibt es **Sonderfälle**, in denen Sie **einen Container** mit mehreren **Uvicorn-Workerprozessen** haben möchten. -In diesen Fällen können Sie das **offizielle Docker-Image** verwenden, welches **Gunicorn** als Prozessmanager enthält, welcher mehrere **Uvicorn-Workerprozesse** ausführt, sowie einige Standardeinstellungen, um die Anzahl der Worker basierend auf den verfügbaren CPU-Kernen automatisch anzupassen. Ich erzähle Ihnen weiter unten in [Offizielles Docker-Image mit Gunicorn – Uvicorn](#offizielles-docker-image-mit-gunicorn-uvicorn) mehr darüber. +In diesen Fällen können Sie die `--workers` Befehlszeilenoption verwenden, um die Anzahl der zu startenden Worker festzulegen: + +```{ .dockerfile .annotate } +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 + +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` + +1. Hier verwenden wir die `--workers` Befehlszeilenoption, um die Anzahl der Worker auf 4 festzulegen. Hier sind einige Beispiele, wann das sinnvoll sein könnte: -#### Eine einfache Anwendung +#### Eine einfache Anwendung { #a-simple-app } -Sie könnten einen Prozessmanager im Container haben wollen, wenn Ihre Anwendung **einfach genug** ist, sodass Sie die Anzahl der Prozesse nicht (zumindest noch nicht) zu stark tunen müssen und Sie einfach einen automatisierten Standard verwenden können (mit dem offiziellen Docker-Image), und Sie führen es auf einem **einzelnen Server** aus, nicht auf einem Cluster. +Sie könnten einen Prozessmanager im Container haben wollen, wenn Ihre Anwendung **einfach genug** ist, sodass Sie es auf einem **einzelnen Server** ausführen können, nicht auf einem Cluster. -#### Docker Compose +#### Docker Compose { #docker-compose } Sie könnten das Deployment auf einem **einzelnen Server** (kein Cluster) mit **Docker Compose** durchführen, sodass Sie keine einfache Möglichkeit hätten, die Replikation von Containern (mit Docker Compose) zu verwalten und gleichzeitig das gemeinsame Netzwerk mit **Load Balancing** zu haben. Dann möchten Sie vielleicht **einen einzelnen Container** mit einem **Prozessmanager** haben, der darin **mehrere Workerprozesse** startet. -#### Prometheus und andere Gründe - -Sie könnten auch **andere Gründe** haben, die es einfacher machen würden, einen **einzelnen Container** mit **mehreren Prozessen** zu haben, anstatt **mehrere Container** mit **einem einzelnen Prozess** in jedem von ihnen. - -Beispielsweise könnten Sie (abhängig von Ihrem Setup) ein Tool wie einen Prometheus-Exporter im selben Container haben, welcher Zugriff auf **jeden der eingehenden Requests** haben sollte. - -Wenn Sie in hier **mehrere Container** hätten, würde Prometheus beim **Lesen der Metriken** standardmäßig jedes Mal diejenigen für **einen einzelnen Container** abrufen (für den Container, der den spezifischen Request verarbeitet hat), anstatt die **akkumulierten Metriken** für alle replizierten Container abzurufen. - -In diesem Fall könnte einfacher sein, **einen Container** mit **mehreren Prozessen** und ein lokales Tool (z. B. einen Prometheus-Exporter) in demselben Container zu haben, welches Prometheus-Metriken für alle internen Prozesse sammelt und diese Metriken für diesen einzelnen Container offenlegt. - --- Der Hauptpunkt ist, dass **keine** dieser Regeln **in Stein gemeißelt** ist, der man blind folgen muss. Sie können diese Ideen verwenden, um **Ihren eigenen Anwendungsfall zu evaluieren**, zu entscheiden, welcher Ansatz für Ihr System am besten geeignet ist und herauszufinden, wie Sie folgende Konzepte verwalten: @@ -506,25 +540,25 @@ Der Hauptpunkt ist, dass **keine** dieser Regeln **in Stein gemeißelt** ist, de * Arbeitsspeicher * Schritte vor dem Start -## Arbeitsspeicher +## Arbeitsspeicher { #memory } Wenn Sie **einen einzelnen Prozess pro Container** ausführen, wird von jedem dieser Container (mehr als einer, wenn sie repliziert werden) eine mehr oder weniger klar definierte, stabile und begrenzte Menge an Arbeitsspeicher verbraucht. -Und dann können Sie dieselben Speichergrenzen und -anforderungen in Ihren Konfigurationen für Ihr Container-Management-System festlegen (z. B. in **Kubernetes**). Auf diese Weise ist es in der Lage, die Container auf den **verfügbaren Maschinen** zu replizieren, wobei die von denen benötigte Speichermenge und die auf den Maschinen im Cluster verfügbare Menge berücksichtigt werden. +Und dann können Sie dieselben Speichergrenzen und -anforderungen in Ihren Konfigurationen für Ihr Container-Management-System festlegen (z. B. in **Kubernetes**). Auf diese Weise ist es in der Lage, die Container auf den **verfügbaren Maschinen** zu replizieren, wobei die von diesen benötigte Speichermenge und die auf den Maschinen im Cluster verfügbare Menge berücksichtigt werden. -Wenn Ihre Anwendung **einfach** ist, wird dies wahrscheinlich **kein Problem darstellen** und Sie müssen möglicherweise keine festen Speichergrenzen angeben. Wenn Sie jedoch **viel Speicher verbrauchen** (z. B. bei **Modellen für maschinelles Lernen**), sollten Sie überprüfen, wie viel Speicher Sie verbrauchen, und die **Anzahl der Container** anpassen, die in **jeder Maschine** ausgeführt werden. (und möglicherweise weitere Maschinen zu Ihrem Cluster hinzufügen). +Wenn Ihre Anwendung **einfach** ist, wird dies wahrscheinlich **kein Problem darstellen** und Sie müssen möglicherweise keine festen Speichergrenzen angeben. Wenn Sie jedoch **viel Speicher verbrauchen** (z. B. bei **Modellen für maschinelles Lernen**), sollten Sie überprüfen, wie viel Speicher Sie verbrauchen, und die **Anzahl der Container** anpassen, die in **jeder Maschine** ausgeführt werden (und möglicherweise weitere Maschinen zu Ihrem Cluster hinzufügen). -Wenn Sie **mehrere Prozesse pro Container** ausführen (zum Beispiel mit dem offiziellen Docker-Image), müssen Sie sicherstellen, dass die Anzahl der gestarteten Prozesse nicht **mehr Speicher verbraucht** als verfügbar ist. +Wenn Sie **mehrere Prozesse pro Container** ausführen, müssen Sie sicherstellen, dass die Anzahl der gestarteten Prozesse nicht **mehr Speicher verbraucht** als verfügbar ist. -## Schritte vor dem Start und Container +## Schritte vor dem Start und Container { #previous-steps-before-starting-and-containers } Wenn Sie Container (z. B. Docker, Kubernetes) verwenden, können Sie hauptsächlich zwei Ansätze verwenden. -### Mehrere Container +### Mehrere Container { #multiple-containers } -Wenn Sie **mehrere Container** haben, von denen wahrscheinlich jeder einen **einzelnen Prozess** ausführt (z. B. in einem **Kubernetes**-Cluster), dann möchten Sie wahrscheinlich einen **separaten Container** haben, welcher die Arbeit der **Vorab-Schritte** in einem einzelnen Container, mit einem einzelnenen Prozess ausführt, **bevor** die replizierten Workercontainer ausgeführt werden. +Wenn Sie **mehrere Container** haben, von denen wahrscheinlich jeder einen **einzelnen Prozess** ausführt (z. B. in einem **Kubernetes**-Cluster), dann möchten Sie wahrscheinlich einen **separaten Container** haben, welcher die Arbeit der **Vorab-Schritte** in einem einzelnen Container, mit einem einzelnen Prozess ausführt, **bevor** die replizierten Workercontainer ausgeführt werden. -/// info +/// info | Info Wenn Sie Kubernetes verwenden, wäre dies wahrscheinlich ein Init-Container. @@ -532,83 +566,29 @@ Wenn Sie Kubernetes verwenden, wäre dies wahrscheinlich ein tiangolo/uvicorn-gunicorn-fastapi. Dieses ist jedoch jetzt veraltet. ⛔️ -Dieses Image wäre vor allem in den oben beschriebenen Situationen nützlich: [Container mit mehreren Prozessen und Sonderfälle](#container-mit-mehreren-prozessen-und-sonderfalle). +Sie sollten wahrscheinlich **nicht** dieses Basis-Docker-Image (oder ein anderes ähnliches) verwenden. -* tiangolo/uvicorn-gunicorn-fastapi. +Wenn Sie **Kubernetes** (oder andere) verwenden und bereits **Replikation** auf Cluster-Ebene mit mehreren **Containern** eingerichtet haben. In diesen Fällen ist es besser, **ein Image von Grund auf neu zu erstellen**, wie oben beschrieben: [Ein Docker-Image für FastAPI erstellen](#build-a-docker-image-for-fastapi). -/// warning | "Achtung" +Und wenn Sie mehrere Worker benötigen, können Sie einfach die `--workers` Befehlszeilenoption verwenden. -Es besteht eine hohe Wahrscheinlichkeit, dass Sie dieses oder ein ähnliches Basisimage **nicht** benötigen und es besser wäre, wenn Sie das Image von Grund auf neu erstellen würden, wie [oben beschrieben in: Ein Docker-Image für FastAPI erstellen](#ein-docker-image-fur-fastapi-erstellen). +/// note | Technische Details + +Das Docker-Image wurde erstellt, als Uvicorn das Verwalten und Neustarten von ausgefallenen Workern noch nicht unterstützte, weshalb es notwendig war, Gunicorn mit Uvicorn zu verwenden, was zu einer erheblichen Komplexität führte, nur damit Gunicorn die Uvicorn-Workerprozesse verwaltet und neu startet. + +Aber jetzt, da Uvicorn (und der `fastapi`-Befehl) die Verwendung von `--workers` unterstützen, gibt es keinen Grund, ein Basis-Docker-Image an Stelle eines eigenen (das praktisch denselben Code enthält 😅) zu verwenden. /// -Dieses Image verfügt über einen **Auto-Tuning**-Mechanismus, um die **Anzahl der Arbeitsprozesse** basierend auf den verfügbaren CPU-Kernen festzulegen. - -Es verfügt über **vernünftige Standardeinstellungen**, aber Sie können trotzdem alle Konfigurationen mit **Umgebungsvariablen** oder Konfigurationsdateien ändern und aktualisieren. - -Es unterstützt auch die Ausführung von **Vorab-Schritten vor dem Start** mit einem Skript. - -/// tip | "Tipp" - -Um alle Konfigurationen und Optionen anzuzeigen, gehen Sie zur Docker-Image-Seite: tiangolo/uvicorn-gunicorn-fastapi. - -/// - -### Anzahl der Prozesse auf dem offiziellen Docker-Image - -Die **Anzahl der Prozesse** auf diesem Image wird **automatisch** anhand der verfügbaren CPU-**Kerne** berechnet. - -Das bedeutet, dass versucht wird, so viel **Leistung** wie möglich aus der CPU herauszuquetschen. - -Sie können das auch in der Konfiguration anpassen, indem Sie **Umgebungsvariablen**, usw. verwenden. - -Das bedeutet aber auch, da die Anzahl der Prozesse von der CPU abhängt, welche der Container ausführt, dass die **Menge des verbrauchten Speichers** ebenfalls davon abhängt. - -Wenn Ihre Anwendung also viel Speicher verbraucht (z. B. bei Modellen für maschinelles Lernen) und Ihr Server über viele CPU-Kerne, **aber wenig Speicher** verfügt, könnte Ihr Container am Ende versuchen, mehr Speicher als vorhanden zu verwenden, was zu erheblichen Leistungseinbußen (oder sogar zum Absturz) führen kann. 🚨 - -### Ein `Dockerfile` erstellen - -So würden Sie ein `Dockerfile` basierend auf diesem Image erstellen: - -```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 -``` - -### Größere Anwendungen - -Wenn Sie dem Abschnitt zum Erstellen von [größeren Anwendungen mit mehreren Dateien](../tutorial/bigger-applications.md){.internal-link target=_blank} gefolgt sind, könnte Ihr `Dockerfile` stattdessen wie folgt aussehen: - -```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 -``` - -### Wann verwenden - -Sie sollten dieses offizielle Basisimage (oder ein ähnliches) wahrscheinlich **nicht** benutzen, wenn Sie **Kubernetes** (oder andere) verwenden und Sie bereits **Replikation** auf Cluster ebene mit mehreren **Containern** eingerichtet haben. In diesen Fällen ist es besser, **ein Image von Grund auf zu erstellen**, wie oben beschrieben: [Ein Docker-Image für FastAPI erstellen](#ein-docker-image-fur-fastapi-erstellen). - -Dieses Image wäre vor allem in den oben in [Container mit mehreren Prozessen und Sonderfälle](#container-mit-mehreren-prozessen-und-sonderfalle) beschriebenen Sonderfällen nützlich. Wenn Ihre Anwendung beispielsweise **einfach genug** ist, dass das Festlegen einer Standardanzahl von Prozessen basierend auf der CPU gut funktioniert, möchten Sie sich nicht mit der manuellen Konfiguration der Replikation auf Cluster ebene herumschlagen und führen nicht mehr als einen Container mit Ihrer Anwendung aus. Oder wenn Sie das Deployment mit **Docker Compose** durchführen und auf einem einzelnen Server laufen, usw. - -## Deployment des Containerimages +## Deployment des Containerimages { #deploy-the-container-image } Nachdem Sie ein Containerimage (Docker) haben, gibt es mehrere Möglichkeiten, es bereitzustellen. @@ -620,100 +600,11 @@ Zum Beispiel: * Mit einem anderen Tool wie Nomad * Mit einem Cloud-Dienst, der Ihr Containerimage nimmt und es bereitstellt -## Docker-Image mit Poetry +## Docker-Image mit `uv` { #docker-image-with-uv } -Wenn Sie Poetry verwenden, um die Abhängigkeiten Ihres Projekts zu verwalten, können Sie Dockers mehrphasige Builds verwenden: +Wenn Sie uv verwenden, um Ihr Projekt zu installieren und zu verwalten, können Sie deren uv-Docker-Leitfaden befolgen. -```{ .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. Dies ist die erste Phase, genannt `requirements-stage` – „Anforderungsphase“. - -2. Setze `/tmp` als aktuelles Arbeitsverzeichnis. - - Hier werden wir die Datei `requirements.txt` generieren. - -3. Installiere Poetry in dieser Docker-Phase. - -4. Kopiere die Dateien `pyproject.toml` und `poetry.lock` in das Verzeichnis `/tmp`. - - Da es `./poetry.lock*` verwendet (endet mit einem `*`), stürzt es nicht ab, wenn diese Datei noch nicht verfügbar ist. - -5. Generiere die Datei `requirements.txt`. - -6. Dies ist die letzte Phase. Alles hier bleibt im endgültigen Containerimage erhalten. - -7. Setze das aktuelle Arbeitsverzeichnis auf `/code`. - -8. Kopiere die Datei `requirements.txt` in das Verzeichnis `/code`. - - Diese Datei existiert nur in der vorherigen Docker-Phase, deshalb verwenden wir `--from-requirements-stage`, um sie zu kopieren. - -9. Installiere die Paketabhängigkeiten von der generierten Datei `requirements.txt`. - -10. Kopiere das Verzeichnis `app` in das Verzeichnis `/code`. - -11. Führe den Befehl `uvicorn` aus und weise ihn an, das aus `app.main` importierte `app`-Objekt zu verwenden. - -/// tip | "Tipp" - -Klicken Sie auf die Zahlenblasen, um zu sehen, was jede Zeile bewirkt. - -/// - -Eine **Docker-Phase** ist ein Teil eines `Dockerfile`s, welcher als **temporäres Containerimage** fungiert und nur zum Generieren einiger Dateien für die spätere Verwendung verwendet wird. - -Die erste Phase wird nur zur **Installation von Poetry** und zur **Generierung der `requirements.txt`** mit deren Projektabhängigkeiten aus der Datei `pyproject.toml` von Poetry verwendet. - -Diese `requirements.txt`-Datei wird später in der **nächsten Phase** mit `pip` verwendet. - -Im endgültigen Containerimage bleibt **nur die letzte Stufe** erhalten. Die vorherigen Stufen werden verworfen. - -Bei der Verwendung von Poetry wäre es sinnvoll, **mehrstufige Docker-Builds** zu verwenden, da Poetry und seine Abhängigkeiten nicht wirklich im endgültigen Containerimage installiert sein müssen, sondern Sie brauchen **nur** die Datei `requirements.txt`, um Ihre Projektabhängigkeiten zu installieren. - -Dann würden Sie im nächsten (und letzten) Schritt das Image mehr oder weniger auf die gleiche Weise wie zuvor beschrieben erstellen. - -### Hinter einem TLS-Terminierungsproxy – Poetry - -Auch hier gilt: Wenn Sie Ihren Container hinter einem TLS-Terminierungsproxy (Load Balancer) wie Nginx oder Traefik ausführen, fügen Sie dem Befehl die Option `--proxy-headers` hinzu: - -```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] -``` - -## Zusammenfassung +## Zusammenfassung { #recap } Mithilfe von Containersystemen (z. B. mit **Docker** und **Kubernetes**) ist es ziemlich einfach, alle **Deployment-Konzepte** zu handhaben: @@ -727,5 +618,3 @@ Mithilfe von Containersystemen (z. B. mit **Docker** und **Kubernetes**) ist es In den meisten Fällen möchten Sie wahrscheinlich kein Basisimage verwenden und stattdessen **ein Containerimage von Grund auf erstellen**, eines basierend auf dem offiziellen Python-Docker-Image. Indem Sie auf die **Reihenfolge** der Anweisungen im `Dockerfile` und den **Docker-Cache** achten, können Sie **die Build-Zeiten minimieren**, um Ihre Produktivität zu erhöhen (und Langeweile zu vermeiden). 😎 - -In bestimmten Sonderfällen möchten Sie möglicherweise das offizielle Docker-Image für FastAPI verwenden. 🤓 diff --git a/docs/de/docs/deployment/https.md b/docs/de/docs/deployment/https.md index b1f0aca77e..1c4ce6b44f 100644 --- a/docs/de/docs/deployment/https.md +++ b/docs/de/docs/deployment/https.md @@ -1,10 +1,10 @@ -# Über HTTPS +# Über HTTPS { #about-https } Es ist leicht anzunehmen, dass HTTPS etwas ist, was einfach nur „aktiviert“ wird oder nicht. Aber es ist viel komplexer als das. -/// tip | "Tipp" +/// tip | Tipp Wenn Sie es eilig haben oder es Ihnen egal ist, fahren Sie mit den nächsten Abschnitten fort, um Schritt-für-Schritt-Anleitungen für die Einrichtung der verschiedenen Technologien zu erhalten. @@ -22,19 +22,19 @@ Aus **Sicht des Entwicklers** sollten Sie beim Nachdenken über HTTPS Folgendes * Die Verschlüsselung der Verbindung erfolgt auf **TCP-Ebene**. * Das ist eine Schicht **unter HTTP**. * Die Handhabung von **Zertifikaten und Verschlüsselung** erfolgt also **vor HTTP**. -* **TCP weiß nichts über „Domains“**. Nur über IP-Adressen. +* **TCP weiß nichts über „Domains“**. Nur über IP-Adressen. * Die Informationen über die angeforderte **spezifische Domain** befinden sich in den **HTTP-Daten**. * Die **HTTPS-Zertifikate** „zertifizieren“ eine **bestimmte Domain**, aber das Protokoll und die Verschlüsselung erfolgen auf TCP-Ebene, **ohne zu wissen**, um welche Domain es sich handelt. * **Standardmäßig** bedeutet das, dass Sie nur **ein HTTPS-Zertifikat pro IP-Adresse** haben können. * Ganz gleich, wie groß Ihr Server ist oder wie klein die einzelnen Anwendungen darauf sind. * Hierfür gibt es jedoch eine **Lösung**. -* Es gibt eine **Erweiterung** zum **TLS**-Protokoll (dasjenige, das die Verschlüsselung auf TCP-Ebene, vor HTTP, verwaltet) namens **SNI**. - * Mit dieser SNI-Erweiterung kann ein einzelner Server (mit einer **einzelnen IP-Adresse**) über **mehrere HTTPS-Zertifikate** verfügen und **mehrere HTTPS-Domains/Anwendungen** bedienen. +* Es gibt eine **Erweiterung** zum **TLS**-Protokoll (dasjenige, das die Verschlüsselung auf TCP-Ebene, vor HTTP, verwaltet) namens **SNI**. + * Mit dieser SNI-Erweiterung kann ein einzelner Server (mit einer **einzelnen IP-Adresse**) über **mehrere HTTPS-Zertifikate** verfügen und **mehrere HTTPS-Domains/Anwendungen bereitstellen**. * Damit das funktioniert, muss eine **einzelne** Komponente (Programm), die auf dem Server ausgeführt wird und welche die **öffentliche IP-Adresse** überwacht, **alle HTTPS-Zertifikate** des Servers haben. * **Nachdem** eine sichere Verbindung hergestellt wurde, ist das Kommunikationsprotokoll **immer noch HTTP**. * Die Inhalte sind **verschlüsselt**, auch wenn sie mit dem **HTTP-Protokoll** gesendet werden. -Es ist eine gängige Praxis, **ein Programm/HTTP-Server** auf dem Server (der Maschine, dem Host usw.) laufen zu lassen, welches **alle HTTPS-Aspekte verwaltet**: Empfangen der **verschlüsselten HTTPS-Requests**, Senden der **entschlüsselten HTTP-Requests** an die eigentliche HTTP-Anwendung die auf demselben Server läuft (in diesem Fall die **FastAPI**-Anwendung), entgegennehmen der **HTTP-Response** von der Anwendung, **verschlüsseln derselben** mithilfe des entsprechenden **HTTPS-Zertifikats** und Zurücksenden zum Client über **HTTPS**. Dieser Server wird oft als **TLS-Terminierungsproxy** bezeichnet. +Es ist eine gängige Praxis, **ein Programm/HTTP-Server** auf dem Server (der Maschine, dem Host usw.) laufen zu lassen, welches **alle HTTPS-Aspekte verwaltet**: Empfangen der **verschlüsselten HTTPS-Requests**, Senden der **entschlüsselten HTTP-Requests** an die eigentliche HTTP-Anwendung die auf demselben Server läuft (in diesem Fall die **FastAPI**-Anwendung), entgegennehmen der **HTTP-Response** von der Anwendung, **verschlüsseln derselben** mithilfe des entsprechenden **HTTPS-Zertifikats** und Zurücksenden zum Client über **HTTPS**. Dieser Server wird oft als **TLS-Terminierungsproxy** bezeichnet. Einige der Optionen, die Sie als TLS-Terminierungsproxy verwenden können, sind: @@ -43,7 +43,7 @@ Einige der Optionen, die Sie als TLS-Terminierungsproxy verwenden können, sind: * Nginx * HAProxy -## Let's Encrypt +## Let's Encrypt { #lets-encrypt } Vor Let's Encrypt wurden diese **HTTPS-Zertifikate** von vertrauenswürdigen Dritten verkauft. @@ -57,13 +57,13 @@ Die Domains werden sicher verifiziert und die Zertifikate werden automatisch gen Die Idee besteht darin, den Erwerb und die Erneuerung der Zertifikate zu automatisieren, sodass Sie **sicheres HTTPS, kostenlos und für immer** haben können. -## HTTPS für Entwickler +## HTTPS für Entwickler { #https-for-developers } Hier ist ein Beispiel, wie eine HTTPS-API aussehen könnte, Schritt für Schritt, wobei vor allem die für Entwickler wichtigen Ideen berücksichtigt werden. -### Domainname +### Domainname { #domain-name } -Alles beginnt wahrscheinlich damit, dass Sie einen **Domainnamen erwerben**. Anschließend konfigurieren Sie ihn in einem DNS-Server (wahrscheinlich beim selben Cloud-Anbieter). +Alles beginnt wahrscheinlich damit, dass Sie einen **Domainnamen erwerben**. Anschließend konfigurieren Sie ihn in einem DNS-Server (wahrscheinlich beim selben Cloudanbieter). Sie würden wahrscheinlich einen Cloud-Server (eine virtuelle Maschine) oder etwas Ähnliches bekommen, und dieser hätte eine feste **öffentliche IP-Adresse**. @@ -71,33 +71,33 @@ In dem oder den DNS-Server(n) würden Sie einen Eintrag (einen „`A record`“) Sie würden dies wahrscheinlich nur einmal tun, beim ersten Mal, wenn Sie alles einrichten. -/// tip | "Tipp" +/// tip | Tipp Dieser Domainnamen-Aspekt liegt weit vor HTTPS, aber da alles von der Domain und der IP-Adresse abhängt, lohnt es sich, das hier zu erwähnen. /// -### DNS +### DNS { #dns } Konzentrieren wir uns nun auf alle tatsächlichen HTTPS-Aspekte. -Zuerst würde der Browser mithilfe der **DNS-Server** herausfinden, welches die **IP für die Domain** ist, in diesem Fall für `someapp.example.com`. +Zuerst würde der Browser mithilfe der **DNS-Server** herausfinden, welches die **IP für die Domain** ist, in diesem Fall `someapp.example.com`. Die DNS-Server geben dem Browser eine bestimmte **IP-Adresse** zurück. Das wäre die von Ihrem Server verwendete öffentliche IP-Adresse, die Sie in den DNS-Servern konfiguriert haben. - + -### TLS-Handshake-Start +### TLS-Handshake-Start { #tls-handshake-start } Der Browser kommuniziert dann mit dieser IP-Adresse über **Port 443** (den HTTPS-Port). Der erste Teil der Kommunikation besteht lediglich darin, die Verbindung zwischen dem Client und dem Server herzustellen und die zu verwendenden kryptografischen Schlüssel usw. zu vereinbaren. - + Diese Interaktion zwischen dem Client und dem Server zum Aufbau der TLS-Verbindung wird als **TLS-Handshake** bezeichnet. -### TLS mit SNI-Erweiterung +### TLS mit SNI-Erweiterung { #tls-with-sni-extension } **Nur ein Prozess** im Server kann an einem bestimmten **Port** einer bestimmten **IP-Adresse** lauschen. Möglicherweise gibt es andere Prozesse, die an anderen Ports dieselbe IP-Adresse abhören, jedoch nur einen für jede Kombination aus IP-Adresse und Port. @@ -111,7 +111,7 @@ Mithilfe der oben beschriebenen **SNI-Erweiterung** würde der TLS-Terminierungs In diesem Fall würde er das Zertifikat für `someapp.example.com` verwenden. - + Der Client **vertraut** bereits der Entität, die das TLS-Zertifikat generiert hat (in diesem Fall Let's Encrypt, aber wir werden später mehr darüber erfahren), sodass er **verifizieren** kann, dass das Zertifikat gültig ist. @@ -121,59 +121,59 @@ Danach verfügen der Client und der Server über eine **verschlüsselte TCP-Verb Und genau das ist **HTTPS**, es ist einfach **HTTP** innerhalb einer **sicheren TLS-Verbindung**, statt einer puren (unverschlüsselten) TCP-Verbindung. -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass die Verschlüsselung der Kommunikation auf der **TCP-Ebene** und nicht auf der HTTP-Ebene erfolgt. /// -### HTTPS-Request +### HTTPS-Request { #https-request } Da Client und Server (sprich, der Browser und der TLS-Terminierungsproxy) nun über eine **verschlüsselte TCP-Verbindung** verfügen, können sie die **HTTP-Kommunikation** starten. Der Client sendet also einen **HTTPS-Request**. Das ist einfach ein HTTP-Request über eine verschlüsselte TLS-Verbindung. - + -### Den Request entschlüsseln +### Den Request entschlüsseln { #decrypt-the-request } Der TLS-Terminierungsproxy würde die vereinbarte Verschlüsselung zum **Entschlüsseln des Requests** verwenden und den **einfachen (entschlüsselten) HTTP-Request** an den Prozess weiterleiten, der die Anwendung ausführt (z. B. einen Prozess, bei dem Uvicorn die FastAPI-Anwendung ausführt). - + -### HTTP-Response +### HTTP-Response { #http-response } Die Anwendung würde den Request verarbeiten und eine **einfache (unverschlüsselte) HTTP-Response** an den TLS-Terminierungsproxy senden. - + -### HTTPS-Response +### HTTPS-Response { #https-response } Der TLS-Terminierungsproxy würde dann die Response mithilfe der zuvor vereinbarten Kryptografie (als das Zertifikat für `someapp.example.com` verhandelt wurde) **verschlüsseln** und sie an den Browser zurücksenden. Als Nächstes überprüft der Browser, ob die Response gültig und mit dem richtigen kryptografischen Schlüssel usw. verschlüsselt ist. Anschließend **entschlüsselt er die Response** und verarbeitet sie. - + Der Client (Browser) weiß, dass die Response vom richtigen Server kommt, da dieser die Kryptografie verwendet, die zuvor mit dem **HTTPS-Zertifikat** vereinbart wurde. -### Mehrere Anwendungen +### Mehrere Anwendungen { #multiple-applications } Auf demselben Server (oder denselben Servern) könnten sich **mehrere Anwendungen** befinden, beispielsweise andere API-Programme oder eine Datenbank. Nur ein Prozess kann diese spezifische IP und den Port verarbeiten (in unserem Beispiel der TLS-Terminierungsproxy), aber die anderen Anwendungen/Prozesse können auch auf dem/den Server(n) ausgeführt werden, solange sie nicht versuchen, dieselbe **Kombination aus öffentlicher IP und Port** zu verwenden. - + Auf diese Weise könnte der TLS-Terminierungsproxy HTTPS und Zertifikate für **mehrere Domains**, für mehrere Anwendungen, verarbeiten und die Requests dann jeweils an die richtige Anwendung weiterleiten. -### Verlängerung des Zertifikats +### Verlängerung des Zertifikats { #certificate-renewal } Irgendwann in der Zukunft würde jedes Zertifikat **ablaufen** (etwa 3 Monate nach dem Erwerb). Und dann gäbe es ein anderes Programm (in manchen Fällen ist es ein anderes Programm, in manchen Fällen ist es derselbe TLS-Terminierungsproxy), das mit Let's Encrypt kommuniziert und das/die Zertifikat(e) erneuert. - + Die **TLS-Zertifikate** sind **einem Domainnamen zugeordnet**, nicht einer IP-Adresse. @@ -190,7 +190,39 @@ Um dies zu erreichen und den unterschiedlichen Anwendungsanforderungen gerecht z Dieser ganze Erneuerungsprozess, während die Anwendung weiterhin bereitgestellt wird, ist einer der Hauptgründe, warum Sie ein **separates System zur Verarbeitung von HTTPS** mit einem TLS-Terminierungsproxy haben möchten, anstatt einfach die TLS-Zertifikate direkt mit dem Anwendungsserver zu verwenden (z. B. Uvicorn). -## Zusammenfassung +## Proxy-Forwarded-Header { #proxy-forwarded-headers } + +Wenn Sie einen Proxy zur Verarbeitung von HTTPS verwenden, weiß Ihr **Anwendungsserver** (z. B. Uvicorn über das FastAPI CLI) nichts über den HTTPS-Prozess, er kommuniziert per einfachem HTTP mit dem **TLS-Terminierungsproxy**. + +Dieser **Proxy** würde normalerweise unmittelbar vor dem Übermitteln der Anfrage an den **Anwendungsserver** einige HTTP-Header dynamisch setzen, um dem Anwendungsserver mitzuteilen, dass der Request vom Proxy **weitergeleitet** wird. + +/// note | Technische Details + +Die Proxy-Header sind: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +Trotzdem, da der **Anwendungsserver** nicht weiß, dass er sich hinter einem vertrauenswürdigen **Proxy** befindet, würde er diesen Headern standardmäßig nicht vertrauen. + +Sie können den **Anwendungsserver** jedoch so konfigurieren, dass er den vom **Proxy** gesendeten *Forwarded*-Headern vertraut. Wenn Sie das FastAPI CLI verwenden, können Sie die *CLI-Option* `--forwarded-allow-ips` nutzen, um anzugeben, von welchen IPs er diesen *Forwarded*-Headern vertrauen soll. + +Wenn der **Anwendungsserver** beispielsweise nur Kommunikation vom vertrauenswürdigen **Proxy** empfängt, können Sie `--forwarded-allow-ips="*"` setzen, um allen eingehenden IPs zu vertrauen, da er nur Requests von der vom **Proxy** verwendeten IP erhalten wird. + +Auf diese Weise kann die Anwendung ihre eigene öffentliche URL, ob sie HTTPS verwendet, die Domain, usw. erkennen. + +Das ist z. B. nützlich, um Redirects korrekt zu handhaben. + +/// tip | Tipp + +Mehr dazu finden Sie in der Dokumentation zu [Hinter einem Proxy – Proxy-Forwarded-Header aktivieren](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} + +/// + +## Zusammenfassung { #recap } **HTTPS** zu haben ist sehr wichtig und in den meisten Fällen eine **kritische Anforderung**. Die meiste Arbeit, die Sie als Entwickler in Bezug auf HTTPS aufwenden müssen, besteht lediglich darin, **diese Konzepte zu verstehen** und wie sie funktionieren. diff --git a/docs/de/docs/deployment/index.md b/docs/de/docs/deployment/index.md index 1aa1310977..65c76edcea 100644 --- a/docs/de/docs/deployment/index.md +++ b/docs/de/docs/deployment/index.md @@ -1,18 +1,18 @@ -# Deployment +# Deployment { #deployment } Das Deployment einer **FastAPI**-Anwendung ist relativ einfach. -## Was bedeutet Deployment? +## Was bedeutet Deployment { #what-does-deployment-mean } -**Deployment** (Deutsch etwa: **Bereitstellen der Anwendung**) bedeutet, die notwendigen Schritte durchzuführen, um die Anwendung **für die Endbenutzer verfügbar** zu machen. +**Deployment** bedeutet, die notwendigen Schritte durchzuführen, um die Anwendung **für die Endbenutzer verfügbar** zu machen. Bei einer **Web-API** bedeutet das normalerweise, diese auf einem **entfernten Rechner** zu platzieren, mit einem **Serverprogramm**, welches gute Leistung, Stabilität, usw. bietet, damit Ihre **Benutzer** auf die Anwendung effizient und ohne Unterbrechungen oder Probleme **zugreifen** können. Das steht im Gegensatz zu den **Entwicklungsphasen**, in denen Sie ständig den Code ändern, kaputt machen, reparieren, den Entwicklungsserver stoppen und neu starten, usw. -## Deployment-Strategien +## Deployment-Strategien { #deployment-strategies } -Abhängig von Ihrem spezifischen Anwendungsfall und den von Ihnen verwendeten Tools gibt es mehrere Möglichkeiten, das zu tun. +Es gibt mehrere Möglichkeiten, dies zu tun, abhängig von Ihrem spezifischen Anwendungsfall und den von Ihnen verwendeten Tools. Sie könnten mithilfe einer Kombination von Tools selbst **einen Server bereitstellen**, Sie könnten einen **Cloud-Dienst** nutzen, der einen Teil der Arbeit für Sie erledigt, oder andere mögliche Optionen. diff --git a/docs/de/docs/deployment/manually.md b/docs/de/docs/deployment/manually.md index 2b4ed3fad9..2de2913a50 100644 --- a/docs/de/docs/deployment/manually.md +++ b/docs/de/docs/deployment/manually.md @@ -1,30 +1,82 @@ -# Einen Server manuell ausführen – Uvicorn +# Einen Server manuell ausführen { #run-a-server-manually } -Das Wichtigste, was Sie zum Ausführen einer **FastAPI**-Anwendung auf einer entfernten Servermaschine benötigen, ist ein ASGI-Serverprogramm, wie **Uvicorn**. +## Den `fastapi run` Befehl verwenden { #use-the-fastapi-run-command } -Es gibt 3 Hauptalternativen: +Kurz gesagt, nutzen Sie `fastapi run`, um Ihre FastAPI-Anwendung bereitzustellen: -* Uvicorn: ein hochperformanter ASGI-Server. -* Hypercorn: ein ASGI-Server, der unter anderem mit HTTP/2 und Trio kompatibel ist. -* Daphne: Der für Django Channels entwickelte ASGI-Server. +
-## Servermaschine und Serverprogramm +```console +$ fastapi run main.py -Bei den Benennungen gibt es ein kleines Detail, das Sie beachten sollten. 💡 + FastAPI Starting production server 🚀 -Das Wort „**Server**“ bezieht sich häufig sowohl auf den entfernten-/Cloud-Computer (die physische oder virtuelle Maschine) als auch auf das Programm, das auf dieser Maschine ausgeführt wird (z. B. Uvicorn). + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp -Denken Sie einfach daran, wenn Sie „Server“ im Allgemeinen lesen, dass es sich auf eines dieser beiden Dinge beziehen kann. + module 🐍 main.py + + code Importing the FastAPI app object from the module with + the following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +Das würde in den meisten Fällen funktionieren. 😎 + +Sie könnten diesen Befehl beispielsweise verwenden, um Ihre **FastAPI**-App in einem Container, auf einem Server usw. zu starten. + +## ASGI-Server { #asgi-servers } + +Lassen Sie uns ein wenig tiefer in die Details eintauchen. + +FastAPI verwendet einen Standard zum Erstellen von Python-Webframeworks und -Servern, der als ASGI bekannt ist. FastAPI ist ein ASGI-Webframework. + +Das Wichtigste, was Sie benötigen, um eine **FastAPI**-Anwendung (oder eine andere ASGI-Anwendung) auf einer entfernten Servermaschine auszuführen, ist ein ASGI-Serverprogramm wie **Uvicorn**, der standardmäßig im `fastapi`-Kommando enthalten ist. + +Es gibt mehrere Alternativen, einschließlich: + +* Uvicorn: ein hochperformanter ASGI-Server. +* Hypercorn: ein ASGI-Server, der unter anderem kompatibel mit HTTP/2 und Trio ist. +* Daphne: der für Django Channels entwickelte ASGI-Server. +* Granian: Ein Rust HTTP-Server für Python-Anwendungen. +* NGINX Unit: NGINX Unit ist eine leichte und vielseitige Laufzeitumgebung für Webanwendungen. + +## Servermaschine und Serverprogramm { #server-machine-and-server-program } + +Es gibt ein kleines Detail bei den Namen, das Sie beachten sollten. 💡 + +Das Wort „**Server**“ wird häufig verwendet, um sowohl den entfernten/Cloud-Computer (die physische oder virtuelle Maschine) als auch das Programm zu bezeichnen, das auf dieser Maschine läuft (z. B. Uvicorn). + +Denken Sie einfach daran, dass sich „Server“ im Allgemeinen auf eines dieser beiden Dinge beziehen kann. Wenn man sich auf die entfernte Maschine bezieht, wird sie üblicherweise als **Server**, aber auch als **Maschine**, **VM** (virtuelle Maschine) oder **Knoten** bezeichnet. Diese Begriffe beziehen sich auf irgendeine Art von entfernten Rechner, normalerweise unter Linux, auf dem Sie Programme ausführen. -## Das Serverprogramm installieren +## Das Serverprogramm installieren { #install-the-server-program } -Sie können einen ASGI-kompatiblen Server installieren mit: +Wenn Sie FastAPI installieren, wird es mit einem Produktionsserver, Uvicorn, geliefert, und Sie können ihn mit dem `fastapi run` Befehl starten. -//// tab | Uvicorn +Aber Sie können auch ein ASGI-Serverprogramm manuell installieren. -* Uvicorn, ein blitzschneller ASGI-Server, basierend auf uvloop und httptools. +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und dann die Serveranwendung installieren. + +Zum Beispiel, um Uvicorn zu installieren:
@@ -36,39 +88,21 @@ $ pip install "uvicorn[standard]"
-/// tip | "Tipp" +Ein ähnlicher Prozess würde für jedes andere ASGI-Serverprogramm gelten. + +/// tip | Tipp Durch das Hinzufügen von `standard` installiert und verwendet Uvicorn einige empfohlene zusätzliche Abhängigkeiten. -Inklusive `uvloop`, einen hochperformanten Drop-in-Ersatz für `asyncio`, welcher für einen großen Leistungsschub bei der Nebenläufigkeit sorgt. +Dazu gehört `uvloop`, der hochperformante Drop-in-Ersatz für `asyncio`, der den großen Nebenläufigkeits-Performanz-Schub bietet. + +Wenn Sie FastAPI mit etwas wie `pip install "fastapi[standard]"` installieren, erhalten Sie auch `uvicorn[standard]`. /// -//// +## Das Serverprogramm ausführen { #run-the-server-program } -//// tab | Hypercorn - -* Hypercorn, ein ASGI-Server, der auch mit HTTP/2 kompatibel ist. - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -... oder jeden anderen ASGI-Server. - -//// - -## Das Serverprogramm ausführen - -Anschließend können Sie Ihre Anwendung auf die gleiche Weise ausführen, wie Sie es in den Tutorials getan haben, jedoch ohne die Option `--reload`, z. B.: - -//// tab | Uvicorn +Wenn Sie einen ASGI-Server manuell installiert haben, müssen Sie normalerweise einen Importstring in einem speziellen Format übergeben, damit er Ihre FastAPI-Anwendung importiert:
@@ -80,72 +114,36 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
-//// +/// note | Hinweis -//// tab | Hypercorn +Der Befehl `uvicorn main:app` bezieht sich auf: -
+* `main`: die Datei `main.py` (das Python-„Modul“). +* `app`: das Objekt, das innerhalb von `main.py` mit der Zeile `app = FastAPI()` erstellt wurde. -```console -$ hypercorn main:app --bind 0.0.0.0:80 +Es ist äquivalent zu: -Running on 0.0.0.0:8080 over http (CTRL + C to quit) +```Python +from main import app ``` -
- -//// - -/// warning | "Achtung" - -Denken Sie daran, die Option `--reload` zu entfernen, wenn Sie diese verwendet haben. - -Die Option `--reload` verbraucht viel mehr Ressourcen, ist instabiler, usw. - -Sie hilft sehr während der **Entwicklung**, aber Sie sollten sie **nicht** in der **Produktion** verwenden. - /// -## Hypercorn mit Trio +Jedes alternative ASGI-Serverprogramm hätte einen ähnlichen Befehl, Sie können in deren jeweiligen Dokumentationen mehr lesen. -Starlette und **FastAPI** basieren auf AnyIO, welches diese sowohl mit der Python-Standardbibliothek asyncio, als auch mit Trio kompatibel macht. +/// warning | Achtung -Dennoch ist Uvicorn derzeit nur mit asyncio kompatibel und verwendet normalerweise `uvloop`, den leistungsstarken Drop-in-Ersatz für `asyncio`. +Uvicorn und andere Server unterstützen eine `--reload`-Option, die während der Entwicklung nützlich ist. -Wenn Sie jedoch **Trio** direkt verwenden möchten, können Sie **Hypercorn** verwenden, da dieses es unterstützt. ✨ +Die `--reload`-Option verbraucht viel mehr Ressourcen, ist instabiler, usw. -### Hypercorn mit Trio installieren +Sie hilft während der **Entwicklung**, Sie sollten sie jedoch **nicht** in der **Produktion** verwenden. -Zuerst müssen Sie Hypercorn mit Trio-Unterstützung installieren: +/// -
+## Deployment-Konzepte { #deployment-concepts } -```console -$ pip install "hypercorn[trio]" ----> 100% -``` - -
- -### Mit Trio ausführen - -Dann können Sie die Befehlszeilenoption `--worker-class` mit dem Wert `trio` übergeben: - -
- -```console -$ hypercorn main:app --worker-class trio -``` - -
- -Und das startet Hypercorn mit Ihrer Anwendung und verwendet Trio als Backend. - -Jetzt können Sie Trio intern in Ihrer Anwendung verwenden. Oder noch besser: Sie können AnyIO verwenden, sodass Ihr Code sowohl mit Trio als auch asyncio kompatibel ist. 🎉 - -## Konzepte des Deployments - -Obige Beispiele führen das Serverprogramm (z. B. Uvicorn) aus, starten **einen einzelnen Prozess** und überwachen alle IPs (`0.0.0.0`) an einem vordefinierten Port (z. B. `80`). +Diese Beispiele führen das Serverprogramm (z. B. Uvicorn) aus, starten **einen einzelnen Prozess** und überwachen alle IPs (`0.0.0.0`) an einem vordefinierten Port (z. B. `80`). Das ist die Grundidee. Aber Sie möchten sich wahrscheinlich um einige zusätzliche Dinge kümmern, wie zum Beispiel: @@ -153,7 +151,7 @@ Das ist die Grundidee. Aber Sie möchten sich wahrscheinlich um einige zusätzli * Beim Hochfahren ausführen * Neustarts * Replikation (die Anzahl der laufenden Prozesse) -* Arbeitsspeicher +* Speicher * Schritte vor dem Start In den nächsten Kapiteln erzähle ich Ihnen mehr über jedes dieser Konzepte, wie Sie über diese nachdenken, und gebe Ihnen einige konkrete Beispiele mit Strategien für den Umgang damit. 🚀 diff --git a/docs/de/docs/deployment/server-workers.md b/docs/de/docs/deployment/server-workers.md index 5cd282b4b4..169ed822b4 100644 --- a/docs/de/docs/deployment/server-workers.md +++ b/docs/de/docs/deployment/server-workers.md @@ -1,4 +1,4 @@ -# Serverworker – Gunicorn mit Uvicorn +# Serverworker – Uvicorn mit Workern { #server-workers-uvicorn-with-workers } Schauen wir uns die Deployment-Konzepte von früher noch einmal an: @@ -9,123 +9,79 @@ Schauen wir uns die Deployment-Konzepte von früher noch einmal an: * Arbeitsspeicher * Schritte vor dem Start -Bis zu diesem Punkt, in allen Tutorials in der Dokumentation, haben Sie wahrscheinlich ein **Serverprogramm** wie Uvicorn ausgeführt, in einem **einzelnen Prozess**. +Bis zu diesem Punkt, in allen Tutorials in der Dokumentation, haben Sie wahrscheinlich ein **Serverprogramm** ausgeführt, zum Beispiel mit dem `fastapi`-Befehl, der Uvicorn startet, und einen **einzelnen Prozess** ausführt. -Wenn Sie Anwendungen bereitstellen, möchten Sie wahrscheinlich eine gewisse **Replikation von Prozessen**, um **mehrere CPU-Kerne** zu nutzen und mehr Requests bearbeiten zu können. +Wenn Sie Anwendungen bereitstellen, möchten Sie wahrscheinlich eine gewisse **Replikation von Prozessen**, um **mehrere Kerne** zu nutzen und mehr Requests bearbeiten zu können. Wie Sie im vorherigen Kapitel über [Deployment-Konzepte](concepts.md){.internal-link target=_blank} gesehen haben, gibt es mehrere Strategien, die Sie anwenden können. -Hier zeige ich Ihnen, wie Sie **Gunicorn** mit **Uvicorn Workerprozessen** verwenden. +Hier zeige ich Ihnen, wie Sie **Uvicorn** mit **Workerprozessen** verwenden, indem Sie den `fastapi`-Befehl oder den `uvicorn`-Befehl direkt verwenden. -/// info +/// info | Info Wenn Sie Container verwenden, beispielsweise mit Docker oder Kubernetes, erzähle ich Ihnen mehr darüber im nächsten Kapitel: [FastAPI in Containern – Docker](docker.md){.internal-link target=_blank}. -Insbesondere wenn die Anwendung auf **Kubernetes** läuft, werden Sie Gunicorn wahrscheinlich **nicht** verwenden wollen und stattdessen **einen einzelnen Uvicorn-Prozess pro Container** ausführen wollen, aber ich werde Ihnen später in diesem Kapitel mehr darüber erzählen. +Insbesondere wenn die Anwendung auf **Kubernetes** läuft, werden Sie wahrscheinlich **keine** Worker verwenden wollen, und stattdessen **einen einzelnen Uvicorn-Prozess pro Container** ausführen wollen, aber ich werde Ihnen später in diesem Kapitel mehr darüber erzählen. /// -## Gunicorn mit Uvicorn-Workern +## Mehrere Worker { #multiple-workers } -**Gunicorn** ist hauptsächlich ein Anwendungsserver, der den **WSGI-Standard** verwendet. Das bedeutet, dass Gunicorn Anwendungen wie Flask und Django ausliefern kann. Gunicorn selbst ist nicht mit **FastAPI** kompatibel, da FastAPI den neuesten **ASGI-Standard** verwendet. +Sie können mehrere Worker mit der `--workers`-Befehlszeilenoption starten: -Aber Gunicorn kann als **Prozessmanager** arbeiten und Benutzer können ihm mitteilen, welche bestimmte **Workerprozessklasse** verwendet werden soll. Dann würde Gunicorn einen oder mehrere **Workerprozesse** starten, diese Klasse verwendend. +//// tab | `fastapi` -Und **Uvicorn** hat eine **Gunicorn-kompatible Workerklasse**. - -Mit dieser Kombination würde Gunicorn als **Prozessmanager** fungieren und den **Port** und die **IP** abhören. Und er würde die Kommunikation an die Workerprozesse **weiterleiten**, welche die **Uvicorn-Klasse** ausführen. - -Und dann wäre die Gunicorn-kompatible **Uvicorn-Worker**-Klasse dafür verantwortlich, die von Gunicorn gesendeten Daten in den ASGI-Standard zu konvertieren, damit FastAPI diese verwenden kann. - -## Gunicorn und Uvicorn installieren +Wenn Sie den `fastapi`-Befehl verwenden:
```console -$ pip install "uvicorn[standard]" gunicorn +$ fastapi run --workers 4 main.py ----> 100% + FastAPI Starting production server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. ```
-Dadurch wird sowohl Uvicorn mit zusätzlichen `standard`-Packages (um eine hohe Leistung zu erzielen) als auch Gunicorn installiert. +//// -## Gunicorn mit Uvicorn-Workern ausführen +//// tab | `uvicorn` -Dann können Sie Gunicorn ausführen mit: - -
- -```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. -``` - -
- -Sehen wir uns an, was jede dieser Optionen bedeutet: - -* `main:app`: Das ist die gleiche Syntax, die auch von Uvicorn verwendet wird. `main` bedeutet das Python-Modul mit dem Namen `main`, also eine Datei `main.py`. Und `app` ist der Name der Variable, welche die **FastAPI**-Anwendung ist. - * Stellen Sie sich einfach vor, dass `main:app` einer Python-`import`-Anweisung wie der folgenden entspricht: - - ```Python - from main import app - ``` - - * Der Doppelpunkt in `main:app` entspricht also dem Python-`import`-Teil in `from main import app`. - -* `--workers`: Die Anzahl der zu verwendenden Workerprozesse, jeder führt einen Uvicorn-Worker aus, in diesem Fall 4 Worker. - -* `--worker-class`: Die Gunicorn-kompatible Workerklasse zur Verwendung in den Workerprozessen. - * Hier übergeben wir die Klasse, die Gunicorn etwa so importiert und verwendet: - - ```Python - import uvicorn.workers.UvicornWorker - ``` - -* `--bind`: Das teilt Gunicorn die IP und den Port mit, welche abgehört werden sollen, wobei ein Doppelpunkt (`:`) verwendet wird, um die IP und den Port zu trennen. - * Wenn Sie Uvicorn direkt ausführen würden, würden Sie anstelle von `--bind 0.0.0.0:80` (die Gunicorn-Option) stattdessen `--host 0.0.0.0` und `--port 80` verwenden. - -In der Ausgabe können Sie sehen, dass die **PID** (Prozess-ID) jedes Prozesses angezeigt wird (es ist nur eine Zahl). - -Sie können sehen, dass: - -* Der Gunicorn **Prozessmanager** beginnt, mit der PID `19499` (in Ihrem Fall ist es eine andere Nummer). -* Dann beginnt er zu lauschen: `Listening at: http://0.0.0.0:80`. -* Dann erkennt er, dass er die Workerklasse `uvicorn.workers.UvicornWorker` verwenden muss. -* Und dann werden **4 Worker** gestartet, jeder mit seiner eigenen PID: `19511`, `19513`, `19514` und `19515`. - -Gunicorn würde sich bei Bedarf auch um die Verwaltung **beendeter Prozesse** und den **Neustart** von Prozessen kümmern, um die Anzahl der Worker aufrechtzuerhalten. Das hilft also teilweise beim **Neustarts**-Konzept aus der obigen Liste. - -Dennoch möchten Sie wahrscheinlich auch etwas außerhalb haben, um sicherzustellen, dass Gunicorn bei Bedarf **neu gestartet wird**, und er auch **beim Hochfahren ausgeführt wird**, usw. - -## Uvicorn mit Workern - -Uvicorn bietet ebenfalls die Möglichkeit, mehrere **Workerprozesse** zu starten und auszuführen. - -Dennoch sind die Fähigkeiten von Uvicorn zur Abwicklung von Workerprozessen derzeit eingeschränkter als die von Gunicorn. Wenn Sie also einen Prozessmanager auf dieser Ebene (auf der Python-Ebene) haben möchten, ist es vermutlich besser, es mit Gunicorn als Prozessmanager zu versuchen. - -Wie auch immer, Sie würden es so ausführen: +Wenn Sie den `uvicorn`-Befehl direkt verwenden möchten:
@@ -149,15 +105,17 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
+//// + Die einzige neue Option hier ist `--workers`, die Uvicorn anweist, 4 Workerprozesse zu starten. Sie können auch sehen, dass die **PID** jedes Prozesses angezeigt wird, `27365` für den übergeordneten Prozess (dies ist der **Prozessmanager**) und eine für jeden Workerprozess: `27368`, `27369`, `27370` und `27367`. -## Deployment-Konzepte +## Deployment-Konzepte { #deployment-concepts } -Hier haben Sie gesehen, wie Sie mit **Gunicorn** (oder Uvicorn) **Uvicorn-Workerprozesse** verwalten, um die Ausführung der Anwendung zu **parallelisieren**, **mehrere Kerne** der CPU zu nutzen und in der Lage zu sein, **mehr Requests** zu bedienen. +Hier haben Sie gesehen, wie Sie mehrere **Worker** verwenden, um die Ausführung der Anwendung zu **parallelisieren**, **mehrere Kerne** der CPU zu nutzen und in der Lage zu sein, **mehr Requests** zu bearbeiten. -In der Liste der Deployment-Konzepte von oben würde die Verwendung von Workern hauptsächlich beim **Replikation**-Teil und ein wenig bei **Neustarts** helfen, aber Sie müssen sich trotzdem um die anderen kümmern: +In der Liste der Deployment-Konzepte von oben würde die Verwendung von Workern hauptsächlich bei der **Replikation** und ein wenig bei **Neustarts** helfen, aber Sie müssen sich trotzdem um die anderen kümmern: * **Sicherheit – HTTPS** * **Beim Hochfahren ausführen** @@ -166,18 +124,16 @@ In der Liste der Deployment-Konzepte von oben würde die Verwendung von Workern * **Arbeitsspeicher** * **Schritte vor dem Start** -## Container und Docker +## Container und Docker { #containers-and-docker } Im nächsten Kapitel über [FastAPI in Containern – Docker](docker.md){.internal-link target=_blank} werde ich einige Strategien erläutern, die Sie für den Umgang mit den anderen **Deployment-Konzepten** verwenden können. -Ich zeige Ihnen auch das **offizielle Docker-Image**, welches **Gunicorn mit Uvicorn-Workern** und einige Standardkonfigurationen enthält, die für einfache Fälle nützlich sein können. +Ich zeige Ihnen, wie Sie **Ihr eigenes Image von Grund auf erstellen**, um einen einzelnen Uvicorn-Prozess auszuführen. Es ist ein einfacher Vorgang und wahrscheinlich das, was Sie tun möchten, wenn Sie ein verteiltes Containerverwaltungssystem wie **Kubernetes** verwenden. -Dort zeige ich Ihnen auch, wie Sie **Ihr eigenes Image von Grund auf erstellen**, um einen einzelnen Uvicorn-Prozess (ohne Gunicorn) auszuführen. Es ist ein einfacher Vorgang und wahrscheinlich das, was Sie tun möchten, wenn Sie ein verteiltes Containerverwaltungssystem wie **Kubernetes** verwenden. +## Zusammenfassung { #recap } -## Zusammenfassung +Sie können mehrere Workerprozesse mit der `--workers`-CLI-Option über die `fastapi`- oder `uvicorn`-Befehle nutzen, um **Multikern-CPUs** auszunutzen und **mehrere Prozesse parallel** auszuführen. -Sie können **Gunicorn** (oder auch Uvicorn) als Prozessmanager mit Uvicorn-Workern verwenden, um **Multikern-CPUs** zu nutzen und **mehrere Prozesse parallel** auszuführen. - -Sie können diese Tools und Ideen nutzen, wenn Sie **Ihr eigenes Deployment-System** einrichten und sich dabei selbst um die anderen Deployment-Konzepte kümmern. +Sie könnten diese Tools und Ideen nutzen, wenn Sie **Ihr eigenes Deployment-System** einrichten und sich dabei selbst um die anderen Deployment-Konzepte kümmern. Schauen Sie sich das nächste Kapitel an, um mehr über **FastAPI** mit Containern (z. B. Docker und Kubernetes) zu erfahren. Sie werden sehen, dass diese Tools auch einfache Möglichkeiten bieten, die anderen **Deployment-Konzepte** zu lösen. ✨ diff --git a/docs/de/docs/deployment/versions.md b/docs/de/docs/deployment/versions.md index 2d10ac4b64..d7ecb762e9 100644 --- a/docs/de/docs/deployment/versions.md +++ b/docs/de/docs/deployment/versions.md @@ -1,4 +1,4 @@ -# Über FastAPI-Versionen +# Über FastAPI-Versionen { #about-fastapi-versions } **FastAPI** wird bereits in vielen Anwendungen und Systemen produktiv eingesetzt. Und die Testabdeckung wird bei 100 % gehalten. Aber seine Entwicklung geht immer noch schnell voran. @@ -8,41 +8,41 @@ Aus diesem Grund sind die aktuellen Versionen immer noch `0.x.x`, was darauf hin Sie können jetzt Produktionsanwendungen mit **FastAPI** erstellen (und das tun Sie wahrscheinlich schon seit einiger Zeit), Sie müssen nur sicherstellen, dass Sie eine Version verwenden, die korrekt mit dem Rest Ihres Codes funktioniert. -## `fastapi`-Version pinnen +## Ihre `fastapi`-Version pinnen { #pin-your-fastapi-version } Als Erstes sollten Sie die Version von **FastAPI**, die Sie verwenden, an die höchste Version „pinnen“, von der Sie wissen, dass sie für Ihre Anwendung korrekt funktioniert. -Angenommen, Sie verwenden in Ihrer Anwendung die Version `0.45.0`. +Angenommen, Sie verwenden in Ihrer App die Version `0.112.0`. Wenn Sie eine `requirements.txt`-Datei verwenden, können Sie die Version wie folgt angeben: ```txt -fastapi==0.45.0 +fastapi[standard]==0.112.0 ``` -Das würde bedeuten, dass Sie genau die Version `0.45.0` verwenden. +Das würde bedeuten, dass Sie genau die Version `0.112.0` verwenden. Oder Sie können sie auch anpinnen mit: ```txt -fastapi>=0.45.0,<0.46.0 +fastapi[standard]>=0.112.0,<0.113.0 ``` -Das würde bedeuten, dass Sie eine Version `0.45.0` oder höher verwenden würden, aber kleiner als `0.46.0`, beispielsweise würde eine Version `0.45.2` immer noch akzeptiert. +Das würde bedeuten, dass Sie eine Version `0.112.0` oder höher verwenden würden, aber kleiner als `0.113.0`, beispielsweise würde eine Version `0.112.2` immer noch akzeptiert. -Wenn Sie zum Verwalten Ihrer Installationen andere Tools wie Poetry, Pipenv oder andere verwenden, sie verfügen alle über eine Möglichkeit, bestimmte Versionen für Ihre Packages zu definieren. +Wenn Sie zum Verwalten Ihrer Installationen andere Tools wie `uv`, Poetry, Pipenv oder andere verwenden, sie verfügen alle über eine Möglichkeit, bestimmte Versionen für Ihre Packages zu definieren. -## Verfügbare Versionen +## Verfügbare Versionen { #available-versions } -Die verfügbaren Versionen können Sie in den [Release Notes](../release-notes.md){.internal-link target=_blank} einsehen (z. B. um zu überprüfen, welches die neueste Version ist). +Die verfügbaren Versionen können Sie in den [Versionshinweisen](../release-notes.md){.internal-link target=_blank} einsehen (z. B. um zu überprüfen, welches die neueste Version ist). -## Über Versionen +## Über Versionen { #about-versions } Gemäß den Konventionen zur semantischen Versionierung könnte jede Version unter `1.0.0` potenziell nicht abwärtskompatible Änderungen hinzufügen. FastAPI folgt auch der Konvention, dass jede „PATCH“-Versionsänderung für Bugfixes und abwärtskompatible Änderungen gedacht ist. -/// tip | "Tipp" +/// tip | Tipp Der „PATCH“ ist die letzte Zahl, zum Beispiel ist in `0.2.3` die PATCH-Version `3`. @@ -56,15 +56,15 @@ fastapi>=0.45.0,<0.46.0 Nicht abwärtskompatible Änderungen und neue Funktionen werden in „MINOR“-Versionen hinzugefügt. -/// tip | "Tipp" +/// tip | Tipp „MINOR“ ist die Zahl in der Mitte, zum Beispiel ist in `0.2.3` die MINOR-Version `2`. /// -## Upgrade der FastAPI-Versionen +## Upgrade der FastAPI-Versionen { #upgrading-the-fastapi-versions } -Sie sollten Tests für Ihre Anwendung hinzufügen. +Sie sollten Tests für Ihre App hinzufügen. Mit **FastAPI** ist das sehr einfach (dank Starlette), schauen Sie sich die Dokumentation an: [Testen](../tutorial/testing.md){.internal-link target=_blank} @@ -72,7 +72,7 @@ Nachdem Sie Tests erstellt haben, können Sie die **FastAPI**-Version auf eine n Wenn alles funktioniert oder nachdem Sie die erforderlichen Änderungen vorgenommen haben und alle Ihre Tests bestehen, können Sie Ihr `fastapi` an die neue aktuelle Version pinnen. -## Über Starlette +## Über Starlette { #about-starlette } Sie sollten die Version von `starlette` nicht pinnen. @@ -80,13 +80,14 @@ Verschiedene Versionen von **FastAPI** verwenden eine bestimmte neuere Version v Sie können **FastAPI** also einfach die korrekte Starlette-Version verwenden lassen. -## Über Pydantic +## Über Pydantic { #about-pydantic } Pydantic integriert die Tests für **FastAPI** in seine eigenen Tests, sodass neue Versionen von Pydantic (über `1.0.0`) immer mit FastAPI kompatibel sind. -Sie können Pydantic an jede für Sie geeignete Version über `1.0.0` und unter `2.0.0` anpinnen. +Sie können Pydantic an jede für Sie geeignete Version über `1.0.0` anpinnen. Zum Beispiel: + ```txt -pydantic>=1.2.0,<2.0.0 +pydantic>=2.7.0,<3.0.0 ``` diff --git a/docs/de/docs/environment-variables.md b/docs/de/docs/environment-variables.md new file mode 100644 index 0000000000..9d8c9f75c5 --- /dev/null +++ b/docs/de/docs/environment-variables.md @@ -0,0 +1,298 @@ +# Umgebungsvariablen { #environment-variables } + +/// tip | Tipp + +Wenn Sie bereits wissen, was „Umgebungsvariablen“ sind und wie man sie verwendet, können Sie dies überspringen. + +/// + +Eine Umgebungsvariable (auch bekannt als „**env var**“) ist eine Variable, die **außerhalb** des Python-Codes im **Betriebssystem** lebt und von Ihrem Python-Code (oder auch von anderen Programmen) gelesen werden kann. + +Umgebungsvariablen können nützlich sein, um **Einstellungen** der Anwendung zu handhaben, als Teil der **Installation** von Python usw. + +## Umgebungsvariablen erstellen und verwenden { #create-and-use-env-vars } + +Sie können Umgebungsvariablen in der **Shell (Terminal)** erstellen und verwenden, ohne Python zu benötigen: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Sie können eine Umgebungsvariable MY_NAME erstellen mit +$ export MY_NAME="Wade Wilson" + +// Dann können Sie sie mit anderen Programmen verwenden, etwa +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Erstellen Sie eine Umgebungsvariable MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Verwenden Sie sie mit anderen Programmen, etwa +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Umgebungsvariablen in Python lesen { #read-env-vars-in-python } + +Sie können auch Umgebungsvariablen **außerhalb** von Python erstellen, im Terminal (oder mit jeder anderen Methode) und sie dann **in Python** lesen. + +Zum Beispiel könnten Sie eine Datei `main.py` haben mit: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip | Tipp + +Das zweite Argument von `os.getenv()` ist der Defaultwert, der zurückgegeben wird. + +Wenn er nicht angegeben wird, ist er standardmäßig `None`. Hier geben wir `"World"` als den zu verwendenden Defaultwert an. + +/// + +Dann könnten Sie das Python-Programm aufrufen: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Hier setzen wir die Umgebungsvariable noch nicht +$ python main.py + +// Da wir die Umgebungsvariable nicht gesetzt haben, erhalten wir den Defaultwert + +Hello World from Python + +// Aber wenn wir zuerst eine Umgebungsvariable erstellen +$ export MY_NAME="Wade Wilson" + +// Und dann das Programm erneut aufrufen +$ python main.py + +// Jetzt kann es die Umgebungsvariable lesen + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Hier setzen wir die Umgebungsvariable noch nicht +$ python main.py + +// Da wir die Umgebungsvariable nicht gesetzt haben, erhalten wir den Defaultwert + +Hello World from Python + +// Aber wenn wir zuerst eine Umgebungsvariable erstellen +$ $Env:MY_NAME = "Wade Wilson" + +// Und dann das Programm erneut aufrufen +$ python main.py + +// Jetzt kann es die Umgebungsvariable lesen + +Hello Wade Wilson from Python +``` + +
+ +//// + +Da Umgebungsvariablen außerhalb des Codes gesetzt werden können, aber vom Code gelesen werden können und nicht mit den restlichen Dateien gespeichert (in `git` committet) werden müssen, werden sie häufig für Konfigurationen oder **Einstellungen** verwendet. + +Sie können auch eine Umgebungsvariable nur für einen **spezifischen Programmaufruf** erstellen, die nur für dieses Programm und nur für dessen Dauer verfügbar ist. + +Um dies zu tun, erstellen Sie sie direkt vor dem Programmaufruf, in derselben Zeile: + +
+ +```console +// Erstellen Sie eine Umgebungsvariable MY_NAME in der Zeile für diesen Programmaufruf +$ MY_NAME="Wade Wilson" python main.py + +// Jetzt kann es die Umgebungsvariable lesen + +Hello Wade Wilson from Python + +// Die Umgebungsvariable existiert danach nicht mehr +$ python main.py + +Hello World from Python +``` + +
+ +/// tip | Tipp + +Sie können mehr darüber lesen auf The Twelve-Factor App: Config. + +/// + +## Typen und Validierung { #types-and-validation } + +Diese Umgebungsvariablen können nur **Textstrings** handhaben, da sie extern zu Python sind und kompatibel mit anderen Programmen und dem Rest des Systems (und sogar mit verschiedenen Betriebssystemen, wie Linux, Windows, macOS) sein müssen. + +Das bedeutet, dass **jeder Wert**, der in Python von einer Umgebungsvariable gelesen wird, **ein `str` sein wird**, und jede Konvertierung in einen anderen Typ oder jede Validierung muss im Code vorgenommen werden. + +Sie werden mehr darüber lernen, wie man Umgebungsvariablen zur Handhabung von **Anwendungseinstellungen** verwendet, im [Handbuch für fortgeschrittene Benutzer – Einstellungen und Umgebungsvariablen](./advanced/settings.md){.internal-link target=_blank}. + +## `PATH`-Umgebungsvariable { #path-environment-variable } + +Es gibt eine **spezielle** Umgebungsvariable namens **`PATH`**, die von den Betriebssystemen (Linux, macOS, Windows) verwendet wird, um Programme zu finden, die ausgeführt werden sollen. + +Der Wert der Variable `PATH` ist ein langer String, der aus Verzeichnissen besteht, die auf Linux und macOS durch einen Doppelpunkt `:` und auf Windows durch ein Semikolon `;` getrennt sind. + +Zum Beispiel könnte die `PATH`-Umgebungsvariable so aussehen: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Das bedeutet, dass das System nach Programmen in den Verzeichnissen suchen sollte: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +Das bedeutet, dass das System nach Programmen in den Verzeichnissen suchen sollte: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +Wenn Sie einen **Befehl** im Terminal eingeben, **sucht** das Betriebssystem nach dem Programm in **jedem dieser Verzeichnisse**, die in der `PATH`-Umgebungsvariablen aufgeführt sind. + +Zum Beispiel, wenn Sie `python` im Terminal eingeben, sucht das Betriebssystem nach einem Programm namens `python` im **ersten Verzeichnis** in dieser Liste. + +Wenn es es findet, wird es **benutzt**. Andernfalls sucht es weiter in den **anderen Verzeichnissen**. + +### Python installieren und den `PATH` aktualisieren { #installing-python-and-updating-the-path } + +Wenn Sie Python installieren, könnten Sie gefragt werden, ob Sie die `PATH`-Umgebungsvariable aktualisieren möchten. + +//// tab | Linux, macOS + +Angenommen, Sie installieren Python und es landet in einem Verzeichnis `/opt/custompython/bin`. + +Wenn Sie erlauben, die `PATH`-Umgebungsvariable zu aktualisieren, fügt der Installer `/opt/custompython/bin` zur `PATH`-Umgebungsvariable hinzu. + +Das könnte so aussehen: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +Auf diese Weise, wenn Sie `python` im Terminal eingeben, findet das System das Python-Programm in `/opt/custompython/bin` (das letzte Verzeichnis) und verwendet dieses. + +//// + +//// tab | Windows + +Angenommen, Sie installieren Python und es landet in einem Verzeichnis `C:\opt\custompython\bin`. + +Wenn Sie erlauben, die `PATH`-Umgebungsvariable zu aktualisieren, fügt der Installer `C:\opt\custompython\bin` zur `PATH`-Umgebungsvariable hinzu. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +Auf diese Weise, wenn Sie `python` im Terminal eingeben, findet das System das Python-Programm in `C:\opt\custompython\bin` (das letzte Verzeichnis) und verwendet dieses. + +//// + +Also, wenn Sie tippen: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +Das System wird das `python` Programm in `/opt/custompython/bin` **finden** und es ausführen. + +Es wäre ungefähr gleichbedeutend mit der Eingabe von: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +Das System wird das `python` Programm in `C:\opt\custompython\bin\python` **finden** und es ausführen. + +Es wäre ungefähr gleichbedeutend mit der Eingabe von: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +Diese Informationen werden nützlich sein, wenn Sie über [Virtuelle Umgebungen](virtual-environments.md){.internal-link target=_blank} lernen. + +## Fazit { #conclusion } + +Mit diesem Wissen sollten Sie ein grundlegendes Verständnis davon haben, was **Umgebungsvariablen** sind und wie man sie in Python verwendet. + +Sie können auch mehr darüber in der Wikipedia zu Umgebungsvariablen lesen. + +In vielen Fällen ist es nicht sehr offensichtlich, wie Umgebungsvariablen nützlich und sofort anwendbar sein könnten. Aber sie tauchen immer wieder in vielen verschiedenen Szenarien auf, wenn Sie entwickeln, deshalb ist es gut, darüber Bescheid zu wissen. + +Zum Beispiel werden Sie diese Informationen im nächsten Abschnitt über [Virtuelle Umgebungen](virtual-environments.md) benötigen. diff --git a/docs/de/docs/fastapi-cli.md b/docs/de/docs/fastapi-cli.md new file mode 100644 index 0000000000..ab9c8373e9 --- /dev/null +++ b/docs/de/docs/fastapi-cli.md @@ -0,0 +1,75 @@ +# FastAPI CLI { #fastapi-cli } + +**FastAPI CLI** ist ein Kommandozeilenprogramm, mit dem Sie Ihre FastAPI-App bereitstellen, Ihr FastAPI-Projekt verwalten und mehr. + +Wenn Sie FastAPI installieren (z. B. mit `pip install "fastapi[standard]"`), wird ein Package namens `fastapi-cli` mitgeliefert, das den Befehl `fastapi` im Terminal bereitstellt. + +Um Ihre FastAPI-App für die Entwicklung auszuführen, können Sie den Befehl `fastapi dev` verwenden: + +
+ +```console +$ fastapi dev main.py + + FastAPI Starting development server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to + quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. +``` + +
+ +Das Kommandozeilenprogramm namens `fastapi` ist das **FastAPI CLI**. + +FastAPI CLI nimmt den Pfad zu Ihrem Python-Programm (z. B. `main.py`), erkennt automatisch die `FastAPI`-Instanz (häufig `app` genannt), bestimmt den korrekten Importprozess und stellt sie dann bereit. + +Für die Produktion würden Sie stattdessen `fastapi run` verwenden. 🚀 + +Intern verwendet das **FastAPI CLI** Uvicorn, einen leistungsstarken, produktionsreifen, ASGI-Server. 😎 + +## `fastapi dev` { #fastapi-dev } + +Das Ausführen von `fastapi dev` startet den Entwicklermodus. + +Standardmäßig ist **Autoreload** aktiviert, das den Server automatisch neu lädt, wenn Sie Änderungen an Ihrem Code vornehmen. Dies ist ressourcenintensiv und könnte weniger stabil sein als wenn es deaktiviert ist. Sie sollten es nur für die Entwicklung verwenden. Es horcht auch auf der IP-Adresse `127.0.0.1`, die die IP für Ihre Maschine ist, um nur mit sich selbst zu kommunizieren (`localhost`). + +## `fastapi run` { #fastapi-run } + +Das Ausführen von `fastapi run` startet FastAPI standardmäßig im Produktionsmodus. + +Standardmäßig ist **Autoreload** deaktiviert. Es horcht auch auf der IP-Adresse `0.0.0.0`, was alle verfügbaren IP-Adressen bedeutet, so wird es öffentlich zugänglich für jeden, der mit der Maschine kommunizieren kann. So würden Sie es normalerweise in der Produktion ausführen, beispielsweise in einem Container. + +In den meisten Fällen würden (und sollten) Sie einen „Terminierungsproxy“ haben, der HTTPS für Sie verwaltet. Dies hängt davon ab, wie Sie Ihre Anwendung bereitstellen. Ihr Anbieter könnte dies für Sie erledigen, oder Sie müssen es selbst einrichten. + +/// tip | Tipp + +Sie können mehr darüber in der [Deployment-Dokumentation](deployment/index.md){.internal-link target=_blank} erfahren. + +/// diff --git a/docs/de/docs/features.md b/docs/de/docs/features.md index 8fdf426229..0b51e9737a 100644 --- a/docs/de/docs/features.md +++ b/docs/de/docs/features.md @@ -1,21 +1,21 @@ -# Merkmale +# Merkmale { #features } -## FastAPI Merkmale +## FastAPI Merkmale { #fastapi-features } **FastAPI** ermöglicht Ihnen Folgendes: -### Basiert auf offenen Standards +### Basiert auf offenen Standards { #based-on-open-standards } -* OpenAPI für die Erstellung von APIs, inklusive Deklarationen von Pfad-Operationen, Parametern, Requestbodys, Sicherheit, usw. +* OpenAPI für die Erstellung von APIs, inklusive Deklarationen von Pfad-Operationen, Parametern, Requestbodys, Sicherheit, usw. * Automatische Dokumentation der Datenmodelle mit JSON Schema (da OpenAPI selbst auf JSON Schema basiert). * Um diese Standards herum entworfen, nach sorgfältigem Studium. Statt einer nachträglichen Schicht darüber. * Dies ermöglicht auch automatische **Client-Code-Generierung** in vielen Sprachen. -### Automatische Dokumentation +### Automatische Dokumentation { #automatic-docs } Interaktive API-Dokumentation und erkundbare Web-Benutzeroberflächen. Da das Framework auf OpenAPI basiert, gibt es mehrere Optionen, zwei sind standardmäßig vorhanden. -* Swagger UI, bietet interaktive Erkundung, testen und rufen Sie ihre API direkt im Webbrowser auf. +* Swagger UI, bietet interaktive Erkundung, testen und rufen Sie Ihre API direkt im Webbrowser auf. ![Swagger UI Interaktion](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) @@ -23,22 +23,21 @@ Interaktive API-Dokumentation und erkundbare Web-Benutzeroberflächen. Da das Fr ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Nur modernes Python +### Nur modernes Python { #just-modern-python } -Alles basiert auf **Python 3.8 Typ**-Deklarationen (dank Pydantic). Es muss keine neue Syntax gelernt werden, nur standardisiertes modernes Python. +Alles basiert auf Standard-**Python-Typ**deklarationen (dank Pydantic). Es muss keine neue Syntax gelernt werden, nur standardisiertes modernes Python. Wenn Sie eine zweiminütige Auffrischung benötigen, wie man Python-Typen verwendet (auch wenn Sie FastAPI nicht benutzen), schauen Sie sich das kurze Tutorial an: [Einführung in Python-Typen](python-types.md){.internal-link target=_blank}. Sie schreiben Standard-Python mit Typen: ```Python -from typing import List, Dict from datetime import date from pydantic import BaseModel -# Deklarieren Sie eine Variable als ein `str` -# und bekommen Sie Editor-Unterstütung innerhalb der Funktion +# Deklarieren Sie eine Variable als ein str +# und bekommen Sie Editor-Unterstützung innerhalb der Funktion def main(user_id: str): return user_id @@ -64,25 +63,25 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` -/// info +/// info | Info `**second_user_data` bedeutet: -Nimm die Schlüssel-Wert-Paare des `second_user_data` Dicts und übergib sie direkt als Schlüsselwort-Argumente. Äquivalent zu: `User(id=4, name="Mary", joined="2018-11-30")`. +Nimm die Schlüssel-Wert-Paare des `second_user_data` Dicts und übergebe sie direkt als Schlüsselwort-Argumente. Äquivalent zu: `User(id=4, name="Mary", joined="2018-11-30")` /// -### Editor Unterstützung +### Editor Unterstützung { #editor-support } Das ganze Framework wurde so entworfen, dass es einfach und intuitiv zu benutzen ist; alle Entscheidungen wurden auf mehreren Editoren getestet, sogar vor der Implementierung, um die bestmögliche Entwicklererfahrung zu gewährleisten. -In der letzten Python-Entwickler-Umfrage wurde klar, dass die meist genutzte Funktion die „Autovervollständigung“ ist. +In den Python-Entwickler-Umfragen wird klar, dass die meist genutzte Funktion die „Autovervollständigung“ ist. Das gesamte **FastAPI**-Framework ist darauf ausgelegt, das zu erfüllen. Autovervollständigung funktioniert überall. Sie werden selten noch mal in der Dokumentation nachschauen müssen. -So kann ihr Editor Sie unterstützen: +So kann Ihr Editor Sie unterstützen: * in Visual Studio Code: @@ -92,23 +91,23 @@ So kann ihr Editor Sie unterstützen: ![Editor Unterstützung](https://fastapi.tiangolo.com/img/pycharm-completion.png) -Sie bekommen sogar Autovervollständigung an Stellen, an denen Sie dies vorher nicht für möglich gehalten hätten. Zum Beispiel der `price` Schlüssel in einem JSON Datensatz (dieser könnte auch verschachtelt sein), der aus einer Anfrage kommt. +Sie bekommen sogar Autovervollständigung an Stellen, an denen Sie dies vorher nicht für möglich gehalten hätten. Zum Beispiel der `price` Schlüssel in einem JSON Datensatz (dieser könnte auch verschachtelt sein), der aus einem Request kommt. Nie wieder falsche Schlüsselnamen tippen, Hin und Herhüpfen zwischen der Dokumentation, Hoch- und Runterscrollen, um herauszufinden, ob es `username` oder `user_name` war. -### Kompakt +### Kompakt { #short } Es gibt für alles sensible **Defaultwerte**, mit optionaler Konfiguration überall. Alle Parameter können feinjustiert werden, damit sie tun, was Sie benötigen, und die API definieren, die Sie brauchen. Aber standardmäßig **„funktioniert einfach alles“**. -### Validierung +### Validierung { #validation } * Validierung für die meisten (oder alle?) Python-**Datentypen**, hierzu gehören: * JSON Objekte (`dict`). * JSON Listen (`list`), die den Typ ihrer Elemente definieren. * Strings (`str`) mit definierter minimaler und maximaler Länge. - * Zahlen (`int`, `float`) mit Mindest- und Maximal-Werten, usw. + * Zahlen (`int`, `float`) mit Mindest- und Maximalwerten, usw. * Validierung für mehr exotische Typen, wie: * URL. @@ -118,49 +117,49 @@ Aber standardmäßig **„funktioniert einfach alles“**. Die gesamte Validierung übernimmt das gut etablierte und robuste **Pydantic**. -### Sicherheit und Authentifizierung +### Sicherheit und Authentifizierung { #security-and-authentication } -Sicherheit und Authentifizierung ist integriert. Ohne Kompromisse bei Datenbanken oder Datenmodellen. +Sicherheit und Authentifizierung sind integriert. Ohne Kompromisse bei Datenbanken oder Datenmodellen. Alle in OpenAPI definierten Sicherheitsschemas, inklusive: -* HTTP Basic Authentifizierung. +* HTTP Basic. * **OAuth2** (auch mit **JWT Tokens**). Siehe dazu das Tutorial zu [OAuth2 mit JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. * API Schlüssel in: - * Header-Feldern. - * Anfrageparametern. + * Headern. + * Query-Parametern. * Cookies, usw. Zusätzlich alle Sicherheitsfunktionen von Starlette (inklusive **Session Cookies**). -Alles als wiederverwendbare Tools und Komponenten gebaut, die einfach in ihre Systeme, Datenspeicher, relationalen und nicht-relationalen Datenbanken, usw., integriert werden können. +Alles als wiederverwendbare Tools und Komponenten gebaut, die einfach in Ihre Systeme, Datenspeicher, relationale und nicht-relationale Datenbanken, usw., integriert werden können. -### Einbringen von Abhängigkeiten (Dependency Injection) +### Dependency Injection { #dependency-injection } -FastAPI enthält ein extrem einfach zu verwendendes, aber extrem mächtiges Dependency Injection System. +FastAPI enthält ein extrem einfach zu verwendendes, aber extrem mächtiges Dependency Injection System. * Selbst Abhängigkeiten können Abhängigkeiten haben, woraus eine Hierarchie oder ein **„Graph“ von Abhängigkeiten** entsteht. * Alles **automatisch gehandhabt** durch das Framework. -* Alle Abhängigkeiten können Daten von Anfragen anfordern und das Verhalten von **Pfadoperationen** und der automatisierten Dokumentation **modifizieren**. +* Alle Abhängigkeiten können Daten von Requests anfordern und das Verhalten von **Pfadoperationen** und der automatisierten Dokumentation **modifizieren**. * **Automatische Validierung** selbst für solche Parameter von *Pfadoperationen*, welche in Abhängigkeiten definiert sind. * Unterstützung für komplexe Authentifizierungssysteme, **Datenbankverbindungen**, usw. * **Keine Kompromisse** bei Datenbanken, Frontends, usw., sondern einfache Integration mit allen. -### Unbegrenzte Erweiterungen +### Unbegrenzte Erweiterungen { #unlimited-plug-ins } Oder mit anderen Worten, sie werden nicht benötigt. Importieren und nutzen Sie den Code, den Sie brauchen. Jede Integration wurde so entworfen, dass sie so einfach zu nutzen ist (mit Abhängigkeiten), dass Sie eine Erweiterung für Ihre Anwendung mit nur zwei Zeilen Code erstellen können. Hierbei nutzen Sie die gleiche Struktur und Syntax, wie bei *Pfadoperationen*. -### Getestet +### Getestet { #tested } * 100 % Testabdeckung. -* 100 % Typen annotiert. +* 100 % Typen annotiert. * Verwendet in Produktionsanwendungen. -## Starlette's Merkmale +## Starlette Merkmale { #starlette-features } -**FastAPI** ist vollkommen kompatibel (und basiert auf) Starlette. Das bedeutet, wenn Sie eigenen Starlette Quellcode haben, funktioniert der. +**FastAPI** ist vollkommen kompatibel (und basiert auf) Starlette. Das bedeutet, wenn Sie eigenen Starlette Quellcode haben, funktioniert der. `FastAPI` ist tatsächlich eine Unterklasse von `Starlette`. Wenn Sie also bereits Starlette kennen oder benutzen, das meiste funktioniert genau so. @@ -168,31 +167,31 @@ Mit **FastAPI** bekommen Sie alles von **Starlette** (da FastAPI nur Starlette a * Schwer beeindruckende Performanz. Es ist eines der schnellsten Python-Frameworks, auf Augenhöhe mit **NodeJS** und **Go**. * **WebSocket**-Unterstützung. -* Hintergrundaufgaben im selben Prozess. -* Ereignisse beim Starten und Herunterfahren. -* Testclient baut auf HTTPX auf. +* Hintergrundtasks im selben Prozess. +* Startup- und Shutdown-Events. +* Testclient basierend auf HTTPX. * **CORS**, GZip, statische Dateien, Responses streamen. * **Sitzungs- und Cookie**-Unterstützung. * 100 % Testabdeckung. * 100 % Typen annotierte Codebasis. -## Pydantic's Merkmale +## Pydantic Merkmale { #pydantic-features } **FastAPI** ist vollkommen kompatibel (und basiert auf) Pydantic. Das bedeutet, wenn Sie eigenen Pydantic Quellcode haben, funktioniert der. -Inklusive externer Bibliotheken, die auf Pydantic basieren, wie ORMs, ODMs für Datenbanken. +Inklusive externer Bibliotheken, die auf Pydantic basieren, wie ORMs, ODMs für Datenbanken. -Daher können Sie in vielen Fällen das Objekt einer Anfrage **direkt zur Datenbank** schicken, weil alles automatisch validiert wird. +Daher können Sie in vielen Fällen das Objekt eines Requests **direkt zur Datenbank** schicken, weil alles automatisch validiert wird. -Das gleiche gilt auch für die andere Richtung: Sie können in vielen Fällen das Objekt aus der Datenbank **direkt zum Client** schicken. +Das gleiche gilt auch für die andere Richtung: Sie können in vielen Fällen das Objekt aus der Datenbank **direkt zum Client** senden. Mit **FastAPI** bekommen Sie alle Funktionen von **Pydantic** (da FastAPI für die gesamte Datenverarbeitung Pydantic nutzt): * **Kein Kopfzerbrechen**: * Keine neue Schemadefinition-Mikrosprache zu lernen. * Wenn Sie Pythons Typen kennen, wissen Sie, wie man Pydantic verwendet. -* Gutes Zusammenspiel mit Ihrer/Ihrem **IDE/Linter/Gehirn**: - * Weil Pydantics Datenstrukturen einfach nur Instanzen ihrer definierten Klassen sind; Autovervollständigung, Linting, mypy und ihre Intuition sollten alle einwandfrei mit ihren validierten Daten funktionieren. +* Gutes Zusammenspiel mit Ihrer/Ihrem **IDE/Linter/Gehirn**: + * Weil Pydantics Datenstrukturen einfach nur Instanzen ihrer definierten Klassen sind; Autovervollständigung, Linting, mypy und Ihre Intuition sollten alle einwandfrei mit Ihren validierten Daten funktionieren. * Validierung von **komplexen Strukturen**: * Benutzung von hierarchischen Pydantic-Modellen, Python-`typing`s `List` und `Dict`, etc. * Die Validierer erlauben es, komplexe Datenschemen klar und einfach zu definieren, überprüft und dokumentiert als JSON Schema. diff --git a/docs/de/docs/help-fastapi.md b/docs/de/docs/help-fastapi.md index 2c84a5e5b9..6cbafca0b4 100644 --- a/docs/de/docs/help-fastapi.md +++ b/docs/de/docs/help-fastapi.md @@ -1,74 +1,75 @@ -# FastAPI helfen – Hilfe erhalten +# FastAPI helfen – Hilfe erhalten { #help-fastapi-get-help } -Gefällt Ihnen **FastAPI**? +Mögen Sie **FastAPI**? Möchten Sie FastAPI, anderen Benutzern und dem Autor helfen? Oder möchten Sie Hilfe zu **FastAPI** erhalten? -Es gibt sehr einfache Möglichkeiten zu helfen (manche erfordern nur ein oder zwei Klicks). +Es gibt sehr einfache Möglichkeiten zu helfen (einige erfordern nur ein oder zwei Klicks). -Und es gibt auch viele Möglichkeiten, Hilfe zu bekommen. +Und es gibt auch mehrere Möglichkeiten, Hilfe zu bekommen. -## Newsletter abonnieren +## Newsletter abonnieren { #subscribe-to-the-newsletter } -Sie können den (unregelmäßig erscheinenden) [**FastAPI and Friends**-Newsletter](newsletter.md){.internal-link target=_blank} abonnieren, um auf dem Laufenden zu bleiben: +Sie können den (unregelmäßigen) [**FastAPI and friends**-Newsletter](newsletter.md){.internal-link target=_blank} abonnieren, um über folgende Themen informiert zu bleiben: -* Neuigkeiten über FastAPI and Friends 🚀 +* Neuigkeiten über FastAPI und Freunde 🚀 * Anleitungen 📝 * Funktionen ✨ * Breaking Changes 🚨 * Tipps und Tricks ✅ -## FastAPI auf Twitter folgen -Folgen Sie @fastapi auf **Twitter**, um die neuesten Nachrichten über **FastAPI** zu erhalten. 🐦 +## FastAPI auf X (Twitter) folgen { #follow-fastapi-on-x-twitter } -## **FastAPI** auf GitHub einen Stern geben +Folgen Sie @fastapi auf **X (Twitter)**, um die neuesten Nachrichten über **FastAPI** zu erhalten. 🐦 -Sie können FastAPI auf GitHub „starren“ (durch Klicken auf den Stern-Button oben rechts): https://github.com/fastapi/fastapi. ⭐️ +## **FastAPI** auf GitHub einen Stern geben { #star-fastapi-in-github } + +Sie können FastAPI auf GitHub „starren“ (klicken Sie auf den Stern-Button oben rechts): https://github.com/fastapi/fastapi. ⭐️ Durch das Hinzufügen eines Sterns können andere Benutzer es leichter finden und sehen, dass es für andere bereits nützlich war. -## Das GitHub-Repository auf Releases beobachten +## Das GitHub-Repository auf Releases überwachen { #watch-the-github-repository-for-releases } -Sie können FastAPI in GitHub beobachten (Klicken Sie oben rechts auf den Button „watch“): https://github.com/fastapi/fastapi. 👀 +Sie können FastAPI auf GitHub „beobachten“ (klicken Sie auf den „watch“-Button oben rechts): https://github.com/fastapi/fastapi. 👀 Dort können Sie „Releases only“ auswählen. -Auf diese Weise erhalten Sie Benachrichtigungen (per E-Mail), wenn es einen neuen Release (eine neue Version) von **FastAPI** mit Fehlerbehebungen und neuen Funktionen gibt. +Auf diese Weise erhalten Sie Benachrichtigungen (per E-Mail), wenn es ein neues Release (eine neue Version) von **FastAPI** mit Bugfixes und neuen Funktionen gibt. -## Mit dem Autor vernetzen +## Mit dem Autor vernetzen { #connect-with-the-author } -Sie können sich mit mir (Sebastián Ramírez / `tiangolo`), dem Autor, verbinden. +Sie können sich mit mir (Sebastián Ramírez / `tiangolo`), dem Autor, vernetzen. -Insbesondere: +Sie können: -* Folgen Sie mir auf **GitHub**. - * Finden Sie andere Open-Source-Projekte, die ich erstellt habe und die Ihnen helfen könnten. - * Folgen Sie mir, um mitzubekommen, wenn ich ein neues Open-Source-Projekt erstelle. -* Folgen Sie mir auf **Twitter** oder Mastodon. - * Berichten Sie mir, wie Sie FastAPI verwenden (das höre ich gerne). - * Bekommen Sie mit, wenn ich Ankündigungen mache oder neue Tools veröffentliche. - * Sie können auch @fastapi auf Twitter folgen (ein separates Konto). -* Folgen Sie mir auf **LinkedIn**. - * Bekommen Sie mit, wenn ich Ankündigungen mache oder neue Tools veröffentliche (obwohl ich Twitter häufiger verwende 🤷‍♂). -* Lesen Sie, was ich schreibe (oder folgen Sie mir) auf **Dev.to** oder **Medium**. - * Lesen Sie andere Ideen, Artikel, und erfahren Sie mehr über die von mir erstellten Tools. - * Folgen Sie mir, um zu lesen, wenn ich etwas Neues veröffentliche. +* Mir auf **GitHub** folgen. + * Andere Open-Source-Projekte sehen, die ich erstellt habe und die Ihnen helfen könnten. + * Mir folgen, um zu sehen, wenn ich ein neues Open-Source-Projekt erstelle. +* Mir auf **X (Twitter)** folgen oder Mastodon. + * Mir mitteilen, wie Sie FastAPI verwenden (ich höre das gerne). + * Mitbekommen, wenn ich Ankündigungen mache oder neue Tools veröffentliche. + * Sie können auch @fastapi auf X (Twitter) folgen (ein separates Konto). +* Mir auf **LinkedIn** folgen. + * Mitbekommen, wenn ich Ankündigungen mache oder neue Tools veröffentliche (obwohl ich X (Twitter) häufiger verwende 🤷‍♂). +* Lesen, was ich schreibe (oder mir folgen) auf **Dev.to** oder **Medium**. + * Andere Ideen, Artikel lesen und mehr über die von mir erstellten Tools erfahren. + * Mir folgen, um zu lesen, wenn ich etwas Neues veröffentliche. -## Über **FastAPI** tweeten +## Über **FastAPI** tweeten { #tweet-about-fastapi } -Tweeten Sie über **FastAPI** und teilen Sie mir und anderen mit, warum es Ihnen gefällt. 🎉 +Tweeten Sie über **FastAPI** und teilen Sie mir und anderen mit, warum es Ihnen gefällt. 🎉 Ich höre gerne, wie **FastAPI** verwendet wird, was Ihnen daran gefallen hat, in welchem Projekt/Unternehmen Sie es verwenden, usw. -## Für FastAPI abstimmen +## Für FastAPI abstimmen { #vote-for-fastapi } -* Stimmen Sie für **FastAPI** auf Slant. +* Stimmen Sie für **FastAPI** auf Slant. * Stimmen Sie für **FastAPI** auf AlternativeTo. -* Berichten Sie auf StackShare, dass Sie **FastAPI** verwenden. +* Sagen Sie auf StackShare, dass Sie **FastAPI** verwenden. -## Anderen bei Fragen auf GitHub helfen +## Anderen bei Fragen auf GitHub helfen { #help-others-with-questions-in-github } Sie können versuchen, anderen bei ihren Fragen zu helfen: @@ -77,19 +78,19 @@ Sie können versuchen, anderen bei ihren Fragen zu helfen: In vielen Fällen kennen Sie möglicherweise bereits die Antwort auf diese Fragen. 🤓 -Wenn Sie vielen Menschen bei ihren Fragen helfen, werden Sie offizieller [FastAPI-Experte](fastapi-people.md#experten){.internal-link target=_blank}. 🎉 +Wenn Sie vielen Menschen bei ihren Fragen helfen, werden Sie offizieller [FastAPI-Experte](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉 -Denken Sie aber daran, der wichtigste Punkt ist: Versuchen Sie, freundlich zu sein. Die Leute bringen ihre Frustrationen mit und fragen in vielen Fällen nicht auf die beste Art und Weise, aber versuchen Sie dennoch so gut wie möglich, freundlich zu sein. 🤗 +Denken Sie daran, der wichtigste Punkt ist: Versuchen Sie, freundlich zu sein. Die Leute bringen ihre Frustrationen mit und fragen in vielen Fällen nicht auf die beste Art und Weise, aber versuchen Sie dennoch so gut wie möglich, freundlich zu sein. 🤗 -Die **FastAPI**-Community soll freundlich und einladend sein. Und auch kein Mobbing oder respektloses Verhalten gegenüber anderen akzeptieren. Wir müssen uns umeinander kümmern. +Die **FastAPI**-Community soll freundlich und einladend sein. Akzeptieren Sie gleichzeitig kein Mobbing oder respektloses Verhalten gegenüber anderen. Wir müssen uns umeinander kümmern. --- -So helfen Sie anderen bei Fragen (in Diskussionen oder Problemen): +So helfen Sie anderen bei Fragen (in Diskussionen oder Issues): -### Die Frage verstehen +### Die Frage verstehen { #understand-the-question } -* Fragen Sie sich, ob Sie verstehen, was das **Ziel** und der Anwendungsfall der fragenden Person ist. +* Prüfen Sie, ob Sie verstehen können, was der **Zweck** und der Anwendungsfall der fragenden Person ist. * Überprüfen Sie dann, ob die Frage (die überwiegende Mehrheit sind Fragen) **klar** ist. @@ -97,23 +98,23 @@ So helfen Sie anderen bei Fragen (in Diskussionen oder Problemen): * Wenn Sie die Frage nicht verstehen können, fragen Sie nach weiteren **Details**. -### Das Problem reproduzieren +### Das Problem reproduzieren { #reproduce-the-problem } -In den meisten Fällen und bei den meisten Fragen ist etwas mit dem von der Person erstellten **eigenen Quellcode** los. +In den meisten Fällen und bei den meisten Fragen gibt es etwas in Bezug auf den **originalen Code** der Person. In vielen Fällen wird nur ein Fragment des Codes gepostet, aber das reicht nicht aus, um **das Problem zu reproduzieren**. -* Sie können die Person darum bitten, ein minimales, reproduzierbares Beispiel bereitzustellen, welches Sie **kopieren, einfügen** und lokal ausführen können, um den gleichen Fehler oder das gleiche Verhalten zu sehen, das die Person sieht, oder um ihren Anwendungsfall besser zu verstehen. +* Sie können die Person bitten, ein minimales, reproduzierbares Beispiel bereitzustellen, welches Sie **kopieren, einfügen** und lokal ausführen können, um den gleichen Fehler oder das gleiche Verhalten zu sehen, das die Person sieht, oder um ihren Anwendungsfall besser zu verstehen. -* Wenn Sie in Geberlaune sind, können Sie versuchen, selbst ein solches Beispiel zu erstellen, nur basierend auf der Beschreibung des Problems. Denken Sie jedoch daran, dass dies viel Zeit in Anspruch nehmen kann und dass es besser sein kann, zunächst um eine Klärung des Problems zu bitten. +* Wenn Sie in Geberlaune sind, können Sie ein solches Beispiel selbst erstellen, nur basierend auf der Beschreibung des Problems. Denken Sie jedoch daran, dass dies viel Zeit in Anspruch nehmen kann und dass es besser sein kann, zunächst um eine Klärung des Problems zu bitten. -### Lösungen vorschlagen +### Lösungen vorschlagen { #suggest-solutions } * Nachdem Sie die Frage verstanden haben, können Sie eine mögliche **Antwort** geben. * In vielen Fällen ist es besser, das **zugrunde liegende Problem oder den Anwendungsfall** zu verstehen, da es möglicherweise einen besseren Weg zur Lösung gibt als das, was die Person versucht. -### Um Schließung bitten +### Um Schließung bitten { #ask-to-close } Wenn die Person antwortet, besteht eine hohe Chance, dass Sie ihr Problem gelöst haben. Herzlichen Glückwunsch, **Sie sind ein Held**! 🦸 @@ -122,15 +123,15 @@ Wenn die Person antwortet, besteht eine hohe Chance, dass Sie ihr Problem gelös * In GitHub-Diskussionen: den Kommentar als **Antwort** zu markieren. * In GitHub-Issues: Das Issue zu **schließen**. -## Das GitHub-Repository beobachten +## Das GitHub-Repository beobachten { #watch-the-github-repository } -Sie können FastAPI auf GitHub „beobachten“ (Klicken Sie oben rechts auf die Schaltfläche „watch“): https://github.com/fastapi/fastapi. 👀 +Sie können FastAPI auf GitHub „beobachten“ (klicken Sie auf den „watch“-Button oben rechts): https://github.com/fastapi/fastapi. 👀 -Wenn Sie dann „Watching“ statt „Releases only“ auswählen, erhalten Sie Benachrichtigungen, wenn jemand ein neues Issue eröffnet oder eine neue Frage stellt. Sie können auch spezifizieren, dass Sie nur über neue Issues, Diskussionen, PRs, usw. benachrichtigt werden möchten. +Wenn Sie dann „Watching“ statt „Releases only“ auswählen, erhalten Sie Benachrichtigungen, wenn jemand ein neues Issue eröffnet oder eine neue Frage stellt. Sie können auch spezifizieren, dass Sie nur über neue Issues, Diskussionen, PRs usw. benachrichtigt werden möchten. Dann können Sie versuchen, bei der Lösung solcher Fragen zu helfen. -## Fragen stellen +## Fragen stellen { #ask-questions } Sie können im GitHub-Repository eine neue Frage erstellen, zum Beispiel: @@ -139,9 +140,9 @@ Sie können im GitHub-Repository diese Datei bearbeiten. * Stellen Sie sicher, dass Sie Ihren Link am Anfang des entsprechenden Abschnitts einfügen. -* Um zu helfen, [die Dokumentation in Ihre Sprache zu übersetzen](contributing.md#ubersetzungen){.internal-link target=_blank}. +* Um zu helfen, [die Dokumentation in Ihre Sprache zu übersetzen](contributing.md#translations){.internal-link target=_blank}. * Sie können auch dabei helfen, die von anderen erstellten Übersetzungen zu überprüfen (Review). * Um neue Dokumentationsabschnitte vorzuschlagen. -* Um ein bestehendes Problem / einen bestehenden Bug zu beheben. +* Um ein bestehendes Problem/Bug zu beheben. * Stellen Sie sicher, dass Sie Tests hinzufügen. * Um eine neue Funktionalität hinzuzufügen. * Stellen Sie sicher, dass Sie Tests hinzufügen. * Stellen Sie sicher, dass Sie Dokumentation hinzufügen, falls das notwendig ist. -## FastAPI pflegen +## FastAPI pflegen { #help-maintain-fastapi } -Helfen Sie mir, **FastAPI** instand zu halten! 🤓 +Helfen Sie mir, **FastAPI** zu pflegen! 🤓 Es gibt viel zu tun, und das meiste davon können **SIE** tun. Die Hauptaufgaben, die Sie jetzt erledigen können, sind: -* [Helfen Sie anderen bei Fragen auf GitHub](#anderen-bei-fragen-auf-github-helfen){.internal-link target=_blank} (siehe Abschnitt oben). -* [Prüfen Sie Pull Requests](#pull-requests-prufen){.internal-link target=_blank} (siehe Abschnitt oben). +* [Anderen bei Fragen auf GitHub helfen](#help-others-with-questions-in-github){.internal-link target=_blank} (siehe Abschnitt oben). +* [Pull Requests prüfen](#review-pull-requests){.internal-link target=_blank} (siehe Abschnitt oben). -Diese beiden Dinge sind es, die **die meiste Zeit in Anspruch nehmen**. Das ist die Hauptarbeit bei der Wartung von FastAPI. +Diese beiden Aufgaben sind die Dinge, die **am meisten Zeit verbrauchen**. Das ist die Hauptarbeit bei der Wartung von FastAPI. -Wenn Sie mir dabei helfen können, **helfen Sie mir, FastAPI am Laufen zu erhalten** und sorgen dafür, dass es weiterhin **schneller und besser voranschreitet**. 🚀 +Wenn Sie mir dabei helfen können, **helfen Sie mir, FastAPI zu pflegen** und Sie stellen sicher, dass es weiterhin **schneller und besser voranschreitet**. 🚀 -## Beim Chat mitmachen +## Am Chat teilnehmen { #join-the-chat } Treten Sie dem 👥 Discord-Chatserver 👥 bei und treffen Sie sich mit anderen Mitgliedern der FastAPI-Community. -/// tip | "Tipp" +/// tip | Tipp -Wenn Sie Fragen haben, stellen Sie sie bei GitHub Diskussionen, es besteht eine viel bessere Chance, dass Sie hier Hilfe von den [FastAPI-Experten](fastapi-people.md#experten){.internal-link target=_blank} erhalten. +Bei Fragen stellen Sie sie in GitHub-Diskussionen, dort besteht eine viel größere Chance, dass Sie Hilfe von den [FastAPI-Experten](fastapi-people.md#fastapi-experts){.internal-link target=_blank} erhalten. Nutzen Sie den Chat nur für andere allgemeine Gespräche. /// -### Den Chat nicht für Fragen verwenden +### Den Chat nicht für Fragen verwenden { #dont-use-the-chat-for-questions } -Bedenken Sie, da Chats mehr „freie Konversation“ ermöglichen, dass es verlockend ist, Fragen zu stellen, die zu allgemein und schwierig zu beantworten sind, sodass Sie möglicherweise keine Antworten erhalten. +Bedenken Sie, dass Sie in Chats, die „freie Konversation“ erlauben, leicht Fragen stellen können, die zu allgemein und schwer zu beantworten sind, sodass Sie möglicherweise keine Antworten erhalten. -Auf GitHub hilft Ihnen die Vorlage dabei, die richtige Frage zu schreiben, sodass Sie leichter eine gute Antwort erhalten oder das Problem sogar selbst lösen können, noch bevor Sie fragen. Und auf GitHub kann ich sicherstellen, dass ich immer alles beantworte, auch wenn es einige Zeit dauert. Ich persönlich kann das mit den Chat-Systemen nicht machen. 😅 +Auf GitHub hilft Ihnen die Vorlage dabei, die richtige Frage zu stellen, sodass Sie leichter eine gute Antwort erhalten können, oder sogar das Problem selbst lösen, bevor Sie überhaupt fragen. Und auf GitHub kann ich sicherstellen, dass ich immer alles beantworte, auch wenn es einige Zeit dauert. Persönlich kann ich das mit den Chat-Systemen nicht machen. 😅 -Unterhaltungen in den Chat-Systemen sind außerdem nicht so leicht durchsuchbar wie auf GitHub, sodass Fragen und Antworten möglicherweise im Gespräch verloren gehen. Und nur die auf GitHub machen einen [FastAPI-Experten](fastapi-people.md#experten){.internal-link target=_blank}, Sie werden also höchstwahrscheinlich mehr Aufmerksamkeit auf GitHub erhalten. +Unterhaltungen in den Chat-Systemen sind auch nicht so leicht durchsuchbar wie auf GitHub, sodass Fragen und Antworten möglicherweise im Gespräch verloren gehen. Und nur die auf GitHub machen einen [FastAPI-Experten](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, Sie werden also höchstwahrscheinlich mehr Aufmerksamkeit auf GitHub erhalten. Auf der anderen Seite gibt es Tausende von Benutzern in den Chat-Systemen, sodass die Wahrscheinlichkeit hoch ist, dass Sie dort fast immer jemanden zum Reden finden. 😄 -## Den Autor sponsern +## Den Autor sponsern { #sponsor-the-author } -Sie können den Autor (mich) auch über GitHub-Sponsoren finanziell unterstützen. - -Dort könnten Sie mir als Dankeschön einen Kaffee spendieren ☕️. 😄 - -Und Sie können auch Silber- oder Gold-Sponsor für FastAPI werden. 🏅🎉 - -## Die Tools sponsern, die FastAPI unterstützen - -Wie Sie in der Dokumentation gesehen haben, steht FastAPI auf den Schultern von Giganten, Starlette und Pydantic. - -Sie können auch sponsern: - -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) +Wenn Ihr **Produkt/Firma** auf **FastAPI** angewiesen ist oder in Zusammenhang steht und Sie seine Benutzer erreichen möchten, können Sie den Autor (mich) über GitHub-Sponsoren unterstützen. Je nach Stufe können Sie einige zusätzliche Vorteile erhalten, wie z. B. ein Abzeichen in der Dokumentation. 🎁 --- diff --git a/docs/de/docs/history-design-future.md b/docs/de/docs/history-design-future.md index ee917608e4..45198ff1cb 100644 --- a/docs/de/docs/history-design-future.md +++ b/docs/de/docs/history-design-future.md @@ -1,12 +1,12 @@ -# Geschichte, Design und Zukunft +# Geschichte, Design und Zukunft { #history-design-and-future } Vor einiger Zeit fragte ein **FastAPI**-Benutzer: -> Was ist die Geschichte dieses Projekts? Es scheint, als wäre es in ein paar Wochen aus dem Nichts zu etwas Großartigem geworden [...] +> Was ist die Geschichte dieses Projekts? Es scheint aus dem Nichts in ein paar Wochen zu etwas Großartigem geworden zu sein [...] Hier ist ein wenig über diese Geschichte. -## Alternativen +## Alternativen { #alternatives } Ich habe seit mehreren Jahren APIs mit komplexen Anforderungen (maschinelles Lernen, verteilte Systeme, asynchrone Jobs, NoSQL-Datenbanken, usw.) erstellt und leitete mehrere Entwicklerteams. @@ -28,7 +28,7 @@ Aber irgendwann gab es keine andere Möglichkeit, als etwas zu schaffen, das all -## Investigation +## Untersuchung { #investigation } Durch die Nutzung all dieser vorherigen Alternativen hatte ich die Möglichkeit, von allen zu lernen, Ideen aufzunehmen und sie auf die beste Weise zu kombinieren, die ich für mich und die Entwicklerteams, mit denen ich zusammengearbeitet habe, finden konnte. @@ -38,13 +38,13 @@ Der beste Ansatz bestand außerdem darin, bereits bestehende Standards zu nutzen Bevor ich also überhaupt angefangen habe, **FastAPI** zu schreiben, habe ich mehrere Monate damit verbracht, die Spezifikationen für OpenAPI, JSON Schema, OAuth2, usw. zu studieren und deren Beziehungen, Überschneidungen und Unterschiede zu verstehen. -## Design +## Design { #design } Dann habe ich einige Zeit damit verbracht, die Entwickler-„API“ zu entwerfen, die ich als Benutzer haben wollte (als Entwickler, welcher FastAPI verwendet). Ich habe mehrere Ideen in den beliebtesten Python-Editoren getestet: PyCharm, VS Code, Jedi-basierte Editoren. -Laut der letzten Python-Entwickler-Umfrage, deckt das etwa 80 % der Benutzer ab. +Laut der letzten Python-Entwickler-Umfrage deckt das etwa 80 % der Benutzer ab. Das bedeutet, dass **FastAPI** speziell mit den Editoren getestet wurde, die von 80 % der Python-Entwickler verwendet werden. Und da die meisten anderen Editoren in der Regel ähnlich funktionieren, sollten alle diese Vorteile für praktisch alle Editoren funktionieren. @@ -52,19 +52,19 @@ Auf diese Weise konnte ich die besten Möglichkeiten finden, die Codeverdoppelun Alles auf eine Weise, die allen Entwicklern das beste Entwicklungserlebnis bot. -## Anforderungen +## Anforderungen { #requirements } -Nachdem ich mehrere Alternativen getestet hatte, entschied ich, dass ich **Pydantic** wegen seiner Vorteile verwenden würde. +Nachdem ich mehrere Alternativen getestet hatte, entschied ich, dass ich **Pydantic** wegen seiner Vorteile verwenden würde. Dann habe ich zu dessen Code beigetragen, um es vollständig mit JSON Schema kompatibel zu machen, und so verschiedene Möglichkeiten zum Definieren von einschränkenden Deklarationen (Constraints) zu unterstützen, und die Editorunterstützung (Typprüfungen, Codevervollständigung) zu verbessern, basierend auf den Tests in mehreren Editoren. -Während der Entwicklung habe ich auch zu **Starlette** beigetragen, der anderen Schlüsselanforderung. +Während der Entwicklung habe ich auch zu **Starlette** beigetragen, der anderen Schlüsselanforderung. -## Entwicklung +## Entwicklung { #development } Als ich mit der Erstellung von **FastAPI** selbst begann, waren die meisten Teile bereits vorhanden, das Design definiert, die Anforderungen und Tools bereit und das Wissen über die Standards und Spezifikationen klar und frisch. -## Zukunft +## Zukunft { #future } Zu diesem Zeitpunkt ist bereits klar, dass **FastAPI** mit seinen Ideen für viele Menschen nützlich ist. diff --git a/docs/de/docs/how-to/conditional-openapi.md b/docs/de/docs/how-to/conditional-openapi.md index 7f277bb888..f6a2fad3bc 100644 --- a/docs/de/docs/how-to/conditional-openapi.md +++ b/docs/de/docs/how-to/conditional-openapi.md @@ -1,8 +1,8 @@ -# Bedingte OpenAPI +# Bedingte OpenAPI { #conditional-openapi } Bei Bedarf können Sie OpenAPI mithilfe von Einstellungen und Umgebungsvariablen abhängig von der Umgebung bedingt konfigurieren und sogar vollständig deaktivieren. -## Über Sicherheit, APIs und Dokumentation +## Über Sicherheit, APIs und Dokumentation { #about-security-apis-and-docs } Das Verstecken Ihrer Dokumentationsoberflächen in der Produktion *sollte nicht* die Methode sein, Ihre API zu schützen. @@ -10,32 +10,30 @@ Dadurch wird Ihrer API keine zusätzliche Sicherheit hinzugefügt, die *Pfadoper Wenn Ihr Code eine Sicherheitslücke aufweist, ist diese weiterhin vorhanden. -Das Verstecken der Dokumentation macht es nur schwieriger zu verstehen, wie mit Ihrer API interagiert werden kann, und könnte es auch schwieriger machen, diese in der Produktion zu debuggen. Man könnte es einfach als eine Form von Security through obscurity betrachten. +Das Verstecken der Dokumentation macht es nur schwieriger zu verstehen, wie mit Ihrer API interagiert werden kann, und könnte es auch schwieriger machen, diese in der Produktion zu debuggen. Man könnte es einfach als eine Form von Sicherheit durch Verschleierung betrachten. Wenn Sie Ihre API sichern möchten, gibt es mehrere bessere Dinge, die Sie tun können, zum Beispiel: -* Stellen Sie sicher, dass Sie über gut definierte Pydantic-Modelle für Ihre Requestbodys und Responses verfügen. +* Stellen Sie sicher, dass Sie über gut definierte Pydantic-Modelle für Ihre Requestbodys und Responses verfügen. * Konfigurieren Sie alle erforderlichen Berechtigungen und Rollen mithilfe von Abhängigkeiten. * Speichern Sie niemals Klartext-Passwörter, sondern nur Passwort-Hashes. -* Implementieren und verwenden Sie gängige kryptografische Tools wie Passlib und JWT-Tokens, usw. +* Implementieren und verwenden Sie gängige kryptografische Tools wie pwdlib und JWT-Tokens, usw. * Fügen Sie bei Bedarf detailliertere Berechtigungskontrollen mit OAuth2-Scopes hinzu. * ... usw. Dennoch kann es sein, dass Sie einen ganz bestimmten Anwendungsfall haben, bei dem Sie die API-Dokumentation für eine bestimmte Umgebung (z. B. für die Produktion) oder abhängig von Konfigurationen aus Umgebungsvariablen wirklich deaktivieren müssen. -## Bedingte OpenAPI aus Einstellungen und Umgebungsvariablen +## Bedingte OpenAPI aus Einstellungen und Umgebungsvariablen { #conditional-openapi-from-settings-and-env-vars } Sie können problemlos dieselben Pydantic-Einstellungen verwenden, um Ihre generierte OpenAPI und die Dokumentationsoberflächen zu konfigurieren. Zum Beispiel: -```Python hl_lines="6 11" -{!../../../docs_src/conditional_openapi/tutorial001.py!} -``` +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} Hier deklarieren wir die Einstellung `openapi_url` mit dem gleichen Defaultwert `"/openapi.json"`. -Und dann verwenden wir das beim Erstellen der `FastAPI`-App. +Und dann verwenden wir es beim Erstellen der `FastAPI`-App. Dann könnten Sie OpenAPI (einschließlich der Dokumentationsoberflächen) deaktivieren, indem Sie die Umgebungsvariable `OPENAPI_URL` auf einen leeren String setzen, wie zum Beispiel: diff --git a/docs/de/docs/how-to/configure-swagger-ui.md b/docs/de/docs/how-to/configure-swagger-ui.md index c18091efd1..351cb996c5 100644 --- a/docs/de/docs/how-to/configure-swagger-ui.md +++ b/docs/de/docs/how-to/configure-swagger-ui.md @@ -1,14 +1,14 @@ -# Swagger-Oberfläche konfigurieren +# Swagger-Oberfläche konfigurieren { #configure-swagger-ui } -Sie können einige zusätzliche Parameter der Swagger-Oberfläche konfigurieren. +Sie können einige zusätzliche Parameter der Swagger-Oberfläche konfigurieren. Um diese zu konfigurieren, übergeben Sie das Argument `swagger_ui_parameters` beim Erstellen des `FastAPI()`-App-Objekts oder an die Funktion `get_swagger_ui_html()`. -`swagger_ui_parameters` empfängt ein Dict mit den Konfigurationen, die direkt an die Swagger-Oberfläche übergeben werden. +`swagger_ui_parameters` empfängt ein Dictionary mit den Konfigurationen, die direkt an die Swagger-Oberfläche übergeben werden. FastAPI konvertiert die Konfigurationen nach **JSON**, um diese mit JavaScript kompatibel zu machen, da die Swagger-Oberfläche das benötigt. -## Syntaxhervorhebung deaktivieren +## Syntaxhervorhebung deaktivieren { #disable-syntax-highlighting } Sie könnten beispielsweise die Syntaxhervorhebung in der Swagger-Oberfläche deaktivieren. @@ -18,51 +18,43 @@ Ohne Änderung der Einstellungen ist die Syntaxhervorhebung standardmäßig akti Sie können sie jedoch deaktivieren, indem Sie `syntaxHighlight` auf `False` setzen: -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial001.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} ... und dann zeigt die Swagger-Oberfläche die Syntaxhervorhebung nicht mehr an: -## Das Theme ändern +## Das Theme ändern { #change-the-theme } -Auf die gleiche Weise könnten Sie das Theme der Syntaxhervorhebung mit dem Schlüssel `syntaxHighlight.theme` festlegen (beachten Sie, dass er einen Punkt in der Mitte hat): +Auf die gleiche Weise könnten Sie das Theme der Syntaxhervorhebung mit dem Schlüssel `"syntaxHighlight.theme"` festlegen (beachten Sie, dass er einen Punkt in der Mitte hat): -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial002.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} Obige Konfiguration würde das Theme für die Farbe der Syntaxhervorhebung ändern: -## Defaultparameter der Swagger-Oberfläche ändern +## Defaultparameter der Swagger-Oberfläche ändern { #change-default-swagger-ui-parameters } FastAPI enthält einige Defaultkonfigurationsparameter, die für die meisten Anwendungsfälle geeignet sind. Es umfasst die folgenden Defaultkonfigurationen: -```Python -{!../../../fastapi/openapi/docs.py[ln:7-23]!} -``` +{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} Sie können jede davon überschreiben, indem Sie im Argument `swagger_ui_parameters` einen anderen Wert festlegen. Um beispielsweise `deepLinking` zu deaktivieren, könnten Sie folgende Einstellungen an `swagger_ui_parameters` übergeben: -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial003.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} -## Andere Parameter der Swagger-Oberfläche +## Andere Parameter der Swagger-Oberfläche { #other-swagger-ui-parameters } -Um alle anderen möglichen Konfigurationen zu sehen, die Sie verwenden können, lesen Sie die offizielle Dokumentation für die Parameter der Swagger-Oberfläche. +Um alle anderen möglichen Konfigurationen zu sehen, die Sie verwenden können, lesen Sie die offizielle Dokumentation für die Parameter der Swagger-Oberfläche. -## JavaScript-basierte Einstellungen +## Nur-JavaScript-Einstellungen { #javascript-only-settings } -Die Swagger-Oberfläche erlaubt, dass andere Konfigurationen auch **JavaScript**-Objekte sein können (z. B. JavaScript-Funktionen). +Die Swagger-Oberfläche erlaubt, dass andere Konfigurationen auch **Nur-JavaScript**-Objekte sein können (z. B. JavaScript-Funktionen). FastAPI umfasst auch diese Nur-JavaScript-`presets`-Einstellungen: diff --git a/docs/de/docs/how-to/custom-docs-ui-assets.md b/docs/de/docs/how-to/custom-docs-ui-assets.md index e8750f7c22..6b8b1a1767 100644 --- a/docs/de/docs/how-to/custom-docs-ui-assets.md +++ b/docs/de/docs/how-to/custom-docs-ui-assets.md @@ -1,28 +1,26 @@ -# Statische Assets der Dokumentationsoberfläche (selbst hosten) +# Statische Assets der Dokumentationsoberfläche (Selbst-Hosting) { #custom-docs-ui-static-assets-self-hosting } Die API-Dokumentation verwendet **Swagger UI** und **ReDoc**, und jede dieser Dokumentationen benötigt einige JavaScript- und CSS-Dateien. -Standardmäßig werden diese Dateien von einem CDN bereitgestellt. +Standardmäßig werden diese Dateien von einem CDN bereitgestellt. Es ist jedoch möglich, das anzupassen, ein bestimmtes CDN festzulegen oder die Dateien selbst bereitzustellen. -## Benutzerdefiniertes CDN für JavaScript und CSS +## Benutzerdefiniertes CDN für JavaScript und CSS { #custom-cdn-for-javascript-and-css } -Nehmen wir an, Sie möchten ein anderes CDN verwenden, zum Beispiel möchten Sie `https://unpkg.com/` verwenden. +Nehmen wir an, Sie möchten ein anderes CDN verwenden, zum Beispiel möchten Sie `https://unpkg.com/` verwenden. Das kann nützlich sein, wenn Sie beispielsweise in einem Land leben, in dem bestimmte URLs eingeschränkt sind. -### Die automatischen Dokumentationen deaktivieren +### Die automatischen Dokumentationen deaktivieren { #disable-the-automatic-docs } Der erste Schritt besteht darin, die automatischen Dokumentationen zu deaktivieren, da diese standardmäßig das Standard-CDN verwenden. Um diese zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -```Python hl_lines="8" -{!../../../docs_src/custom_docs_ui/tutorial001.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} -### Die benutzerdefinierten Dokumentationen hinzufügen +### Die benutzerdefinierten Dokumentationen hinzufügen { #include-the-custom-docs } Jetzt können Sie die *Pfadoperationen* für die benutzerdefinierten Dokumentationen erstellen. @@ -34,13 +32,11 @@ Sie können die internen Funktionen von FastAPI wiederverwenden, um die HTML-Sei * `swagger_js_url`: die URL, unter welcher der HTML-Code für Ihre Swagger-UI-Dokumentation die **JavaScript**-Datei abrufen kann. Dies ist die benutzerdefinierte CDN-URL. * `swagger_css_url`: die URL, unter welcher der HTML-Code für Ihre Swagger-UI-Dokumentation die **CSS**-Datei abrufen kann. Dies ist die benutzerdefinierte CDN-URL. -Und genau so für ReDoc ... +Und ähnlich für ReDoc ... -```Python hl_lines="2-6 11-19 22-24 27-33" -{!../../../docs_src/custom_docs_ui/tutorial001.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} -/// tip | "Tipp" +/// tip | Tipp Die *Pfadoperation* für `swagger_ui_redirect` ist ein Hilfsmittel bei der Verwendung von OAuth2. @@ -50,25 +46,23 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U /// -### Eine *Pfadoperation* erstellen, um es zu testen +### Eine *Pfadoperation* erstellen, um es zu testen { #create-a-path-operation-to-test-it } Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -```Python hl_lines="36-38" -{!../../../docs_src/custom_docs_ui/tutorial001.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} -### Es ausprobieren +### Es testen { #test-it } -Jetzt sollten Sie in der Lage sein, zu Ihrer Dokumentation auf http://127.0.0.1:8000/docs zu gehen und die Seite neu zuladen, die Assets werden nun vom neuen CDN geladen. +Jetzt sollten Sie in der Lage sein, zu Ihrer Dokumentation auf http://127.0.0.1:8000/docs zu gehen und die Seite neu zu laden, die Assets werden nun vom neuen CDN geladen. -## JavaScript und CSS für die Dokumentation selbst hosten +## JavaScript und CSS für die Dokumentation selbst hosten { #self-hosting-javascript-and-css-for-docs } -Das Selbst Hosten von JavaScript und CSS kann nützlich sein, wenn Sie beispielsweise möchten, dass Ihre Anwendung auch offline, ohne bestehenden Internetzugang oder in einem lokalen Netzwerk weiter funktioniert. +Das Selbst-Hosting von JavaScript und CSS kann nützlich sein, wenn Sie beispielsweise möchten, dass Ihre Anwendung auch offline, ohne bestehenden Internetzugang oder in einem lokalen Netzwerk weiter funktioniert. Hier erfahren Sie, wie Sie diese Dateien selbst in derselben FastAPI-App bereitstellen und die Dokumentation für deren Verwendung konfigurieren. -### Projektdateistruktur +### Projektdateistruktur { #project-file-structure } Nehmen wir an, die Dateistruktur Ihres Projekts sieht folgendermaßen aus: @@ -91,11 +85,11 @@ Ihre neue Dateistruktur könnte so aussehen: └── static/ ``` -### Die Dateien herunterladen +### Die Dateien herunterladen { #download-the-files } Laden Sie die für die Dokumentation benötigten statischen Dateien herunter und legen Sie diese im Verzeichnis `static/` ab. -Sie können wahrscheinlich mit der rechten Maustaste auf jeden Link klicken und eine Option wie etwa `Link speichern unter...` auswählen. +Sie können wahrscheinlich mit der rechten Maustaste auf jeden Link klicken und eine Option wie etwa „Link speichern unter ...“ auswählen. **Swagger UI** verwendet folgende Dateien: @@ -104,7 +98,7 @@ Sie können wahrscheinlich mit der rechten Maustaste auf jeden Link klicken und Und **ReDoc** verwendet diese Datei: -* `redoc.standalone.js` +* `redoc.standalone.js` Danach könnte Ihre Dateistruktur wie folgt aussehen: @@ -119,16 +113,14 @@ Danach könnte Ihre Dateistruktur wie folgt aussehen: └── swagger-ui.css ``` -### Die statischen Dateien bereitstellen +### Die statischen Dateien bereitstellen { #serve-the-static-files } * Importieren Sie `StaticFiles`. * „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad. -```Python hl_lines="7 11" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} -### Die statischen Dateien testen +### Die statischen Dateien testen { #test-the-static-files } Starten Sie Ihre Anwendung und gehen Sie auf http://127.0.0.1:8000/static/redoc.standalone.js. @@ -137,14 +129,8 @@ Sie sollten eine sehr lange JavaScript-Datei für **ReDoc** sehen. Sie könnte beginnen mit etwas wie: ```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 - +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): ... ``` @@ -152,21 +138,19 @@ Das zeigt, dass Sie statische Dateien aus Ihrer Anwendung bereitstellen können Jetzt können wir die Anwendung so konfigurieren, dass sie diese statischen Dateien für die Dokumentation verwendet. -### Die automatischen Dokumentationen deaktivieren, für statische Dateien +### Die automatischen Dokumentationen für statische Dateien deaktivieren { #disable-the-automatic-docs-for-static-files } Wie bei der Verwendung eines benutzerdefinierten CDN besteht der erste Schritt darin, die automatischen Dokumentationen zu deaktivieren, da diese standardmäßig das CDN verwenden. -Um diese zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: +Um sie zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -```Python hl_lines="9" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} -### Die benutzerdefinierten Dokumentationen, mit statischen Dateien, hinzufügen +### Die benutzerdefinierten Dokumentationen für statische Dateien hinzufügen { #include-the-custom-docs-for-static-files } Und genau wie bei einem benutzerdefinierten CDN können Sie jetzt die *Pfadoperationen* für die benutzerdefinierten Dokumentationen erstellen. -Auch hier können Sie die internen Funktionen von FastAPI wiederverwenden, um die HTML-Seiten für die Dokumentationen zu erstellen, und diesen die erforderlichen Argumente übergeben: +Auch hier können Sie die internen Funktionen von FastAPI wiederverwenden, um die HTML-Seiten für die Dokumentationen zu erstellen und ihnen die erforderlichen Argumente zu übergeben: * `openapi_url`: die URL, unter der die HTML-Seite für die Dokumentation das OpenAPI-Schema für Ihre API abrufen kann. Sie können hier das Attribut `app.openapi_url` verwenden. * `title`: der Titel Ihrer API. @@ -174,13 +158,11 @@ Auch hier können Sie die internen Funktionen von FastAPI wiederverwenden, um di * `swagger_js_url`: die URL, unter welcher der HTML-Code für Ihre Swagger-UI-Dokumentation die **JavaScript**-Datei abrufen kann. **Das ist die, welche jetzt von Ihrer eigenen Anwendung bereitgestellt wird**. * `swagger_css_url`: die URL, unter welcher der HTML-Code für Ihre Swagger-UI-Dokumentation die **CSS**-Datei abrufen kann. **Das ist die, welche jetzt von Ihrer eigenen Anwendung bereitgestellt wird**. -Und genau so für ReDoc ... +Und ähnlich für ReDoc ... -```Python hl_lines="2-6 14-22 25-27 30-36" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} -/// tip | "Tipp" +/// tip | Tipp Die *Pfadoperation* für `swagger_ui_redirect` ist ein Hilfsmittel bei der Verwendung von OAuth2. @@ -190,16 +172,14 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U /// -### Eine *Pfadoperation* erstellen, um statische Dateien zu testen +### Eine *Pfadoperation* erstellen, um statische Dateien zu testen { #create-a-path-operation-to-test-static-files } Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -```Python hl_lines="39-41" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} -### Benutzeroberfläche, mit statischen Dateien, testen +### Benutzeroberfläche mit statischen Dateien testen { #test-static-files-ui } Jetzt sollten Sie in der Lage sein, Ihr WLAN zu trennen, gehen Sie zu Ihrer Dokumentation unter http://127.0.0.1:8000/docs und laden Sie die Seite neu. -Und selbst ohne Internet könnten Sie die Dokumentation für Ihre API sehen und damit interagieren. +Und selbst ohne Internet können Sie die Dokumentation für Ihre API sehen und mit ihr interagieren. diff --git a/docs/de/docs/how-to/custom-request-and-route.md b/docs/de/docs/how-to/custom-request-and-route.md index a0c4a0e0cf..246717c04d 100644 --- a/docs/de/docs/how-to/custom-request-and-route.md +++ b/docs/de/docs/how-to/custom-request-and-route.md @@ -1,12 +1,12 @@ -# Benutzerdefinierte Request- und APIRoute-Klasse +# Benutzerdefinierte Request- und APIRoute-Klasse { #custom-request-and-apiroute-class } In einigen Fällen möchten Sie möglicherweise die von den Klassen `Request` und `APIRoute` verwendete Logik überschreiben. Das kann insbesondere eine gute Alternative zur Logik in einer Middleware sein. -Wenn Sie beispielsweise den Requestbody lesen oder manipulieren möchten, bevor er von Ihrer Anwendung verarbeitet wird. +Wenn Sie beispielsweise den Requestbody lesen oder manipulieren möchten, bevor er von Ihrer Anwendung verarbeitet wird. -/// danger | "Gefahr" +/// danger | Gefahr Dies ist eine „fortgeschrittene“ Funktion. @@ -14,7 +14,7 @@ Wenn Sie gerade erst mit **FastAPI** beginnen, möchten Sie diesen Abschnitt vie /// -## Anwendungsfälle +## Anwendungsfälle { #use-cases } Einige Anwendungsfälle sind: @@ -22,15 +22,15 @@ Einige Anwendungsfälle sind: * Dekomprimierung gzip-komprimierter Requestbodys. * Automatisches Loggen aller Requestbodys. -## Handhaben von benutzerdefinierten Requestbody-Kodierungen +## Handhaben von benutzerdefinierten Requestbody-Kodierungen { #handling-custom-request-body-encodings } Sehen wir uns an, wie Sie eine benutzerdefinierte `Request`-Unterklasse verwenden, um gzip-Requests zu dekomprimieren. Und eine `APIRoute`-Unterklasse zur Verwendung dieser benutzerdefinierten Requestklasse. -### Eine benutzerdefinierte `GzipRequest`-Klasse erstellen +### Eine benutzerdefinierte `GzipRequest`-Klasse erstellen { #create-a-custom-gziprequest-class } -/// tip | "Tipp" +/// tip | Tipp Dies ist nur ein einfaches Beispiel, um zu demonstrieren, wie es funktioniert. Wenn Sie Gzip-Unterstützung benötigen, können Sie die bereitgestellte [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank} verwenden. @@ -42,35 +42,31 @@ Wenn der Header kein `gzip` enthält, wird nicht versucht, den Body zu dekomprim Auf diese Weise kann dieselbe Routenklasse gzip-komprimierte oder unkomprimierte Requests verarbeiten. -```Python hl_lines="8-15" -{!../../../docs_src/custom_request_and_route/tutorial001.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} -### Eine benutzerdefinierte `GzipRoute`-Klasse erstellen +### Eine benutzerdefinierte `GzipRoute`-Klasse erstellen { #create-a-custom-gziproute-class } Als Nächstes erstellen wir eine benutzerdefinierte Unterklasse von `fastapi.routing.APIRoute`, welche `GzipRequest` nutzt. Dieses Mal wird die Methode `APIRoute.get_route_handler()` überschrieben. -Diese Methode gibt eine Funktion zurück. Und diese Funktion empfängt einen Request und gibt eine Response zurück. +Diese Methode gibt eine Funktion zurück. Und diese Funktion empfängt einen Request und gibt eine Response zurück. Hier verwenden wir sie, um aus dem ursprünglichen Request einen `GzipRequest` zu erstellen. -```Python hl_lines="18-26" -{!../../../docs_src/custom_request_and_route/tutorial001.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} -/// note | "Technische Details" +/// note | Technische Details -Ein `Request` hat ein `request.scope`-Attribut, welches einfach ein Python-`dict` ist, welches die mit dem Request verbundenen Metadaten enthält. +Ein `Request` hat ein `request.scope`-Attribut, welches einfach ein Python-`dict` ist, welches die mit dem Request verbundenen Metadaten enthält. -Ein `Request` hat auch ein `request.receive`, welches eine Funktion ist, die den Hauptteil des Requests empfängt. +Ein `Request` hat auch ein `request.receive`, welches eine Funktion ist, die den Body des Requests empfängt. Das `scope`-`dict` und die `receive`-Funktion sind beide Teil der ASGI-Spezifikation. Und diese beiden Dinge, `scope` und `receive`, werden benötigt, um eine neue `Request`-Instanz zu erstellen. -Um mehr über den `Request` zu erfahren, schauen Sie sich Starlettes Dokumentation zu Requests an. +Um mehr über den `Request` zu erfahren, schauen Sie sich Starlettes Dokumentation zu Requests an. /// @@ -82,11 +78,11 @@ Danach ist die gesamte Verarbeitungslogik dieselbe. Aufgrund unserer Änderungen in `GzipRequest.body` wird der Requestbody jedoch bei Bedarf automatisch dekomprimiert, wenn er von **FastAPI** geladen wird. -## Zugriff auf den Requestbody in einem Exceptionhandler +## Zugriff auf den Requestbody in einem Exceptionhandler { #accessing-the-request-body-in-an-exception-handler } -/// tip | "Tipp" +/// tip | Tipp -Um dasselbe Problem zu lösen, ist es wahrscheinlich viel einfacher, den `body` in einem benutzerdefinierten Handler für `RequestValidationError` zu verwenden ([Fehlerbehandlung](../tutorial/handling-errors.md#den-requestvalidationerror-body-verwenden){.internal-link target=_blank}). +Um dasselbe Problem zu lösen, ist es wahrscheinlich viel einfacher, den `body` in einem benutzerdefinierten Handler für `RequestValidationError` zu verwenden ([Fehlerbehandlung](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). Dieses Beispiel ist jedoch immer noch gültig und zeigt, wie mit den internen Komponenten interagiert wird. @@ -96,26 +92,18 @@ Wir können denselben Ansatz auch verwenden, um in einem Exceptionhandler auf de Alles, was wir tun müssen, ist, den Request innerhalb eines `try`/`except`-Blocks zu handhaben: -```Python hl_lines="13 15" -{!../../../docs_src/custom_request_and_route/tutorial002.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} Wenn eine Exception auftritt, befindet sich die `Request`-Instanz weiterhin im Gültigkeitsbereich, sodass wir den Requestbody lesen und bei der Fehlerbehandlung verwenden können: -```Python hl_lines="16-18" -{!../../../docs_src/custom_request_and_route/tutorial002.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} -## Benutzerdefinierte `APIRoute`-Klasse in einem Router +## Benutzerdefinierte `APIRoute`-Klasse in einem Router { #custom-apiroute-class-in-a-router } Sie können auch den Parameter `route_class` eines `APIRouter` festlegen: -```Python hl_lines="26" -{!../../../docs_src/custom_request_and_route/tutorial003.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} In diesem Beispiel verwenden die *Pfadoperationen* unter dem `router` die benutzerdefinierte `TimedRoute`-Klasse und haben in der Response einen zusätzlichen `X-Response-Time`-Header mit der Zeit, die zum Generieren der Response benötigt wurde: -```Python hl_lines="13-20" -{!../../../docs_src/custom_request_and_route/tutorial003.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} diff --git a/docs/de/docs/how-to/extending-openapi.md b/docs/de/docs/how-to/extending-openapi.md index 347c5bed3f..146ee098bc 100644 --- a/docs/de/docs/how-to/extending-openapi.md +++ b/docs/de/docs/how-to/extending-openapi.md @@ -1,24 +1,24 @@ -# OpenAPI erweitern +# OpenAPI erweitern { #extending-openapi } -In einigen Fällen müssen Sie möglicherweise das generierte OpenAPI-Schema ändern. +Es gibt einige Fälle, in denen Sie das generierte OpenAPI-Schema ändern müssen. In diesem Abschnitt erfahren Sie, wie. -## Der normale Vorgang +## Der normale Vorgang { #the-normal-process } Der normale (Standard-)Prozess ist wie folgt. -Eine `FastAPI`-Anwendung (-Instanz) verfügt über eine `.openapi()`-Methode, von der erwartet wird, dass sie das OpenAPI-Schema zurückgibt. +Eine `FastAPI`-Anwendung (Instanz) verfügt über eine `.openapi()`-Methode, von der erwartet wird, dass sie das OpenAPI-Schema zurückgibt. Als Teil der Erstellung des Anwendungsobjekts wird eine *Pfadoperation* für `/openapi.json` (oder welcher Wert für den Parameter `openapi_url` gesetzt wurde) registriert. -Diese gibt lediglich eine JSON-Response zurück, mit dem Ergebnis der Methode `.openapi()` der Anwendung. +Diese gibt lediglich eine JSON-Response zurück, mit dem Ergebnis der Methode `.openapi()` der Anwendung. Standardmäßig überprüft die Methode `.openapi()` die Eigenschaft `.openapi_schema`, um zu sehen, ob diese Inhalt hat, und gibt diesen zurück. Ist das nicht der Fall, wird der Inhalt mithilfe der Hilfsfunktion unter `fastapi.openapi.utils.get_openapi` generiert. -Und diese Funktion `get_openapi()` erhält als Parameter: +Diese Funktion `get_openapi()` erhält als Parameter: * `title`: Der OpenAPI-Titel, der in der Dokumentation angezeigt wird. * `version`: Die Version Ihrer API, z. B. `2.5.0`. @@ -27,63 +27,53 @@ Und diese Funktion `get_openapi()` erhält als Parameter: * `description`: Die Beschreibung Ihrer API. Dies kann Markdown enthalten und wird in der Dokumentation angezeigt. * `routes`: Eine Liste von Routen, dies sind alle registrierten *Pfadoperationen*. Sie stammen von `app.routes`. -/// info +/// info | Info Der Parameter `summary` ist in OpenAPI 3.1.0 und höher verfügbar und wird von FastAPI 0.99.0 und höher unterstützt. /// -## Überschreiben der Standardeinstellungen +## Überschreiben der Standardeinstellungen { #overriding-the-defaults } Mithilfe der oben genannten Informationen können Sie dieselbe Hilfsfunktion verwenden, um das OpenAPI-Schema zu generieren und jeden benötigten Teil zu überschreiben. -Fügen wir beispielsweise ReDocs OpenAPI-Erweiterung zum Einbinden eines benutzerdefinierten Logos hinzu. +Fügen wir beispielsweise ReDocs OpenAPI-Erweiterung zum Einbinden eines benutzerdefinierten Logos hinzu. -### Normales **FastAPI** +### Normales **FastAPI** { #normal-fastapi } Schreiben Sie zunächst wie gewohnt Ihre ganze **FastAPI**-Anwendung: -```Python hl_lines="1 4 7-9" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} -### Das OpenAPI-Schema generieren +### Das OpenAPI-Schema generieren { #generate-the-openapi-schema } Verwenden Sie dann dieselbe Hilfsfunktion, um das OpenAPI-Schema innerhalb einer `custom_openapi()`-Funktion zu generieren: -```Python hl_lines="2 15-21" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} -### Das OpenAPI-Schema ändern +### Das OpenAPI-Schema ändern { #modify-the-openapi-schema } Jetzt können Sie die ReDoc-Erweiterung hinzufügen und dem `info`-„Objekt“ im OpenAPI-Schema ein benutzerdefiniertes `x-logo` hinzufügen: -```Python hl_lines="22-24" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} -### Zwischenspeichern des OpenAPI-Schemas +### Zwischenspeichern des OpenAPI-Schemas { #cache-the-openapi-schema } Sie können die Eigenschaft `.openapi_schema` als „Cache“ verwenden, um Ihr generiertes Schema zu speichern. Auf diese Weise muss Ihre Anwendung das Schema nicht jedes Mal generieren, wenn ein Benutzer Ihre API-Dokumentation öffnet. -Es wird nur einmal generiert und dann wird dasselbe zwischengespeicherte Schema für die nächsten Requests verwendet. +Es wird nur einmal generiert und dann wird dasselbe zwischengespeicherte Schema für die nächsten Requests verwendet. -```Python hl_lines="13-14 25-26" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} -### Die Methode überschreiben +### Die Methode überschreiben { #override-the-method } Jetzt können Sie die Methode `.openapi()` durch Ihre neue Funktion ersetzen. -```Python hl_lines="29" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} -### Testen +### Es testen { #check-it } Sobald Sie auf http://127.0.0.1:8000/redoc gehen, werden Sie sehen, dass Ihr benutzerdefiniertes Logo verwendet wird (in diesem Beispiel das Logo von **FastAPI**): diff --git a/docs/de/docs/how-to/general.md b/docs/de/docs/how-to/general.md index b38b5fabfb..0045eab749 100644 --- a/docs/de/docs/how-to/general.md +++ b/docs/de/docs/how-to/general.md @@ -1,39 +1,39 @@ -# Allgemeines – How-To – Rezepte +# Allgemeines – How-To – Rezepte { #general-how-to-recipes } Hier finden Sie mehrere Verweise auf andere Stellen in der Dokumentation, für allgemeine oder häufige Fragen. -## Daten filtern – Sicherheit +## Daten filtern – Sicherheit { #filter-data-security } Um sicherzustellen, dass Sie nicht mehr Daten zurückgeben, als Sie sollten, lesen Sie die Dokumentation unter [Tutorial – Responsemodell – Rückgabetyp](../tutorial/response-model.md){.internal-link target=_blank}. -## Dokumentations-Tags – OpenAPI +## Dokumentations-Tags – OpenAPI { #documentation-tags-openapi } Um Tags zu Ihren *Pfadoperationen* hinzuzufügen und diese in der Oberfläche der Dokumentation zu gruppieren, lesen Sie die Dokumentation unter [Tutorial – Pfadoperation-Konfiguration – Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. -## Zusammenfassung und Beschreibung in der Dokumentation – OpenAPI +## Zusammenfassung und Beschreibung in der Dokumentation – OpenAPI { #documentation-summary-and-description-openapi } -Um Ihren *Pfadoperationen* eine Zusammenfassung und Beschreibung hinzuzufügen und diese in der Oberfläche der Dokumentation anzuzeigen, lesen Sie die Dokumentation unter [Tutorial – Pfadoperation-Konfiguration – Zusammenfassung und Beschreibung](../tutorial/path-operation-configuration.md#zusammenfassung-und-beschreibung){.internal-link target=_blank}. +Um Ihren *Pfadoperationen* eine Zusammenfassung und Beschreibung hinzuzufügen und diese in der Oberfläche der Dokumentation anzuzeigen, lesen Sie die Dokumentation unter [Tutorial – Pfadoperation-Konfiguration – Zusammenfassung und Beschreibung](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. -## Beschreibung der Response in der Dokumentation – OpenAPI +## Beschreibung der Response in der Dokumentation – OpenAPI { #documentation-response-description-openapi } -Um die Beschreibung der Response zu definieren, welche in der Oberfläche der Dokumentation angezeigt wird, lesen Sie die Dokumentation unter [Tutorial – Pfadoperation-Konfiguration – Beschreibung der Response](../tutorial/path-operation-configuration.md#beschreibung-der-response){.internal-link target=_blank}. +Um die Beschreibung der Response zu definieren, welche in der Oberfläche der Dokumentation angezeigt wird, lesen Sie die Dokumentation unter [Tutorial – Pfadoperation-Konfiguration – Beschreibung der Response](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. -## *Pfadoperation* in der Dokumentation deprecaten – OpenAPI +## *Pfadoperation* in der Dokumentation deprecaten – OpenAPI { #documentation-deprecate-a-path-operation-openapi } -Um eine *Pfadoperation* zu deprecaten – sie als veraltet zu markieren – und das in der Oberfläche der Dokumentation anzuzeigen, lesen Sie die Dokumentation unter [Tutorial – Pfadoperation-Konfiguration – Deprecaten](../tutorial/path-operation-configuration.md#eine-pfadoperation-deprecaten){.internal-link target=_blank}. +Um eine *Pfadoperation* zu deprecaten und das in der Oberfläche der Dokumentation anzuzeigen, lesen Sie die Dokumentation unter [Tutorial – Pfadoperation-Konfiguration – Deprecaten](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. -## Daten in etwas JSON-kompatibles konvertieren +## Daten in etwas JSON-kompatibles konvertieren { #convert-any-data-to-json-compatible } Um Daten in etwas JSON-kompatibles zu konvertieren, lesen Sie die Dokumentation unter [Tutorial – JSON-kompatibler Encoder](../tutorial/encoder.md){.internal-link target=_blank}. -## OpenAPI-Metadaten – Dokumentation +## OpenAPI-Metadaten – Dokumentation { #openapi-metadata-docs } -Um Metadaten zu Ihrem OpenAPI-Schema hinzuzufügen, einschließlich einer Lizenz, Version, Kontakt, usw., lesen Sie die Dokumentation unter [Tutorial – Metadaten und URLs der Dokumentationen](../tutorial/metadata.md){.internal-link target=_blank}. +Um Metadaten zu Ihrem OpenAPI-Schema hinzuzufügen, einschließlich einer Lizenz, Version, Kontakt, usw., lesen Sie die Dokumentation unter [Tutorial – Metadaten und URLs der Dokumentation](../tutorial/metadata.md){.internal-link target=_blank}. -## Benutzerdefinierte OpenAPI-URL +## Benutzerdefinierte OpenAPI-URL { #openapi-custom-url } -Um die OpenAPI-URL anzupassen (oder zu entfernen), lesen Sie die Dokumentation unter [Tutorial – Metadaten und URLs der Dokumentationen](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. +Um die OpenAPI-URL anzupassen (oder zu entfernen), lesen Sie die Dokumentation unter [Tutorial – Metadaten und URLs der Dokumentation](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. -## URLs der OpenAPI-Dokumentationen +## URLs der OpenAPI-Dokumentationen { #openapi-docs-urls } -Um die URLs zu aktualisieren, die für die automatisch generierten Dokumentations-Oberflächen verwendet werden, lesen Sie die Dokumentation unter [Tutorial – Metadaten und URLs der Dokumentationen](../tutorial/metadata.md#urls-der-dokumentationen){.internal-link target=_blank}. +Um die URLs zu aktualisieren, die für die automatisch generierten Dokumentations-Oberflächen verwendet werden, lesen Sie die Dokumentation unter [Tutorial – Metadaten und URLs der Dokumentation](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/de/docs/how-to/graphql.md b/docs/de/docs/how-to/graphql.md index 19af25bb38..d2958dcd9e 100644 --- a/docs/de/docs/how-to/graphql.md +++ b/docs/de/docs/how-to/graphql.md @@ -1,61 +1,59 @@ -# GraphQL +# GraphQL { #graphql } Da **FastAPI** auf dem **ASGI**-Standard basiert, ist es sehr einfach, jede **GraphQL**-Bibliothek zu integrieren, die auch mit ASGI kompatibel ist. Sie können normale FastAPI-*Pfadoperationen* mit GraphQL in derselben Anwendung kombinieren. -/// tip | "Tipp" +/// tip | Tipp **GraphQL** löst einige sehr spezifische Anwendungsfälle. Es hat **Vorteile** und **Nachteile** im Vergleich zu gängigen **Web-APIs**. -Wiegen Sie ab, ob die **Vorteile** für Ihren Anwendungsfall die **Nachteile** ausgleichen. 🤓 +Stellen Sie sicher, dass Sie prüfen, ob die **Vorteile** für Ihren Anwendungsfall die **Nachteile** ausgleichen. 🤓 /// -## GraphQL-Bibliotheken +## GraphQL-Bibliotheken { #graphql-libraries } -Hier sind einige der **GraphQL**-Bibliotheken, welche **ASGI** unterstützen. Diese könnten Sie mit **FastAPI** verwenden: +Hier sind einige der **GraphQL**-Bibliotheken, die **ASGI**-Unterstützung haben. Sie könnten sie mit **FastAPI** verwenden: * Strawberry 🍓 * Mit Dokumentation für FastAPI * Ariadne * Mit Dokumentation für FastAPI * Tartiflette - * Mit Tartiflette ASGI, für ASGI-Integration + * Mit Tartiflette ASGI für ASGI-Integration * Graphene * Mit starlette-graphene3 -## GraphQL mit Strawberry +## GraphQL mit Strawberry { #graphql-with-strawberry } -Wenn Sie mit **GraphQL** arbeiten möchten oder müssen, ist **Strawberry** die **empfohlene** Bibliothek, da deren Design dem Design von **FastAPI** am nächsten kommt und alles auf **Typannotationen** basiert. +Wenn Sie mit **GraphQL** arbeiten möchten oder müssen, ist **Strawberry** die **empfohlene** Bibliothek, da deren Design **FastAPIs** Design am nächsten kommt und alles auf **Typannotationen** basiert. -Abhängig von Ihrem Anwendungsfall bevorzugen Sie vielleicht eine andere Bibliothek, aber wenn Sie mich fragen würden, würde ich Ihnen wahrscheinlich empfehlen, **Strawberry** auszuprobieren. +Abhängig von Ihrem Anwendungsfall könnten Sie eine andere Bibliothek vorziehen, aber wenn Sie mich fragen würden, würde ich Ihnen wahrscheinlich empfehlen, **Strawberry** auszuprobieren. Hier ist eine kleine Vorschau, wie Sie Strawberry mit FastAPI integrieren können: -```Python hl_lines="3 22 25-26" -{!../../../docs_src/graphql/tutorial001.py!} -``` +{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} Weitere Informationen zu Strawberry finden Sie in der Strawberry-Dokumentation. -Und auch die Dokumentation zu Strawberry mit FastAPI. +Und auch in der Dokumentation zu Strawberry mit FastAPI. -## Ältere `GraphQLApp` von Starlette +## Ältere `GraphQLApp` von Starlette { #older-graphqlapp-from-starlette } Frühere Versionen von Starlette enthielten eine `GraphQLApp`-Klasse zur Integration mit Graphene. -Das wurde von Starlette deprecated, aber wenn Sie Code haben, der das verwendet, können Sie einfach zu starlette-graphene3 **migrieren**, welches denselben Anwendungsfall abdeckt und über eine **fast identische Schnittstelle** verfügt. +Das wurde von Starlette deprecatet, aber wenn Sie Code haben, der das verwendet, können Sie einfach zu starlette-graphene3 **migrieren**, das denselben Anwendungsfall abdeckt und eine **fast identische Schnittstelle** hat. -/// tip | "Tipp" +/// tip | Tipp Wenn Sie GraphQL benötigen, würde ich Ihnen trotzdem empfehlen, sich Strawberry anzuschauen, da es auf Typannotationen basiert, statt auf benutzerdefinierten Klassen und Typen. /// -## Mehr darüber lernen +## Mehr darüber lernen { #learn-more } Weitere Informationen zu **GraphQL** finden Sie in der offiziellen GraphQL-Dokumentation. diff --git a/docs/de/docs/how-to/index.md b/docs/de/docs/how-to/index.md index 75779a01ce..36229dcd79 100644 --- a/docs/de/docs/how-to/index.md +++ b/docs/de/docs/how-to/index.md @@ -1,4 +1,4 @@ -# How-To – Rezepte +# How-To – Rezepte { #how-to-recipes } Hier finden Sie verschiedene Rezepte und „How-To“-Anleitungen zu **verschiedenen Themen**. @@ -6,7 +6,7 @@ Die meisten dieser Ideen sind mehr oder weniger **unabhängig**, und in den meis Wenn etwas für Ihr Projekt interessant und nützlich erscheint, lesen Sie es, andernfalls überspringen Sie es einfach. -/// tip | "Tipp" +/// tip | Tipp Wenn Sie strukturiert **FastAPI lernen** möchten (empfohlen), lesen Sie stattdessen Kapitel für Kapitel das [Tutorial – Benutzerhandbuch](../tutorial/index.md){.internal-link target=_blank}. diff --git a/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 0000000000..7f60492ee9 --- /dev/null +++ b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,133 @@ +# Von Pydantic v1 zu Pydantic v2 migrieren { #migrate-from-pydantic-v1-to-pydantic-v2 } + +Wenn Sie eine ältere FastAPI-App haben, nutzen Sie möglicherweise Pydantic Version 1. + +FastAPI unterstützt seit Version 0.100.0 sowohl Pydantic v1 als auch v2. + +Wenn Sie Pydantic v2 installiert hatten, wurde dieses verwendet. Wenn stattdessen Pydantic v1 installiert war, wurde jenes verwendet. + +Pydantic v1 ist jetzt deprecatet und die Unterstützung dafür wird in den nächsten Versionen von FastAPI entfernt, Sie sollten also zu **Pydantic v2 migrieren**. Auf diese Weise erhalten Sie die neuesten Features, Verbesserungen und Fixes. + +/// warning | Achtung + +Außerdem hat das Pydantic-Team die Unterstützung für Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**. + +Wenn Sie die neuesten Features von Python nutzen möchten, müssen Sie sicherstellen, dass Sie Pydantic v2 verwenden. + +/// + +Wenn Sie eine ältere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **neuen Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen. + +## Offizieller Leitfaden { #official-guide } + +Pydantic hat einen offiziellen Migrationsleitfaden von v1 zu v2. + +Er enthält auch, was sich geändert hat, wie Validierungen nun korrekter und strikter sind, mögliche Stolpersteine, usw. + +Sie können ihn lesen, um besser zu verstehen, was sich geändert hat. + +## Tests { #tests } + +Stellen Sie sicher, dass Sie [Tests](../tutorial/testing.md){.internal-link target=_blank} für Ihre App haben und diese in Continuous Integration (CI) ausführen. + +Auf diese Weise können Sie das Update durchführen und sicherstellen, dass weiterhin alles wie erwartet funktioniert. + +## `bump-pydantic` { #bump-pydantic } + +In vielen Fällen, wenn Sie reguläre Pydantic-Modelle ohne Anpassungen verwenden, können Sie den Großteil des Prozesses der Migration von Pydantic v1 auf Pydantic v2 automatisieren. + +Sie können `bump-pydantic` vom selben Pydantic-Team verwenden. + +Dieses Tool hilft Ihnen, den Großteil des zu ändernden Codes automatisch anzupassen. + +Danach können Sie die Tests ausführen und prüfen, ob alles funktioniert. Falls ja, sind Sie fertig. 😎 + +## Pydantic v1 in v2 { #pydantic-v1-in-v2 } + +Pydantic v2 enthält alles aus Pydantic v1 als Untermodul `pydantic.v1`. + +Das bedeutet, Sie können die neueste Version von Pydantic v2 installieren und die alten Pydantic‑v1‑Komponenten aus diesem Untermodul importieren und verwenden, als hätten Sie das alte Pydantic v1 installiert. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### FastAPI-Unterstützung für Pydantic v1 in v2 { #fastapi-support-for-pydantic-v1-in-v2 } + +Seit FastAPI 0.119.0 gibt es außerdem eine teilweise Unterstützung für Pydantic v1 innerhalb von Pydantic v2, um die Migration auf v2 zu erleichtern. + +Sie könnten also Pydantic auf die neueste Version 2 aktualisieren und die Importe so ändern, dass das Untermodul `pydantic.v1` verwendet wird, und in vielen Fällen würde es einfach funktionieren. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | Achtung + +Beachten Sie, dass, da das Pydantic‑Team Pydantic v1 in neueren Python‑Versionen nicht mehr unterstützt, beginnend mit Python 3.14, auch die Verwendung von `pydantic.v1` unter Python 3.14 und höher nicht unterstützt wird. + +/// + +### Pydantic v1 und v2 in derselben App { #pydantic-v1-and-v2-on-the-same-app } + +Es wird von Pydantic **nicht unterstützt**, dass ein Pydantic‑v2‑Modell Felder hat, die als Pydantic‑v1‑Modelle definiert sind, und umgekehrt. + +```mermaid +graph TB + subgraph "❌ Nicht unterstützt" + direction TB + subgraph V2["Pydantic-v2-Modell"] + V1Field["Pydantic-v1-Modell"] + end + subgraph V1["Pydantic-v1-Modell"] + V2Field["Pydantic-v2-Modell"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +... aber Sie können getrennte Modelle, die Pydantic v1 bzw. v2 nutzen, in derselben App verwenden. + +```mermaid +graph TB + subgraph "✅ Unterstützt" + direction TB + subgraph V2["Pydantic-v2-Modell"] + V2Field["Pydantic-v2-Modell"] + end + subgraph V1["Pydantic-v1-Modell"] + V1Field["Pydantic-v1-Modell"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +In einigen Fällen ist es sogar möglich, sowohl Pydantic‑v1‑ als auch Pydantic‑v2‑Modelle in derselben **Pfadoperation** Ihrer FastAPI‑App zu verwenden: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +Im obigen Beispiel ist das Eingabemodell ein Pydantic‑v1‑Modell, und das Ausgabemodell (definiert in `response_model=ItemV2`) ist ein Pydantic‑v2‑Modell. + +### Pydantic v1 Parameter { #pydantic-v1-parameters } + +Wenn Sie einige der FastAPI-spezifischen Tools für Parameter wie `Body`, `Query`, `Form`, usw. zusammen mit Pydantic‑v1‑Modellen verwenden müssen, können Sie die aus `fastapi.temp_pydantic_v1_params` importieren, während Sie die Migration zu Pydantic v2 abschließen: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### In Schritten migrieren { #migrate-in-steps } + +/// tip | Tipp + +Probieren Sie zuerst `bump-pydantic` aus. Wenn Ihre Tests erfolgreich sind und das funktioniert, sind Sie mit einem einzigen Befehl fertig. ✨ + +/// + +Wenn `bump-pydantic` für Ihren Anwendungsfall nicht funktioniert, können Sie die Unterstützung für Pydantic‑v1‑ und Pydantic‑v2‑Modelle in derselben App nutzen, um die Migration zu Pydantic v2 schrittweise durchzuführen. + +Sie könnten zuerst Pydantic auf die neueste Version 2 aktualisieren und die Importe so ändern, dass für all Ihre Modelle `pydantic.v1` verwendet wird. + +Anschließend können Sie beginnen, Ihre Modelle gruppenweise von Pydantic v1 auf v2 zu migrieren – in kleinen, schrittweisen Etappen. 🚶 diff --git a/docs/de/docs/how-to/separate-openapi-schemas.md b/docs/de/docs/how-to/separate-openapi-schemas.md index eaecb27de4..31653590b5 100644 --- a/docs/de/docs/how-to/separate-openapi-schemas.md +++ b/docs/de/docs/how-to/separate-openapi-schemas.md @@ -1,136 +1,26 @@ -# Separate OpenAPI-Schemas für Eingabe und Ausgabe oder nicht +# Separate OpenAPI-Schemas für Eingabe und Ausgabe oder nicht { #separate-openapi-schemas-for-input-and-output-or-not } Bei Verwendung von **Pydantic v2** ist die generierte OpenAPI etwas genauer und **korrekter** als zuvor. 😎 -Tatsächlich gibt es in einigen Fällen sogar **zwei JSON-Schemas** in OpenAPI für dasselbe Pydantic-Modell für Eingabe und Ausgabe, je nachdem, ob sie **Defaultwerte** haben. +Tatsächlich gibt es in einigen Fällen sogar **zwei JSON-Schemas** in OpenAPI für dasselbe Pydantic-Modell, für Eingabe und Ausgabe, je nachdem, ob sie **Defaultwerte** haben. Sehen wir uns an, wie das funktioniert und wie Sie es bei Bedarf ändern können. -## Pydantic-Modelle für Eingabe und Ausgabe +## Pydantic-Modelle für Eingabe und Ausgabe { #pydantic-models-for-input-and-output } Nehmen wir an, Sie haben ein Pydantic-Modell mit Defaultwerten wie dieses: -//// tab | Python 3.10+ +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} -```Python hl_lines="7" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-7]!} - -# Code unterhalb weggelassen 👇 -``` - -
-👀 Vollständige Dateivorschau - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} -``` - -
- -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-9]!} - -# Code unterhalb weggelassen 👇 -``` - -
-👀 Vollständige Dateivorschau - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} -``` - -
- -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-9]!} - -# Code unterhalb weggelassen 👇 -``` - -
-👀 Vollständige Dateivorschau - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} -``` - -
- -//// - -### Modell für Eingabe +### Modell für Eingabe { #model-for-input } Wenn Sie dieses Modell wie hier als Eingabe verwenden: -//// tab | Python 3.10+ - -```Python hl_lines="14" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-15]!} - -# Code unterhalb weggelassen 👇 -``` - -
-👀 Vollständige Dateivorschau - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} -``` - -
- -//// - -//// tab | Python 3.9+ - -```Python hl_lines="16" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-17]!} - -# Code unterhalb weggelassen 👇 -``` - -
-👀 Vollständige Dateivorschau - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} -``` - -
- -//// - -//// tab | Python 3.8+ - -```Python hl_lines="16" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-17]!} - -# Code unterhalb weggelassen 👇 -``` - -
-👀 Vollständige Dateivorschau - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} -``` - -
- -//// +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} ... dann ist das Feld `description` **nicht erforderlich**. Weil es den Defaultwert `None` hat. -### Eingabemodell in der Dokumentation +### Eingabemodell in der Dokumentation { #input-model-in-docs } Sie können überprüfen, dass das Feld `description` in der Dokumentation kein **rotes Sternchen** enthält, es ist nicht als erforderlich markiert: @@ -138,39 +28,17 @@ Sie können überprüfen, dass das Feld `description` in der Dokumentation kein -### Modell für die Ausgabe +### Modell für die Ausgabe { #model-for-output } Wenn Sie jedoch dasselbe Modell als Ausgabe verwenden, wie hier: -//// tab | Python 3.10+ +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} -```Python hl_lines="19" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} -``` +... dann, weil `description` einen Defaultwert hat, wird es, wenn Sie für dieses Feld **nichts zurückgeben**, immer noch diesen **Defaultwert** haben. -//// +### Modell für Ausgabe-Responsedaten { #model-for-output-response-data } -//// tab | Python 3.9+ - -```Python hl_lines="21" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} -``` - -//// - -... dann, weil `description` einen Defaultwert hat, wird es, wenn Sie für dieses Feld **nichts zurückgeben**, immer noch diesen **Defaultwert** haben. - -### Modell für Ausgabe-Responsedaten - -Wenn Sie mit der Dokumentation interagieren und die Response überprüfen, enthält die JSON-Response den Defaultwert (`null`), obwohl der Code nichts in eines der `description`-Felder geschrieben hat: +Wenn Sie mit der Dokumentation interagieren und die Response überprüfen, enthält die JSON-Response den Defaultwert (`null`), obwohl der Code nichts in eines der `description`-Felder geschrieben hat:
@@ -187,7 +55,7 @@ Aus diesem Grund kann das JSON-Schema für ein Modell unterschiedlich sein, je n * für die **Eingabe** ist `description` **nicht erforderlich** * für die **Ausgabe** ist es **erforderlich** (und möglicherweise `None` oder, in JSON-Begriffen, `null`) -### Ausgabemodell in der Dokumentation +### Ausgabemodell in der Dokumentation { #model-for-output-in-docs } Sie können das Ausgabemodell auch in der Dokumentation überprüfen. **Sowohl** `name` **als auch** `description` sind mit einem **roten Sternchen** als **erforderlich** markiert: @@ -195,7 +63,7 @@ Sie können das Ausgabemodell auch in der Dokumentation überprüfen. **Sowohl**
-### Eingabe- und Ausgabemodell in der Dokumentation +### Eingabe- und Ausgabemodell in der Dokumentation { #model-for-input-and-output-in-docs } Und wenn Sie alle verfügbaren Schemas (JSON-Schemas) in OpenAPI überprüfen, werden Sie feststellen, dass es zwei gibt, ein `Item-Input` und ein `Item-Output`. @@ -209,7 +77,7 @@ Aber für `Item-Output` ist `description` **erforderlich**, es hat ein rotes Ste Mit dieser Funktion von **Pydantic v2** ist Ihre API-Dokumentation **präziser**, und wenn Sie über automatisch generierte Clients und SDKs verfügen, sind diese auch präziser, mit einer besseren **Entwicklererfahrung** und Konsistenz. 🎉 -## Schemas nicht trennen +## Schemas nicht trennen { #do-not-separate-schemas } Nun gibt es einige Fälle, in denen Sie möglicherweise **dasselbe Schema für Eingabe und Ausgabe** haben möchten. @@ -217,37 +85,15 @@ Der Hauptanwendungsfall hierfür besteht wahrscheinlich darin, dass Sie das mal In diesem Fall können Sie diese Funktion in **FastAPI** mit dem Parameter `separate_input_output_schemas=False` deaktivieren. -/// info +/// info | Info Unterstützung für `separate_input_output_schemas` wurde in FastAPI `0.102.0` hinzugefügt. 🤓 /// -//// tab | Python 3.10+ +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} -```Python hl_lines="10" -{!> ../../../docs_src/separate_openapi_schemas/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/separate_openapi_schemas/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12" -{!> ../../../docs_src/separate_openapi_schemas/tutorial002.py!} -``` - -//// - -### Gleiches Schema für Eingabe- und Ausgabemodelle in der Dokumentation +### Gleiches Schema für Eingabe- und Ausgabemodelle in der Dokumentation { #same-schema-for-input-and-output-models-in-docs } Und jetzt wird es ein einziges Schema für die Eingabe und Ausgabe des Modells geben, nur `Item`, und es wird `description` als **nicht erforderlich** kennzeichnen: diff --git a/docs/de/docs/how-to/testing-database.md b/docs/de/docs/how-to/testing-database.md new file mode 100644 index 0000000000..1a6095e53b --- /dev/null +++ b/docs/de/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Eine Datenbank testen { #testing-a-database } + +Sie können sich über Datenbanken, SQL und SQLModel in der SQLModel-Dokumentation informieren. 🤓 + +Es gibt ein kurzes Tutorial zur Verwendung von SQLModel mit FastAPI. ✨ + +Dieses Tutorial enthält einen Abschnitt über das Testen von SQL-Datenbanken. 😎 diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md index af024d18d0..4be65071bb 100644 --- a/docs/de/docs/index.md +++ b/docs/de/docs/index.md @@ -1,21 +1,21 @@ -# FastAPI +# FastAPI { #fastapi }

- FastAPI + FastAPI

- FastAPI Framework, hochperformant, leicht zu erlernen, schnell zu programmieren, einsatzbereit + FastAPI-Framework, hohe Performanz, leicht zu lernen, schnell zu entwickeln, produktionsreif

- Test + Test - Coverage + Testabdeckung Package-Version @@ -27,7 +27,7 @@ --- -**Dokumentation**: https://fastapi.tiangolo.com +**Dokumentation**: https://fastapi.tiangolo.com/de **Quellcode**: https://github.com/fastapi/fastapi @@ -37,19 +37,18 @@ FastAPI ist ein modernes, schnelles (hoch performantes) Webframework zur Erstell Seine Schlüssel-Merkmale sind: -* **Schnell**: Sehr hohe Leistung, auf Augenhöhe mit **NodeJS** und **Go** (Dank Starlette und Pydantic). [Eines der schnellsten verfügbaren Python-Frameworks](#performanz). - -* **Schnell zu programmieren**: Erhöhen Sie die Geschwindigkeit bei der Entwicklung von Funktionen um etwa 200 % bis 300 %. * +* **Schnell**: Sehr hohe Performanz, auf Augenhöhe mit **NodeJS** und **Go** (dank Starlette und Pydantic). [Eines der schnellsten verfügbaren Python-Frameworks](#performance). +* **Schnell zu entwickeln**: Erhöhen Sie die Geschwindigkeit bei der Entwicklung von Features um etwa 200 % bis 300 %. * * **Weniger Bugs**: Verringern Sie die von Menschen (Entwicklern) verursachten Fehler um etwa 40 %. * -* **Intuitiv**: Exzellente Editor-Unterstützung. Code-Vervollständigung überall. Weniger Debuggen. -* **Einfach**: So konzipiert, dass es einfach zu benutzen und zu erlernen ist. Weniger Zeit für das Lesen der Dokumentation. -* **Kurz**: Minimieren Sie die Verdoppelung von Code. Mehrere Funktionen aus jeder Parameterdeklaration. Weniger Bugs. +* **Intuitiv**: Hervorragende Editor-Unterstützung. Code-Vervollständigung überall. Weniger Zeit mit Debuggen verbringen. +* **Einfach**: So konzipiert, dass es einfach zu benutzen und zu erlernen ist. Weniger Zeit mit dem Lesen von Dokumentation verbringen. +* **Kurz**: Minimieren Sie die Verdoppelung von Code. Mehrere Features aus jeder Parameterdeklaration. Weniger Bugs. * **Robust**: Erhalten Sie produktionsreifen Code. Mit automatischer, interaktiver Dokumentation. * **Standards-basiert**: Basierend auf (und vollständig kompatibel mit) den offenen Standards für APIs: OpenAPI (früher bekannt als Swagger) und JSON Schema. -* Schätzung auf Basis von Tests in einem internen Entwicklungsteam, das Produktionsanwendungen erstellt. +* Schätzung basierend auf Tests in einem internen Entwicklungsteam, das Produktionsanwendungen erstellt. -## Sponsoren +## Sponsoren { #sponsors } @@ -64,55 +63,55 @@ Seine Schlüssel-Merkmale sind: -Andere Sponsoren +Andere Sponsoren -## Meinungen +## Meinungen { #opinions } -„_[...] Ich verwende **FastAPI** heutzutage sehr oft. [...] Ich habe tatsächlich vor, es für alle **ML-Dienste meines Teams bei Microsoft** zu verwenden. Einige davon werden in das Kernprodukt **Windows** und einige **Office**-Produkte integriert._“ +„_[...] Ich verwende **FastAPI** heutzutage sehr oft. [...] Ich habe tatsächlich vor, es für alle **ML-Services meines Teams bei Microsoft** zu verwenden. Einige davon werden in das Kernprodukt **Windows** und einige **Office**-Produkte integriert._“ -

Kabir Khan - Microsoft (Ref)
+
Kabir Khan – Microsoft (Ref.)
--- -„_Wir haben die **FastAPI**-Bibliothek genommen, um einen **REST**-Server zu erstellen, der abgefragt werden kann, um **Vorhersagen** zu erhalten. [für Ludwig]_“ +„_Wir haben die **FastAPI**-Bibliothek übernommen, um einen **REST**-Server zu erstellen, der für **Vorhersagen** abgefragt werden kann. [für Ludwig]_“ -
Piero Molino, Yaroslav Dudin, und Sai Sumanth Miryala - Uber (Ref)
+
Piero Molino, Yaroslav Dudin, und Sai Sumanth Miryala – Uber (Ref.)
--- „_**Netflix** freut sich, die Open-Source-Veröffentlichung unseres **Krisenmanagement**-Orchestrierung-Frameworks bekannt zu geben: **Dispatch**! [erstellt mit **FastAPI**]_“ -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (Ref)
+
Kevin Glisson, Marc Vilanova, Forest Monsen – Netflix (Ref.)
--- -„_Ich bin überglücklich mit **FastAPI**. Es macht so viel Spaß!_“ +„_Ich bin hellauf begeistert von **FastAPI**. Es macht so viel Spaß!_“ -
Brian Okken - Host des Python Bytes Podcast (Ref)
+
Brian Okken – Python Bytes Podcast-Host (Ref.)
--- „_Ehrlich, was Du gebaut hast, sieht super solide und poliert aus. In vielerlei Hinsicht ist es so, wie ich **Hug** haben wollte – es ist wirklich inspirierend, jemanden so etwas bauen zu sehen._“ -
Timothy Crosley - Autor von Hug (Ref)
+
Timothy Crosley – Hug-Autor (Ref.)
--- -„_Wenn Sie ein **modernes Framework** zum Erstellen von REST-APIs erlernen möchten, schauen Sie sich **FastAPI** an. [...] Es ist schnell, einfach zu verwenden und leicht zu erlernen [...]_“ +„_Wenn Sie ein **modernes Framework** zum Erstellen von REST-APIs erlernen möchten, schauen Sie sich **FastAPI** an. [...] Es ist schnell, einfach zu verwenden und leicht zu lernen [...]_“ „_Wir haben zu **FastAPI** für unsere **APIs** gewechselt [...] Ich denke, es wird Ihnen gefallen [...]_“ -
Ines Montani - Matthew Honnibal - Gründer von Explosion AI - Autoren von spaCy (Ref) - (Ref)
+
Ines Montani – Matthew Honnibal – Explosion AI-Gründer – spaCy-Autoren (Ref.)(Ref.)
--- -„_Falls irgendjemand eine Produktions-Python-API erstellen möchte, kann ich **FastAPI** wärmstens empfehlen. Es ist **wunderschön konzipiert**, **einfach zu verwenden** und **hoch skalierbar**; es ist zu einer **Schlüsselkomponente** in unserer API-First-Entwicklungsstrategie geworden und treibt viele Automatisierungen und Dienste an, wie etwa unseren virtuellen TAC-Ingenieur._“ +„_Falls irgendjemand eine Produktions-Python-API erstellen möchte, kann ich **FastAPI** wärmstens empfehlen. Es ist **wunderschön konzipiert**, **einfach zu verwenden** und **hoch skalierbar**; es ist zu einer **Schlüsselkomponente** unserer API-First-Entwicklungsstrategie geworden und treibt viele Automatisierungen und Services an, wie etwa unseren Virtual TAC Engineer._“ -
Deon Pillsbury - Cisco (Ref)
+
Deon Pillsbury – Cisco (Ref.)
--- -## **Typer**, das FastAPI der CLIs +## **Typer**, das FastAPI der CLIs { #typer-the-fastapi-of-clis } @@ -120,42 +119,34 @@ Wenn Sie eine Starlette für die Webanteile. -* Pydantic für die Datenanteile. +* Starlette für die Webanteile. +* Pydantic für die Datenanteile. -## Installation +## Installation { #installation } + +Erstellen und aktivieren Sie eine virtuelle Umgebung und installieren Sie dann FastAPI:
```console -$ pip install fastapi +$ pip install "fastapi[standard]" ---> 100% ```
-Sie benötigen außerdem einen ASGI-Server. Für die Produktumgebung beispielsweise Uvicorn oder Hypercorn. +**Hinweis**: Stellen Sie sicher, dass Sie `"fastapi[standard]"` in Anführungszeichen setzen, damit es in allen Terminals funktioniert. -
+## Beispiel { #example } -```console -$ pip install "uvicorn[standard]" +### Erstellung { #create-it } ----> 100% -``` - -
- -## Beispiel - -### Erstellung - -* Erstellen Sie eine Datei `main.py` mit: +Erstellen Sie eine Datei `main.py` mit: ```Python from typing import Union @@ -198,23 +189,37 @@ async def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -**Anmerkung**: +**Hinweis**: + +Wenn Sie das nicht kennen, schauen Sie sich den Abschnitt _„In Eile?“_ über `async` und `await` in der Dokumentation an. -Wenn Sie das nicht kennen, schauen Sie sich den Abschnitt _„In Eile?“_ über `async` und `await` in der Dokumentation an.
-### Starten +### Starten { #run-it } -Führen Sie den Server aus: +Starten Sie den Server mit:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + │ fastapi run │ + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] INFO: Waiting for application startup. INFO: Application startup complete. ``` @@ -222,34 +227,34 @@ INFO: Application startup complete.
-Was macht der Befehl uvicorn main:app --reload ... +Was der Befehl fastapi dev main.py macht ... -Der Befehl `uvicorn main:app` bezieht sich auf: +Der Befehl `fastapi dev` liest Ihre `main.py`-Datei, erkennt die **FastAPI**-App darin und startet einen Server mit Uvicorn. -* `main`: die Datei `main.py` (das Python-„Modul“). -* `app`: das Objekt, das innerhalb von `main.py` mit der Zeile `app = FastAPI()` erzeugt wurde. -* `--reload`: lässt den Server nach Codeänderungen neu starten. Tun Sie das nur während der Entwicklung. +Standardmäßig wird `fastapi dev` mit aktiviertem Auto-Reload für die lokale Entwicklung gestartet. + +Sie können mehr darüber in der FastAPI CLI Dokumentation lesen.
-### Testen +### Es testen { #check-it } Öffnen Sie Ihren Browser unter http://127.0.0.1:8000/items/5?q=somequery. -Sie erhalten die JSON-Response: +Sie sehen die JSON-Response als: ```JSON {"item_id": 5, "q": "somequery"} ``` -Damit haben Sie bereits eine API erstellt, welche: +Sie haben bereits eine API erstellt, welche: -* HTTP-Anfragen auf den _Pfaden_ `/` und `/items/{item_id}` entgegennimmt. -* Beide _Pfade_ erhalten `GET` Operationen (auch bekannt als HTTP _Methoden_). -* Der _Pfad_ `/items/{item_id}` hat einen _Pfadparameter_ `item_id`, der ein `int` sein sollte. -* Der _Pfad_ `/items/{item_id}` hat einen optionalen `str` _Query Parameter_ `q`. +* HTTP-Requests auf den _Pfaden_ `/` und `/items/{item_id}` entgegennimmt. +* Beide _Pfade_ nehmen `GET` Operationen (auch bekannt als HTTP-_Methoden_) entgegen. +* Der _Pfad_ `/items/{item_id}` hat einen _Pfad-Parameter_ `item_id`, der ein `int` sein sollte. +* Der _Pfad_ `/items/{item_id}` hat einen optionalen `str`-_Query-Parameter_ `q`. -### Interaktive API-Dokumentation +### Interaktive API-Dokumentation { #interactive-api-docs } Gehen Sie nun auf http://127.0.0.1:8000/docs. @@ -257,19 +262,19 @@ Sie sehen die automatische interaktive API-Dokumentation (bereitgestellt von http://127.0.0.1:8000/redoc. +Und jetzt gehen Sie auf http://127.0.0.1:8000/redoc. Sie sehen die alternative automatische Dokumentation (bereitgestellt von ReDoc): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Beispiel Aktualisierung +## Beispiel Aktualisierung { #example-upgrade } -Ändern Sie jetzt die Datei `main.py`, um den Body einer `PUT`-Anfrage zu empfangen. +Ändern Sie jetzt die Datei `main.py`, um den Body eines `PUT`-Requests zu empfangen. -Deklarieren Sie den Body mithilfe von Standard-Python-Typen, dank Pydantic. +Deklarieren Sie den Body mit Standard-Python-Typen, dank Pydantic. ```Python hl_lines="4 9-12 25-27" from typing import Union @@ -301,9 +306,9 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -Der Server sollte automatisch neu geladen werden (weil Sie oben `--reload` zum Befehl `uvicorn` hinzugefügt haben). +Der `fastapi dev`-Server sollte automatisch neu laden. -### Aktualisierung der interaktiven API-Dokumentation +### Interaktive API-Dokumentation aktualisieren { #interactive-api-docs-upgrade } Gehen Sie jetzt auf http://127.0.0.1:8000/docs. @@ -311,31 +316,31 @@ Gehen Sie jetzt auf http://127.0.0.1:8000/redoc. +Und jetzt gehen Sie auf http://127.0.0.1:8000/redoc. -* Die alternative Dokumentation wird ebenfalls den neuen Abfrageparameter und -inhalt widerspiegeln: +* Die alternative Dokumentation wird ebenfalls den neuen Query-Parameter und Body widerspiegeln: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Zusammenfassung +### Zusammenfassung { #recap } -Zusammengefasst deklarieren Sie **einmal** die Typen von Parametern, Body, etc. als Funktionsparameter. +Zusammengefasst deklarieren Sie **einmal** die Typen von Parametern, Body, usw. als Funktionsparameter. Das machen Sie mit modernen Standard-Python-Typen. Sie müssen keine neue Syntax, Methoden oder Klassen einer bestimmten Bibliothek usw. lernen. -Nur Standard-**Python+**. +Nur Standard-**Python**. Zum Beispiel für ein `int`: @@ -356,22 +361,22 @@ item: Item * Typprüfungen. * Validierung von Daten: * Automatische und eindeutige Fehler, wenn die Daten ungültig sind. - * Validierung auch für tief verschachtelte JSON-Objekte. + * Validierung sogar für tief verschachtelte JSON-Objekte. * Konvertierung von Eingabedaten: Aus dem Netzwerk kommend, zu Python-Daten und -Typen. Lesen von: * JSON. * Pfad-Parametern. - * Abfrage-Parametern. + * Query-Parametern. * Cookies. - * Header-Feldern. + * Headern. * Formularen. * Dateien. * Konvertierung von Ausgabedaten: Konvertierung von Python-Daten und -Typen zu Netzwerkdaten (als JSON): * Konvertieren von Python-Typen (`str`, `int`, `float`, `bool`, `list`, usw.). - * `Datetime`-Objekte. + * `datetime`-Objekte. * `UUID`-Objekte. * Datenbankmodelle. * ... und viele mehr. -* Automatische interaktive API-Dokumentation, einschließlich 2 alternativer Benutzeroberflächen: +* Automatische interaktive API-Dokumentation, einschließlich zwei alternativer Benutzeroberflächen: * Swagger UI. * ReDoc. @@ -379,13 +384,13 @@ item: Item Um auf das vorherige Codebeispiel zurückzukommen, **FastAPI** wird: -* Überprüfen, dass es eine `item_id` im Pfad für `GET`- und `PUT`-Anfragen gibt. -* Überprüfen, ob die `item_id` vom Typ `int` für `GET`- und `PUT`-Anfragen ist. - * Falls nicht, wird dem Client ein nützlicher, eindeutiger Fehler angezeigt. -* Prüfen, ob es einen optionalen Abfrageparameter namens `q` (wie in `http://127.0.0.1:8000/items/foo?q=somequery`) für `GET`-Anfragen gibt. +* Validieren, dass es eine `item_id` im Pfad für `GET`- und `PUT`-Requests gibt. +* Validieren, ob die `item_id` vom Typ `int` für `GET`- und `PUT`-Requests ist. + * Falls nicht, sieht der Client einen hilfreichen, klaren Fehler. +* Prüfen, ob es einen optionalen Query-Parameter namens `q` (wie in `http://127.0.0.1:8000/items/foo?q=somequery`) für `GET`-Requests gibt. * Da der `q`-Parameter mit `= None` deklariert ist, ist er optional. * Ohne das `None` wäre er erforderlich (wie der Body im Fall von `PUT`). -* Bei `PUT`-Anfragen an `/items/{item_id}` den Body als JSON lesen: +* Bei `PUT`-Requests an `/items/{item_id}` den Body als JSON lesen: * Prüfen, ob er ein erforderliches Attribut `name` hat, das ein `str` sein muss. * Prüfen, ob er ein erforderliches Attribut `price` hat, das ein `float` sein muss. * Prüfen, ob er ein optionales Attribut `is_offer` hat, das ein `bool` sein muss, falls vorhanden. @@ -394,7 +399,7 @@ Um auf das vorherige Codebeispiel zurückzukommen, **FastAPI** wird: * Alles mit OpenAPI dokumentieren, welches verwendet werden kann von: * Interaktiven Dokumentationssystemen. * Automatisch Client-Code generierenden Systemen für viele Sprachen. -* Zwei interaktive Dokumentation-Webschnittstellen direkt zur Verfügung stellen. +* Zwei interaktive Dokumentations-Weboberflächen direkt bereitstellen. --- @@ -418,57 +423,79 @@ Versuchen Sie, diese Zeile zu ändern: ... "item_price": item.price ... ``` -... und sehen Sie, wie Ihr Editor die Attribute automatisch ausfüllt und ihre Typen kennt: +... und sehen Sie, wie Ihr Editor die Attribute automatisch vervollständigt und ihre Typen kennt: ![Editor Unterstützung](https://fastapi.tiangolo.com/img/vscode-completion.png) -Für ein vollständigeres Beispiel, mit weiteren Funktionen, siehe das Tutorial - Benutzerhandbuch. +Für ein vollständigeres Beispiel, mit weiteren Funktionen, siehe das Tutorial – Benutzerhandbuch. -**Spoiler-Alarm**: Das Tutorial - Benutzerhandbuch enthält: +**Spoiler-Alarm**: Das Tutorial – Benutzerhandbuch enthält: -* Deklaration von **Parametern** von anderen verschiedenen Stellen wie: **Header-Felder**, **Cookies**, **Formularfelder** und **Dateien**. -* Wie man **Validierungseinschränkungen** wie `maximum_length` oder `regex` setzt. +* Deklaration von **Parametern** von anderen verschiedenen Stellen wie: **Header**, **Cookies**, **Formularfelder** und **Dateien**. +* Wie man **Validierungs-Constraints** wie `maximum_length` oder `regex` setzt. * Ein sehr leistungsfähiges und einfach zu bedienendes System für **Dependency Injection**. -* Sicherheit und Authentifizierung, einschließlich Unterstützung für **OAuth2** mit **JWT-Tokens** und **HTTP-Basic**-Authentifizierung. +* Sicherheit und Authentifizierung, einschließlich Unterstützung für **OAuth2** mit **JWT-Tokens** und **HTTP Basic** Authentifizierung. * Fortgeschrittenere (aber ebenso einfache) Techniken zur Deklaration **tief verschachtelter JSON-Modelle** (dank Pydantic). -* **GraphQL** Integration mit Strawberry und anderen Bibliotheken. -* Viele zusätzliche Funktionen (dank Starlette) wie: +* **GraphQL**-Integration mit Strawberry und anderen Bibliotheken. +* Viele zusätzliche Features (dank Starlette) wie: * **WebSockets** - * extrem einfache Tests auf Basis von `httpx` und `pytest` + * extrem einfache Tests auf Basis von HTTPX und `pytest` * **CORS** - * **Cookie Sessions** + * **Cookie-Sessions** * ... und mehr. -## Performanz +## Performanz { #performance } -Unabhängige TechEmpower-Benchmarks zeigen **FastAPI**-Anwendungen, die unter Uvicorn laufen, als eines der schnellsten verfügbaren Python-Frameworks, nur noch hinter Starlette und Uvicorn selbst (intern von FastAPI verwendet). +Unabhängige TechEmpower-Benchmarks zeigen **FastAPI**-Anwendungen, die unter Uvicorn laufen, als eines der schnellsten verfügbaren Python-Frameworks, nur hinter Starlette und Uvicorn selbst (intern von FastAPI verwendet). (*) -Um mehr darüber zu erfahren, siehe den Abschnitt Benchmarks. +Um mehr darüber zu erfahren, siehe den Abschnitt Benchmarks. -## Optionale Abhängigkeiten +## Abhängigkeiten { #dependencies } -Wird von Pydantic verwendet: +FastAPI hängt von Pydantic und Starlette ab. -* email-validator - für E-Mail-Validierung. -* pydantic-settings - für die Verwaltung von Einstellungen. -* pydantic-extra-types - für zusätzliche Typen, mit Pydantic zu verwenden. +### `standard`-Abhängigkeiten { #standard-dependencies } -Wird von Starlette verwendet: +Wenn Sie FastAPI mit `pip install "fastapi[standard]"` installieren, kommt es mit der `standard`-Gruppe optionaler Abhängigkeiten: -* httpx - erforderlich, wenn Sie den `TestClient` verwenden möchten. -* jinja2 - erforderlich, wenn Sie die Standardkonfiguration für Templates verwenden möchten. -* python-multipart - erforderlich, wenn Sie Formulare mittels `request.form()` „parsen“ möchten. -* itsdangerous - erforderlich für `SessionMiddleware` Unterstützung. -* pyyaml - erforderlich für Starlette's `SchemaGenerator` Unterstützung (Sie brauchen das wahrscheinlich nicht mit FastAPI). -* ujson - erforderlich, wenn Sie `UJSONResponse` verwenden möchten. +Verwendet von Pydantic: -Wird von FastAPI / Starlette verwendet: +* email-validator – für E-Mail-Validierung. -* uvicorn - für den Server, der Ihre Anwendung lädt und serviert. -* orjson - erforderlich, wenn Sie `ORJSONResponse` verwenden möchten. +Verwendet von Starlette: -Sie können diese alle mit `pip install "fastapi[all]"` installieren. +* httpx – erforderlich, wenn Sie den `TestClient` verwenden möchten. +* jinja2 – erforderlich, wenn Sie die Default-Template-Konfiguration verwenden möchten. +* python-multipart – erforderlich, wenn Sie Formulare mittels `request.form()` „parsen“ möchten. -## Lizenz +Verwendet von FastAPI: + +* uvicorn – für den Server, der Ihre Anwendung lädt und bereitstellt. Dies umfasst `uvicorn[standard]`, das einige Abhängigkeiten (z. B. `uvloop`) beinhaltet, die für eine Bereitstellung mit hoher Performanz benötigt werden. +* `fastapi-cli[standard]` – um den `fastapi`-Befehl bereitzustellen. + * Dies beinhaltet `fastapi-cloud-cli`, das es Ihnen ermöglicht, Ihre FastAPI-Anwendung auf FastAPI Cloud bereitzustellen. + +### Ohne `standard`-Abhängigkeiten { #without-standard-dependencies } + +Wenn Sie die `standard` optionalen Abhängigkeiten nicht einschließen möchten, können Sie mit `pip install fastapi` statt `pip install "fastapi[standard]"` installieren. + +### Ohne `fastapi-cloud-cli` { #without-fastapi-cloud-cli } + +Wenn Sie FastAPI mit den Standardabhängigkeiten, aber ohne das `fastapi-cloud-cli` installieren möchten, können Sie mit `pip install "fastapi[standard-no-fastapi-cloud-cli]"` installieren. + +### Zusätzliche optionale Abhängigkeiten { #additional-optional-dependencies } + +Es gibt einige zusätzliche Abhängigkeiten, die Sie installieren möchten. + +Zusätzliche optionale Pydantic-Abhängigkeiten: + +* pydantic-settings – für die Verwaltung von Einstellungen. +* pydantic-extra-types – für zusätzliche Typen zur Verwendung mit Pydantic. + +Zusätzliche optionale FastAPI-Abhängigkeiten: + +* orjson – erforderlich, wenn Sie `ORJSONResponse` verwenden möchten. +* ujson – erforderlich, wenn Sie `UJSONResponse` verwenden möchten. + +## Lizenz { #license } Dieses Projekt ist unter den Bedingungen der MIT-Lizenz lizenziert. diff --git a/docs/de/docs/learn/index.md b/docs/de/docs/learn/index.md index b5582f55b6..e1f583fb38 100644 --- a/docs/de/docs/learn/index.md +++ b/docs/de/docs/learn/index.md @@ -1,5 +1,5 @@ -# Lernen +# Lernen { #learn } -Hier finden Sie die einführenden Kapitel und Tutorials zum Erlernen von **FastAPI**. +Hier sind die einführenden Abschnitte und Tutorials, um **FastAPI** zu lernen. Sie könnten dies als **Buch**, als **Kurs**, als **offizielle** und empfohlene Methode zum Erlernen von FastAPI betrachten. 😎 diff --git a/docs/de/docs/project-generation.md b/docs/de/docs/project-generation.md index c47bcb6d30..e6da4949c1 100644 --- a/docs/de/docs/project-generation.md +++ b/docs/de/docs/project-generation.md @@ -1,84 +1,28 @@ -# Projektgenerierung – Vorlage +# Full Stack FastAPI Template { #full-stack-fastapi-template } -Sie können einen Projektgenerator für den Einstieg verwenden, welcher einen Großteil der Ersteinrichtung, Sicherheit, Datenbank und einige API-Endpunkte bereits für Sie erstellt. +Vorlagen, die normalerweise mit einem bestimmten Setup geliefert werden, sind so konzipiert, dass sie flexibel und anpassbar sind. Dies ermöglicht es Ihnen, sie zu ändern und an die Anforderungen Ihres Projekts anzupassen und sie somit zu einem hervorragenden Ausgangspunkt zu machen. 🏁 -Ein Projektgenerator verfügt immer über ein sehr spezifisches Setup, das Sie aktualisieren und an Ihre eigenen Bedürfnisse anpassen sollten, aber es könnte ein guter Ausgangspunkt für Ihr Projekt sein. +Sie können diese Vorlage verwenden, um loszulegen, da sie bereits vieles der anfänglichen Einrichtung, Sicherheit, Datenbank und einige API-Endpunkte für Sie eingerichtet hat. -## Full Stack FastAPI PostgreSQL +GitHub-Repository: Full Stack FastAPI Template -GitHub: https://github.com/tiangolo/full-stack-fastapi-postgresql +## Full Stack FastAPI Template – Technologiestack und Funktionen { #full-stack-fastapi-template-technology-stack-and-features } -### Full Stack FastAPI PostgreSQL – Funktionen - -* Vollständige **Docker**-Integration (Docker-basiert). -* Docker-Schwarmmodus-Deployment. -* **Docker Compose**-Integration und Optimierung für die lokale Entwicklung. -* **Produktionsbereit** Python-Webserver, verwendet Uvicorn und Gunicorn. -* Python **FastAPI**-Backend: - * **Schnell**: Sehr hohe Leistung, auf Augenhöhe mit **NodeJS** und **Go** (dank Starlette und Pydantic). - * **Intuitiv**: Hervorragende Editor-Unterstützung. Codevervollständigung überall. Weniger Zeitaufwand für das Debuggen. - * **Einfach**: Einfach zu bedienen und zu erlernen. Weniger Zeit für das Lesen von Dokumentationen. - * **Kurz**: Codeverdoppelung minimieren. Mehrere Funktionalitäten aus jeder Parameterdeklaration. - * **Robust**: Erhalten Sie produktionsbereiten Code. Mit automatischer, interaktiver Dokumentation. - * **Standards-basiert**: Basierend auf (und vollständig kompatibel mit) den offenen Standards für APIs: OpenAPI und JSON Schema. - * **Viele weitere Funktionen**, einschließlich automatischer Validierung, Serialisierung, interaktiver Dokumentation, Authentifizierung mit OAuth2-JWT-Tokens, usw. -* **Sicheres Passwort**-Hashing standardmäßig. -* **JWT-Token**-Authentifizierung. -* **SQLAlchemy**-Modelle (unabhängig von Flask-Erweiterungen, sodass sie direkt mit Celery-Workern verwendet werden können). -* Grundlegende Startmodelle für Benutzer (ändern und entfernen Sie nach Bedarf). -* **Alembic**-Migrationen. -* **CORS** (Cross Origin Resource Sharing). -* **Celery**-Worker, welche Modelle und Code aus dem Rest des Backends selektiv importieren und verwenden können. -* REST-Backend-Tests basierend auf **Pytest**, integriert in Docker, sodass Sie die vollständige API-Interaktion unabhängig von der Datenbank testen können. Da es in Docker ausgeführt wird, kann jedes Mal ein neuer Datenspeicher von Grund auf erstellt werden (Sie können also ElasticSearch, MongoDB, CouchDB oder was auch immer Sie möchten verwenden und einfach testen, ob die API funktioniert). -* Einfache Python-Integration mit **Jupyter-Kerneln** für Remote- oder In-Docker-Entwicklung mit Erweiterungen wie Atom Hydrogen oder Visual Studio Code Jupyter. -* **Vue**-Frontend: - * Mit Vue CLI generiert. - * Handhabung der **JWT-Authentifizierung**. - * Login-View. - * Nach der Anmeldung Hauptansicht des Dashboards. - * Haupt-Dashboard mit Benutzererstellung und -bearbeitung. - * Bearbeitung des eigenen Benutzers. - * **Vuex**. - * **Vue-Router**. - * **Vuetify** für schöne Material-Designkomponenten. - * **TypeScript**. - * Docker-Server basierend auf **Nginx** (konfiguriert, um gut mit Vue-Router zu funktionieren). - * Mehrstufigen Docker-Erstellung, sodass Sie kompilierten Code nicht speichern oder committen müssen. - * Frontend-Tests, welche zur Erstellungszeit ausgeführt werden (können auch deaktiviert werden). - * So modular wie möglich gestaltet, sodass es sofort einsatzbereit ist. Sie können es aber mit Vue CLI neu generieren oder es so wie Sie möchten erstellen und wiederverwenden, was Sie möchten. -* **PGAdmin** für die PostgreSQL-Datenbank, können Sie problemlos ändern, sodass PHPMyAdmin und MySQL verwendet wird. -* **Flower** für die Überwachung von Celery-Jobs. -* Load Balancing zwischen Frontend und Backend mit **Traefik**, sodass Sie beide unter derselben Domain haben können, getrennt durch den Pfad, aber von unterschiedlichen Containern ausgeliefert. -* Traefik-Integration, einschließlich automatischer Generierung von Let's Encrypt-**HTTPS**-Zertifikaten. -* GitLab **CI** (kontinuierliche Integration), einschließlich Frontend- und Backend-Testen. - -## Full Stack FastAPI Couchbase - -GitHub: https://github.com/tiangolo/full-stack-fastapi-couchbase - -⚠️ **WARNUNG** ⚠️ - -Wenn Sie ein neues Projekt von Grund auf starten, prüfen Sie die Alternativen hier. - -Zum Beispiel könnte der Projektgenerator Full Stack FastAPI PostgreSQL eine bessere Alternative sein, da er aktiv gepflegt und genutzt wird. Und er enthält alle neuen Funktionen und Verbesserungen. - -Es steht Ihnen weiterhin frei, den Couchbase-basierten Generator zu verwenden, wenn Sie möchten. Er sollte wahrscheinlich immer noch gut funktionieren, und wenn Sie bereits ein Projekt damit erstellt haben, ist das auch in Ordnung (und Sie haben es wahrscheinlich bereits an Ihre Bedürfnisse angepasst). - -Weitere Informationen hierzu finden Sie in der Dokumentation des Repos. - -## Full Stack FastAPI MongoDB - -... könnte später kommen, abhängig von meiner verfügbaren Zeit und anderen Faktoren. 😅 🎉 - -## Modelle für maschinelles Lernen mit spaCy und FastAPI - -GitHub: https://github.com/microsoft/cookiecutter-spacy-fastapi - -### Modelle für maschinelles Lernen mit spaCy und FastAPI – Funktionen - -* **spaCy** NER-Modellintegration. -* **Azure Cognitive Search**-Anforderungsformat integriert. -* **Produktionsbereit** Python-Webserver, verwendet Uvicorn und Gunicorn. -* **Azure DevOps** Kubernetes (AKS) CI/CD-Deployment integriert. -* **Mehrsprachig** Wählen Sie bei der Projekteinrichtung ganz einfach eine der integrierten Sprachen von spaCy aus. -* **Einfach erweiterbar** auf andere Modellframeworks (Pytorch, Tensorflow), nicht nur auf SpaCy. +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/de) für die Python-Backend-API. + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) für die Interaktion mit der Python-SQL-Datenbank (ORM). + - 🔍 [Pydantic](https://docs.pydantic.dev), verwendet von FastAPI, für die Datenvalidierung und das Einstellungsmanagement. + - 💾 [PostgreSQL](https://www.postgresql.org) als SQL-Datenbank. +- 🚀 [React](https://react.dev) für das Frontend. + - 💃 Verwendung von TypeScript, Hooks, [Vite](https://vitejs.dev) und anderen Teilen eines modernen Frontend-Stacks. + - 🎨 [Chakra UI](https://chakra-ui.com) für die Frontend-Komponenten. + - 🤖 Ein automatisch generierter Frontend-Client. + - 🧪 [Playwright](https://playwright.dev) für End-to-End-Tests. + - 🦇 Unterstützung des Dunkelmodus. +- 🐋 [Docker Compose](https://www.docker.com) für Entwicklung und Produktion. +- 🔒 Sicheres Passwort-Hashing standardmäßig. +- 🔑 JWT-Token-Authentifizierung. +- 📫 E-Mail-basierte Passwortwiederherstellung. +- ✅ Tests mit [Pytest](https://pytest.org). +- 📞 [Traefik](https://traefik.io) als Reverse-Proxy / Load Balancer. +- 🚢 Deployment-Anleitungen unter Verwendung von Docker Compose, einschließlich der Einrichtung eines Frontend-Traefik-Proxys zur Handhabung automatischer HTTPS-Zertifikate. +- 🏭 CI (kontinuierliche Integration) und CD (kontinuierliche Bereitstellung) basierend auf GitHub Actions. diff --git a/docs/de/docs/python-types.md b/docs/de/docs/python-types.md index 9bbff83d33..317ee4e629 100644 --- a/docs/de/docs/python-types.md +++ b/docs/de/docs/python-types.md @@ -1,8 +1,8 @@ -# Einführung in Python-Typen +# Einführung in Python-Typen { #python-types-intro } -Python hat Unterstützung für optionale „Typhinweise“ (Englisch: „Type Hints“). Auch „Typ Annotationen“ genannt. +Python hat Unterstützung für optionale „Typhinweise“ (auch „Typannotationen“ genannt). -Diese **„Typhinweise“** oder -Annotationen sind eine spezielle Syntax, die es erlaubt, den Typ einer Variablen zu deklarieren. +Diese **„Typhinweise“** oder -Annotationen sind eine spezielle Syntax, die es erlaubt, den Typ einer Variablen zu deklarieren. Durch das Deklarieren von Typen für Ihre Variablen können Editoren und Tools bessere Unterstützung bieten. @@ -12,19 +12,17 @@ Dies ist lediglich eine **schnelle Anleitung / Auffrischung** über Pythons Typh Aber selbst wenn Sie **FastAPI** nie verwenden, wird es für Sie nützlich sein, ein wenig darüber zu lernen. -/// note | "Hinweis" +/// note | Hinweis Wenn Sie ein Python-Experte sind und bereits alles über Typhinweise wissen, überspringen Sie dieses Kapitel und fahren Sie mit dem nächsten fort. /// -## Motivation +## Motivation { #motivation } Fangen wir mit einem einfachen Beispiel an: -```Python -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py *} Dieses Programm gibt aus: @@ -38,11 +36,9 @@ Die Funktion macht Folgendes: * Schreibt den ersten Buchstaben eines jeden Wortes groß, mithilfe von `title()`. * Verkettet sie mit einem Leerzeichen in der Mitte. -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py hl[2] *} -### Bearbeiten Sie es +### Es bearbeiten { #edit-it } Es ist ein sehr einfaches Programm. @@ -62,7 +58,7 @@ Aber leider erhalten Sie nichts Nützliches: -### Typen hinzufügen +### Typen hinzufügen { #add-types } Lassen Sie uns eine einzelne Zeile aus der vorherigen Version ändern. @@ -82,9 +78,7 @@ Das war's. Das sind die „Typhinweise“: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial002.py!} -``` +{* ../../docs_src/python_types/tutorial002.py hl[1] *} Das ist nicht das gleiche wie das Deklarieren von Defaultwerten, wie es hier der Fall ist: @@ -108,13 +102,11 @@ Hier können Sie durch die Optionen blättern, bis Sie diejenige finden, bei der -## Mehr Motivation +## Mehr Motivation { #more-motivation } Sehen Sie sich diese Funktion an, sie hat bereits Typhinweise: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial003.py!} -``` +{* ../../docs_src/python_types/tutorial003.py hl[1] *} Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervollständigung, sondern auch eine Fehlerprüfung: @@ -122,17 +114,15 @@ Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervoll Jetzt, da Sie wissen, dass Sie das reparieren müssen, konvertieren Sie `age` mittels `str(age)` in einen String: -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial004.py!} -``` +{* ../../docs_src/python_types/tutorial004.py hl[2] *} -## Deklarieren von Typen +## Deklarieren von Typen { #declaring-types } Sie haben gerade den Haupt-Einsatzort für die Deklaration von Typhinweisen gesehen. Als Funktionsparameter. Das ist auch meistens, wie sie in **FastAPI** verwendet werden. -### Einfache Typen +### Einfache Typen { #simple-types } Sie können alle Standard-Python-Typen deklarieren, nicht nur `str`. @@ -143,11 +133,9 @@ Zum Beispiel diese: * `bool` * `bytes` -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial005.py!} -``` +{* ../../docs_src/python_types/tutorial005.py hl[1] *} -### Generische Typen mit Typ-Parametern +### Generische Typen mit Typ-Parametern { #generic-types-with-type-parameters } Es gibt Datenstrukturen, die andere Werte enthalten können, wie etwa `dict`, `list`, `set` und `tuple`. Die inneren Werte können auch ihren eigenen Typ haben. @@ -155,7 +143,7 @@ Diese Typen mit inneren Typen werden „**generische**“ Typen genannt. Es ist Um diese Typen und die inneren Typen zu deklarieren, können Sie Pythons Standardmodul `typing` verwenden. Es existiert speziell für die Unterstützung dieser Typhinweise. -#### Neuere Python-Versionen +#### Neuere Python-Versionen { #newer-versions-of-python } Die Syntax, welche `typing` verwendet, ist **kompatibel** mit allen Versionen, von Python 3.6 aufwärts zu den neuesten, inklusive Python 3.9, Python 3.10, usw. @@ -169,7 +157,7 @@ Zum Beispiel bedeutet „**Python 3.6+**“, dass das Beispiel kompatibel mit Py Wenn Sie über die **neueste Version von Python** verfügen, verwenden Sie die Beispiele für die neueste Version, diese werden die **beste und einfachste Syntax** haben, zum Beispiel, „**Python 3.10+**“. -#### Liste +#### Liste { #list } Definieren wir zum Beispiel eine Variable, die eine `list` von `str` – eine Liste von Strings – sein soll. @@ -182,7 +170,7 @@ Als Typ nehmen Sie `list`. Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckigen Klammern umfasst: ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial006_py39.py!} +{!> ../../docs_src/python_types/tutorial006_py39.py!} ``` //// @@ -192,7 +180,7 @@ Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckige Von `typing` importieren Sie `List` (mit Großbuchstaben `L`): ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial006.py!} +{!> ../../docs_src/python_types/tutorial006.py!} ``` Deklarieren Sie die Variable mit der gleichen Doppelpunkt-Syntax (`:`). @@ -202,12 +190,12 @@ Als Typ nehmen Sie das `List`, das Sie von `typing` importiert haben. Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckigen Klammern umfasst: ```Python hl_lines="4" -{!> ../../../docs_src/python_types/tutorial006.py!} +{!> ../../docs_src/python_types/tutorial006.py!} ``` //// -/// tip | "Tipp" +/// info | Info Die inneren Typen in den eckigen Klammern werden als „Typ-Parameter“ bezeichnet. @@ -217,7 +205,7 @@ In diesem Fall ist `str` der Typ-Parameter, der an `List` übergeben wird (oder Das bedeutet: Die Variable `items` ist eine Liste – `list` – und jedes der Elemente in dieser Liste ist ein String – `str`. -/// tip | "Tipp" +/// tip | Tipp Wenn Sie Python 3.9 oder höher verwenden, müssen Sie `List` nicht von `typing` importieren, Sie können stattdessen den regulären `list`-Typ verwenden. @@ -233,14 +221,14 @@ Beachten Sie, dass die Variable `item` eines der Elemente in der Liste `items` i Und trotzdem weiß der Editor, dass es sich um ein `str` handelt, und bietet entsprechende Unterstützung. -#### Tupel und Menge +#### Tupel und Menge { #tuple-and-set } Das Gleiche gilt für die Deklaration eines Tupels – `tuple` – und einer Menge – `set`: //// tab | Python 3.9+ ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial007_py39.py!} +{!> ../../docs_src/python_types/tutorial007_py39.py!} ``` //// @@ -248,7 +236,7 @@ Das Gleiche gilt für die Deklaration eines Tupels – `tuple` – und einer Men //// tab | Python 3.8+ ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial007.py!} +{!> ../../docs_src/python_types/tutorial007.py!} ``` //// @@ -258,7 +246,7 @@ Das bedeutet: * Die Variable `items_t` ist ein `tuple` mit 3 Elementen, einem `int`, einem weiteren `int` und einem `str`. * Die Variable `items_s` ist ein `set`, und jedes seiner Elemente ist vom Typ `bytes`. -#### Dict +#### Dict { #dict } Um ein `dict` zu definieren, übergeben Sie zwei Typ-Parameter, getrennt durch Kommas. @@ -269,7 +257,7 @@ Der zweite Typ-Parameter ist für die Werte des `dict`: //// tab | Python 3.9+ ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial008_py39.py!} +{!> ../../docs_src/python_types/tutorial008_py39.py!} ``` //// @@ -277,7 +265,7 @@ Der zweite Typ-Parameter ist für die Werte des `dict`: //// tab | Python 3.8+ ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial008.py!} +{!> ../../docs_src/python_types/tutorial008.py!} ``` //// @@ -288,7 +276,7 @@ Das bedeutet: * Die Schlüssel dieses `dict` sind vom Typ `str` (z. B. die Namen der einzelnen Artikel). * Die Werte dieses `dict` sind vom Typ `float` (z. B. der Preis jedes Artikels). -#### Union +#### Union { #union } Sie können deklarieren, dass eine Variable einer von **verschiedenen Typen** sein kann, zum Beispiel ein `int` oder ein `str`. @@ -299,7 +287,7 @@ In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die mö //// tab | Python 3.10+ ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial008b_py310.py!} +{!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` //// @@ -307,21 +295,21 @@ In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die mö //// tab | Python 3.8+ ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial008b.py!} +{!> ../../docs_src/python_types/tutorial008b.py!} ``` //// In beiden Fällen bedeutet das, dass `item` ein `int` oder ein `str` sein kann. -#### Vielleicht `None` +#### Vielleicht `None` { #possibly-none } Sie können deklarieren, dass ein Wert ein `str`, aber vielleicht auch `None` sein kann. In Python 3.6 und darüber (inklusive Python 3.10) können Sie das deklarieren, indem Sie `Optional` vom `typing` Modul importieren und verwenden. ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009.py!} ``` Wenn Sie `Optional[str]` anstelle von nur `str` verwenden, wird Ihr Editor Ihnen dabei helfen, Fehler zu erkennen, bei denen Sie annehmen könnten, dass ein Wert immer eine String (`str`) ist, obwohl er auch `None` sein könnte. @@ -333,7 +321,7 @@ Das bedeutet auch, dass Sie in Python 3.10 `Something | None` verwenden können: //// tab | Python 3.10+ ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial009_py310.py!} +{!> ../../docs_src/python_types/tutorial009_py310.py!} ``` //// @@ -341,7 +329,7 @@ Das bedeutet auch, dass Sie in Python 3.10 `Something | None` verwenden können: //// tab | Python 3.8+ ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial009.py!} +{!> ../../docs_src/python_types/tutorial009.py!} ``` //// @@ -349,12 +337,12 @@ Das bedeutet auch, dass Sie in Python 3.10 `Something | None` verwenden können: //// tab | Python 3.8+ Alternative ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial009b.py!} +{!> ../../docs_src/python_types/tutorial009b.py!} ``` //// -#### `Union` oder `Optional` verwenden? +#### `Union` oder `Optional` verwenden? { #using-union-or-optional } Wenn Sie eine Python-Version unterhalb 3.10 verwenden, hier ist mein sehr **subjektiver** Standpunkt dazu: @@ -369,9 +357,7 @@ Es geht nur um Wörter und Namen. Aber diese Worte können beeinflussen, wie Sie Nehmen wir zum Beispiel diese Funktion: -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009c.py!} -``` +{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} Der Parameter `name` ist definiert als `Optional[str]`, aber er ist **nicht optional**, Sie können die Funktion nicht ohne diesen Parameter aufrufen: @@ -382,18 +368,16 @@ say_hi() # Oh, nein, das löst einen Fehler aus! 😱 Der `name` Parameter wird **immer noch benötigt** (nicht *optional*), weil er keinen Default-Wert hat. `name` akzeptiert aber dennoch `None` als Wert: ```Python -say_hi(name=None) # Das funktioniert, None is gültig 🎉 +say_hi(name=None) # Das funktioniert, None ist gültig 🎉 ``` Die gute Nachricht ist, dass Sie sich darüber keine Sorgen mehr machen müssen, wenn Sie Python 3.10 verwenden, da Sie einfach `|` verwenden können, um Vereinigungen von Typen zu definieren: -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009c_py310.py!} -``` +{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} Und dann müssen Sie sich nicht mehr um Namen wie `Optional` und `Union` kümmern. 😎 -#### Generische Typen +#### Generische Typen { #generic-types } Diese Typen, die Typ-Parameter in eckigen Klammern akzeptieren, werden **generische Typen** oder **Generics** genannt. @@ -445,21 +429,17 @@ Verwenden Sie für den Rest, wie unter Python 3.8, das `typing`-Modul: //// -### Klassen als Typen +### Klassen als Typen { #classes-as-types } Sie können auch eine Klasse als Typ einer Variablen deklarieren. Nehmen wir an, Sie haben eine Klasse `Person`, mit einem Namen: -```Python hl_lines="1-3" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} Dann können Sie eine Variable vom Typ `Person` deklarieren: -```Python hl_lines="6" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[6] *} Und wiederum bekommen Sie die volle Editor-Unterstützung: @@ -469,9 +449,9 @@ Beachten Sie, das bedeutet: „`one_person` ist eine **Instanz** der Klasse `Per Es bedeutet nicht: „`one_person` ist die **Klasse** genannt `Person`“. -## Pydantic Modelle +## Pydantic-Modelle { #pydantic-models } -Pydantic ist eine Python-Bibliothek für die Validierung von Daten. +Pydantic ist eine Python-Bibliothek für die Validierung von Daten. Sie deklarieren die „Form“ der Daten als Klassen mit Attributen. @@ -486,7 +466,7 @@ Ein Beispiel aus der offiziellen Pydantic Dokumentation: //// tab | Python 3.10+ ```Python -{!> ../../../docs_src/python_types/tutorial011_py310.py!} +{!> ../../docs_src/python_types/tutorial011_py310.py!} ``` //// @@ -494,7 +474,7 @@ Ein Beispiel aus der offiziellen Pydantic Dokumentation: //// tab | Python 3.9+ ```Python -{!> ../../../docs_src/python_types/tutorial011_py39.py!} +{!> ../../docs_src/python_types/tutorial011_py39.py!} ``` //// @@ -502,37 +482,37 @@ Ein Beispiel aus der offiziellen Pydantic Dokumentation: //// tab | Python 3.8+ ```Python -{!> ../../../docs_src/python_types/tutorial011.py!} +{!> ../../docs_src/python_types/tutorial011.py!} ``` //// -/// info +/// info | Info -Um mehr über Pydantic zu erfahren, schauen Sie sich dessen Dokumentation an. +Um mehr über Pydantic zu erfahren, schauen Sie sich dessen Dokumentation an. /// **FastAPI** basiert vollständig auf Pydantic. -Viel mehr von all dem werden Sie in praktischer Anwendung im [Tutorial - Benutzerhandbuch](tutorial/index.md){.internal-link target=_blank} sehen. +Viel mehr von all dem werden Sie in praktischer Anwendung im [Tutorial – Benutzerhandbuch](tutorial/index.md){.internal-link target=_blank} sehen. -/// tip | "Tipp" +/// tip | Tipp -Pydantic verhält sich speziell, wenn Sie `Optional` oder `Union[Etwas, None]` ohne einen Default-Wert verwenden. Sie können darüber in der Pydantic Dokumentation unter Required fields mehr erfahren. +Pydantic verhält sich speziell, wenn Sie `Optional` oder `Union[Something, None]` ohne einen Defaultwert verwenden. Sie können darüber in der Pydantic Dokumentation unter Erforderliche optionale Felder mehr erfahren. /// -## Typhinweise mit Metadaten-Annotationen +## Typhinweise mit Metadaten-Annotationen { #type-hints-with-metadata-annotations } -Python bietet auch die Möglichkeit, **zusätzliche Metadaten** in Typhinweisen unterzubringen, mittels `Annotated`. +Python bietet auch die Möglichkeit, **zusätzliche Metadaten** in Typhinweisen unterzubringen, mittels `Annotated`. //// tab | Python 3.9+ In Python 3.9 ist `Annotated` ein Teil der Standardbibliothek, Sie können es von `typing` importieren. ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial013_py39.py!} +{!> ../../docs_src/python_types/tutorial013_py39.py!} ``` //// @@ -544,14 +524,14 @@ In Versionen niedriger als Python 3.9 importieren Sie `Annotated` von `typing_ex Es wird bereits mit **FastAPI** installiert sein. ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial013.py!} +{!> ../../docs_src/python_types/tutorial013.py!} ``` //// Python selbst macht nichts mit `Annotated`. Für Editoren und andere Tools ist der Typ immer noch `str`. -Aber Sie können `Annotated` nutzen, um **FastAPI** mit Metadaten zu versorgen, die ihm sagen, wie sich ihre Anwendung verhalten soll. +Aber Sie können `Annotated` nutzen, um **FastAPI** mit Metadaten zu versorgen, die ihm sagen, wie sich Ihre Anwendung verhalten soll. Wichtig ist, dass **der erste *Typ-Parameter***, den Sie `Annotated` übergeben, der **tatsächliche Typ** ist. Der Rest sind Metadaten für andere Tools. @@ -559,15 +539,15 @@ Im Moment müssen Sie nur wissen, dass `Annotated` existiert, und dass es Standa Später werden Sie sehen, wie **mächtig** es sein kann. -/// tip | "Tipp" +/// tip | Tipp -Der Umstand, dass es **Standard-Python** ist, bedeutet, dass Sie immer noch die **bestmögliche Entwickler-Erfahrung** in ihrem Editor haben, sowie mit den Tools, die Sie nutzen, um ihren Code zu analysieren, zu refaktorisieren, usw. ✨ +Der Umstand, dass es **Standard-Python** ist, bedeutet, dass Sie immer noch die **bestmögliche Entwickler-Erfahrung** in Ihrem Editor haben, sowie mit den Tools, die Sie nutzen, um Ihren Code zu analysieren, zu refaktorisieren, usw. ✨ Und ebenfalls, dass Ihr Code sehr kompatibel mit vielen anderen Python-Tools und -Bibliotheken sein wird. 🚀 /// -## Typhinweise in **FastAPI** +## Typhinweise in **FastAPI** { #type-hints-in-fastapi } **FastAPI** macht sich diese Typhinweise zunutze, um mehrere Dinge zu tun. @@ -578,18 +558,18 @@ Mit **FastAPI** deklarieren Sie Parameter mit Typhinweisen, und Sie erhalten: ... und **FastAPI** verwendet dieselben Deklarationen, um: -* **Anforderungen** zu definieren: aus Anfrage-Pfadparametern, Abfrageparametern, Header-Feldern, Bodys, Abhängigkeiten, usw. -* **Daten umzuwandeln**: aus der Anfrage in den erforderlichen Typ. -* **Daten zu validieren**: aus jeder Anfrage: +* **Anforderungen** zu definieren: aus Request-Pfadparametern, Query-Parametern, Header-Feldern, Bodys, Abhängigkeiten, usw. +* **Daten umzuwandeln**: aus dem Request in den erforderlichen Typ. +* **Daten zu validieren**: aus jedem Request: * **Automatische Fehler** generieren, die an den Client zurückgegeben werden, wenn die Daten ungültig sind. * Die API mit OpenAPI zu **dokumentieren**: * Die dann von den Benutzeroberflächen der automatisch generierten interaktiven Dokumentation verwendet wird. -Das mag alles abstrakt klingen. Machen Sie sich keine Sorgen. Sie werden all das in Aktion sehen im [Tutorial - Benutzerhandbuch](tutorial/index.md){.internal-link target=_blank}. +Das mag alles abstrakt klingen. Machen Sie sich keine Sorgen. Sie werden all das in Aktion sehen im [Tutorial – Benutzerhandbuch](tutorial/index.md){.internal-link target=_blank}. Das Wichtigste ist, dass **FastAPI** durch die Verwendung von Standard-Python-Typen an einer einzigen Stelle (anstatt weitere Klassen, Dekoratoren usw. hinzuzufügen) einen Großteil der Arbeit für Sie erledigt. -/// info +/// info | Info Wenn Sie bereits das ganze Tutorial durchgearbeitet haben und mehr über Typen erfahren wollen, dann ist eine gute Ressource der „Cheat Sheet“ von `mypy`. diff --git a/docs/de/docs/resources/index.md b/docs/de/docs/resources/index.md index abf270d9fd..2c5046c731 100644 --- a/docs/de/docs/resources/index.md +++ b/docs/de/docs/resources/index.md @@ -1,3 +1,3 @@ -# Ressourcen +# Ressourcen { #resources } Zusätzliche Ressourcen, externe Links, Artikel und mehr. ✈️ diff --git a/docs/de/docs/tutorial/background-tasks.md b/docs/de/docs/tutorial/background-tasks.md index 0852288d59..2c381ccfac 100644 --- a/docs/de/docs/tutorial/background-tasks.md +++ b/docs/de/docs/tutorial/background-tasks.md @@ -1,8 +1,8 @@ -# Hintergrundtasks +# Hintergrundtasks { #background-tasks } -Sie können Hintergrundtasks (Hintergrund-Aufgaben) definieren, die *nach* der Rückgabe einer Response ausgeführt werden sollen. +Sie können Hintergrundtasks definieren, die *nach* der Rückgabe einer Response ausgeführt werden sollen. -Das ist nützlich für Vorgänge, die nach einem Request ausgeführt werden müssen, bei denen der Client jedoch nicht unbedingt auf den Abschluss des Vorgangs warten muss, bevor er die Response erhält. +Das ist nützlich für Vorgänge, die nach einem Request ausgeführt werden müssen, bei denen der Client jedoch nicht unbedingt auf den Abschluss des Vorgangs warten muss, bevor er die Response erhält. Hierzu zählen beispielsweise: @@ -11,17 +11,15 @@ Hierzu zählen beispielsweise: * Daten verarbeiten: * Angenommen, Sie erhalten eine Datei, die einen langsamen Prozess durchlaufen muss. Sie können als Response „Accepted“ (HTTP 202) zurückgeben und die Datei im Hintergrund verarbeiten. -## `BackgroundTasks` verwenden +## `BackgroundTasks` verwenden { #using-backgroundtasks } Importieren Sie zunächst `BackgroundTasks` und definieren Sie einen Parameter in Ihrer *Pfadoperation-Funktion* mit der Typdeklaration `BackgroundTasks`: -```Python hl_lines="1 13" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} **FastAPI** erstellt für Sie das Objekt vom Typ `BackgroundTasks` und übergibt es als diesen Parameter. -## Eine Taskfunktion erstellen +## Eine Taskfunktion erstellen { #create-a-task-function } Erstellen Sie eine Funktion, die als Hintergrundtask ausgeführt werden soll. @@ -33,17 +31,13 @@ In diesem Fall schreibt die Taskfunktion in eine Datei (den Versand einer E-Mail Und da der Schreibvorgang nicht `async` und `await` verwendet, definieren wir die Funktion mit normalem `def`: -```Python hl_lines="6-9" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} -## Den Hintergrundtask hinzufügen +## Den Hintergrundtask hinzufügen { #add-the-background-task } Übergeben Sie innerhalb Ihrer *Pfadoperation-Funktion* Ihre Taskfunktion mit der Methode `.add_task()` an das *Hintergrundtasks*-Objekt: -```Python hl_lines="14" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} `.add_task()` erhält als Argumente: @@ -51,63 +45,15 @@ Und da der Schreibvorgang nicht `async` und `await` verwendet, definieren wir di * Eine beliebige Folge von Argumenten, die der Reihe nach an die Taskfunktion übergeben werden sollen (`email`). * Alle Schlüsselwort-Argumente, die an die Taskfunktion übergeben werden sollen (`message="some notification"`). -## Dependency Injection +## Dependency Injection { #dependency-injection } Die Verwendung von `BackgroundTasks` funktioniert auch mit dem Dependency Injection System. Sie können einen Parameter vom Typ `BackgroundTasks` auf mehreren Ebenen deklarieren: in einer *Pfadoperation-Funktion*, in einer Abhängigkeit (Dependable), in einer Unterabhängigkeit usw. **FastAPI** weiß, was jeweils zu tun ist und wie dasselbe Objekt wiederverwendet werden kann, sodass alle Hintergrundtasks zusammengeführt und anschließend im Hintergrund ausgeführt werden: -//// tab | Python 3.10+ -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002_an_py310.py!} -``` +{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} -//// - -//// tab | Python 3.9+ - -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14 16 23 26" -{!> ../../../docs_src/background_tasks/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11 13 20 23" -{!> ../../../docs_src/background_tasks/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002.py!} -``` - -//// In obigem Beispiel werden die Nachrichten, *nachdem* die Response gesendet wurde, in die Datei `log.txt` geschrieben. @@ -115,9 +61,9 @@ Wenn im Request ein Query-Parameter enthalten war, wird dieser in einem Hintergr Und dann schreibt ein weiterer Hintergrundtask, der in der *Pfadoperation-Funktion* erstellt wird, eine Nachricht unter Verwendung des Pfad-Parameters `email`. -## Technische Details +## Technische Details { #technical-details } -Die Klasse `BackgroundTasks` stammt direkt von `starlette.background`. +Die Klasse `BackgroundTasks` stammt direkt von `starlette.background`. Sie wird direkt in FastAPI importiert/inkludiert, sodass Sie sie von `fastapi` importieren können und vermeiden, versehentlich das alternative `BackgroundTask` (ohne das `s` am Ende) von `starlette.background` zu importieren. @@ -125,18 +71,16 @@ Indem Sie nur `BackgroundTasks` (und nicht `BackgroundTask`) verwenden, ist es d Es ist immer noch möglich, `BackgroundTask` allein in FastAPI zu verwenden, aber Sie müssen das Objekt in Ihrem Code erstellen und eine Starlette-`Response` zurückgeben, die es enthält. -Weitere Details finden Sie in der offiziellen Starlette-Dokumentation für Hintergrundtasks. +Weitere Details finden Sie in Starlettes offizieller Dokumentation für Hintergrundtasks. -## Vorbehalt +## Vorbehalt { #caveat } Wenn Sie umfangreiche Hintergrundberechnungen durchführen müssen und diese nicht unbedingt vom selben Prozess ausgeführt werden müssen (z. B. müssen Sie Speicher, Variablen, usw. nicht gemeinsam nutzen), könnte die Verwendung anderer größerer Tools wie z. B. Celery von Vorteil sein. Sie erfordern in der Regel komplexere Konfigurationen und einen Nachrichten-/Job-Queue-Manager wie RabbitMQ oder Redis, ermöglichen Ihnen jedoch die Ausführung von Hintergrundtasks in mehreren Prozessen und insbesondere auf mehreren Servern. -Um ein Beispiel zu sehen, sehen Sie sich die [Projektgeneratoren](../project-generation.md){.internal-link target=_blank} an. Sie alle enthalten Celery, bereits konfiguriert. +Wenn Sie jedoch über dieselbe **FastAPI**-App auf Variablen und Objekte zugreifen oder kleine Hintergrundtasks ausführen müssen (z. B. das Senden einer E-Mail-Benachrichtigung), können Sie einfach `BackgroundTasks` verwenden. -Wenn Sie jedoch über dieselbe **FastAPI**-Anwendung auf Variablen und Objekte zugreifen oder kleine Hintergrundtasks ausführen müssen (z. B. das Senden einer E-Mail-Benachrichtigung), können Sie einfach `BackgroundTasks` verwenden. - -## Zusammenfassung +## Zusammenfassung { #recap } Importieren und verwenden Sie `BackgroundTasks` mit Parametern in *Pfadoperation-Funktionen* und Abhängigkeiten, um Hintergrundtasks hinzuzufügen. diff --git a/docs/de/docs/tutorial/bigger-applications.md b/docs/de/docs/tutorial/bigger-applications.md index 986a99a38c..928d50adff 100644 --- a/docs/de/docs/tutorial/bigger-applications.md +++ b/docs/de/docs/tutorial/bigger-applications.md @@ -1,16 +1,16 @@ -# Größere Anwendungen – mehrere Dateien +# Größere Anwendungen – mehrere Dateien { #bigger-applications-multiple-files } Wenn Sie eine Anwendung oder eine Web-API erstellen, ist es selten der Fall, dass Sie alles in einer einzigen Datei unterbringen können. **FastAPI** bietet ein praktisches Werkzeug zur Strukturierung Ihrer Anwendung bei gleichzeitiger Wahrung der Flexibilität. -/// info +/// info | Info Wenn Sie von Flask kommen, wäre dies das Äquivalent zu Flasks Blueprints. /// -## Eine Beispiel-Dateistruktur +## Eine Beispiel-Dateistruktur { #an-example-file-structure } Nehmen wir an, Sie haben eine Dateistruktur wie diese: @@ -29,7 +29,7 @@ Nehmen wir an, Sie haben eine Dateistruktur wie diese: │   └── admin.py ``` -/// tip | "Tipp" +/// tip | Tipp Es gibt mehrere `__init__.py`-Dateien: eine in jedem Verzeichnis oder Unterverzeichnis. @@ -52,7 +52,7 @@ from app.routers import items * Es gibt auch ein Unterverzeichnis `app/internal/` mit einer weiteren Datei `__init__.py`, es handelt sich also um ein weiteres „Python-Subpackage“: `app.internal`. * Und die Datei `app/internal/admin.py` ist ein weiteres Submodul: `app.internal.admin`. - + Die gleiche Dateistruktur mit Kommentaren: @@ -71,7 +71,7 @@ Die gleiche Dateistruktur mit Kommentaren: │   └── admin.py # „admin“-Submodul, z. B. import app.internal.admin ``` -## `APIRouter` +## `APIRouter` { #apirouter } Nehmen wir an, die Datei, die nur für die Verwaltung von Benutzern zuständig ist, ist das Submodul unter `/app/routers/users.py`. @@ -81,22 +81,22 @@ Aber es ist immer noch Teil derselben **FastAPI**-Anwendung/Web-API (es ist Teil Sie können die *Pfadoperationen* für dieses Modul mit `APIRouter` erstellen. -### `APIRouter` importieren +### `APIRouter` importieren { #import-apirouter } Sie importieren ihn und erstellen eine „Instanz“ auf die gleiche Weise wie mit der Klasse `FastAPI`: ```Python hl_lines="1 3" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` -### *Pfadoperationen* mit `APIRouter` +### *Pfadoperationen* mit `APIRouter` { #path-operations-with-apirouter } Und dann verwenden Sie ihn, um Ihre *Pfadoperationen* zu deklarieren. Verwenden Sie ihn auf die gleiche Weise wie die Klasse `FastAPI`: ```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` Sie können sich `APIRouter` als eine „Mini-`FastAPI`“-Klasse vorstellen. @@ -105,7 +105,7 @@ Alle die gleichen Optionen werden unterstützt. Alle die gleichen `parameters`, `responses`, `dependencies`, `tags`, usw. -/// tip | "Tipp" +/// tip | Tipp In diesem Beispiel heißt die Variable `router`, aber Sie können ihr einen beliebigen Namen geben. @@ -113,7 +113,7 @@ In diesem Beispiel heißt die Variable `router`, aber Sie können ihr einen beli Wir werden diesen `APIRouter` in die Hauptanwendung `FastAPI` einbinden, aber zuerst kümmern wir uns um die Abhängigkeiten und einen anderen `APIRouter`. -## Abhängigkeiten +## Abhängigkeiten { #dependencies } Wir sehen, dass wir einige Abhängigkeiten benötigen, die an mehreren Stellen der Anwendung verwendet werden. @@ -124,7 +124,7 @@ Wir werden nun eine einfache Abhängigkeit verwenden, um einen benutzerdefiniert //// tab | Python 3.9+ ```Python hl_lines="3 6-8" title="app/dependencies.py" -{!> ../../../docs_src/bigger_applications/app_an_py39/dependencies.py!} +{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} ``` //// @@ -132,26 +132,26 @@ Wir werden nun eine einfache Abhängigkeit verwenden, um einen benutzerdefiniert //// tab | Python 3.8+ ```Python hl_lines="1 5-7" title="app/dependencies.py" -{!> ../../../docs_src/bigger_applications/app_an/dependencies.py!} +{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} ``` //// //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. /// ```Python hl_lines="1 4-6" title="app/dependencies.py" -{!> ../../../docs_src/bigger_applications/app/dependencies.py!} +{!> ../../docs_src/bigger_applications/app/dependencies.py!} ``` //// -/// tip | "Tipp" +/// tip | Tipp Um dieses Beispiel zu vereinfachen, verwenden wir einen erfundenen Header. @@ -159,7 +159,7 @@ Aber in der Praxis werden Sie mit den integrierten [Sicherheits-Werkzeugen](secu /// -## Ein weiteres Modul mit `APIRouter`. +## Ein weiteres Modul mit `APIRouter` { #another-module-with-apirouter } Nehmen wir an, Sie haben im Modul unter `app/routers/items.py` auch die Endpunkte, die für die Verarbeitung von Artikeln („Items“) aus Ihrer Anwendung vorgesehen sind. @@ -182,7 +182,7 @@ Wir wissen, dass alle *Pfadoperationen* in diesem Modul folgendes haben: Anstatt also alles zu jeder *Pfadoperation* hinzuzufügen, können wir es dem `APIRouter` hinzufügen. ```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` Da der Pfad jeder *Pfadoperation* mit `/` beginnen muss, wie in: @@ -199,9 +199,9 @@ Das Präfix lautet in diesem Fall also `/items`. Wir können auch eine Liste von `tags` und zusätzliche `responses` hinzufügen, die auf alle in diesem Router enthaltenen *Pfadoperationen* angewendet werden. -Und wir können eine Liste von `dependencies` hinzufügen, die allen *Pfadoperationen* im Router hinzugefügt und für jeden an sie gerichteten Request ausgeführt/aufgelöst werden. +Und wir können eine Liste von `dependencies` hinzufügen, die allen *Pfadoperationen* im Router hinzugefügt und für jeden an sie gerichteten Request ausgeführt/aufgelöst werden. -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass ähnlich wie bei [Abhängigkeiten in *Pfadoperation-Dekoratoren*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} kein Wert an Ihre *Pfadoperation-Funktion* übergeben wird. @@ -222,19 +222,19 @@ Das Endergebnis ist, dass die Pfade für diese Artikel jetzt wie folgt lauten: * Zuerst werden die Router-Abhängigkeiten ausgeführt, dann die [`dependencies` im Dekorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} und dann die normalen Parameterabhängigkeiten. * Sie können auch [`Security`-Abhängigkeiten mit `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank} hinzufügen. -/// tip | "Tipp" +/// tip | Tipp `dependencies` im `APIRouter` können beispielsweise verwendet werden, um eine Authentifizierung für eine ganze Gruppe von *Pfadoperationen* zu erfordern. Selbst wenn die Abhängigkeiten nicht jeder einzeln hinzugefügt werden. /// -/// check +/// check | Testen Die Parameter `prefix`, `tags`, `responses` und `dependencies` sind (wie in vielen anderen Fällen) nur ein Feature von **FastAPI**, um Ihnen dabei zu helfen, Codeverdoppelung zu vermeiden. /// -### Die Abhängigkeiten importieren +### Die Abhängigkeiten importieren { #import-the-dependencies } Der folgende Code befindet sich im Modul `app.routers.items`, also in der Datei `app/routers/items.py`. @@ -243,12 +243,12 @@ Und wir müssen die Abhängigkeitsfunktion aus dem Modul `app.dependencies` impo Daher verwenden wir einen relativen Import mit `..` für die Abhängigkeiten: ```Python hl_lines="3" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` -#### Wie relative Importe funktionieren +#### Wie relative Importe funktionieren { #how-relative-imports-work } -/// tip | "Tipp" +/// tip | Tipp Wenn Sie genau wissen, wie Importe funktionieren, fahren Sie mit dem nächsten Abschnitt unten fort. @@ -270,7 +270,7 @@ Aber diese Datei existiert nicht, unsere Abhängigkeiten befinden sich in einer Erinnern Sie sich, wie unsere Anwendungs-/Dateistruktur aussieht: - + --- @@ -309,55 +309,55 @@ Das würde sich auf ein Paket oberhalb von `app/` beziehen, mit seiner eigenen D Aber jetzt wissen Sie, wie es funktioniert, sodass Sie relative Importe in Ihren eigenen Anwendungen verwenden können, egal wie komplex diese sind. 🤓 -### Einige benutzerdefinierte `tags`, `responses`, und `dependencies` hinzufügen +### Einige benutzerdefinierte `tags`, `responses`, und `dependencies` hinzufügen { #add-some-custom-tags-responses-and-dependencies } Wir fügen weder das Präfix `/items` noch `tags=["items"]` zu jeder *Pfadoperation* hinzu, da wir sie zum `APIRouter` hinzugefügt haben. Aber wir können immer noch _mehr_ `tags` hinzufügen, die auf eine bestimmte *Pfadoperation* angewendet werden, sowie einige zusätzliche `responses`, die speziell für diese *Pfadoperation* gelten: ```Python hl_lines="30-31" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` -/// tip | "Tipp" +/// tip | Tipp Diese letzte Pfadoperation wird eine Kombination von Tags haben: `["items", "custom"]`. -Und sie wird auch beide Responses in der Dokumentation haben, eine für `404` und eine für `403`. +Und sie wird auch beide Responses in der Dokumentation haben, eine für `404` und eine für `403`. /// -## Das Haupt-`FastAPI`. +## Das Haupt-`FastAPI` { #the-main-fastapi } Sehen wir uns nun das Modul unter `app/main.py` an. Hier importieren und verwenden Sie die Klasse `FastAPI`. -Dies ist die Hauptdatei Ihrer Anwendung, die alles zusammen bindet. +Dies ist die Hauptdatei Ihrer Anwendung, die alles zusammenfügt. Und da sich der Großteil Ihrer Logik jetzt in seinem eigenen spezifischen Modul befindet, wird die Hauptdatei recht einfach sein. -### `FastAPI` importieren +### `FastAPI` importieren { #import-fastapi } Sie importieren und erstellen wie gewohnt eine `FastAPI`-Klasse. Und wir können sogar [globale Abhängigkeiten](dependencies/global-dependencies.md){.internal-link target=_blank} deklarieren, die mit den Abhängigkeiten für jeden `APIRouter` kombiniert werden: ```Python hl_lines="1 3 7" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` -### Den `APIRouter` importieren +### Den `APIRouter` importieren { #import-the-apirouter } Jetzt importieren wir die anderen Submodule, die `APIRouter` haben: ```Python hl_lines="4-5" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` Da es sich bei den Dateien `app/routers/users.py` und `app/routers/items.py` um Submodule handelt, die Teil desselben Python-Packages `app` sind, können wir einen einzelnen Punkt `.` verwenden, um sie mit „relativen Imports“ zu importieren. -### Wie das Importieren funktioniert +### Wie das Importieren funktioniert { #how-the-importing-works } Die Sektion: @@ -381,7 +381,7 @@ Wir könnten sie auch wie folgt importieren: from app.routers import items, users ``` -/// info +/// info | Info Die erste Version ist ein „relativer Import“: @@ -399,7 +399,7 @@ Um mehr über Python-Packages und -Module zu erfahren, lesen Sie ```console -$ uvicorn app.main:app --reload +$ fastapi dev app/main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -538,7 +537,7 @@ Sie sehen die automatische API-Dokumentation, einschließlich der Pfade aller Su -## Den gleichen Router mehrmals mit unterschiedlichem `prefix` inkludieren +## Den gleichen Router mehrmals mit unterschiedlichem `prefix` inkludieren { #include-the-same-router-multiple-times-with-different-prefix } Sie können `.include_router()` auch mehrmals mit *demselben* Router und unterschiedlichen Präfixen verwenden. @@ -546,7 +545,7 @@ Dies könnte beispielsweise nützlich sein, um dieselbe API unter verschiedenen Dies ist eine fortgeschrittene Verwendung, die Sie möglicherweise nicht wirklich benötigen, aber für den Fall, dass Sie sie benötigen, ist sie vorhanden. -## Einen `APIRouter` in einen anderen einfügen +## Einen `APIRouter` in einen anderen einfügen { #include-an-apirouter-in-another } Auf die gleiche Weise, wie Sie einen `APIRouter` in eine `FastAPI`-Anwendung einbinden können, können Sie einen `APIRouter` in einen anderen `APIRouter` einbinden, indem Sie Folgendes verwenden: diff --git a/docs/de/docs/tutorial/body-fields.md b/docs/de/docs/tutorial/body-fields.md index 33f7713ee4..b73d57d2d8 100644 --- a/docs/de/docs/tutorial/body-fields.md +++ b/docs/de/docs/tutorial/body-fields.md @@ -1,159 +1,59 @@ -# Body – Felder +# Body – Felder { #body-fields } -So wie Sie zusätzliche Validation und Metadaten in Parametern der **Pfadoperation-Funktion** mittels `Query`, `Path` und `Body` deklarieren, können Sie auch innerhalb von Pydantic-Modellen zusätzliche Validation und Metadaten deklarieren, mittels Pydantics `Field`. +So wie Sie zusätzliche Validierung und Metadaten in Parametern der *Pfadoperation-Funktion* mittels `Query`, `Path` und `Body` deklarieren, können Sie auch innerhalb von Pydantic-Modellen zusätzliche Validierung und Metadaten deklarieren, mittels Pydantics `Field`. -## `Field` importieren +## `Field` importieren { #import-field } Importieren Sie es zuerst: -//// tab | Python 3.10+ +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *} -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="2" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// - -/// warning | "Achtung" +/// warning | Achtung Beachten Sie, dass `Field` direkt von `pydantic` importiert wird, nicht von `fastapi`, wie die anderen (`Query`, `Path`, `Body`, usw.) /// -## Modellattribute deklarieren +## Modellattribute deklarieren { #declare-model-attributes } Dann können Sie `Field` mit Modellattributen deklarieren: -//// tab | Python 3.10+ - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12-15" -{!> ../../../docs_src/body_fields/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9-12" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *} `Field` funktioniert genauso wie `Query`, `Path` und `Body`, es hat die gleichen Parameter, usw. -/// note | "Technische Details" +/// note | Technische Details -Tatsächlich erstellen `Query`, `Path` und andere, die sie kennenlernen werden, Instanzen von Unterklassen einer allgemeinen Klasse `Param`, die ihrerseits eine Unterklasse von Pydantics `FieldInfo`-Klasse ist. +Tatsächlich erstellen `Query`, `Path` und andere, die Sie als nächstes sehen werden, Instanzen von Unterklassen einer allgemeinen Klasse `Param`, welche selbst eine Unterklasse von Pydantics `FieldInfo`-Klasse ist. Und Pydantics `Field` gibt ebenfalls eine Instanz von `FieldInfo` zurück. -`Body` gibt auch Instanzen einer Unterklasse von `FieldInfo` zurück. Und später werden Sie andere sehen, die Unterklassen der `Body`-Klasse sind. +`Body` gibt auch direkt Instanzen einer Unterklasse von `FieldInfo` zurück. Später werden Sie andere sehen, die Unterklassen der `Body`-Klasse sind. -Denken Sie daran, dass `Query`, `Path` und andere von `fastapi` tatsächlich Funktionen sind, die spezielle Klassen zurückgeben. +Denken Sie daran, dass `Query`, `Path` und andere, wenn Sie sie von `fastapi` importieren, tatsächlich Funktionen sind, die spezielle Klassen zurückgeben. /// -/// tip | "Tipp" +/// tip | Tipp -Beachten Sie, dass jedes Modellattribut mit einem Typ, Defaultwert und `Field` die gleiche Struktur hat wie ein Parameter einer Pfadoperation-Funktion, nur mit `Field` statt `Path`, `Query`, `Body`. +Beachten Sie, wie jedes Attribut eines Modells mit einem Typ, Defaultwert und `Field` die gleiche Struktur hat wie ein Parameter einer *Pfadoperation-Funktion*, nur mit `Field` statt `Path`, `Query`, `Body`. /// -## Zusätzliche Information hinzufügen +## Zusätzliche Information hinzufügen { #add-extra-information } Sie können zusätzliche Information in `Field`, `Query`, `Body`, usw. deklarieren. Und es wird im generierten JSON-Schema untergebracht. Sie werden später mehr darüber lernen, wie man zusätzliche Information unterbringt, wenn Sie lernen, Beispiele zu deklarieren. -/// warning | "Achtung" +/// warning | Achtung -Extra-Schlüssel, die `Field` überreicht werden, werden auch im resultierenden OpenAPI-Schema Ihrer Anwendung gelistet. Da diese Schlüssel nicht notwendigerweise Teil der OpenAPI-Spezifikation sind, könnten einige OpenAPI-Tools, wie etwa [der OpenAPI-Validator](https://validator.swagger.io/), nicht mit Ihrem generierten Schema funktionieren. +Extra-Schlüssel, die `Field` überreicht werden, werden auch im resultierenden OpenAPI-Schema Ihrer Anwendung gelistet. Da diese Schlüssel möglicherweise nicht Teil der OpenAPI-Spezifikation sind, könnten einige OpenAPI-Tools, wie etwa [der OpenAPI-Validator](https://validator.swagger.io/), nicht mit Ihrem generierten Schema funktionieren. /// -## Zusammenfassung +## Zusammenfassung { #recap } Sie können Pydantics `Field` verwenden, um zusätzliche Validierungen und Metadaten für Modellattribute zu deklarieren. -Sie können auch Extra-Schlüssel verwenden, um zusätzliche JSON-Schema-Metadaten zu überreichen. +Sie können auch die zusätzlichen Schlüsselwortargumente verwenden, um zusätzliche JSON-Schema-Metadaten zu übergeben. diff --git a/docs/de/docs/tutorial/body-multiple-params.md b/docs/de/docs/tutorial/body-multiple-params.md index 977e17671b..3b5fa52dde 100644 --- a/docs/de/docs/tutorial/body-multiple-params.md +++ b/docs/de/docs/tutorial/body-multiple-params.md @@ -1,74 +1,24 @@ -# Body – Mehrere Parameter +# Body – Mehrere Parameter { #body-multiple-parameters } -Jetzt, da wir gesehen haben, wie `Path` und `Query` verwendet werden, schauen wir uns fortgeschrittenere Verwendungsmöglichkeiten von Requestbody-Deklarationen an. +Nun, da wir gesehen haben, wie `Path` und `Query` verwendet werden, schauen wir uns fortgeschrittenere Verwendungsmöglichkeiten von Requestbody-Deklarationen an. -## `Path`-, `Query`- und Body-Parameter vermischen +## `Path`-, `Query`- und Body-Parameter vermischen { #mix-path-query-and-body-parameters } Zuerst einmal, Sie können `Path`-, `Query`- und Requestbody-Parameter-Deklarationen frei mischen und **FastAPI** wird wissen, was zu tun ist. Und Sie können auch Body-Parameter als optional kennzeichnen, indem Sie den Defaultwert auf `None` setzen: -//// tab | Python 3.10+ +{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} -```Python hl_lines="18-20" -{!> ../../../docs_src/body_multiple_params/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="18-20" -{!> ../../../docs_src/body_multiple_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="19-21" -{!> ../../../docs_src/body_multiple_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17-19" -{!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19-21" -{!> ../../../docs_src/body_multiple_params/tutorial001.py!} -``` - -//// - -/// note | "Hinweis" +/// note | Hinweis Beachten Sie, dass in diesem Fall das `item`, welches vom Body genommen wird, optional ist. Da es `None` als Defaultwert hat. /// -## Mehrere Body-Parameter +## Mehrere Body-Parameter { #multiple-body-parameters } -Im vorherigen Beispiel erwartete die *Pfadoperation* einen JSON-Body mit den Attributen eines `Item`s, etwa: +Im vorherigen Beispiel erwarteten die *Pfadoperationen* einen JSON-Body mit den Attributen eines `Item`s, etwa: ```JSON { @@ -81,25 +31,11 @@ Im vorherigen Beispiel erwartete die *Pfadoperation* einen JSON-Body mit den Att Aber Sie können auch mehrere Body-Parameter deklarieren, z. B. `item` und `user`: -//// tab | Python 3.10+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial002.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} In diesem Fall wird **FastAPI** bemerken, dass es mehr als einen Body-Parameter in der Funktion gibt (zwei Parameter, die Pydantic-Modelle sind). -Es wird deshalb die Parameternamen als Schlüssel (Feldnamen) im Body verwenden, und erwartet einen Body wie folgt: +Es wird deshalb die Parameternamen als Schlüssel (Feldnamen) im Body verwenden und erwartet einen Body wie folgt: ```JSON { @@ -116,77 +52,27 @@ Es wird deshalb die Parameternamen als Schlüssel (Feldnamen) im Body verwenden, } ``` -/// note | "Hinweis" +/// note | Hinweis Beachten Sie, dass, obwohl `item` wie zuvor deklariert wurde, es nun unter einem Schlüssel `item` im Body erwartet wird. /// -**FastAPI** wird die automatische Konvertierung des Requests übernehmen, sodass der Parameter `item` seinen spezifischen Inhalt bekommt, genau so wie der Parameter `user`. +**FastAPI** wird die automatische Konvertierung des Requests übernehmen, sodass der Parameter `item` seinen spezifischen Inhalt bekommt, und das Gleiche gilt für den Parameter `user`. -Es wird die Validierung dieser zusammengesetzten Daten übernehmen, und sie im OpenAPI-Schema und der automatischen Dokumentation dokumentieren. +Es wird die Validierung dieser zusammengesetzten Daten übernehmen, und diese im OpenAPI-Schema und der automatischen Dokumentation dokumentieren. -## Einzelne Werte im Body +## Einzelne Werte im Body { #singular-values-in-body } -So wie `Query` und `Path` für Query- und Pfad-Parameter, hat **FastAPI** auch das Äquivalent `Body`, um Extra-Daten für Body-Parameter zu definieren. +So wie `Query` und `Path` für Query- und Pfad-Parameter, stellt **FastAPI** das Äquivalent `Body` zur Verfügung, um Extra-Daten für Body-Parameter zu definieren. -Zum Beispiel, das vorherige Modell erweiternd, könnten Sie entscheiden, dass Sie einen weiteren Schlüssel `importance` haben möchten, im selben Body, Seite an Seite mit `item` und `user`. +Zum Beispiel, das vorherige Modell erweiternd, könnten Sie entscheiden, dass Sie einen weiteren Schlüssel `importance` im selben Body haben möchten, neben `item` und `user`. -Wenn Sie diesen Parameter einfach so hinzufügen, wird **FastAPI** annehmen, dass es ein Query-Parameter ist. +Wenn Sie diesen Parameter einfach so hinzufügen, wird **FastAPI** annehmen, dass es ein Query-Parameter ist, da er ein einzelner Wert ist. Aber Sie können **FastAPI** instruieren, ihn als weiteren Body-Schlüssel zu erkennen, indem Sie `Body` verwenden: -//// tab | Python 3.10+ - -```Python hl_lines="23" -{!> ../../../docs_src/body_multiple_params/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="23" -{!> ../../../docs_src/body_multiple_params/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24" -{!> ../../../docs_src/body_multiple_params/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial003.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} In diesem Fall erwartet **FastAPI** einen Body wie: @@ -206,9 +92,9 @@ In diesem Fall erwartet **FastAPI** einen Body wie: } ``` -Wiederum wird es die Daten konvertieren, validieren, dokumentieren, usw. +Wiederum wird es die Datentypen konvertieren, validieren, dokumentieren, usw. -## Mehrere Body-Parameter und Query-Parameter +## Mehrere Body-Parameter und Query-Parameter { #multiple-body-params-and-query } Natürlich können Sie auch, wann immer Sie das brauchen, weitere Query-Parameter hinzufügen, zusätzlich zu den Body-Parametern. @@ -226,71 +112,21 @@ q: str | None = None Zum Beispiel: -//// tab | Python 3.10+ +{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004_an_py310.py!} -``` +/// info | Info -//// - -//// tab | Python 3.9+ - -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="28" -{!> ../../../docs_src/body_multiple_params/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +`Body` hat die gleichen zusätzlichen Validierungs- und Metadaten-Parameter wie `Query`, `Path` und andere, die Sie später kennenlernen werden. /// -```Python hl_lines="25" -{!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} -``` +## Einen einzelnen Body-Parameter einbetten { #embed-a-single-body-parameter } -//// +Nehmen wir an, Sie haben nur einen einzelnen `item`-Body-Parameter von einem Pydantic-Modell `Item`. -//// tab | Python 3.8+ nicht annotiert +Standardmäßig wird **FastAPI** dann seinen Body direkt erwarten. -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004.py!} -``` - -//// - -/// info - -`Body` hat die gleichen zusätzlichen Validierungs- und Metadaten-Parameter wie `Query` und `Path` und andere, die Sie später kennenlernen. - -/// - -## Einen einzelnen Body-Parameter einbetten - -Nehmen wir an, Sie haben nur einen einzelnen `item`-Body-Parameter, ein Pydantic-Modell `Item`. - -Normalerweise wird **FastAPI** dann seinen JSON-Body direkt erwarten. - -Aber wenn Sie möchten, dass es einen JSON-Body erwartet, mit einem Schlüssel `item` und darin den Inhalt des Modells, so wie es das tut, wenn Sie mehrere Body-Parameter deklarieren, dann können Sie den speziellen `Body`-Parameter `embed` setzen: +Aber wenn Sie möchten, dass es einen JSON-Body mit einem Schlüssel `item` erwartet, und darin den Inhalt des Modells, so wie es das tut, wenn Sie mehrere Body-Parameter deklarieren, dann können Sie den speziellen `Body`-Parameter `embed` setzen: ```Python item: Item = Body(embed=True) @@ -298,57 +134,7 @@ item: Item = Body(embed=True) so wie in: -//// tab | Python 3.10+ - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body_multiple_params/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="15" -{!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} In diesem Fall erwartet **FastAPI** einen Body wie: @@ -374,11 +160,11 @@ statt: } ``` -## Zusammenfassung +## Zusammenfassung { #recap } -Sie können mehrere Body-Parameter zu ihrer *Pfadoperation-Funktion* hinzufügen, obwohl ein Request nur einen einzigen Body enthalten kann. +Sie können mehrere Body-Parameter zu Ihrer *Pfadoperation-Funktion* hinzufügen, obwohl ein Request nur einen einzigen Body enthalten kann. -**FastAPI** wird sich darum kümmern, Ihnen korrekte Daten in Ihrer Funktion zu überreichen, und das korrekte Schema in der *Pfadoperation* zu validieren und zu dokumentieren. +Aber **FastAPI** wird sich darum kümmern, Ihnen korrekte Daten in Ihrer Funktion zu überreichen, und das korrekte Schema in der *Pfadoperation* zu validieren und zu dokumentieren. Sie können auch einzelne Werte deklarieren, die als Teil des Bodys empfangen werden. diff --git a/docs/de/docs/tutorial/body-nested-models.md b/docs/de/docs/tutorial/body-nested-models.md index 8aef965f4c..324d31928d 100644 --- a/docs/de/docs/tutorial/body-nested-models.md +++ b/docs/de/docs/tutorial/body-nested-models.md @@ -1,44 +1,28 @@ -# Body – Verschachtelte Modelle +# Body – Verschachtelte Modelle { #body-nested-models } -Mit **FastAPI** können Sie (dank Pydantic) beliebig tief verschachtelte Modelle definieren, validieren und dokumentieren. +Mit **FastAPI** können Sie (dank Pydantic) beliebig tief verschachtelte Modelle definieren, validieren, dokumentieren und verwenden. -## Listen als Felder +## Listen als Felder { #list-fields } -Sie können ein Attribut als Kindtyp definieren, zum Beispiel eine Python-`list`e. +Sie können ein Attribut als Kindtyp definieren, zum Beispiel eine Python-`list`. -//// tab | Python 3.10+ - -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial001.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} Das bewirkt, dass `tags` eine Liste ist, wenngleich es nichts über den Typ der Elemente der Liste aussagt. -## Listen mit Typ-Parametern als Felder +## Listen mit Typ-Parametern als Felder { #list-fields-with-type-parameter } Aber Python erlaubt es, Listen mit inneren Typen, auch „Typ-Parameter“ genannt, zu deklarieren. -### `List` von `typing` importieren +### `List` von `typing` importieren { #import-typings-list } In Python 3.9 oder darüber können Sie einfach `list` verwenden, um diese Typannotationen zu deklarieren, wie wir unten sehen werden. 💡 In Python-Versionen vor 3.9 (3.6 und darüber), müssen Sie zuerst `List` von Pythons Standardmodul `typing` importieren. -```Python hl_lines="1" -{!> ../../../docs_src/body_nested_models/tutorial002.py!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} -### Eine `list`e mit einem Typ-Parameter deklarieren +### Eine `list` mit einem Typ-Parameter deklarieren { #declare-a-list-with-a-type-parameter } Um Typen wie `list`, `dict`, `tuple` mit inneren Typ-Parametern (inneren Typen) zu deklarieren: @@ -65,31 +49,9 @@ Verwenden Sie dieselbe Standardsyntax für Modellattribute mit inneren Typen. In unserem Beispiel können wir also bewirken, dass `tags` spezifisch eine „Liste von Strings“ ist: -//// tab | Python 3.10+ +{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial002.py!} -``` - -//// - -## Set-Typen +## Set-Typen { #set-types } Aber dann denken wir darüber nach und stellen fest, dass sich die Tags nicht wiederholen sollen, es sollen eindeutige Strings sein. @@ -97,37 +59,15 @@ Python hat einen Datentyp speziell für Mengen eindeutiger Dinge: das ../../../docs_src/body_nested_models/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 14" -{!> ../../../docs_src/body_nested_models/tutorial003.py!} -``` - -//// - -Jetzt, selbst wenn Sie einen Request mit duplizierten Daten erhalten, werden diese zu einem Set eindeutiger Dinge konvertiert. +Jetzt, selbst wenn Sie einen Request mit duplizierten Daten erhalten, werden diese zu einem Set eindeutiger Dinge konvertiert. Und wann immer Sie diese Daten ausgeben, selbst wenn die Quelle Duplikate hatte, wird es als Set von eindeutigen Dingen ausgegeben. Und es wird entsprechend annotiert/dokumentiert. -## Verschachtelte Modelle +## Verschachtelte Modelle { #nested-models } Jedes Attribut eines Pydantic-Modells hat einen Typ. @@ -137,63 +77,19 @@ Sie können also tief verschachtelte JSON-„Objekte“ deklarieren, mit spezifi Alles das beliebig tief verschachtelt. -### Ein Kindmodell definieren +### Ein Kindmodell definieren { #define-a-submodel } -Wir können zum Beispiel ein `Image`-Modell definieren. +Für ein Beispiel können wir ein `Image`-Modell definieren. -//// tab | Python 3.10+ +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} -```Python hl_lines="7-9" -{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} -``` +### Das Kindmodell als Typ verwenden { #use-the-submodel-as-a-type } -//// +Und dann können wir es als Typ eines Attributes verwenden: -//// tab | Python 3.9+ +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *} -```Python hl_lines="9-11" -{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-11" -{!> ../../../docs_src/body_nested_models/tutorial004.py!} -``` - -//// - -### Das Kindmodell als Typ verwenden - -Und dann können wir es als Typ eines Attributes verwenden. - -//// tab | Python 3.10+ - -```Python hl_lines="18" -{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial004.py!} -``` - -//// - -Das würde bedeuten, dass **FastAPI** einen Body erwartet wie: +Das würde bedeuten, dass **FastAPI** einen Body wie folgt erwartet: ```JSON { @@ -216,69 +112,25 @@ Wiederum, nur mit dieser Deklaration erhalten Sie von **FastAPI**: * Datenvalidierung * Automatische Dokumentation -## Spezielle Typen und Validierungen +## Spezielle Typen und Validierungen { #special-types-and-validation } -Abgesehen von normalen einfachen Typen, wie `str`, `int`, `float`, usw. können Sie komplexere einfache Typen verwenden, die von `str` erben. +Abgesehen von normalen einfachen Typen wie `str`, `int`, `float`, usw. können Sie komplexere einfache Typen verwenden, die von `str` erben. -Um alle Optionen kennenzulernen, die Sie haben, schauen Sie sich Pydantics Typübersicht an. Sie werden im nächsten Kapitel ein paar Beispiele kennenlernen. +Um alle Optionen kennenzulernen, die Sie haben, schauen Sie sich Pydantics Typübersicht an. Sie werden einige Beispiele im nächsten Kapitel kennenlernen. -Da wir zum Beispiel im `Image`-Modell ein Feld `url` haben, können wir deklarieren, dass das eine Instanz von Pydantics `HttpUrl` sein soll, anstelle eines `str`: +Zum Beispiel, da wir im `Image`-Modell ein Feld `url` haben, können wir deklarieren, dass das eine Instanz von Pydantics `HttpUrl` sein soll, anstelle eines `str`: -//// tab | Python 3.10+ - -```Python hl_lines="2 8" -{!> ../../../docs_src/body_nested_models/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 10" -{!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 10" -{!> ../../../docs_src/body_nested_models/tutorial005.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *} Es wird getestet, ob der String eine gültige URL ist, und als solche wird er in JSON Schema / OpenAPI dokumentiert. -## Attribute mit Listen von Kindmodellen +## Attribute mit Listen von Kindmodellen { #attributes-with-lists-of-submodels } Sie können Pydantic-Modelle auch als Typen innerhalb von `list`, `set`, usw. verwenden: -//// tab | Python 3.10+ +{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} -```Python hl_lines="18" -{!> ../../../docs_src/body_nested_models/tutorial006_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial006.py!} -``` - -//// - -Das wird einen JSON-Body erwarten (konvertieren, validieren, dokumentieren), wie: +Das wird einen JSON-Body erwarten (konvertieren, validieren, dokumentieren, usw.) wie: ```JSON hl_lines="11" { @@ -304,49 +156,27 @@ Das wird einen JSON-Body erwarten (konvertieren, validieren, dokumentieren), wie } ``` -/// info +/// info | Info Beachten Sie, dass der `images`-Schlüssel jetzt eine Liste von Bild-Objekten hat. /// -## Tief verschachtelte Modelle +## Tief verschachtelte Modelle { #deeply-nested-models } Sie können beliebig tief verschachtelte Modelle definieren: -//// tab | Python 3.10+ +{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} -```Python hl_lines="7 12 18 21 25" -{!> ../../../docs_src/body_nested_models/tutorial007_py310.py!} -``` +/// info | Info -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9 14 20 23 27" -{!> ../../../docs_src/body_nested_models/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 14 20 23 27" -{!> ../../../docs_src/body_nested_models/tutorial007.py!} -``` - -//// - -/// info - -Beachten Sie, wie `Offer` eine Liste von `Item`s hat, von denen jedes seinerseits eine optionale Liste von `Image`s hat. +Beachten Sie, wie `Offer` eine Liste von `Item`s hat, die ihrerseits eine optionale Liste von `Image`s haben. /// -## Bodys aus reinen Listen +## Bodys aus reinen Listen { #bodies-of-pure-lists } -Wenn Sie möchten, dass das äußerste Element des JSON-Bodys ein JSON-`array` (eine Python-`list`e) ist, können Sie den Typ im Funktionsparameter deklarieren, mit der gleichen Syntax wie in Pydantic-Modellen: +Wenn das äußerste Element des JSON-Bodys, das Sie erwarten, ein JSON-`array` (eine Python-`list`) ist, können Sie den Typ im Funktionsparameter deklarieren, mit der gleichen Syntax wie in Pydantic-Modellen: ```Python images: List[Image] @@ -360,23 +190,9 @@ images: list[Image] so wie in: -//// tab | Python 3.9+ +{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} -```Python hl_lines="13" -{!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15" -{!> ../../../docs_src/body_nested_models/tutorial008.py!} -``` - -//// - -## Editor-Unterstützung überall +## Editor-Unterstützung überall { #editor-support-everywhere } Und Sie erhalten Editor-Unterstützung überall. @@ -388,11 +204,11 @@ Sie würden diese Editor-Unterstützung nicht erhalten, wenn Sie direkt mit `dic Aber Sie müssen sich auch nicht weiter um die Modelle kümmern, hereinkommende Dicts werden automatisch in sie konvertiert. Und was Sie zurückgeben, wird automatisch nach JSON konvertiert. -## Bodys mit beliebigen `dict`s +## Bodys mit beliebigen `dict`s { #bodies-of-arbitrary-dicts } Sie können einen Body auch als `dict` deklarieren, mit Schlüsseln eines Typs und Werten eines anderen Typs. -So brauchen Sie vorher nicht zu wissen, wie die Feld-/Attribut-Namen lauten (wie es bei Pydantic-Modellen der Fall wäre). +So brauchen Sie vorher nicht zu wissen, wie die Feld-/Attributnamen lauten (wie es bei Pydantic-Modellen der Fall wäre). Das ist nützlich, wenn Sie Schlüssel empfangen, deren Namen Sie nicht bereits kennen. @@ -402,25 +218,11 @@ Ein anderer nützlicher Anwendungsfall ist, wenn Sie Schlüssel eines anderen Ty Das schauen wir uns mal an. -Im folgenden Beispiel akzeptieren Sie irgendein `dict`, solange es `int`-Schlüssel und `float`-Werte hat. +Im folgenden Beispiel akzeptieren Sie irgendein `dict`, solange es `int`-Schlüssel und `float`-Werte hat: -//// tab | Python 3.9+ +{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} -```Python hl_lines="7" -{!> ../../../docs_src/body_nested_models/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/body_nested_models/tutorial009.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Bedenken Sie, dass JSON nur `str` als Schlüssel unterstützt. @@ -428,11 +230,11 @@ Aber Pydantic hat automatische Datenkonvertierung. Das bedeutet, dass Ihre API-Clients nur Strings senden können, aber solange diese Strings nur Zahlen enthalten, wird Pydantic sie konvertieren und validieren. -Und das `dict` welches Sie als `weights` erhalten, wird `int`-Schlüssel und `float`-Werte haben. +Und das `dict`, welches Sie als `weights` erhalten, wird `int`-Schlüssel und `float`-Werte haben. /// -## Zusammenfassung +## Zusammenfassung { #recap } Mit **FastAPI** haben Sie die maximale Flexibilität von Pydantic-Modellen, während Ihr Code einfach, kurz und elegant bleibt. diff --git a/docs/de/docs/tutorial/body-updates.md b/docs/de/docs/tutorial/body-updates.md index b835549146..aa62199feb 100644 --- a/docs/de/docs/tutorial/body-updates.md +++ b/docs/de/docs/tutorial/body-updates.md @@ -1,38 +1,16 @@ -# Body – Aktualisierungen +# Body – Aktualisierungen { #body-updates } -## Ersetzendes Aktualisieren mit `PUT` +## Ersetzendes Aktualisieren mit `PUT` { #update-replacing-with-put } Um einen Artikel zu aktualisieren, können Sie die HTTP `PUT` Operation verwenden. -Sie können den `jsonable_encoder` verwenden, um die empfangenen Daten in etwas zu konvertieren, das als JSON gespeichert werden kann (in z. B. einer NoSQL-Datenbank). Zum Beispiel, um ein `datetime` in einen `str` zu konvertieren. +Sie können den `jsonable_encoder` verwenden, um die empfangenen Daten in etwas zu konvertieren, das als JSON gespeichert werden kann (z. B. in einer NoSQL-Datenbank). Zum Beispiel, um ein `datetime` in einen `str` zu konvertieren. -//// tab | Python 3.10+ - -```Python hl_lines="28-33" -{!> ../../../docs_src/body_updates/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="30-35" -{!> ../../../docs_src/body_updates/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="30-35" -{!> ../../../docs_src/body_updates/tutorial001.py!} -``` - -//// +{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} `PUT` wird verwendet, um Daten zu empfangen, die die existierenden Daten ersetzen sollen. -### Warnung bezüglich des Ersetzens +### Warnung bezüglich des Ersetzens { #warning-about-replacing } Das bedeutet, dass, wenn Sie den Artikel `bar` aktualisieren wollen, mittels `PUT` und folgendem Body: @@ -44,17 +22,17 @@ Das bedeutet, dass, wenn Sie den Artikel `bar` aktualisieren wollen, mittels `PU } ``` -das Eingabemodell nun den Defaultwert `"tax": 10.5` hat, weil Sie das bereits gespeicherte Attribut `"tax": 20.2` nicht mit übergeben haben. +weil das bereits gespeicherte Attribut `"tax": 20.2` nicht enthalten ist, das Eingabemodell den Defaultwert `"tax": 10.5` erhalten würde. -Die Daten werden darum mit einem „neuen“ `tax`-Wert von `10.5` abgespeichert. +Und die Daten würden mit diesem „neuen“ `tax` von `10.5` gespeichert werden. -## Teilweises Ersetzen mit `PATCH` +## Teil-Aktualisierungen mit `PATCH` { #partial-updates-with-patch } Sie können auch die HTTP `PATCH` Operation verwenden, um Daten *teilweise* zu ersetzen. -Das bedeutet, sie senden nur die Daten, die Sie aktualisieren wollen, der Rest bleibt unverändert. +Das bedeutet, Sie senden nur die Daten, die Sie aktualisieren wollen, der Rest bleibt unverändert. -/// note | "Hinweis" +/// note | Hinweis `PATCH` wird seltener verwendet und ist weniger bekannt als `PUT`. @@ -66,55 +44,33 @@ Aber dieser Leitfaden zeigt Ihnen mehr oder weniger, wie die beiden normalerweis /// -### Pydantics `exclude_unset`-Parameter verwenden +### Pydantics `exclude_unset`-Parameter verwenden { #using-pydantics-exclude-unset-parameter } Wenn Sie Teil-Aktualisierungen entgegennehmen, ist der `exclude_unset`-Parameter in der `.model_dump()`-Methode von Pydantic-Modellen sehr nützlich. Wie in `item.model_dump(exclude_unset=True)`. -/// info +/// info | Info -In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie deprecated (aber immer noch unterstützt) und in `.model_dump()` umbenannt. +In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie deprecatet (aber immer noch unterstützt) und in `.model_dump()` umbenannt. Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können. /// -Das wird ein `dict` erstellen, mit nur den Daten, die gesetzt wurden als das `item`-Modell erstellt wurde, Defaultwerte ausgeschlossen. +Das wird ein `dict` erstellen, mit nur den Daten, die gesetzt wurden, als das `item`-Modell erstellt wurde, Defaultwerte ausgeschlossen. -Sie können das verwenden, um ein `dict` zu erstellen, das nur die (im Request) gesendeten Daten enthält, ohne Defaultwerte: +Sie können das verwenden, um ein `dict` zu erstellen, das nur die (im Request) gesendeten Daten enthält, ohne Defaultwerte: -//// tab | Python 3.10+ +{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} -```Python hl_lines="32" -{!> ../../../docs_src/body_updates/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="34" -{!> ../../../docs_src/body_updates/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="34" -{!> ../../../docs_src/body_updates/tutorial002.py!} -``` - -//// - -### Pydantics `update`-Parameter verwenden +### Pydantics `update`-Parameter verwenden { #using-pydantics-update-parameter } Jetzt können Sie eine Kopie des existierenden Modells mittels `.model_copy()` erstellen, wobei Sie dem `update`-Parameter ein `dict` mit den zu ändernden Daten übergeben. -/// info +/// info | Info -In Pydantic v1 hieß diese Methode `.copy()`, in Pydantic v2 wurde sie deprecated (aber immer noch unterstützt) und in `.model_copy()` umbenannt. +In Pydantic v1 hieß diese Methode `.copy()`, in Pydantic v2 wurde sie deprecatet (aber immer noch unterstützt) und in `.model_copy()` umbenannt. Die Beispiele hier verwenden `.copy()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_copy()` verwenden, wenn Sie Pydantic v2 verwenden können. @@ -122,33 +78,11 @@ Die Beispiele hier verwenden `.copy()` für die Kompatibilität mit Pydantic v1, Wie in `stored_item_model.model_copy(update=update_data)`: -//// tab | Python 3.10+ +{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} -```Python hl_lines="33" -{!> ../../../docs_src/body_updates/tutorial002_py310.py!} -``` +### Rekapitulation zu Teil-Aktualisierungen { #partial-updates-recap } -//// - -//// tab | Python 3.9+ - -```Python hl_lines="35" -{!> ../../../docs_src/body_updates/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="35" -{!> ../../../docs_src/body_updates/tutorial002.py!} -``` - -//// - -### Rekapitulation zum teilweisen Ersetzen - -Zusammengefasst, um Teil-Ersetzungen vorzunehmen: +Zusammengefasst, um Teil-Aktualisierungen vorzunehmen: * (Optional) verwenden Sie `PATCH` statt `PUT`. * Lesen Sie die bereits gespeicherten Daten aus. @@ -156,36 +90,14 @@ Zusammengefasst, um Teil-Ersetzungen vorzunehmen: * Erzeugen Sie aus dem empfangenen Modell ein `dict` ohne Defaultwerte (mittels `exclude_unset`). * So ersetzen Sie nur die tatsächlich vom Benutzer gesetzten Werte, statt dass bereits gespeicherte Werte mit Defaultwerten des Modells überschrieben werden. * Erzeugen Sie eine Kopie ihres gespeicherten Modells, wobei Sie die Attribute mit den empfangenen Teil-Ersetzungen aktualisieren (mittels des `update`-Parameters). -* Konvertieren Sie das kopierte Modell zu etwas, das in ihrer Datenbank gespeichert werden kann (indem Sie beispielsweise `jsonable_encoder` verwenden). +* Konvertieren Sie das kopierte Modell zu etwas, das in Ihrer Datenbank gespeichert werden kann (indem Sie beispielsweise `jsonable_encoder` verwenden). * Das ist vergleichbar dazu, die `.model_dump()`-Methode des Modells erneut aufzurufen, aber es wird sicherstellen, dass die Werte zu Daten konvertiert werden, die ihrerseits zu JSON konvertiert werden können, zum Beispiel `datetime` zu `str`. * Speichern Sie die Daten in Ihrer Datenbank. * Geben Sie das aktualisierte Modell zurück. -//// tab | Python 3.10+ +{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} -```Python hl_lines="28-35" -{!> ../../../docs_src/body_updates/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="30-37" -{!> ../../../docs_src/body_updates/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="30-37" -{!> ../../../docs_src/body_updates/tutorial002.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Sie können tatsächlich die gleiche Technik mit einer HTTP `PUT` Operation verwenden. @@ -193,7 +105,7 @@ Aber dieses Beispiel verwendet `PATCH`, da dieses für solche Anwendungsfälle g /// -/// note | "Hinweis" +/// note | Hinweis Beachten Sie, dass das hereinkommende Modell immer noch validiert wird. diff --git a/docs/de/docs/tutorial/body.md b/docs/de/docs/tutorial/body.md index 3fdd4ade32..1e6382b6f2 100644 --- a/docs/de/docs/tutorial/body.md +++ b/docs/de/docs/tutorial/body.md @@ -1,68 +1,40 @@ -# Requestbody +# Requestbody { #request-body } -Wenn Sie Daten von einem Client (sagen wir, einem Browser) zu Ihrer API senden, dann senden Sie diese als einen **Requestbody** (Deutsch: Anfragekörper). +Wenn Sie Daten von einem Client (sagen wir, einem Browser) zu Ihrer API senden müssen, senden Sie sie als **Requestbody**. -Ein **Request**body sind Daten, die vom Client zu Ihrer API gesendet werden. Ein **Response**body (Deutsch: Antwortkörper) sind Daten, die Ihre API zum Client sendet. +Ein **Request**body sind Daten, die vom Client zu Ihrer API gesendet werden. Ein **Response**body sind Daten, die Ihre API zum Client sendet. -Ihre API sendet fast immer einen **Response**body. Aber Clients senden nicht unbedingt immer **Request**bodys (sondern nur Metadaten). +Ihre API muss fast immer einen **Response**body senden. Aber Clients müssen nicht unbedingt immer **Requestbodys** senden, manchmal fordern sie nur einen Pfad an, vielleicht mit einigen Query-Parametern, aber senden keinen Body. -Um einen **Request**body zu deklarieren, verwenden Sie Pydantic-Modelle mit allen deren Fähigkeiten und Vorzügen. +Um einen **Request**body zu deklarieren, verwenden Sie Pydantic-Modelle mit all deren Fähigkeiten und Vorzügen. -/// info +/// info | Info -Um Daten zu versenden, sollten Sie eines von: `POST` (meistverwendet), `PUT`, `DELETE` oder `PATCH` verwenden. +Um Daten zu senden, sollten Sie eines von: `POST` (meistverwendet), `PUT`, `DELETE` oder `PATCH` verwenden. -Senden Sie einen Body mit einem `GET`-Request, dann führt das laut Spezifikation zu undefiniertem Verhalten. Trotzdem wird es von FastAPI unterstützt, für sehr komplexe/extreme Anwendungsfälle. +Das Senden eines Bodys mit einem `GET`-Request hat ein undefiniertes Verhalten in den Spezifikationen, wird aber dennoch von FastAPI unterstützt, nur für sehr komplexe/extreme Anwendungsfälle. -Da aber davon abgeraten wird, zeigt die interaktive Dokumentation mit Swagger-Benutzeroberfläche die Dokumentation für den Body auch nicht an, wenn `GET` verwendet wird. Dazwischengeschaltete Proxys unterstützen es möglicherweise auch nicht. +Da davon abgeraten wird, zeigt die interaktive Dokumentation mit Swagger-Benutzeroberfläche die Dokumentation für den Body nicht an, wenn `GET` verwendet wird, und zwischengeschaltete Proxys unterstützen es möglicherweise nicht. /// -## Importieren Sie Pydantics `BaseModel` +## Pydantics `BaseModel` importieren { #import-pydantics-basemodel } Zuerst müssen Sie `BaseModel` von `pydantic` importieren: -//// tab | Python 3.10+ +{* ../../docs_src/body/tutorial001_py310.py hl[2] *} -```Python hl_lines="2" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// - -## Erstellen Sie Ihr Datenmodell +## Ihr Datenmodell erstellen { #create-your-data-model } Dann deklarieren Sie Ihr Datenmodell als eine Klasse, die von `BaseModel` erbt. -Verwenden Sie Standard-Python-Typen für die Klassenattribute: +Verwenden Sie Standard-Python-Typen für alle Attribute: -//// tab | Python 3.10+ +{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} -```Python hl_lines="5-9" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` +Wie auch bei der Deklaration von Query-Parametern gilt: Wenn ein Modellattribut einen Defaultwert hat, ist das Attribut nicht erforderlich. Andernfalls ist es erforderlich. Verwenden Sie `None`, um es einfach optional zu machen. -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7-11" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// - -Wie auch bei Query-Parametern gilt, wenn ein Modellattribut einen Defaultwert hat, ist das Attribut nicht erforderlich. Ansonsten ist es erforderlich. Verwenden Sie `None`, um es als optional zu kennzeichnen. - -Zum Beispiel deklariert das obige Modell ein JSON "`object`" (oder Python-`dict`) wie dieses: +Zum Beispiel deklariert das obige Modell ein JSON „`object`“ (oder Python-`dict`) wie dieses: ```JSON { @@ -73,7 +45,7 @@ Zum Beispiel deklariert das obige Modell ein JSON "`object`" (oder Python-`dict` } ``` -Da `description` und `tax` optional sind (mit `None` als Defaultwert), wäre folgendes JSON "`object`" auch gültig: +Da `description` und `tax` optional sind (mit `None` als Defaultwert), wäre folgendes JSON „`object`“ auch gültig: ```JSON { @@ -82,165 +54,120 @@ Da `description` und `tax` optional sind (mit `None` als Defaultwert), wäre fol } ``` -## Deklarieren Sie es als Parameter +## Als Parameter deklarieren { #declare-it-as-a-parameter } Um es zu Ihrer *Pfadoperation* hinzuzufügen, deklarieren Sie es auf die gleiche Weise, wie Sie Pfad- und Query-Parameter deklariert haben: -//// tab | Python 3.10+ +{* ../../docs_src/body/tutorial001_py310.py hl[16] *} -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` +... und deklarieren Sie dessen Typ als das Modell, welches Sie erstellt haben, `Item`. -//// +## Resultate { #results } -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// - -... und deklarieren Sie seinen Typ als das Modell, welches Sie erstellt haben, `Item`. - -## Resultate - -Mit nur dieser Python-Typdeklaration, wird **FastAPI**: +Mit nur dieser Python-Typdeklaration wird **FastAPI**: * Den Requestbody als JSON lesen. * Die entsprechenden Typen konvertieren (falls nötig). * Diese Daten validieren. - * Wenn die Daten ungültig sind, einen klar lesbaren Fehler zurückgeben, der anzeigt, wo und was die inkorrekten Daten waren. + * Wenn die Daten ungültig sind, wird ein klar lesbarer Fehler zurückgegeben, der genau anzeigt, wo und was die inkorrekten Daten sind. * Ihnen die erhaltenen Daten im Parameter `item` übergeben. - * Da Sie diesen in der Funktion als vom Typ `Item` deklariert haben, erhalten Sie die ganze Editor-Unterstützung (Autovervollständigung, usw.) für alle Attribute und deren Typen. -* Eine JSON Schema Definition für Ihr Modell generieren, welche Sie überall sonst verwenden können, wenn es für Ihr Projekt Sinn macht. -* Diese Schemas werden Teil des generierten OpenAPI-Schemas und werden von den UIs der automatischen Dokumentation verwendet. + * Da Sie ihn in der Funktion als vom Typ `Item` deklariert haben, erhalten Sie auch die volle Unterstützung des Editors (Autovervollständigung, usw.) für alle Attribute und deren Typen. +* JSON Schema-Definitionen für Ihr Modell generieren, die Sie auch überall sonst verwenden können, wenn es für Ihr Projekt Sinn macht. +* Diese Schemas werden Teil des generierten OpenAPI-Schemas und werden von den UIs der automatischen Dokumentation genutzt. -## Automatische Dokumentation +## Automatische Dokumentation { #automatic-docs } -Die JSON-Schemas Ihrer Modelle werden Teil ihrer OpenAPI-generierten Schemas und werden in der interaktiven API Dokumentation angezeigt: +Die JSON-Schemas Ihrer Modelle werden Teil Ihres OpenAPI-generierten Schemas und in der interaktiven API-Dokumentation angezeigt: -Und werden auch verwendet in der API-Dokumentation innerhalb jeder *Pfadoperation*, welche sie braucht: +Und werden auch in der API-Dokumentation innerhalb jeder *Pfadoperation*, die sie benötigt, verwendet: -## Editor Unterstützung +## Editor-Unterstützung { #editor-support } -In Ihrem Editor, innerhalb Ihrer Funktion, erhalten Sie Typhinweise und Code-Vervollständigung überall (was nicht der Fall wäre, wenn Sie ein `dict` anstelle eines Pydantic Modells erhalten hätten): +In Ihrem Editor erhalten Sie innerhalb Ihrer Funktion Typhinweise und Code-Vervollständigung überall (was nicht der Fall wäre, wenn Sie ein `dict` anstelle eines Pydantic-Modells erhalten hätten): -Sie bekommen auch Fehler-Meldungen für inkorrekte Typoperationen: +Sie bekommen auch Fehlermeldungen für inkorrekte Typoperationen: Das ist nicht zufällig so, das ganze Framework wurde um dieses Design herum aufgebaut. -Und es wurde in der Designphase gründlich getestet, vor der Implementierung, um sicherzustellen, dass es mit jedem Editor funktioniert. +Und es wurde in der Designphase gründlich getestet, bevor irgendeine Implementierung stattfand, um sicherzustellen, dass es mit allen Editoren funktioniert. -Es gab sogar ein paar Änderungen an Pydantic selbst, um das zu unterstützen. +Es gab sogar einige Änderungen an Pydantic selbst, um dies zu unterstützen. -Die vorherigen Screenshots zeigten Visual Studio Code. +Die vorherigen Screenshots wurden mit Visual Studio Code aufgenommen. -Aber Sie bekommen die gleiche Editor-Unterstützung in PyCharm und in den meisten anderen Python-Editoren: +Aber Sie würden die gleiche Editor-Unterstützung in PyCharm und den meisten anderen Python-Editoren erhalten: -/// tip | "Tipp" +/// tip | Tipp -Wenn Sie PyCharm als Ihren Editor verwenden, probieren Sie das Pydantic PyCharm Plugin aus. +Wenn Sie PyCharm als Ihren Editor verwenden, können Sie das Pydantic PyCharm Plugin ausprobieren. Es verbessert die Editor-Unterstützung für Pydantic-Modelle, mit: * Code-Vervollständigung * Typüberprüfungen * Refaktorisierung -* Suchen +* Suche * Inspektionen /// -## Das Modell verwenden +## Das Modell verwenden { #use-the-model } -Innerhalb der Funktion können Sie alle Attribute des Modells direkt verwenden: +Innerhalb der Funktion können Sie alle Attribute des Modellobjekts direkt verwenden: -//// tab | Python 3.10+ +{* ../../docs_src/body/tutorial002_py310.py *} -```Python hl_lines="19" -{!> ../../../docs_src/body/tutorial002_py310.py!} -``` +/// info | Info -//// +In Pydantic v1 hieß die Methode `.dict()`, sie wurde in Pydantic v2 deprecatet (aber weiterhin unterstützt) und in `.model_dump()` umbenannt. -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/body/tutorial002.py!} -``` - -//// - -## Requestbody- + Pfad-Parameter - -Sie können Pfad- und Requestbody-Parameter gleichzeitig deklarieren. - -**FastAPI** erkennt, dass Funktionsparameter, die mit Pfad-Parametern übereinstimmen, **vom Pfad genommen** werden sollen, und dass Funktionsparameter, welche Pydantic-Modelle sind, **vom Requestbody genommen** werden sollen. - -//// tab | Python 3.10+ - -```Python hl_lines="15-16" -{!> ../../../docs_src/body/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17-18" -{!> ../../../docs_src/body/tutorial003.py!} -``` - -//// - -## Requestbody- + Pfad- + Query-Parameter - -Sie können auch zur gleichen Zeit **Body-**, **Pfad-** und **Query-Parameter** deklarieren. - -**FastAPI** wird jeden Parameter korrekt erkennen und die Daten vom richtigen Ort holen. - -//// tab | Python 3.10+ - -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial004.py!} -``` - -//// - -Die Funktionsparameter werden wie folgt erkannt: - -* Wenn der Parameter auch im **Pfad** deklariert wurde, wird er als Pfad-Parameter interpretiert. -* Wenn der Parameter ein **einfacher Typ** ist (wie `int`, `float`, `str`, `bool`, usw.), wird er als **Query**-Parameter interpretiert. -* Wenn der Parameter vom Typ eines **Pydantic-Modells** ist, wird er als Request**body** interpretiert. - -/// note | "Hinweis" - -FastAPI weiß, dass der Wert von `q` nicht erforderlich ist, wegen des definierten Defaultwertes `= None` - -Das `Union` in `Union[str, None]` wird von FastAPI nicht verwendet, aber es erlaubt Ihrem Editor, Sie besser zu unterstützen und Fehler zu erkennen. +Die Beispiele hier verwenden `.dict()` zur Kompatibilität mit Pydantic v1, aber Sie sollten stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 nutzen können. /// -## Ohne Pydantic +## Requestbody- + Pfad-Parameter { #request-body-path-parameters } -Wenn Sie keine Pydantic-Modelle verwenden wollen, können Sie auch **Body**-Parameter nehmen. Siehe die Dokumentation unter [Body – Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#einzelne-werte-im-body){.internal-link target=\_blank}. +Sie können Pfad-Parameter und den Requestbody gleichzeitig deklarieren. + +**FastAPI** erkennt, dass Funktionsparameter, die mit Pfad-Parametern übereinstimmen, **vom Pfad genommen** werden sollen, und dass Funktionsparameter, welche Pydantic-Modelle sind, **vom Requestbody genommen** werden sollen. + +{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} + + +## Requestbody- + Pfad- + Query-Parameter { #request-body-path-query-parameters } + +Sie können auch zur gleichen Zeit **Body-**, **Pfad-** und **Query-Parameter** deklarieren. + +**FastAPI** wird jeden von ihnen korrekt erkennen und die Daten vom richtigen Ort holen. + +{* ../../docs_src/body/tutorial004_py310.py hl[16] *} + +Die Funktionsparameter werden wie folgt erkannt: + +* Wenn der Parameter auch im **Pfad** deklariert wurde, wird er als Pfad-Parameter verwendet. +* Wenn der Parameter ein **einfacher Typ** ist (wie `int`, `float`, `str`, `bool`, usw.), wird er als **Query**-Parameter interpretiert. +* Wenn der Parameter vom Typ eines **Pydantic-Modells** ist, wird er als Request**body** interpretiert. + +/// note | Hinweis + +FastAPI weiß, dass der Wert von `q` nicht erforderlich ist, aufgrund des definierten Defaultwertes `= None`. + +Das `str | None` (Python 3.10+) oder `Union` in `Union[str, None]` (Python 3.8+) wird von FastAPI nicht verwendet, um zu bestimmen, dass der Wert nicht erforderlich ist. FastAPI weiß, dass er nicht erforderlich ist, weil er einen Defaultwert von `= None` hat. + +Das Hinzufügen der Typannotationen ermöglicht jedoch Ihrem Editor, Ihnen eine bessere Unterstützung zu bieten und Fehler zu erkennen. + +/// + +## Ohne Pydantic { #without-pydantic } + +Wenn Sie keine Pydantic-Modelle verwenden möchten, können Sie auch **Body**-Parameter verwenden. Siehe die Dokumentation unter [Body – Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. diff --git a/docs/de/docs/tutorial/cookie-param-models.md b/docs/de/docs/tutorial/cookie-param-models.md new file mode 100644 index 0000000000..2baf3d70dd --- /dev/null +++ b/docs/de/docs/tutorial/cookie-param-models.md @@ -0,0 +1,76 @@ +# Cookie-Parameter-Modelle { #cookie-parameter-models } + +Wenn Sie eine Gruppe von **Cookies** haben, die zusammengehören, können Sie ein **Pydantic-Modell** erstellen, um diese zu deklarieren. 🍪 + +Damit können Sie das Modell an **mehreren Stellen wiederverwenden** und auch Validierungen und Metadaten für alle Parameter gleichzeitig deklarieren. 😎 + +/// note | Hinweis + +Dies wird seit FastAPI Version `0.115.0` unterstützt. 🤓 + +/// + +/// tip | Tipp + +Diese gleiche Technik gilt für `Query`, `Cookie` und `Header`. 😎 + +/// + +## Cookies mit einem Pydantic-Modell { #cookies-with-a-pydantic-model } + +Deklarieren Sie die **Cookie**-Parameter, die Sie benötigen, in einem **Pydantic-Modell**, und deklarieren Sie dann den Parameter als `Cookie`: + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +**FastAPI** wird die Daten für **jedes Feld** aus den im Request empfangenen **Cookies** **extrahieren** und Ihnen das von Ihnen definierte Pydantic-Modell bereitstellen. + +## Die Dokumentation testen { #check-the-docs } + +Sie können die definierten Cookies in der Dokumentationsoberfläche unter `/docs` sehen: + +
+ +
+ +/// info | Info + +Bitte beachten Sie, dass Browser Cookies auf spezielle Weise und im Hintergrund bearbeiten, sodass sie **nicht** leicht **JavaScript** erlauben, diese zu berühren. + +Wenn Sie zur **API-Dokumentationsoberfläche** unter `/docs` gehen, können Sie die **Dokumentation** für Cookies für Ihre *Pfadoperationen* sehen. + +Aber selbst wenn Sie die **Daten ausfüllen** und auf „Ausführen“ klicken, werden aufgrund der Tatsache, dass die Dokumentationsoberfläche mit **JavaScript** arbeitet, die Cookies nicht gesendet, und Sie werden eine **Fehlermeldung** sehen, als ob Sie keine Werte eingegeben hätten. + +/// + +## Zusätzliche Cookies verbieten { #forbid-extra-cookies } + +In einigen speziellen Anwendungsfällen (wahrscheinlich nicht sehr häufig) möchten Sie möglicherweise die Cookies, die Sie empfangen möchten, **einschränken**. + +Ihre API hat jetzt die Macht, ihre eigene Cookie-Einwilligung zu kontrollieren. 🤪🍪 + +Sie können die Modellkonfiguration von Pydantic verwenden, um `extra` Felder zu verbieten (`forbid`): + +{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} + +Wenn ein Client versucht, einige **zusätzliche Cookies** zu senden, erhält er eine **Error-Response**. + +Arme Cookie-Banner, wie sie sich mühen, Ihre Einwilligung zu erhalten, dass die API sie ablehnen darf. 🍪 + +Wenn der Client beispielsweise versucht, ein `santa_tracker`-Cookie mit einem Wert von `good-list-please` zu senden, erhält der Client eine **Error-Response**, die ihm mitteilt, dass das `santa_tracker` Cookie nicht erlaubt ist: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## Zusammenfassung { #summary } + +Sie können **Pydantic-Modelle** verwenden, um **Cookies** in **FastAPI** zu deklarieren. 😎 diff --git a/docs/de/docs/tutorial/cookie-params.md b/docs/de/docs/tutorial/cookie-params.md index 0060db8e88..81a753211a 100644 --- a/docs/de/docs/tutorial/cookie-params.md +++ b/docs/de/docs/tutorial/cookie-params.md @@ -1,135 +1,45 @@ -# Cookie-Parameter +# Cookie-Parameter { #cookie-parameters } -So wie `Query`- und `Path`-Parameter können Sie auch Cookie-Parameter definieren. +Sie können Cookie-Parameter auf die gleiche Weise definieren wie `Query`- und `Path`-Parameter. -## `Cookie` importieren +## `Cookie` importieren { #import-cookie } Importieren Sie zuerst `Cookie`: -//// tab | Python 3.10+ +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} -``` +## `Cookie`-Parameter deklarieren { #declare-cookie-parameters } -//// +Deklarieren Sie dann die Cookie-Parameter mit derselben Struktur wie bei `Path` und `Query`. -//// tab | Python 3.9+ +Sie können den Defaultwert sowie alle zusätzlichen Validierungen oder Annotierungsparameter definieren: -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} -``` +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} -//// +/// note | Technische Details -//// tab | Python 3.8+ +`Cookie` ist eine „Schwester“-Klasse von `Path` und `Query`. Sie erbt auch von derselben gemeinsamen `Param`-Klasse. -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Aber denken Sie daran, dass, wenn Sie `Query`, `Path`, `Cookie` und andere von `fastapi` importieren, diese tatsächlich Funktionen sind, die spezielle Klassen zurückgeben. /// -```Python hl_lines="1" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` +/// info | Info -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Um Cookies zu deklarieren, müssen Sie `Cookie` verwenden, da die Parameter sonst als Query-Parameter interpretiert würden. /// -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` +/// info | Info -//// +Beachten Sie, dass **Browser Cookies auf besondere Weise und hinter den Kulissen handhaben** und **JavaScript** **nicht** ohne Weiteres erlauben, auf sie zuzugreifen. -## `Cookie`-Parameter deklarieren +Wenn Sie zur **API-Dokumentations-UI** unter `/docs` gehen, können Sie die **Dokumentation** zu Cookies für Ihre *Pfadoperationen* sehen. -Dann deklarieren Sie Ihre Cookie-Parameter, auf die gleiche Weise, wie Sie auch `Path`- und `Query`-Parameter deklarieren. - -Der erste Wert ist der Typ. Sie können `Cookie` die gehabten Extra Validierungs- und Beschreibungsparameter hinzufügen. Danach können Sie einen Defaultwert vergeben: - -//// tab | Python 3.10+ - -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/cookie_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Aber selbst wenn Sie die **Daten ausfüllen** und auf „Execute“ klicken, da die Dokumentations-UI mit **JavaScript** arbeitet, werden die Cookies nicht gesendet, und Sie sehen eine **Fehler**-Meldung, als hätten Sie keine Werte eingegeben. /// -```Python hl_lines="7" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` +## Zusammenfassung { #recap } -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` - -//// - -/// note | "Technische Details" - -`Cookie` ist eine Schwesterklasse von `Path` und `Query`. Sie erbt von derselben gemeinsamen `Param`-Elternklasse. - -Aber erinnern Sie sich, dass, wenn Sie `Query`, `Path`, `Cookie` und andere von `fastapi` importieren, diese tatsächlich Funktionen sind, welche spezielle Klassen zurückgeben. - -/// - -/// info - -Um Cookies zu deklarieren, müssen Sie `Cookie` verwenden, da diese Parameter sonst als Query-Parameter interpretiert werden würden. - -/// - -## Zusammenfassung - -Deklarieren Sie Cookies mittels `Cookie`, auf die gleiche Weise wie bei `Query` und `Path`. +Deklarieren Sie Cookies mit `Cookie` und verwenden Sie dabei das gleiche allgemeine Muster wie bei `Query` und `Path`. diff --git a/docs/de/docs/tutorial/cors.md b/docs/de/docs/tutorial/cors.md new file mode 100644 index 0000000000..191a7b4ef3 --- /dev/null +++ b/docs/de/docs/tutorial/cors.md @@ -0,0 +1,88 @@ +# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing } + +CORS oder „Cross-Origin Resource Sharing“ bezieht sich auf Situationen, in denen ein Frontend, das in einem Browser läuft, JavaScript-Code enthält, der mit einem Backend kommuniziert, und das Backend sich in einem anderen „Origin“ als das Frontend befindet. + +## Origin { #origin } + +Ein Origin ist die Kombination aus Protokoll (`http`, `https`), Domain (`myapp.com`, `localhost`, `localhost.tiangolo.com`) und Port (`80`, `443`, `8080`). + +Alle folgenden sind also unterschiedliche Origins: + +* `http://localhost` +* `https://localhost` +* `http://localhost:8080` + +Auch wenn sie alle in `localhost` sind, verwenden sie unterschiedliche Protokolle oder Ports, daher sind sie unterschiedliche „Origins“. + +## Schritte { #steps } + +Angenommen, Sie haben ein Frontend, das in Ihrem Browser unter `http://localhost:8080` läuft, und dessen JavaScript versucht, mit einem Backend zu kommunizieren, das unter `http://localhost` läuft (da wir keinen Port angegeben haben, geht der Browser vom Default-Port `80` aus). + +Dann wird der Browser ein HTTP-`OPTIONS`-Request an das `:80`-Backend senden, und wenn das Backend die entsprechenden Header sendet, die die Kommunikation von diesem anderen Origin (`http://localhost:8080`) autorisieren, lässt der `:8080`-Browser das JavaScript im Frontend seinen Request an das `:80`-Backend senden. + +Um dies zu erreichen, muss das `:80`-Backend eine Liste von „erlaubten Origins“ haben. + +In diesem Fall müsste die Liste `http://localhost:8080` enthalten, damit das `:8080`-Frontend korrekt funktioniert. + +## Wildcards { #wildcards } + +Es ist auch möglich, die Liste als `"*"` (ein „Wildcard“) zu deklarieren, um anzuzeigen, dass alle erlaubt sind. + +Aber das erlaubt nur bestimmte Arten der Kommunikation und schließt alles aus, was Anmeldeinformationen beinhaltet: Cookies, Autorisierungsheader wie die, die mit Bearer Tokens verwendet werden, usw. + +Um sicherzustellen, dass alles korrekt funktioniert, ist es besser, die erlaubten Origins explizit anzugeben. + +## `CORSMiddleware` verwenden { #use-corsmiddleware } + +Sie können das in Ihrer **FastAPI**-Anwendung mit der `CORSMiddleware` konfigurieren. + +* Importieren Sie `CORSMiddleware`. +* Erstellen Sie eine Liste der erlaubten Origins (als Strings). +* Fügen Sie es als „Middleware“ zu Ihrer **FastAPI**-Anwendung hinzu. + +Sie können auch angeben, ob Ihr Backend erlaubt: + +* Anmeldeinformationen (Autorisierungsheader, Cookies, usw.). +* Bestimmte HTTP-Methoden (`POST`, `PUT`) oder alle mit der Wildcard `"*"`. +* Bestimmte HTTP-Header oder alle mit der Wildcard `"*"`. + +{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} + +Die von der `CORSMiddleware`-Implementierung verwendeten Defaultparameter sind standardmäßig restriktiv, daher müssen Sie bestimmte Origins, Methoden oder Header ausdrücklich aktivieren, damit Browser sie in einem Cross-Domain-Kontext verwenden dürfen. + +Die folgenden Argumente werden unterstützt: + +* `allow_origins` – Eine Liste von Origins, die Cross-Origin-Requests machen dürfen. z. B. `['https://example.org', 'https://www.example.org']`. Sie können `['*']` verwenden, um jedes Origin zuzulassen. +* `allow_origin_regex` – Ein Regex-String zum Abgleichen gegen Origins, die Cross-Origin-Requests machen dürfen. z. B. `'https://.*\.example\.org'`. +* `allow_methods` – Eine Liste von HTTP-Methoden, die für Cross-Origin-Requests erlaubt sein sollen. Standardmäßig `['GET']`. Sie können `['*']` verwenden, um alle Standardmethoden zu erlauben. +* `allow_headers` – Eine Liste von HTTP-Requestheadern, die für Cross-Origin-Requests unterstützt werden sollten. Standardmäßig `[]`. Sie können `['*']` verwenden, um alle Header zu erlauben. Die Header `Accept`, `Accept-Language`, `Content-Language` und `Content-Type` sind immer für einfache CORS-Requests erlaubt. +* `allow_credentials` – Anzeigen, dass Cookies für Cross-Origin-Requests unterstützt werden sollten. Standardmäßig `False`. + + Keines der `allow_origins`, `allow_methods` und `allow_headers` kann auf `['*']` gesetzt werden, wenn `allow_credentials` auf `True` gesetzt ist. Alle müssen explizit angegeben werden. + +* `expose_headers` – Angabe der Responseheader, auf die der Browser zugreifen können soll. Standardmäßig `[]`. +* `max_age` – Legt eine maximale Zeit in Sekunden fest, die Browser CORS-Responses zwischenspeichern dürfen. Standardmäßig `600`. + +Die Middleware antwortet auf zwei besondere Arten von HTTP-Requests ... + +### CORS-Preflight-Requests { #cors-preflight-requests } + +Dies sind alle `OPTIONS`-Requests mit `Origin`- und `Access-Control-Request-Method`-Headern. + +In diesem Fall wird die Middleware den eingehenden Request abfangen und mit entsprechenden CORS-Headern, und entweder einer `200`- oder `400`-Response zu Informationszwecken antworten. + +### Einfache Requests { #simple-requests } + +Jeder Request mit einem `Origin`-Header. In diesem Fall wird die Middleware den Request wie gewohnt durchlassen, aber entsprechende CORS-Header in die Response aufnehmen. + +## Weitere Informationen { #more-info } + +Weitere Informationen zu CORS finden Sie in der Mozilla CORS-Dokumentation. + +/// note | Technische Details + +Sie könnten auch `from starlette.middleware.cors import CORSMiddleware` verwenden. + +**FastAPI** bietet mehrere Middlewares in `fastapi.middleware` nur als Komfort für Sie, den Entwickler. Aber die meisten der verfügbaren Middlewares stammen direkt von Starlette. + +/// diff --git a/docs/de/docs/tutorial/debugging.md b/docs/de/docs/tutorial/debugging.md new file mode 100644 index 0000000000..0a31f86536 --- /dev/null +++ b/docs/de/docs/tutorial/debugging.md @@ -0,0 +1,113 @@ +# Debugging { #debugging } + +Sie können den Debugger in Ihrem Editor verbinden, zum Beispiel mit Visual Studio Code oder PyCharm. + +## `uvicorn` aufrufen { #call-uvicorn } + +Importieren und führen Sie `uvicorn` direkt in Ihrer FastAPI-Anwendung aus: + +{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} + +### Über `__name__ == "__main__"` { #about-name-main } + +Der Hauptzweck von `__name__ == "__main__"` ist, dass Code ausgeführt wird, wenn Ihre Datei mit folgendem Befehl aufgerufen wird: + +
+ +```console +$ python myapp.py +``` + +
+ +aber nicht aufgerufen wird, wenn eine andere Datei sie importiert, wie in: + +```Python +from myapp import app +``` + +#### Weitere Details { #more-details } + +Angenommen, Ihre Datei heißt `myapp.py`. + +Wenn Sie sie mit folgendem Befehl ausführen: + +
+ +```console +$ python myapp.py +``` + +
+ +dann hat in Ihrer Datei die interne Variable `__name__`, die von Python automatisch erstellt wird, als Wert den String `"__main__"`. + +Daher wird der Abschnitt: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +ausgeführt. + +--- + +Dies wird nicht passieren, wenn Sie das Modul (die Datei) importieren. + +Wenn Sie also eine weitere Datei `importer.py` mit folgendem Inhalt haben: + +```Python +from myapp import app + +# Hier mehr Code +``` + +wird in diesem Fall in `myapp.py` die automatisch erstellte Variable `__name__` nicht den Wert `"__main__"` haben. + +Daher wird die Zeile: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +nicht ausgeführt. + +/// info | Info + +Für weitere Informationen besuchen Sie bitte die offizielle Python-Dokumentation. + +/// + +## Ihren Code mit Ihrem Debugger ausführen { #run-your-code-with-your-debugger } + +Da Sie den Uvicorn-Server direkt aus Ihrem Code ausführen, können Sie Ihr Python-Programm (Ihre FastAPI-Anwendung) direkt aus dem Debugger aufrufen. + +--- + +Zum Beispiel können Sie in Visual Studio Code: + +* Zum „Debug“-Panel gehen. +* „Konfiguration hinzufügen ...“ auswählen. +* „Python“ auswählen. +* Den Debugger mit der Option „`Python: Current File (Integrated Terminal)`“ ausführen. + +Der Server wird dann mit Ihrem **FastAPI**-Code gestartet, an Ihren Haltepunkten angehalten, usw. + +So könnte es aussehen: + + + +--- + +Wenn Sie Pycharm verwenden, können Sie: + +* Das Menü „Run“ öffnen. +* Die Option „Debug ...“ auswählen. +* Ein Kontextmenü wird angezeigt. +* Die zu debuggende Datei auswählen (in diesem Fall `main.py`). + +Der Server wird dann mit Ihrem **FastAPI**-Code gestartet, an Ihren Haltepunkten angehalten, usw. + +So könnte es aussehen: + + diff --git a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md index 0a9f05bf90..3d4493f353 100644 --- a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md @@ -1,62 +1,12 @@ -# Klassen als Abhängigkeiten +# Klassen als Abhängigkeiten { #classes-as-dependencies } Bevor wir tiefer in das **Dependency Injection** System eintauchen, lassen Sie uns das vorherige Beispiel verbessern. -## Ein `dict` aus dem vorherigen Beispiel +## Ein `dict` aus dem vorherigen Beispiel { #a-dict-from-the-previous-example } -Im vorherigen Beispiel haben wir ein `dict` von unserer Abhängigkeit („Dependable“) zurückgegeben: +Im vorherigen Beispiel haben wir ein `dict` von unserer Abhängigkeit („Dependable“) zurückgegeben: -//// tab | Python 3.10+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} Aber dann haben wir ein `dict` im Parameter `commons` der *Pfadoperation-Funktion*. @@ -64,7 +14,7 @@ Und wir wissen, dass Editoren nicht viel Unterstützung (wie etwa Code-Vervollst Das können wir besser machen ... -## Was macht eine Abhängigkeit aus +## Was macht eine Abhängigkeit aus { #what-makes-a-dependency } Bisher haben Sie Abhängigkeiten gesehen, die als Funktionen deklariert wurden. @@ -88,7 +38,7 @@ something(some_argument, some_keyword_argument="foo") dann ist das ein „Callable“ (ein „Aufrufbares“). -## Klassen als Abhängigkeiten +## Klassen als Abhängigkeiten { #classes-as-dependencies_1 } Möglicherweise stellen Sie fest, dass Sie zum Erstellen einer Instanz einer Python-Klasse die gleiche Syntax verwenden. @@ -119,165 +69,15 @@ Das gilt auch für Callables ohne Parameter. So wie es auch für *Pfadoperation- Dann können wir das „Dependable“ `common_parameters` der Abhängigkeit von oben in die Klasse `CommonQueryParams` ändern: -//// tab | Python 3.10+ - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12-16" -{!> ../../../docs_src/dependencies/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9-13" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} Achten Sie auf die Methode `__init__`, die zum Erstellen der Instanz der Klasse verwendet wird: -//// tab | Python 3.10+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="13" -{!> ../../../docs_src/dependencies/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} ... sie hat die gleichen Parameter wie unsere vorherige `common_parameters`: -//// tab | Python 3.10+ - -```Python hl_lines="8" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="6" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} Diese Parameter werden von **FastAPI** verwendet, um die Abhängigkeit „aufzulösen“. @@ -289,65 +89,15 @@ In beiden Fällen wird sie haben: In beiden Fällen werden die Daten konvertiert, validiert, im OpenAPI-Schema dokumentiert, usw. -## Verwendung +## Verwenden { #use-it } Jetzt können Sie Ihre Abhängigkeit mithilfe dieser Klasse deklarieren. -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} **FastAPI** ruft die Klasse `CommonQueryParams` auf. Dadurch wird eine „Instanz“ dieser Klasse erstellt und die Instanz wird als Parameter `commons` an Ihre Funktion überreicht. -## Typannotation vs. `Depends` +## Typannotation vs. `Depends` { #type-annotation-vs-depends } Beachten Sie, wie wir `CommonQueryParams` im obigen Code zweimal schreiben: @@ -361,7 +111,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. @@ -397,7 +147,7 @@ commons: Annotated[CommonQueryParams, ... //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. @@ -423,7 +173,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. @@ -437,63 +187,13 @@ commons = Depends(CommonQueryParams) ... wie in: -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} Es wird jedoch empfohlen, den Typ zu deklarieren, da Ihr Editor so weiß, was als Parameter `commons` übergeben wird, und Ihnen dann bei der Codevervollständigung, Typprüfungen, usw. helfen kann: -## Abkürzung +## Abkürzung { #shortcut } Aber Sie sehen, dass wir hier etwas Codeduplizierung haben, indem wir `CommonQueryParams` zweimal schreiben: @@ -507,7 +207,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. @@ -535,7 +235,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. @@ -559,7 +259,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// tab | Python 3.8 nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. @@ -575,61 +275,11 @@ Sie deklarieren die Abhängigkeit als Typ des Parameters und verwenden `Depends( Dasselbe Beispiel würde dann so aussehen: -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} ... und **FastAPI** wird wissen, was zu tun ist. -/// tip | "Tipp" +/// tip | Tipp Wenn Sie das eher verwirrt, als Ihnen zu helfen, ignorieren Sie es, Sie *brauchen* es nicht. diff --git a/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 47d6453c26..59c9fcf48e 100644 --- a/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -1,52 +1,24 @@ -# Abhängigkeiten in Pfadoperation-Dekoratoren +# Abhängigkeiten in Pfadoperation-Dekoratoren { #dependencies-in-path-operation-decorators } Manchmal benötigen Sie den Rückgabewert einer Abhängigkeit innerhalb Ihrer *Pfadoperation-Funktion* nicht wirklich. Oder die Abhängigkeit gibt keinen Wert zurück. -Aber Sie müssen Sie trotzdem ausführen/auflösen. +Aber Sie müssen sie trotzdem ausführen/auflösen. In diesen Fällen können Sie, anstatt einen Parameter der *Pfadoperation-Funktion* mit `Depends` zu deklarieren, eine `list`e von `dependencies` zum *Pfadoperation-Dekorator* hinzufügen. -## `dependencies` zum *Pfadoperation-Dekorator* hinzufügen +## `dependencies` zum *Pfadoperation-Dekorator* hinzufügen { #add-dependencies-to-the-path-operation-decorator } Der *Pfadoperation-Dekorator* erhält ein optionales Argument `dependencies`. Es sollte eine `list`e von `Depends()` sein: -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} Diese Abhängigkeiten werden auf die gleiche Weise wie normale Abhängigkeiten ausgeführt/aufgelöst. Aber ihr Wert (falls sie einen zurückgeben) wird nicht an Ihre *Pfadoperation-Funktion* übergeben. -/// tip | "Tipp" +/// tip | Tipp Einige Editoren prüfen, ob Funktionsparameter nicht verwendet werden, und zeigen das als Fehler an. @@ -56,7 +28,7 @@ Damit wird auch vermieden, neue Entwickler möglicherweise zu verwirren, die ein /// -/// info +/// info | Info In diesem Beispiel verwenden wir zwei erfundene benutzerdefinierte Header `X-Key` und `X-Token`. @@ -64,118 +36,34 @@ Aber in realen Fällen würden Sie bei der Implementierung von Sicherheit mehr V /// -## Abhängigkeitsfehler und -Rückgabewerte +## Abhängigkeitsfehler und -Rückgabewerte { #dependencies-errors-and-return-values } Sie können dieselben Abhängigkeits-*Funktionen* verwenden, die Sie normalerweise verwenden. -### Abhängigkeitsanforderungen +### Abhängigkeitsanforderungen { #dependency-requirements } -Sie können Anforderungen für einen Request (wie Header) oder andere Unterabhängigkeiten deklarieren: +Sie können Anforderungen für einen Request (wie Header) oder andere Unterabhängigkeiten deklarieren: -//// tab | Python 3.9+ +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} -```Python hl_lines="8 13" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7 12" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="6 11" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// - -### Exceptions auslösen +### Exceptions auslösen { #raise-exceptions } Die Abhängigkeiten können Exceptions `raise`n, genau wie normale Abhängigkeiten: -//// tab | Python 3.9+ +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} -```Python hl_lines="10 15" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 14" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8 13" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// - -### Rückgabewerte +### Rückgabewerte { #return-values } Und sie können Werte zurückgeben oder nicht, die Werte werden nicht verwendet. Sie können also eine normale Abhängigkeit (die einen Wert zurückgibt), die Sie bereits an anderer Stelle verwenden, wiederverwenden, und auch wenn der Wert nicht verwendet wird, wird die Abhängigkeit ausgeführt: -//// tab | Python 3.9+ +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} -```Python hl_lines="11 16" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10 15" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9 14" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// - -## Abhängigkeiten für eine Gruppe von *Pfadoperationen* +## Abhängigkeiten für eine Gruppe von *Pfadoperationen* { #dependencies-for-a-group-of-path-operations } Wenn Sie später lesen, wie Sie größere Anwendungen strukturieren ([Größere Anwendungen – Mehrere Dateien](../../tutorial/bigger-applications.md){.internal-link target=_blank}), möglicherweise mit mehreren Dateien, lernen Sie, wie Sie einen einzelnen `dependencies`-Parameter für eine Gruppe von *Pfadoperationen* deklarieren. -## Globale Abhängigkeiten +## Globale Abhängigkeiten { #global-dependencies } Als Nächstes werden wir sehen, wie man Abhängigkeiten zur gesamten `FastAPI`-Anwendung hinzufügt, sodass sie für jede *Pfadoperation* gelten. diff --git a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md index 9c2e6dd86a..e65b073a2e 100644 --- a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,16 +1,16 @@ -# Abhängigkeiten mit yield +# Abhängigkeiten mit `yield` { #dependencies-with-yield } -FastAPI unterstützt Abhängigkeiten, die nach Abschluss einige zusätzliche Schritte ausführen. +FastAPI unterstützt Abhängigkeiten, die nach Abschluss einige zusätzliche Schritte ausführen. Verwenden Sie dazu `yield` statt `return` und schreiben Sie die zusätzlichen Schritte / den zusätzlichen Code danach. -/// tip | "Tipp" +/// tip | Tipp Stellen Sie sicher, dass Sie `yield` nur einmal pro Abhängigkeit verwenden. /// -/// note | "Technische Details" +/// note | Technische Details Jede Funktion, die dekoriert werden kann mit: @@ -23,51 +23,43 @@ Tatsächlich verwendet FastAPI diese beiden Dekoratoren intern. /// -## Eine Datenbank-Abhängigkeit mit `yield`. +## Eine Datenbank-Abhängigkeit mit `yield` { #a-database-dependency-with-yield } Sie könnten damit beispielsweise eine Datenbanksession erstellen und diese nach Abschluss schließen. -Nur der Code vor und einschließlich der `yield`-Anweisung wird ausgeführt, bevor eine Response erzeugt wird: +Nur der Code vor und einschließlich der `yield`-Anweisung wird ausgeführt, bevor eine Response erzeugt wird: -```Python hl_lines="2-4" -{!../../../docs_src/dependencies/tutorial007.py!} -``` +{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} Der ge`yield`ete Wert ist das, was in *Pfadoperationen* und andere Abhängigkeiten eingefügt wird: -```Python hl_lines="4" -{!../../../docs_src/dependencies/tutorial007.py!} -``` +{* ../../docs_src/dependencies/tutorial007.py hl[4] *} -Der auf die `yield`-Anweisung folgende Code wird ausgeführt, nachdem die Response gesendet wurde: +Der auf die `yield`-Anweisung folgende Code wird nach der Response ausgeführt: -```Python hl_lines="5-6" -{!../../../docs_src/dependencies/tutorial007.py!} -``` +{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} -/// tip | "Tipp" +/// tip | Tipp -Sie können `async`hrone oder reguläre Funktionen verwenden. +Sie können `async`- oder reguläre Funktionen verwenden. **FastAPI** wird bei jeder das Richtige tun, so wie auch bei normalen Abhängigkeiten. /// -## Eine Abhängigkeit mit `yield` und `try`. +## Eine Abhängigkeit mit `yield` und `try` { #a-dependency-with-yield-and-try } Wenn Sie einen `try`-Block in einer Abhängigkeit mit `yield` verwenden, empfangen Sie alle Exceptions, die bei Verwendung der Abhängigkeit geworfen wurden. -Wenn beispielsweise ein Code irgendwann in der Mitte, in einer anderen Abhängigkeit oder in einer *Pfadoperation*, ein „Rollback“ einer Datenbanktransaktion oder einen anderen Fehler verursacht, empfangen Sie die resultierende Exception in Ihrer Abhängigkeit. +Wenn beispielsweise ein Code irgendwann in der Mitte, in einer anderen Abhängigkeit oder in einer *Pfadoperation*, ein „Rollback“ einer Datenbanktransaktion macht oder eine andere Exception verursacht, empfangen Sie die Exception in Ihrer Abhängigkeit. Sie können also mit `except SomeException` diese bestimmte Exception innerhalb der Abhängigkeit handhaben. Auf die gleiche Weise können Sie `finally` verwenden, um sicherzustellen, dass die Exit-Schritte ausgeführt werden, unabhängig davon, ob eine Exception geworfen wurde oder nicht. -```Python hl_lines="3 5" -{!../../../docs_src/dependencies/tutorial007.py!} -``` +{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} -## Unterabhängigkeiten mit `yield`. +## Unterabhängigkeiten mit `yield` { #sub-dependencies-with-yield } Sie können Unterabhängigkeiten und „Bäume“ von Unterabhängigkeiten beliebiger Größe und Form haben, und einige oder alle davon können `yield` verwenden. @@ -75,35 +67,7 @@ Sie können Unterabhängigkeiten und „Bäume“ von Unterabhängigkeiten belie Beispielsweise kann `dependency_c` von `dependency_b` und `dependency_b` von `dependency_a` abhängen: -//// tab | Python 3.9+ - -```Python hl_lines="6 14 22" -{!> ../../../docs_src/dependencies/tutorial008_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="5 13 21" -{!> ../../../docs_src/dependencies/tutorial008_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="4 12 20" -{!> ../../../docs_src/dependencies/tutorial008.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} Und alle können `yield` verwenden. @@ -111,35 +75,7 @@ In diesem Fall benötigt `dependency_c` zum Ausführen seines Exit-Codes, dass d Und wiederum benötigt `dependency_b` den Wert von `dependency_a` (hier `dep_a` genannt) für seinen Exit-Code. -//// tab | Python 3.9+ - -```Python hl_lines="18-19 26-27" -{!> ../../../docs_src/dependencies/tutorial008_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17-18 25-26" -{!> ../../../docs_src/dependencies/tutorial008_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="16-17 24-25" -{!> ../../../docs_src/dependencies/tutorial008.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} Auf die gleiche Weise könnten Sie einige Abhängigkeiten mit `yield` und einige andere Abhängigkeiten mit `return` haben, und alle können beliebig voneinander abhängen. @@ -149,7 +85,7 @@ Sie können beliebige Kombinationen von Abhängigkeiten haben. **FastAPI** stellt sicher, dass alles in der richtigen Reihenfolge ausgeführt wird. -/// note | "Technische Details" +/// note | Technische Details Dieses funktioniert dank Pythons Kontextmanager. @@ -157,13 +93,15 @@ Dieses funktioniert dank Pythons ../../../docs_src/dependencies/tutorial008b_an_py39.py!} -``` +Wenn Sie Exceptions abfangen und darauf basierend eine benutzerdefinierte Response erstellen möchten, erstellen Sie einen [benutzerdefinierten Exceptionhandler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. -//// +## Abhängigkeiten mit `yield` und `except` { #dependencies-with-yield-and-except } -//// tab | Python 3.8+ +Wenn Sie eine Exception mit `except` in einer Abhängigkeit mit `yield` abfangen und sie nicht erneut auslösen (oder eine neue Exception auslösen), kann FastAPI nicht feststellen, dass es eine Exception gab, genau so wie es bei normalem Python der Fall wäre: -```Python hl_lines="17-21 30" -{!> ../../../docs_src/dependencies/tutorial008b_an.py!} -``` +{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} -//// +In diesem Fall sieht der Client eine *HTTP 500 Internal Server Error*-Response, wie es sein sollte, da wir keine `HTTPException` oder Ähnliches auslösen, aber der Server hat **keine Logs** oder einen anderen Hinweis darauf, was der Fehler war. 😱 -//// tab | Python 3.8+ nicht annotiert +### In Abhängigkeiten mit `yield` und `except` immer `raise` verwenden { #always-raise-in-dependencies-with-yield-and-except } -/// tip | "Tipp" +Wenn Sie eine Exception in einer Abhängigkeit mit `yield` abfangen, sollten Sie – sofern Sie nicht eine andere `HTTPException` oder Ähnliches auslösen – **die ursprüngliche Exception erneut auslösen**. -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Sie können dieselbe Exception mit `raise` erneut auslösen: -/// +{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} -```Python hl_lines="16-20 29" -{!> ../../../docs_src/dependencies/tutorial008b.py!} -``` +Jetzt erhält der Client dieselbe *HTTP 500 Internal Server Error*-Response, aber der Server enthält unseren benutzerdefinierten `InternalError` in den Logs. 😎 -//// - -Eine Alternative zum Abfangen von Exceptions (und möglicherweise auch zum Auslösen einer weiteren `HTTPException`) besteht darin, einen [benutzerdefinierten Exceptionhandler](../handling-errors.md#benutzerdefinierte-exceptionhandler-definieren){.internal-link target=_blank} zu erstellen. - -## Ausführung von Abhängigkeiten mit `yield` +## Ausführung von Abhängigkeiten mit `yield` { #execution-of-dependencies-with-yield } Die Ausführungsreihenfolge ähnelt mehr oder weniger dem folgenden Diagramm. Die Zeit verläuft von oben nach unten. Und jede Spalte ist einer der interagierenden oder Code-ausführenden Teilnehmer. @@ -227,22 +155,22 @@ participant tasks as Hintergrundtasks opt Löst aus operation -->> dep: Löst Exception aus (z. B. HTTPException) opt Handhabt - dep -->> dep: Kann Exception abfangen, eine neue HTTPException auslösen, andere Exceptions auslösen - dep -->> handler: Leitet Exception automatisch weiter + dep -->> dep: Kann Exception abfangen, eine neue HTTPException auslösen, andere Exception auslösen end handler -->> client: HTTP-Error-Response end + operation ->> client: Sendet Response an Client - Note over client,operation: Response wurde gesendet, kann nicht mehr geändert werden + Note over client,operation: Response wurde bereits gesendet, kann nicht mehr geändert werden opt Tasks operation -->> tasks: Sendet Hintergrundtasks end opt Löst andere Exception aus - tasks -->> tasks: Handhabt Exception im Hintergrundtask-Code + tasks -->> tasks: Handhabt Exceptions im Hintergrundtask-Code end ``` -/// info +/// info | Info Es wird nur **eine Response** an den Client gesendet. Es kann eine Error-Response oder die Response der *Pfadoperation* sein. @@ -250,45 +178,20 @@ Nachdem eine dieser Responses gesendet wurde, kann keine weitere Response gesend /// -/// tip | "Tipp" +/// tip | Tipp -Obiges Diagramm verwendet `HTTPException`, aber Sie können auch jede andere Exception auslösen, die Sie in einer Abhängigkeit mit `yield` abfangen, oder mit einem [benutzerdefinierten Exceptionhandler](../handling-errors.md#benutzerdefinierte-exceptionhandler-definieren){.internal-link target=_blank} erstellt haben. - -Wenn Sie eine Exception auslösen, wird diese mit yield an die Abhängigkeiten übergeben, einschließlich `HTTPException`, und dann **erneut** an die Exceptionhandler. Wenn es für diese Exception keinen Exceptionhandler gibt, wird sie von der internen Default-`ServerErrorMiddleware` gehandhabt, was einen HTTP-Statuscode 500 zurückgibt, um den Client darüber zu informieren, dass ein Fehler auf dem Server aufgetreten ist. +Wenn Sie in dem Code der *Pfadoperation-Funktion* irgendeine Exception auslösen, wird sie an die Abhängigkeiten mit `yield` weitergegeben, einschließlich `HTTPException`. In den meisten Fällen sollten Sie dieselbe Exception oder eine neue aus der Abhängigkeit mit `yield` erneut auslösen, um sicherzustellen, dass sie korrekt gehandhabt wird. /// -## Abhängigkeiten mit `yield`, `HTTPException` und Hintergrundtasks +## Abhängigkeiten mit `yield`, `HTTPException`, `except` und Hintergrundtasks { #dependencies-with-yield-httpexception-except-and-background-tasks } -/// warning | "Achtung" +Abhängigkeiten mit `yield` haben sich im Laufe der Zeit weiterentwickelt, um verschiedene Anwendungsfälle abzudecken und einige Probleme zu beheben. -Sie benötigen diese technischen Details höchstwahrscheinlich nicht, Sie können diesen Abschnitt überspringen und weiter unten fortfahren. +Wenn Sie sehen möchten, was sich in verschiedenen Versionen von FastAPI geändert hat, lesen Sie mehr dazu im fortgeschrittenen Teil, unter [Fortgeschrittene Abhängigkeiten – Abhängigkeiten mit `yield`, `HTTPException`, `except` und Hintergrundtasks](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}. +## Kontextmanager { #context-managers } -Diese Details sind vor allem dann nützlich, wenn Sie eine Version von FastAPI vor 0.106.0 verwendet haben und Ressourcen aus Abhängigkeiten mit `yield` in Hintergrundtasks verwendet haben. - -/// - -Vor FastAPI 0.106.0 war das Auslösen von Exceptions nach `yield` nicht möglich, der Exit-Code in Abhängigkeiten mit `yield` wurde ausgeführt, *nachdem* die Response gesendet wurde, die [Exceptionhandler](../handling-errors.md#benutzerdefinierte-exceptionhandler-definieren){.internal-link target=_blank} wären also bereits ausgeführt worden. - -Dies wurde hauptsächlich so konzipiert, damit die gleichen Objekte, die durch Abhängigkeiten ge`yield`et werden, innerhalb von Hintergrundtasks verwendet werden können, da der Exit-Code ausgeführt wird, nachdem die Hintergrundtasks abgeschlossen sind. - -Da dies jedoch bedeuten würde, darauf zu warten, dass die Response durch das Netzwerk reist, während eine Ressource unnötigerweise in einer Abhängigkeit mit yield gehalten wird (z. B. eine Datenbankverbindung), wurde dies in FastAPI 0.106.0 geändert. - -/// tip | "Tipp" - -Darüber hinaus handelt es sich bei einem Hintergrundtask normalerweise um einen unabhängigen Satz von Logik, der separat behandelt werden sollte, mit eigenen Ressourcen (z. B. einer eigenen Datenbankverbindung). - -Auf diese Weise erhalten Sie wahrscheinlich saubereren Code. - -/// - -Wenn Sie sich früher auf dieses Verhalten verlassen haben, sollten Sie jetzt die Ressourcen für Hintergrundtasks innerhalb des Hintergrundtasks selbst erstellen und intern nur Daten verwenden, die nicht von den Ressourcen von Abhängigkeiten mit `yield` abhängen. - -Anstatt beispielsweise dieselbe Datenbanksitzung zu verwenden, würden Sie eine neue Datenbanksitzung innerhalb des Hintergrundtasks erstellen und die Objekte mithilfe dieser neuen Sitzung aus der Datenbank abrufen. Und anstatt das Objekt aus der Datenbank als Parameter an die Hintergrundtask-Funktion zu übergeben, würden Sie die ID dieses Objekts übergeben und das Objekt dann innerhalb der Hintergrundtask-Funktion erneut laden. - -## Kontextmanager - -### Was sind „Kontextmanager“ +### Was sind „Kontextmanager“ { #what-are-context-managers } „Kontextmanager“ (Englisch „Context Manager“) sind bestimmte Python-Objekte, die Sie in einer `with`-Anweisung verwenden können. @@ -302,13 +205,13 @@ with open("./somefile.txt") as f: Im Hintergrund erstellt das `open("./somefile.txt")` ein Objekt, das als „Kontextmanager“ bezeichnet wird. -Dieser stellt sicher dass, wenn der `with`-Block beendet ist, die Datei geschlossen wird, auch wenn Exceptions geworfen wurden. +Dieser stellt sicher, dass, wenn der `with`-Block beendet ist, die Datei geschlossen wird, auch wenn Exceptions geworfen wurden. Wenn Sie eine Abhängigkeit mit `yield` erstellen, erstellt **FastAPI** dafür intern einen Kontextmanager und kombiniert ihn mit einigen anderen zugehörigen Tools. -### Kontextmanager in Abhängigkeiten mit `yield` verwenden +### Kontextmanager in Abhängigkeiten mit `yield` verwenden { #using-context-managers-in-dependencies-with-yield } -/// warning | "Achtung" +/// warning | Achtung Dies ist mehr oder weniger eine „fortgeschrittene“ Idee. @@ -320,11 +223,9 @@ In Python können Sie Kontextmanager erstellen, indem Sie ../../../docs_src/dependencies/tutorial012_an_py39.py!} -``` +Und alle Ideen aus dem Abschnitt über das [Hinzufügen von `dependencies` zu den *Pfadoperation-Dekoratoren*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} gelten weiterhin, aber in diesem Fall für alle *Pfadoperationen* in der App. -//// +## Abhängigkeiten für Gruppen von *Pfadoperationen* { #dependencies-for-groups-of-path-operations } -//// tab | Python 3.8+ - -```Python hl_lines="16" -{!> ../../../docs_src/dependencies/tutorial012_an.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="15" -{!> ../../../docs_src/dependencies/tutorial012.py!} -``` - -//// - -Und alle Ideen aus dem Abschnitt über das [Hinzufügen von `dependencies` zu den *Pfadoperation-Dekoratoren*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} gelten weiterhin, aber in diesem Fall für alle *Pfadoperationen* in der Anwendung. - -## Abhängigkeiten für Gruppen von *Pfadoperationen* - -Wenn Sie später lesen, wie Sie größere Anwendungen strukturieren ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), möglicherweise mit mehreren Dateien, lernen Sie, wie Sie einen einzelnen `dependencies`-Parameter für eine Gruppe von *Pfadoperationen* deklarieren. +Wenn Sie später lesen, wie Sie größere Anwendungen strukturieren ([Größere Anwendungen – mehrere Dateien](../../tutorial/bigger-applications.md){.internal-link target=_blank}), möglicherweise mit mehreren Dateien, lernen Sie, wie Sie einen einzelnen `dependencies`-Parameter für eine Gruppe von *Pfadoperationen* deklarieren. diff --git a/docs/de/docs/tutorial/dependencies/index.md b/docs/de/docs/tutorial/dependencies/index.md index f7d9ed5109..cb6a612133 100644 --- a/docs/de/docs/tutorial/dependencies/index.md +++ b/docs/de/docs/tutorial/dependencies/index.md @@ -1,10 +1,10 @@ -# Abhängigkeiten +# Abhängigkeiten { #dependencies } -**FastAPI** hat ein sehr mächtiges, aber intuitives **Dependency Injection** System. +**FastAPI** hat ein sehr mächtiges, aber intuitives **Dependency Injection** System. Es ist so konzipiert, sehr einfach zu verwenden zu sein und es jedem Entwickler sehr leicht zu machen, andere Komponenten mit **FastAPI** zu integrieren. -## Was ist „Dependency Injection“ +## Was ist „Dependency Injection“ { #what-is-dependency-injection } **„Dependency Injection“** bedeutet in der Programmierung, dass es für Ihren Code (in diesem Fall Ihre *Pfadoperation-Funktionen*) eine Möglichkeit gibt, Dinge zu deklarieren, die er verwenden möchte und die er zum Funktionieren benötigt: „Abhängigkeiten“ – „Dependencies“. @@ -19,68 +19,18 @@ Das ist sehr nützlich, wenn Sie: All dies, während Sie Codeverdoppelung minimieren. -## Erste Schritte +## Erste Schritte { #first-steps } Sehen wir uns ein sehr einfaches Beispiel an. Es ist so einfach, dass es vorerst nicht sehr nützlich ist. Aber so können wir uns besser auf die Funktionsweise des **Dependency Injection** Systems konzentrieren. -### Erstellen Sie eine Abhängigkeit („Dependable“) +### Eine Abhängigkeit erstellen, oder „Dependable“ { #create-a-dependency-or-dependable } -Konzentrieren wir uns zunächst auf die Abhängigkeit - die Dependency. +Konzentrieren wir uns zunächst auf die Abhängigkeit – die Dependency. Es handelt sich einfach um eine Funktion, die die gleichen Parameter entgegennimmt wie eine *Pfadoperation-Funktion*: -//// tab | Python 3.10+ - -```Python hl_lines="8-9" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="8-11" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-12" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="6-7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8-11" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} Das war's schon. @@ -98,127 +48,27 @@ In diesem Fall erwartet diese Abhängigkeit: * Einen optionalen Query-Parameter `skip`, der ein `int` ist und standardmäßig `0` ist. * Einen optionalen Query-Parameter `limit`, der ein `int` ist und standardmäßig `100` ist. -Und dann wird einfach ein `dict` zurückgegeben, welches diese Werte enthält. +Und dann wird einfach ein `dict` zurückgegeben, welches diese Werte enthält. -/// info +/// info | Info FastAPI unterstützt (und empfiehlt die Verwendung von) `Annotated` seit Version 0.95.0. Wenn Sie eine ältere Version haben, werden Sie Fehler angezeigt bekommen, wenn Sie versuchen, `Annotated` zu verwenden. -Bitte [aktualisieren Sie FastAPI](../../deployment/versions.md#upgrade-der-fastapi-versionen){.internal-link target=_blank} daher mindestens zu Version 0.95.1, bevor Sie `Annotated` verwenden. +Bitte [aktualisieren Sie FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} daher mindestens zu Version 0.95.1, bevor Sie `Annotated` verwenden. /// -### `Depends` importieren +### `Depends` importieren { #import-depends } -//// tab | Python 3.10+ +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -### Deklarieren der Abhängigkeit im „Dependant“ +### Die Abhängigkeit im „Dependant“ deklarieren { #declare-the-dependency-in-the-dependant } So wie auch `Body`, `Query`, usw., verwenden Sie `Depends` mit den Parametern Ihrer *Pfadoperation-Funktion*: -//// tab | Python 3.10+ - -```Python hl_lines="13 18" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="15 20" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="16 21" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11 16" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="15 20" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} Obwohl Sie `Depends` in den Parametern Ihrer Funktion genauso verwenden wie `Body`, `Query`, usw., funktioniert `Depends` etwas anders. @@ -230,13 +80,13 @@ Sie **rufen diese nicht direkt auf** (fügen Sie am Ende keine Klammern hinzu), Und diese Funktion akzeptiert Parameter auf die gleiche Weise wie *Pfadoperation-Funktionen*. -/// tip | "Tipp" +/// tip | Tipp Im nächsten Kapitel erfahren Sie, welche anderen „Dinge“, außer Funktionen, Sie als Abhängigkeiten verwenden können. /// -Immer wenn ein neuer Request eintrifft, kümmert sich **FastAPI** darum: +Immer wenn ein neuer Request eintrifft, kümmert sich **FastAPI** darum: * Ihre Abhängigkeitsfunktion („Dependable“) mit den richtigen Parametern aufzurufen. * Sich das Ergebnis von dieser Funktion zu holen. @@ -255,7 +105,7 @@ common_parameters --> read_users Auf diese Weise schreiben Sie gemeinsam genutzten Code nur einmal, und **FastAPI** kümmert sich darum, ihn für Ihre *Pfadoperationen* aufzurufen. -/// check +/// check | Testen Beachten Sie, dass Sie keine spezielle Klasse erstellen und diese irgendwo an **FastAPI** übergeben müssen, um sie zu „registrieren“ oder so ähnlich. @@ -263,7 +113,7 @@ Sie übergeben es einfach an `Depends` und **FastAPI** weiß, wie der Rest erled /// -## `Annotated`-Abhängigkeiten wiederverwenden +## `Annotated`-Abhängigkeiten wiederverwenden { #share-annotated-dependencies } In den Beispielen oben sehen Sie, dass es ein kleines bisschen **Codeverdoppelung** gibt. @@ -275,31 +125,9 @@ commons: Annotated[dict, Depends(common_parameters)] Da wir jedoch `Annotated` verwenden, können wir diesen `Annotated`-Wert in einer Variablen speichern und an mehreren Stellen verwenden: -//// tab | Python 3.10+ +{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *} -```Python hl_lines="12 16 21" -{!> ../../../docs_src/dependencies/tutorial001_02_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="14 18 23" -{!> ../../../docs_src/dependencies/tutorial001_02_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15 19 24" -{!> ../../../docs_src/dependencies/tutorial001_02_an.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Das ist schlicht Standard-Python, es wird als „Typalias“ bezeichnet und ist eigentlich nicht **FastAPI**-spezifisch. @@ -311,7 +139,7 @@ Die Abhängigkeiten funktionieren weiterhin wie erwartet, und das **Beste daran* Das ist besonders nützlich, wenn Sie es in einer **großen Codebasis** verwenden, in der Sie in **vielen *Pfadoperationen*** immer wieder **dieselben Abhängigkeiten** verwenden. -## `async` oder nicht `async` +## `async` oder nicht `async` { #to-async-or-not-to-async } Da Abhängigkeiten auch von **FastAPI** aufgerufen werden (so wie Ihre *Pfadoperation-Funktionen*), gelten beim Definieren Ihrer Funktionen die gleichen Regeln. @@ -321,13 +149,13 @@ Und Sie können Abhängigkeiten mit `async def` innerhalb normaler `def`-*Pfadop Es spielt keine Rolle. **FastAPI** weiß, was zu tun ist. -/// note | "Hinweis" +/// note | Hinweis -Wenn Ihnen das nichts sagt, lesen Sie den [Async: *„In Eile?“*](../../async.md#in-eile){.internal-link target=_blank}-Abschnitt über `async` und `await` in der Dokumentation. +Wenn Ihnen das nichts sagt, lesen Sie den [Async: *„In Eile?“*](../../async.md#in-a-hurry){.internal-link target=_blank}-Abschnitt über `async` und `await` in der Dokumentation. /// -## Integriert in OpenAPI +## Integriert in OpenAPI { #integrated-with-openapi } Alle Requestdeklarationen, -validierungen und -anforderungen Ihrer Abhängigkeiten (und Unterabhängigkeiten) werden in dasselbe OpenAPI-Schema integriert. @@ -335,9 +163,9 @@ Die interaktive Dokumentation enthält also auch alle Informationen aus diesen A -## Einfache Verwendung +## Einfache Verwendung { #simple-usage } -Näher betrachtet, werden *Pfadoperation-Funktionen* deklariert, um verwendet zu werden, wann immer ein *Pfad* und eine *Operation* übereinstimmen, und dann kümmert sich **FastAPI** darum, die Funktion mit den richtigen Parametern aufzurufen, die Daten aus der Anfrage extrahierend. +Näher betrachtet, werden *Pfadoperation-Funktionen* deklariert, um verwendet zu werden, wann immer ein *Pfad* und eine *Operation* übereinstimmen, und dann kümmert sich **FastAPI** darum, die Funktion mit den richtigen Parametern aufzurufen, die Daten aus dem Request extrahierend. Tatsächlich funktionieren alle (oder die meisten) Webframeworks auf die gleiche Weise. @@ -353,7 +181,7 @@ Andere gebräuchliche Begriffe für dieselbe Idee der „Abhängigkeitsinjektion * Injectables * Komponenten -## **FastAPI**-Plugins +## **FastAPI**-Plugins { #fastapi-plug-ins } Integrationen und „Plugins“ können mit dem **Dependency Injection** System erstellt werden. Aber tatsächlich besteht **keine Notwendigkeit, „Plugins“ zu erstellen**, da es durch die Verwendung von Abhängigkeiten möglich ist, eine unendliche Anzahl von Integrationen und Interaktionen zu deklarieren, die dann für Ihre *Pfadoperation-Funktionen* verfügbar sind. @@ -361,7 +189,7 @@ Und Abhängigkeiten können auf sehr einfache und intuitive Weise erstellt werde Beispiele hierfür finden Sie in den nächsten Kapiteln zu relationalen und NoSQL-Datenbanken, Sicherheit usw. -## **FastAPI**-Kompatibilität +## **FastAPI**-Kompatibilität { #fastapi-compatibility } Die Einfachheit des Dependency Injection Systems macht **FastAPI** kompatibel mit: @@ -371,10 +199,10 @@ Die Einfachheit des Dependency Injection Systems macht **FastAPI** kompatibel mi * externen APIs * Authentifizierungs- und Autorisierungssystemen * API-Nutzungs-Überwachungssystemen -* Responsedaten-Injektionssystemen +* Responsedaten-Injektionssystemen * usw. -## Einfach und leistungsstark +## Einfach und leistungsstark { #simple-and-powerful } Obwohl das hierarchische Dependency Injection System sehr einfach zu definieren und zu verwenden ist, ist es dennoch sehr mächtig. @@ -414,7 +242,7 @@ admin_user --> activate_user paying_user --> pro_items ``` -## Integriert mit **OpenAPI** +## Integriert mit **OpenAPI** { #integrated-with-openapi_1 } Alle diese Abhängigkeiten, während sie ihre Anforderungen deklarieren, fügen auch Parameter, Validierungen, usw. zu Ihren *Pfadoperationen* hinzu. diff --git a/docs/de/docs/tutorial/dependencies/sub-dependencies.md b/docs/de/docs/tutorial/dependencies/sub-dependencies.md index 12664a8cd2..061952f921 100644 --- a/docs/de/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/de/docs/tutorial/dependencies/sub-dependencies.md @@ -1,4 +1,4 @@ -# Unterabhängigkeiten +# Unterabhängigkeiten { #sub-dependencies } Sie können Abhängigkeiten erstellen, die **Unterabhängigkeiten** haben. @@ -6,121 +6,21 @@ Diese können so **tief** verschachtelt sein, wie nötig. **FastAPI** kümmert sich darum, sie aufzulösen. -## Erste Abhängigkeit, „Dependable“ +## Erste Abhängigkeit, „Dependable“ { #first-dependency-dependable } Sie könnten eine erste Abhängigkeit („Dependable“) wie folgt erstellen: -//// tab | Python 3.10+ +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *} -```Python hl_lines="8-9" -{!> ../../../docs_src/dependencies/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="8-9" -{!> ../../../docs_src/dependencies/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-10" -{!> ../../../docs_src/dependencies/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="6-7" -{!> ../../../docs_src/dependencies/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8-9" -{!> ../../../docs_src/dependencies/tutorial005.py!} -``` - -//// - -Diese deklariert einen optionalen Abfrageparameter `q` vom Typ `str` und gibt ihn dann einfach zurück. +Diese deklariert einen optionalen Query-Parameter `q` vom Typ `str` und gibt ihn dann einfach zurück. Das ist recht einfach (nicht sehr nützlich), hilft uns aber dabei, uns auf die Funktionsweise der Unterabhängigkeiten zu konzentrieren. -## Zweite Abhängigkeit, „Dependable“ und „Dependant“ +## Zweite Abhängigkeit, „Dependable“ und „Dependant“ { #second-dependency-dependable-and-dependant } Dann können Sie eine weitere Abhängigkeitsfunktion (ein „Dependable“) erstellen, die gleichzeitig eine eigene Abhängigkeit deklariert (also auch ein „Dependant“ ist): -//// tab | Python 3.10+ - -```Python hl_lines="13" -{!> ../../../docs_src/dependencies/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="13" -{!> ../../../docs_src/dependencies/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14" -{!> ../../../docs_src/dependencies/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="13" -{!> ../../../docs_src/dependencies/tutorial005.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *} Betrachten wir die deklarierten Parameter: @@ -129,67 +29,17 @@ Betrachten wir die deklarierten Parameter: * Sie deklariert außerdem ein optionales `last_query`-Cookie, ein `str`. * Wenn der Benutzer keine Query `q` übermittelt hat, verwenden wir die zuletzt übermittelte Query, die wir zuvor in einem Cookie gespeichert haben. -## Die Abhängigkeit verwenden +## Die Abhängigkeit verwenden { #use-the-dependency } Diese Abhängigkeit verwenden wir nun wie folgt: -//// tab | Python 3.10+ +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *} -```Python hl_lines="23" -{!> ../../../docs_src/dependencies/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="23" -{!> ../../../docs_src/dependencies/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24" -{!> ../../../docs_src/dependencies/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8 nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="22" -{!> ../../../docs_src/dependencies/tutorial005.py!} -``` - -//// - -/// info +/// info | Info Beachten Sie, dass wir in der *Pfadoperation-Funktion* nur eine einzige Abhängigkeit deklarieren, den `query_or_cookie_extractor`. -Aber **FastAPI** wird wissen, dass es zuerst `query_extractor` auflösen muss, um dessen Resultat `query_or_cookie_extractor` zu übergeben, wenn dieses aufgerufen wird. +Aber **FastAPI** wird wissen, dass es zuerst `query_extractor` auflösen muss, um dessen Resultat an `query_or_cookie_extractor` zu übergeben, wenn dieses aufgerufen wird. /// @@ -204,13 +54,13 @@ read_query["/items/"] query_extractor --> query_or_cookie_extractor --> read_query ``` -## Dieselbe Abhängigkeit mehrmals verwenden +## Dieselbe Abhängigkeit mehrmals verwenden { #using-the-same-dependency-multiple-times } -Wenn eine Ihrer Abhängigkeiten mehrmals für dieselbe *Pfadoperation* deklariert wird, beispielsweise wenn mehrere Abhängigkeiten eine gemeinsame Unterabhängigkeit haben, wird **FastAPI** diese Unterabhängigkeit nur einmal pro Request aufrufen. +Wenn eine Ihrer Abhängigkeiten mehrmals für dieselbe *Pfadoperation* deklariert wird, beispielsweise wenn mehrere Abhängigkeiten eine gemeinsame Unterabhängigkeit haben, wird **FastAPI** diese Unterabhängigkeit nur einmal pro Request aufrufen. Und es speichert den zurückgegebenen Wert in einem „Cache“ und übergibt diesen gecachten Wert an alle „Dependanten“, die ihn in diesem spezifischen Request benötigen, anstatt die Abhängigkeit mehrmals für denselben Request aufzurufen. -In einem fortgeschrittenen Szenario, bei dem Sie wissen, dass die Abhängigkeit bei jedem Schritt (möglicherweise mehrmals) in derselben Anfrage aufgerufen werden muss, anstatt den zwischengespeicherten Wert zu verwenden, können Sie den Parameter `use_cache=False` festlegen, wenn Sie `Depends` verwenden: +In einem fortgeschrittenen Szenario, bei dem Sie wissen, dass die Abhängigkeit bei jedem Schritt (möglicherweise mehrmals) in demselben Request aufgerufen werden muss, anstatt den zwischengespeicherten Wert zu verwenden, können Sie den Parameter `use_cache=False` festlegen, wenn Sie `Depends` verwenden: //// tab | Python 3.8+ @@ -223,7 +73,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. @@ -236,7 +86,7 @@ async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False //// -## Zusammenfassung +## Zusammenfassung { #recap } Abgesehen von all den ausgefallenen Wörtern, die hier verwendet werden, ist das **Dependency Injection**-System recht simpel. @@ -244,7 +94,7 @@ Einfach Funktionen, die genauso aussehen wie *Pfadoperation-Funktionen*. Dennoch ist es sehr mächtig und ermöglicht Ihnen die Deklaration beliebig tief verschachtelter Abhängigkeits-„Graphen“ (Bäume). -/// tip | "Tipp" +/// tip | Tipp All dies scheint angesichts dieser einfachen Beispiele möglicherweise nicht so nützlich zu sein. diff --git a/docs/de/docs/tutorial/encoder.md b/docs/de/docs/tutorial/encoder.md index 38a881b4f6..25dc6fa184 100644 --- a/docs/de/docs/tutorial/encoder.md +++ b/docs/de/docs/tutorial/encoder.md @@ -1,12 +1,12 @@ -# JSON-kompatibler Encoder +# JSON-kompatibler Encoder { #json-compatible-encoder } -Es gibt Fälle, da möchten Sie einen Datentyp (etwa ein Pydantic-Modell) in etwas konvertieren, das kompatibel mit JSON ist (etwa ein `dict`, eine `list`e, usw.). +Es gibt Fälle, da möchten Sie einen Datentyp (etwa ein Pydantic-Modell) in etwas konvertieren, das kompatibel mit JSON ist (etwa ein `dict`, eine `list`, usw.). Zum Beispiel, wenn Sie es in einer Datenbank speichern möchten. Dafür bietet **FastAPI** eine Funktion `jsonable_encoder()`. -## `jsonable_encoder` verwenden +## `jsonable_encoder` verwenden { #using-the-jsonable-encoder } Stellen wir uns vor, Sie haben eine Datenbank `fake_db`, die nur JSON-kompatible Daten entgegennimmt. @@ -20,21 +20,7 @@ Sie können für diese Fälle `jsonable_encoder` verwenden. Es nimmt ein Objekt entgegen, wie etwa ein Pydantic-Modell, und gibt eine JSON-kompatible Version zurück: -//// tab | Python 3.10+ - -```Python hl_lines="4 21" -{!> ../../../docs_src/encoder/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="5 22" -{!> ../../../docs_src/encoder/tutorial001.py!} -``` - -//// +{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *} In diesem Beispiel wird das Pydantic-Modell in ein `dict`, und das `datetime`-Objekt in ein `str` konvertiert. @@ -42,7 +28,7 @@ Das Resultat dieses Aufrufs ist etwas, das mit Pythons Standard-Requests. +* Datenkonvertierung für Response-Daten. * Datenvalidierung. * Automatische Annotation und Dokumentation. -## Andere Datentypen +## Andere Datentypen { #other-data-types } Hier sind einige der zusätzlichen Datentypen, die Sie verwenden können: @@ -36,11 +36,11 @@ Hier sind einige der zusätzlichen Datentypen, die Sie verwenden können: * `datetime.timedelta`: * Ein Python-`datetime.timedelta`. * Wird in Requests und Responses als `float` der Gesamtsekunden dargestellt. - * Pydantic ermöglicht auch die Darstellung als „ISO 8601 Zeitdifferenz-Kodierung“, Weitere Informationen finden Sie in der Dokumentation. + * Pydantic ermöglicht auch die Darstellung als „ISO 8601 Zeitdifferenz-Kodierung“, siehe die Dokumentation für weitere Informationen. * `frozenset`: * Wird in Requests und Responses wie ein `set` behandelt: * Bei Requests wird eine Liste gelesen, Duplikate entfernt und in ein `set` umgewandelt. - * Bei Responses wird das `set` in eine `list`e umgewandelt. + * Bei Responses wird das `set` in eine `list` umgewandelt. * Das generierte Schema zeigt an, dass die `set`-Werte eindeutig sind (unter Verwendung von JSON Schemas `uniqueItems`). * `bytes`: * Standard-Python-`bytes`. @@ -49,114 +49,14 @@ Hier sind einige der zusätzlichen Datentypen, die Sie verwenden können: * `Decimal`: * Standard-Python-`Decimal`. * In Requests und Responses wird es wie ein `float` behandelt. -* Sie können alle gültigen Pydantic-Datentypen hier überprüfen: Pydantic data types. +* Sie können alle gültigen Pydantic-Datentypen hier überprüfen: Pydantic-Datentypen. -## Beispiel +## Beispiel { #example } Hier ist ein Beispiel für eine *Pfadoperation* mit Parametern, die einige der oben genannten Typen verwenden. -//// tab | Python 3.10+ - -```Python hl_lines="1 3 12-16" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 3 12-16" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 3 13-17" -{!> ../../../docs_src/extra_data_types/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1 2 11-15" -{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1 2 12-16" -{!> ../../../docs_src/extra_data_types/tutorial001.py!} -``` - -//// +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *} Beachten Sie, dass die Parameter innerhalb der Funktion ihren natürlichen Datentyp haben und Sie beispielsweise normale Datumsmanipulationen durchführen können, wie zum Beispiel: -//// tab | Python 3.10+ - -```Python hl_lines="18-19" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="18-19" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="19-20" -{!> ../../../docs_src/extra_data_types/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17-18" -{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="18-19" -{!> ../../../docs_src/extra_data_types/tutorial001.py!} -``` - -//// +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *} diff --git a/docs/de/docs/tutorial/extra-models.md b/docs/de/docs/tutorial/extra-models.md index cfd0230eb2..967e8535b8 100644 --- a/docs/de/docs/tutorial/extra-models.md +++ b/docs/de/docs/tutorial/extra-models.md @@ -1,56 +1,42 @@ -# Extramodelle +# Extramodelle { #extra-models } -Fahren wir beim letzten Beispiel fort. Es gibt normalerweise mehrere zusammengehörende Modelle. +Im Anschluss an das vorherige Beispiel ist es üblich, mehr als ein zusammenhängendes Modell zu haben. -Insbesondere Benutzermodelle, denn: +Dies gilt insbesondere für Benutzermodelle, denn: -* Das **hereinkommende Modell** sollte ein Passwort haben können. -* Das **herausgehende Modell** sollte kein Passwort haben. -* Das **Datenbankmodell** sollte wahrscheinlich ein gehashtes Passwort haben. +* Das **Eingabemodell** muss ein Passwort enthalten können. +* Das **Ausgabemodell** sollte kein Passwort haben. +* Das **Datenbankmodell** müsste wahrscheinlich ein gehashtes Passwort haben. -/// danger | "Gefahr" +/// danger | Gefahr -Speichern Sie niemals das Klartext-Passwort eines Benutzers. Speichern Sie immer den „sicheren Hash“, den Sie verifizieren können. +Speichern Sie niemals das Klartextpasswort eines Benutzers. Speichern Sie immer einen „sicheren Hash“, den Sie dann verifizieren können. -Falls Ihnen das nichts sagt, in den [Sicherheits-Kapiteln](security/simple-oauth2.md#passwort-hashing){.internal-link target=_blank} werden Sie lernen, was ein „Passwort-Hash“ ist. +Wenn Sie nicht wissen, was das ist, werden Sie in den [Sicherheitskapiteln](security/simple-oauth2.md#password-hashing){.internal-link target=_blank} lernen, was ein „Passworthash“ ist. /// -## Mehrere Modelle +## Mehrere Modelle { #multiple-models } -Hier der generelle Weg, wie die Modelle mit ihren Passwort-Feldern aussehen könnten, und an welchen Orten sie verwendet werden würden. +Hier ist eine allgemeine Idee, wie die Modelle mit ihren Passwortfeldern aussehen könnten und an welchen Stellen sie verwendet werden: -//// tab | Python 3.10+ +{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} -```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" -{!> ../../../docs_src/extra_models/tutorial001_py310.py!} -``` +/// info | Info -//// +In Pydantic v1 hieß die Methode `.dict()`, in Pydantic v2 wurde sie deprecatet (aber weiterhin unterstützt) und in `.model_dump()` umbenannt. -//// tab | Python 3.8+ - -```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" -{!> ../../../docs_src/extra_models/tutorial001.py!} -``` - -//// - -/// info - -In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie deprecated (aber immer noch unterstützt) und in `.model_dump()` umbenannt. - -Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können. +Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, aber Sie sollten `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können. /// -### Über `**user_in.dict()` +### Über `**user_in.dict()` { #about-user-in-dict } -#### Pydantic's `.dict()` +#### Die `.dict()`-Methode von Pydantic { #pydantics-dict } `user_in` ist ein Pydantic-Modell der Klasse `UserIn`. -Pydantic-Modelle haben eine `.dict()`-Methode, die ein `dict` mit den Daten des Modells zurückgibt. +Pydantic-Modelle haben eine `.dict()`-Methode, die ein `dict` mit den Daten des Modells zurückgibt. Wenn wir also ein Pydantic-Objekt `user_in` erstellen, etwa so: @@ -58,21 +44,21 @@ Wenn wir also ein Pydantic-Objekt `user_in` erstellen, etwa so: user_in = UserIn(username="john", password="secret", email="john.doe@example.com") ``` -und wir rufen seine `.dict()`-Methode auf: +und dann aufrufen: ```Python user_dict = user_in.dict() ``` -dann haben wir jetzt in der Variable `user_dict` ein `dict` mit den gleichen Daten (es ist ein `dict` statt eines Pydantic-Modellobjekts). +haben wir jetzt ein `dict` mit den Daten in der Variablen `user_dict` (es ist ein `dict` statt eines Pydantic-Modellobjekts). -Wenn wir es ausgeben: +Und wenn wir aufrufen: ```Python print(user_dict) ``` -bekommen wir ein Python-`dict`: +würden wir ein Python-`dict` erhalten mit: ```Python { @@ -83,17 +69,17 @@ bekommen wir ein Python-`dict`: } ``` -#### Ein `dict` entpacken +#### Ein `dict` entpacken { #unpacking-a-dict } -Wenn wir ein `dict` wie `user_dict` nehmen, und es einer Funktion (oder Klassenmethode) mittels `**user_dict` übergeben, wird Python es „entpacken“. Es wird die Schlüssel und Werte von `user_dict` direkt als Schlüsselwort-Argumente übergeben. +Wenn wir ein `dict` wie `user_dict` nehmen und es einer Funktion (oder Klasse) mit `**user_dict` übergeben, wird Python es „entpacken“. Es wird die Schlüssel und Werte von `user_dict` direkt als Schlüsselwort-Argumente übergeben. -Wenn wir also das `user_dict` von oben nehmen und schreiben: +Setzen wir also das `user_dict` von oben ein: ```Python UserInDB(**user_dict) ``` -dann ist das ungefähr äquivalent zu: +so ist das äquivalent zu: ```Python UserInDB( @@ -104,7 +90,7 @@ UserInDB( ) ``` -Oder, präziser, `user_dict` wird direkt verwendet, welche Werte es auch immer haben mag: +Oder genauer gesagt, dazu, `user_dict` direkt zu verwenden, mit welchen Inhalten es auch immer in der Zukunft haben mag: ```Python UserInDB( @@ -115,34 +101,34 @@ UserInDB( ) ``` -#### Ein Pydantic-Modell aus den Inhalten eines anderen erstellen. +#### Ein Pydantic-Modell aus dem Inhalt eines anderen { #a-pydantic-model-from-the-contents-of-another } -Da wir in obigem Beispiel `user_dict` mittels `user_in.dict()` erzeugt haben, ist dieser Code: +Da wir im obigen Beispiel `user_dict` von `user_in.dict()` bekommen haben, wäre dieser Code: ```Python user_dict = user_in.dict() UserInDB(**user_dict) ``` -äquivalent zu: +gleichwertig zu: ```Python UserInDB(**user_in.dict()) ``` -... weil `user_in.dict()` ein `dict` ist, und dann lassen wir Python es „entpacken“, indem wir es `UserInDB` übergeben, mit vorangestelltem `**`. +... weil `user_in.dict()` ein `dict` ist, und dann lassen wir Python es „entpacken“, indem wir es an `UserInDB` mit vorangestelltem `**` übergeben. -Wir erhalten also ein Pydantic-Modell aus den Daten eines anderen Pydantic-Modells. +Auf diese Weise erhalten wir ein Pydantic-Modell aus den Daten eines anderen Pydantic-Modells. -#### Ein `dict` entpacken und zusätzliche Schlüsselwort-Argumente +#### Ein `dict` entpacken und zusätzliche Schlüsselwort-Argumente { #unpacking-a-dict-and-extra-keywords } -Und dann fügen wir ein noch weiteres Schlüsselwort-Argument hinzu, `hashed_password=hashed_password`: +Und dann fügen wir das zusätzliche Schlüsselwort-Argument `hashed_password=hashed_password` hinzu, wie in: ```Python UserInDB(**user_in.dict(), hashed_password=hashed_password) ``` -... was am Ende ergibt: +... was so ist wie: ```Python UserInDB( @@ -154,136 +140,83 @@ UserInDB( ) ``` -/// warning | "Achtung" +/// warning | Achtung -Die Hilfsfunktionen `fake_password_hasher` und `fake_save_user` demonstrieren nur den möglichen Fluss der Daten und bieten natürlich keine echte Sicherheit. +Die unterstützenden zusätzlichen Funktionen `fake_password_hasher` und `fake_save_user` dienen nur zur Demo eines möglichen Datenflusses, bieten jedoch natürlich keine echte Sicherheit. /// -## Verdopplung vermeiden +## Verdopplung vermeiden { #reduce-duplication } -Reduzierung von Code-Verdoppelung ist eine der Kern-Ideen von **FastAPI**. +Die Reduzierung von Code-Verdoppelung ist eine der Kernideen von **FastAPI**. -Weil Verdoppelung von Code die Wahrscheinlichkeit von Fehlern, Sicherheitsproblemen, Desynchronisation (Code wird nur an einer Stelle verändert, aber nicht an einer anderen), usw. erhöht. +Da die Verdopplung von Code die Wahrscheinlichkeit von Fehlern, Sicherheitsproblemen, Problemen mit der Desynchronisation des Codes (wenn Sie an einer Stelle, aber nicht an der anderen aktualisieren) usw. erhöht. -Unsere Modelle teilen alle eine Menge der Daten und verdoppeln Attribut-Namen und -Typen. +Und diese Modelle teilen alle eine Menge der Daten und verdoppeln Attributnamen und -typen. -Das können wir besser machen. +Wir könnten es besser machen. -Wir deklarieren ein `UserBase`-Modell, das als Basis für unsere anderen Modelle dient. Dann können wir Unterklassen erstellen, die seine Attribute (Typdeklarationen, Validierungen, usw.) erben. +Wir können ein `UserBase`-Modell deklarieren, das als Basis für unsere anderen Modelle dient. Und dann können wir Unterklassen dieses Modells erstellen, die seine Attribute (Typdeklarationen, Validierung usw.) erben. -Die ganze Datenkonvertierung, -validierung, -dokumentation, usw. wird immer noch wie gehabt funktionieren. +Die ganze Datenkonvertierung, -validierung, -dokumentation usw. wird immer noch wie gewohnt funktionieren. -Auf diese Weise beschreiben wir nur noch die Unterschiede zwischen den Modellen (mit Klartext-`password`, mit `hashed_password`, und ohne Passwort): +Auf diese Weise können wir nur die Unterschiede zwischen den Modellen (mit Klartext-`password`, mit `hashed_password` und ohne Passwort) deklarieren: -//// tab | Python 3.10+ +{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} -```Python hl_lines="7 13-14 17-18 21-22" -{!> ../../../docs_src/extra_models/tutorial002_py310.py!} -``` +## `Union` oder `anyOf` { #union-or-anyof } -//// +Sie können deklarieren, dass eine Response eine `Union` mehrerer Typen ist, das bedeutet, dass die Response einer von ihnen ist. -//// tab | Python 3.8+ +Dies wird in OpenAPI mit `anyOf` definiert. -```Python hl_lines="9 15-16 19-20 23-24" -{!> ../../../docs_src/extra_models/tutorial002.py!} -``` +Um das zu tun, verwenden Sie den Standard-Python-Typhinweis `typing.Union`: -//// +/// note | Hinweis -## `Union`, oder `anyOf` - -Sie können deklarieren, dass eine Response eine `Union` mehrerer Typen ist, sprich, einer dieser Typen. - -Das wird in OpenAPI mit `anyOf` angezeigt. - -Um das zu tun, verwenden Sie Pythons Standard-Typhinweis `typing.Union`: - -/// note | "Hinweis" - -Listen Sie, wenn Sie eine `Union` definieren, denjenigen Typ zuerst, der am spezifischsten ist, gefolgt von den weniger spezifischen Typen. Im Beispiel oben, in `Union[PlaneItem, CarItem]` also den spezifischeren `PlaneItem` vor dem weniger spezifischen `CarItem`. +Wenn Sie eine `Union` definieren, listen Sie den spezifischeren Typ zuerst auf, gefolgt vom weniger spezifischen Typ. Im Beispiel unten steht `PlaneItem` vor `CarItem` in `Union[PlaneItem, CarItem]`. /// -//// tab | Python 3.10+ +{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} -```Python hl_lines="1 14-15 18-20 33" -{!> ../../../docs_src/extra_models/tutorial003_py310.py!} -``` -//// +### `Union` in Python 3.10 { #union-in-python-3-10 } -//// tab | Python 3.8+ +In diesem Beispiel übergeben wir `Union[PlaneItem, CarItem]` als Wert des Arguments `response_model`. -```Python hl_lines="1 14-15 18-20 33" -{!> ../../../docs_src/extra_models/tutorial003.py!} -``` +Da wir es als **Wert an ein Argument übergeben**, anstatt es in einer **Typannotation** zu verwenden, müssen wir `Union` verwenden, sogar in Python 3.10. -//// - -### `Union` in Python 3.10 - -In diesem Beispiel übergeben wir dem Argument `response_model` den Wert `Union[PlaneItem, CarItem]`. - -Da wir es als **Wert einem Argument überreichen**, statt es als **Typannotation** zu verwenden, müssen wir `Union` verwenden, selbst in Python 3.10. - -Wenn es eine Typannotation gewesen wäre, hätten wir auch den vertikalen Trennstrich verwenden können, wie in: +Wäre es eine Typannotation gewesen, hätten wir den vertikalen Strich verwenden können, wie in: ```Python some_variable: PlaneItem | CarItem ``` -Aber wenn wir das in der Zuweisung `response_model=PlaneItem | CarItem` machen, erhalten wir eine Fehlermeldung, da Python versucht, eine **ungültige Operation** zwischen `PlaneItem` und `CarItem` durchzuführen, statt es als Typannotation zu interpretieren. +Aber wenn wir das in der Zuweisung `response_model=PlaneItem | CarItem` machen, würden wir einen Fehler erhalten, weil Python versuchen würde, eine **ungültige Operation** zwischen `PlaneItem` und `CarItem` auszuführen, anstatt es als Typannotation zu interpretieren. -## Listen von Modellen +## Liste von Modellen { #list-of-models } -Genauso können Sie eine Response deklarieren, die eine Liste von Objekten ist. +Auf die gleiche Weise können Sie Responses von Listen von Objekten deklarieren. -Verwenden Sie dafür Pythons Standard `typing.List` (oder nur `list` in Python 3.9 und darüber): +Dafür verwenden Sie Pythons Standard-`typing.List` (oder nur `list` in Python 3.9 und höher): -//// tab | Python 3.9+ +{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} -```Python hl_lines="18" -{!> ../../../docs_src/extra_models/tutorial004_py39.py!} -``` -//// +## Response mit beliebigem `dict` { #response-with-arbitrary-dict } -//// tab | Python 3.8+ +Sie können auch eine Response deklarieren, die ein beliebiges `dict` zurückgibt, indem Sie nur die Typen der Schlüssel und Werte ohne ein Pydantic-Modell deklarieren. -```Python hl_lines="1 20" -{!> ../../../docs_src/extra_models/tutorial004.py!} -``` +Dies ist nützlich, wenn Sie die gültigen Feld-/Attributnamen nicht im Voraus kennen (die für ein Pydantic-Modell benötigt werden würden). -//// +In diesem Fall können Sie `typing.Dict` verwenden (oder nur `dict` in Python 3.9 und höher): -## Response mit beliebigem `dict` +{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} -Sie könne auch eine Response deklarieren, die ein beliebiges `dict` zurückgibt, bei dem nur die Typen der Schlüssel und der Werte bekannt sind, ohne ein Pydantic-Modell zu verwenden. -Das ist nützlich, wenn Sie die gültigen Feld-/Attribut-Namen von vorneherein nicht wissen (was für ein Pydantic-Modell notwendig ist). - -In diesem Fall können Sie `typing.Dict` verwenden (oder nur `dict` in Python 3.9 und darüber): - -//// tab | Python 3.9+ - -```Python hl_lines="6" -{!> ../../../docs_src/extra_models/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 8" -{!> ../../../docs_src/extra_models/tutorial005.py!} -``` - -//// - -## Zusammenfassung +## Zusammenfassung { #recap } Verwenden Sie gerne mehrere Pydantic-Modelle und vererben Sie je nach Bedarf. -Sie brauchen kein einzelnes Datenmodell pro Einheit, wenn diese Einheit verschiedene Zustände annehmen kann. So wie unsere Benutzer-„Einheit“, welche einen Zustand mit `password`, einen mit `password_hash` und einen ohne Passwort hatte. +Sie brauchen kein einzelnes Datenmodell pro Einheit, wenn diese Einheit in der Lage sein muss, verschiedene „Zustände“ zu haben. Wie im Fall der Benutzer-„Einheit“ mit einem Zustand einschließlich `password`, `password_hash` und ohne Passwort. diff --git a/docs/de/docs/tutorial/first-steps.md b/docs/de/docs/tutorial/first-steps.md index b9e38707cb..7ec98c53b1 100644 --- a/docs/de/docs/tutorial/first-steps.md +++ b/docs/de/docs/tutorial/first-steps.md @@ -1,66 +1,80 @@ -# Erste Schritte +# Erste Schritte { #first-steps } Die einfachste FastAPI-Datei könnte wie folgt aussehen: -```Python -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py *} -Kopieren Sie dies in eine Datei `main.py`. +Kopieren Sie das in eine Datei `main.py`. Starten Sie den Live-Server:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py -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 Starting development server 🚀 + + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with + the following code: + + from main import app + + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C + to quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. ```
-/// note | "Hinweis" - -Der Befehl `uvicorn main:app` bezieht sich auf: - -* `main`: die Datei `main.py` (das sogenannte Python-„Modul“). -* `app`: das Objekt, welches in der Datei `main.py` mit der Zeile `app = FastAPI()` erzeugt wurde. -* `--reload`: lässt den Server nach Codeänderungen neu starten. Verwenden Sie das nur während der Entwicklung. - -/// - In der Konsolenausgabe sollte es eine Zeile geben, die ungefähr so aussieht: ```hl_lines="4" INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` -Diese Zeile zeigt die URL, unter der Ihre Anwendung auf Ihrem lokalen Computer bereitgestellt wird. +Diese Zeile zeigt die URL, unter der Ihre App auf Ihrem lokalen Computer bereitgestellt wird. -### Testen Sie es +### Es testen { #check-it } -Öffnen Sie Ihren Browser unter http://127.0.0.1:8000. +Öffnen Sie Ihren Browser unter http://127.0.0.1:8000. -Sie werden folgende JSON-Response sehen: +Sie werden die JSON-Response sehen: ```JSON {"message": "Hello World"} ``` -### Interaktive API-Dokumentation +### Interaktive API-Dokumentation { #interactive-api-docs } -Gehen Sie als Nächstes auf http://127.0.0.1:8000/docs . +Gehen Sie als Nächstes auf http://127.0.0.1:8000/docs. Sie werden die automatisch erzeugte, interaktive API-Dokumentation sehen (bereitgestellt durch Swagger UI): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Alternative API-Dokumentation +### Alternative API-Dokumentation { #alternative-api-docs } Gehen Sie nun auf http://127.0.0.1:8000/redoc. @@ -68,31 +82,31 @@ Dort sehen Sie die alternative, automatische Dokumentation (bereitgestellt durch ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -### OpenAPI +### OpenAPI { #openapi } **FastAPI** generiert ein „Schema“ mit all Ihren APIs unter Verwendung des **OpenAPI**-Standards zur Definition von APIs. -#### „Schema“ +#### „Schema“ { #schema } Ein „Schema“ ist eine Definition oder Beschreibung von etwas. Nicht der eigentliche Code, der es implementiert, sondern lediglich eine abstrakte Beschreibung. -#### API-„Schema“ +#### API-„Schema“ { #api-schema } -In diesem Fall ist OpenAPI eine Spezifikation, die vorschreibt, wie ein Schema für Ihre API zu definieren ist. +In diesem Fall ist OpenAPI eine Spezifikation, die vorschreibt, wie ein Schema für Ihre API zu definieren ist. Diese Schemadefinition enthält Ihre API-Pfade, die möglichen Parameter, welche diese entgegennehmen, usw. -#### Daten-„Schema“ +#### Daten-„Schema“ { #data-schema } Der Begriff „Schema“ kann sich auch auf die Form von Daten beziehen, wie z. B. einen JSON-Inhalt. In diesem Fall sind die JSON-Attribute und deren Datentypen, usw. gemeint. -#### OpenAPI und JSON Schema +#### OpenAPI und JSON Schema { #openapi-and-json-schema } OpenAPI definiert ein API-Schema für Ihre API. Dieses Schema enthält Definitionen (oder „Schemas“) der Daten, die von Ihrer API unter Verwendung von **JSON Schema**, dem Standard für JSON-Datenschemata, gesendet und empfangen werden. -#### Überprüfen Sie die `openapi.json` +#### Die `openapi.json` testen { #check-the-openapi-json } Falls Sie wissen möchten, wie das rohe OpenAPI-Schema aussieht: FastAPI generiert automatisch ein JSON (Schema) mit den Beschreibungen Ihrer gesamten API. @@ -121,7 +135,7 @@ Es wird ein JSON angezeigt, welches ungefähr so aussieht: ... ``` -#### Wofür OpenAPI gedacht ist +#### Wofür OpenAPI gedacht ist { #what-is-openapi-for } Das OpenAPI-Schema ist die Grundlage für die beiden enthaltenen interaktiven Dokumentationssysteme. @@ -129,67 +143,33 @@ Es gibt dutzende Alternativen, die alle auf OpenAPI basieren. Sie können jede d Ebenfalls können Sie es verwenden, um automatisch Code für Clients zu generieren, die mit Ihrer API kommunizieren. Zum Beispiel für Frontend-, Mobile- oder IoT-Anwendungen. -## Rückblick, Schritt für Schritt +## Zusammenfassung, Schritt für Schritt { #recap-step-by-step } -### Schritt 1: Importieren von `FastAPI` +### Schritt 1: `FastAPI` importieren { #step-1-import-fastapi } -```Python hl_lines="1" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[1] *} `FastAPI` ist eine Python-Klasse, die die gesamte Funktionalität für Ihre API bereitstellt. -/// note | "Technische Details" +/// note | Technische Details -`FastAPI` ist eine Klasse, die direkt von `Starlette` erbt. +`FastAPI` ist eine Klasse, die direkt von `Starlette` erbt. -Sie können alle Starlette-Funktionalitäten auch mit `FastAPI` nutzen. +Sie können alle Starlette-Funktionalitäten auch mit `FastAPI` nutzen. /// -### Schritt 2: Erzeugen einer `FastAPI`-„Instanz“ +### Schritt 2: Erzeugen einer `FastAPI`-„Instanz“ { #step-2-create-a-fastapi-instance } -```Python hl_lines="3" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[3] *} In diesem Beispiel ist die Variable `app` eine „Instanz“ der Klasse `FastAPI`. Dies wird der Hauptinteraktionspunkt für die Erstellung all Ihrer APIs sein. -Die Variable `app` ist dieselbe, auf die sich der Befehl `uvicorn` bezieht: +### Schritt 3: Erstellen einer *Pfadoperation* { #step-3-create-a-path-operation } -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -Wenn Sie Ihre Anwendung wie folgt erstellen: - -```Python hl_lines="3" -{!../../../docs_src/first_steps/tutorial002.py!} -``` - -Und in eine Datei `main.py` einfügen, dann würden Sie `uvicorn` wie folgt aufrufen: - -
- -```console -$ uvicorn main:my_awesome_api --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -### Schritt 3: Erstellen einer *Pfadoperation* - -#### Pfad +#### Pfad { #path } „Pfad“ bezieht sich hier auf den letzten Teil der URL, beginnend mit dem ersten `/`. @@ -205,7 +185,7 @@ https://example.com/items/foo /items/foo ``` -/// info +/// info | Info Ein „Pfad“ wird häufig auch als „Endpunkt“ oder „Route“ bezeichnet. @@ -213,7 +193,7 @@ Ein „Pfad“ wird häufig auch als „Endpunkt“ oder „Route“ bezeichnet. Bei der Erstellung einer API ist der „Pfad“ die wichtigste Möglichkeit zur Trennung von „Anliegen“ und „Ressourcen“. -#### Operation +#### Operation { #operation } „Operation“ bezieht sich hier auf eine der HTTP-„Methoden“. @@ -248,18 +228,16 @@ In OpenAPI wird folglich jede dieser HTTP-Methoden als „Operation“ bezeichne Wir werden sie auch „**Operationen**“ nennen. -#### Definieren eines *Pfadoperation-Dekorators* +#### Definieren eines *Pfadoperation-Dekorators* { #define-a-path-operation-decorator } -```Python hl_lines="6" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[6] *} -Das `@app.get("/")` sagt **FastAPI**, dass die Funktion direkt darunter für die Bearbeitung von Anfragen zuständig ist, die an: +Das `@app.get("/")` sagt **FastAPI**, dass die Funktion direkt darunter für die Bearbeitung von Requests zuständig ist, die an: - * den Pfad `/` - * unter der Verwendung der get-Operation gehen +* den Pfad `/` +* unter der Verwendung der get-Operation gehen -/// info | "`@decorator` Information" +/// info | `@decorator` Info Diese `@something`-Syntax wird in Python „Dekorator“ genannt. @@ -279,14 +257,14 @@ Sie können auch die anderen Operationen verwenden: * `@app.put()` * `@app.delete()` -Oder die exotischeren: +Und die exotischeren: * `@app.options()` * `@app.head()` * `@app.patch()` * `@app.trace()` -/// tip | "Tipp" +/// tip | Tipp Es steht Ihnen frei, jede Operation (HTTP-Methode) so zu verwenden, wie Sie es möchten. @@ -298,7 +276,7 @@ Wenn Sie beispielsweise GraphQL verwenden, führen Sie normalerweise alle Aktion /// -### Schritt 4: Definieren der **Pfadoperation-Funktion** +### Schritt 4: Definieren der **Pfadoperation-Funktion** { #step-4-define-the-path-operation-function } Das ist unsere „**Pfadoperation-Funktion**“: @@ -306,13 +284,11 @@ Das ist unsere „**Pfadoperation-Funktion**“: * **Operation**: ist `get`. * **Funktion**: ist die Funktion direkt unter dem „Dekorator“ (unter `@app.get("/")`). -```Python hl_lines="7" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[7] *} Dies ist eine Python-Funktion. -Sie wird von **FastAPI** immer dann aufgerufen, wenn sie eine Anfrage an die URL "`/`" mittels einer `GET`-Operation erhält. +Sie wird von **FastAPI** immer dann aufgerufen, wenn sie einen Request an die URL „`/`“ mittels einer `GET`-Operation erhält. In diesem Fall handelt es sich um eine `async`-Funktion. @@ -320,32 +296,28 @@ In diesem Fall handelt es sich um eine `async`-Funktion. Sie könnten sie auch als normale Funktion anstelle von `async def` definieren: -```Python hl_lines="7" -{!../../../docs_src/first_steps/tutorial003.py!} -``` +{* ../../docs_src/first_steps/tutorial003.py hl[7] *} -/// note | "Hinweis" +/// note | Hinweis -Wenn Sie den Unterschied nicht kennen, lesen Sie [Async: *„In Eile?“*](../async.md#in-eile){.internal-link target=_blank}. +Wenn Sie den Unterschied nicht kennen, lesen Sie [Async: *„In Eile?“*](../async.md#in-a-hurry){.internal-link target=_blank}. /// -### Schritt 5: den Inhalt zurückgeben +### Schritt 5: den Inhalt zurückgeben { #step-5-return-the-content } -```Python hl_lines="8" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[8] *} Sie können ein `dict`, eine `list`, einzelne Werte wie `str`, `int`, usw. zurückgeben. Sie können auch Pydantic-Modelle zurückgeben (dazu später mehr). -Es gibt viele andere Objekte und Modelle, die automatisch zu JSON konvertiert werden (einschließlich ORMs usw.). Versuchen Sie, Ihre Lieblingsobjekte zu verwenden. Es ist sehr wahrscheinlich, dass sie bereits unterstützt werden. +Es gibt viele andere Objekte und Modelle, die automatisch zu JSON konvertiert werden (einschließlich ORMs, usw.). Versuchen Sie, Ihre Lieblingsobjekte zu verwenden. Es ist sehr wahrscheinlich, dass sie bereits unterstützt werden. -## Zusammenfassung +## Zusammenfassung { #recap } * Importieren Sie `FastAPI`. * Erstellen Sie eine `app` Instanz. -* Schreiben Sie einen **Pfadoperation-Dekorator** (wie z. B. `@app.get("/")`). -* Schreiben Sie eine **Pfadoperation-Funktion** (wie z. B. oben `def root(): ...`). -* Starten Sie den Entwicklungsserver (z. B. `uvicorn main:app --reload`). +* Schreiben Sie einen **Pfadoperation-Dekorator** unter Verwendung von Dekoratoren wie `@app.get("/")`. +* Definieren Sie eine **Pfadoperation-Funktion**, zum Beispiel `def root(): ...`. +* Starten Sie den Entwicklungsserver mit dem Befehl `fastapi dev`. diff --git a/docs/de/docs/tutorial/handling-errors.md b/docs/de/docs/tutorial/handling-errors.md index 6ee47948c8..58e4607c5a 100644 --- a/docs/de/docs/tutorial/handling-errors.md +++ b/docs/de/docs/tutorial/handling-errors.md @@ -1,53 +1,49 @@ -# Fehlerbehandlung +# Fehler behandeln { #handling-errors } -Es gibt viele Situationen, in denen Sie einem Client, der Ihre API benutzt, einen Fehler zurückgeben müssen. +Es gibt viele Situationen, in denen Sie einem Client, der Ihre API nutzt, einen Fehler mitteilen müssen. -Dieser Client könnte ein Browser mit einem Frontend, Code von jemand anderem, ein IoT-Gerät, usw., sein. +Dieser Client könnte ein Browser mit einem Frontend sein, ein Code von jemand anderem, ein IoT-Gerät usw. -Sie müssten beispielsweise einem Client sagen: +Sie könnten dem Client mitteilen müssen, dass: -* Dass er nicht die notwendigen Berechtigungen hat, eine Aktion auszuführen. -* Dass er zu einer Ressource keinen Zugriff hat. -* Dass die Ressource, auf die er zugreifen möchte, nicht existiert. +* Der Client nicht genügend Berechtigungen für diese Operation hat. +* Der Client keinen Zugriff auf diese Ressource hat. +* Die Ressource, auf die der Client versucht hat, zuzugreifen, nicht existiert. * usw. -In diesen Fällen geben Sie normalerweise einen **HTTP-Statuscode** im Bereich **400** (400 bis 499) zurück. +In diesen Fällen würden Sie normalerweise einen **HTTP-Statuscode** im Bereich **400** (von 400 bis 499) zurückgeben. -Das ist vergleichbar mit den HTTP-Statuscodes im Bereich 200 (von 200 bis 299). Diese „200“er Statuscodes bedeuten, dass der Request in einem bestimmten Aspekt ein „Success“ („Erfolg“) war. +Dies ist vergleichbar mit den HTTP-Statuscodes im Bereich 200 (von 200 bis 299). Diese „200“-Statuscodes bedeuten, dass der Request in irgendeiner Weise erfolgreich war. -Die Statuscodes im 400er-Bereich bedeuten hingegen, dass es einen Fehler gab. +Die Statuscodes im Bereich 400 bedeuten hingegen, dass es einen Fehler seitens des Clients gab. -Erinnern Sie sich an all diese **404 Not Found** Fehler (und Witze)? +Erinnern Sie sich an all diese **„404 Not Found“** Fehler (und Witze)? -## `HTTPException` verwenden +## `HTTPException` verwenden { #use-httpexception } -Um HTTP-Responses mit Fehlern zum Client zurückzugeben, verwenden Sie `HTTPException`. +Um HTTP-Responses mit Fehlern an den Client zurückzugeben, verwenden Sie `HTTPException`. -### `HTTPException` importieren +### `HTTPException` importieren { #import-httpexception } -```Python hl_lines="1" -{!../../../docs_src/handling_errors/tutorial001.py!} -``` +{* ../../docs_src/handling_errors/tutorial001.py hl[1] *} -### Eine `HTTPException` in Ihrem Code auslösen +### Eine `HTTPException` in Ihrem Code auslösen { #raise-an-httpexception-in-your-code } -`HTTPException` ist eine normale Python-Exception mit einigen zusätzlichen Daten, die für APIs relevant sind. +`HTTPException` ist eine normale Python-Exception mit zusätzlichen Daten, die für APIs relevant sind. -Weil es eine Python-Exception ist, geben Sie sie nicht zurück, (`return`), sondern Sie lösen sie aus (`raise`). +Weil es eine Python-Exception ist, geben Sie sie nicht zurück (`return`), sondern lösen sie aus (`raise`). -Das bedeutet auch, wenn Sie in einer Hilfsfunktion sind, die Sie von ihrer *Pfadoperation-Funktion* aus aufrufen, und Sie lösen eine `HTTPException` von innerhalb dieser Hilfsfunktion aus, dann wird der Rest der *Pfadoperation-Funktion* nicht ausgeführt, sondern der Request wird sofort abgebrochen und der HTTP-Error der `HTTP-Exception` wird zum Client gesendet. +Das bedeutet auch, wenn Sie sich innerhalb einer Hilfsfunktion befinden, die Sie innerhalb Ihrer *Pfadoperation-Funktion* aufrufen, und Sie die `HTTPException` aus dieser Hilfsfunktion heraus auslösen, wird der restliche Code in der *Pfadoperation-Funktion* nicht ausgeführt. Der Request wird sofort abgebrochen und der HTTP-Error der `HTTPException` wird an den Client gesendet. -Der Vorteil, eine Exception auszulösen (`raise`), statt sie zurückzugeben (`return`) wird im Abschnitt über Abhängigkeiten und Sicherheit klarer werden. +Der Vorteil des Auslösens einer Exception gegenüber dem Zurückgeben eines Wertes wird im Abschnitt über Abhängigkeiten und Sicherheit deutlicher werden. -Im folgenden Beispiel lösen wir, wenn der Client eine ID anfragt, die nicht existiert, eine Exception mit dem Statuscode `404` aus. +In diesem Beispiel lösen wir eine Exception mit einem Statuscode von `404` aus, wenn der Client einen Artikel mit einer nicht existierenden ID anfordert: -```Python hl_lines="11" -{!../../../docs_src/handling_errors/tutorial001.py!} -``` +{* ../../docs_src/handling_errors/tutorial001.py hl[11] *} -### Die resultierende Response +### Die resultierende Response { #the-resulting-response } -Wenn der Client `http://example.com/items/foo` anfragt (ein `item_id` `"foo"`), erhält dieser Client einen HTTP-Statuscode 200 und folgende JSON-Response: +Wenn der Client `http://example.com/items/foo` anfordert (ein `item_id` `"foo"`), erhält dieser Client einen HTTP-Statuscode 200 und diese JSON-Response: ```JSON { @@ -55,7 +51,7 @@ Wenn der Client `http://example.com/items/foo` anfragt (ein `item_id` `"foo"`), } ``` -Aber wenn der Client `http://example.com/items/bar` anfragt (ein nicht-existierendes `item_id` `"bar"`), erhält er einen HTTP-Statuscode 404 (der „Not Found“-Fehler), und eine JSON-Response wie folgt: +Aber wenn der Client `http://example.com/items/bar` anfordert (ein nicht-existierendes `item_id` `"bar"`), erhält er einen HTTP-Statuscode 404 (der „Not Found“-Error) und eine JSON-Response wie: ```JSON { @@ -63,83 +59,77 @@ Aber wenn der Client `http://example.com/items/bar` anfragt (ein nicht-existiere } ``` -/// tip | "Tipp" +/// tip | Tipp -Wenn Sie eine `HTTPException` auslösen, können Sie dem Parameter `detail` jeden Wert übergeben, der nach JSON konvertiert werden kann, nicht nur `str`. +Wenn Sie eine `HTTPException` auslösen, können Sie dem Parameter `detail` jeden Wert übergeben, der in JSON konvertiert werden kann, nicht nur `str`. -Zum Beispiel ein `dict`, eine `list`, usw. +Sie könnten ein `dict`, eine `list`, usw. übergeben. -Das wird automatisch von **FastAPI** gehandhabt und der Wert nach JSON konvertiert. +Diese werden von **FastAPI** automatisch gehandhabt und in JSON konvertiert. /// -## Benutzerdefinierte Header hinzufügen +## Benutzerdefinierte Header hinzufügen { #add-custom-headers } -Es gibt Situationen, da ist es nützlich, dem HTTP-Error benutzerdefinierte Header hinzufügen zu können, etwa in einigen Sicherheitsszenarien. +Es gibt Situationen, in denen es nützlich ist, dem HTTP-Error benutzerdefinierte Header hinzuzufügen. Zum Beispiel in einigen Sicherheitsszenarien. -Sie müssen das wahrscheinlich nicht direkt in ihrem Code verwenden. +Sie werden es wahrscheinlich nicht direkt in Ihrem Code verwenden müssen. -Aber falls es in einem fortgeschrittenen Szenario notwendig ist, können Sie benutzerdefinierte Header wie folgt hinzufügen: +Aber falls Sie es für ein fortgeschrittenes Szenario benötigen, können Sie benutzerdefinierte Header hinzufügen: -```Python hl_lines="14" -{!../../../docs_src/handling_errors/tutorial002.py!} -``` +{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} -## Benutzerdefinierte Exceptionhandler definieren +## Benutzerdefinierte Exceptionhandler installieren { #install-custom-exception-handlers } -Sie können benutzerdefinierte Exceptionhandler hinzufügen, mithilfe derselben Werkzeuge für Exceptions von Starlette. +Sie können benutzerdefinierte Exceptionhandler mit den gleichen Exception-Werkzeugen von Starlette hinzufügen. -Nehmen wir an, Sie haben eine benutzerdefinierte Exception `UnicornException`, die Sie (oder eine Bibliothek, die Sie verwenden) `raise`n könnten. +Angenommen, Sie haben eine benutzerdefinierte Exception `UnicornException`, die Sie (oder eine Bibliothek, die Sie verwenden) `raise`n könnten. Und Sie möchten diese Exception global mit FastAPI handhaben. -Sie könnten einen benutzerdefinierten Exceptionhandler mittels `@app.exception_handler()` hinzufügen: +Sie könnten einen benutzerdefinierten Exceptionhandler mit `@app.exception_handler()` hinzufügen: -```Python hl_lines="5-7 13-18 24" -{!../../../docs_src/handling_errors/tutorial003.py!} -``` +{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *} -Wenn Sie nun `/unicorns/yolo` anfragen, `raise`d die *Pfadoperation* eine `UnicornException`. +Hier, wenn Sie `/unicorns/yolo` anfordern, wird die *Pfadoperation* eine `UnicornException` `raise`n. Aber diese wird von `unicorn_exception_handler` gehandhabt. -Sie erhalten also einen sauberen Error mit einem Statuscode `418` und dem JSON-Inhalt: +Sie erhalten also einen sauberen Fehler mit einem HTTP-Statuscode von `418` und dem JSON-Inhalt: ```JSON {"message": "Oops! yolo did something. There goes a rainbow..."} ``` -/// note | "Technische Details" +/// note | Technische Details -Sie können auch `from starlette.requests import Request` und `from starlette.responses import JSONResponse` verwenden. +Sie könnten auch `from starlette.requests import Request` und `from starlette.responses import JSONResponse` verwenden. -**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, als Annehmlichkeit für Sie, den Entwickler. Die meisten verfügbaren Responses kommen aber direkt von Starlette. Das Gleiche gilt für `Request`. +**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, nur als Annehmlichkeit für Sie, den Entwickler. Aber die meisten verfügbaren Responses kommen direkt von Starlette. Dasselbe gilt für `Request`. /// -## Die Default-Exceptionhandler überschreiben +## Die Default-Exceptionhandler überschreiben { #override-the-default-exception-handlers } **FastAPI** hat einige Default-Exceptionhandler. -Diese Handler kümmern sich darum, Default-JSON-Responses zurückzugeben, wenn Sie eine `HTTPException` `raise`n, und wenn der Request ungültige Daten enthält. +Diese Handler sind dafür verantwortlich, die Default-JSON-Responses zurückzugeben, wenn Sie eine `HTTPException` `raise`n und wenn der Request ungültige Daten enthält. -Sie können diese Exceptionhandler mit ihren eigenen überschreiben. +Sie können diese Exceptionhandler mit Ihren eigenen überschreiben. -### Requestvalidierung-Exceptions überschreiben +### Überschreiben von Request-Validierungs-Exceptions { #override-request-validation-exceptions } Wenn ein Request ungültige Daten enthält, löst **FastAPI** intern einen `RequestValidationError` aus. -Und bietet auch einen Default-Exceptionhandler dafür. +Und es enthält auch einen Default-Exceptionhandler für diesen. -Um diesen zu überschreiben, importieren Sie den `RequestValidationError` und verwenden Sie ihn in `@app.exception_handler(RequestValidationError)`, um Ihren Exceptionhandler zu dekorieren. +Um diesen zu überschreiben, importieren Sie den `RequestValidationError` und verwenden Sie ihn mit `@app.exception_handler(RequestValidationError)`, um den Exceptionhandler zu dekorieren. -Der Exceptionhandler wird einen `Request` und die Exception entgegennehmen. +Der Exceptionhandler erhält einen `Request` und die Exception. -```Python hl_lines="2 14-16" -{!../../../docs_src/handling_errors/tutorial004.py!} -``` +{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} -Wenn Sie nun `/items/foo` besuchen, erhalten Sie statt des Default-JSON-Errors: +Wenn Sie nun zu `/items/foo` gehen, erhalten Sie anstelle des standardmäßigen JSON-Fehlers mit: ```JSON { @@ -156,7 +146,7 @@ Wenn Sie nun `/items/foo` besuchen, erhalten Sie statt des Default-JSON-Errors: } ``` -eine Textversion: +eine Textversion mit: ``` 1 validation error @@ -164,53 +154,49 @@ path -> item_id value is not a valid integer (type=type_error.integer) ``` -#### `RequestValidationError` vs. `ValidationError` +#### `RequestValidationError` vs. `ValidationError` { #requestvalidationerror-vs-validationerror } -/// warning | "Achtung" +/// warning | Achtung -Das folgende sind technische Details, die Sie überspringen können, wenn sie für Sie nicht wichtig sind. +Dies sind technische Details, die Sie überspringen können, wenn sie für Sie jetzt nicht wichtig sind. /// -`RequestValidationError` ist eine Unterklasse von Pydantics `ValidationError`. +`RequestValidationError` ist eine Unterklasse von Pydantics `ValidationError`. -**FastAPI** verwendet diesen, sodass Sie, wenn Sie ein Pydantic-Modell für `response_model` verwenden, und ihre Daten fehlerhaft sind, einen Fehler in ihrem Log sehen. +**FastAPI** verwendet diesen so, dass, wenn Sie ein Pydantic-Modell in `response_model` verwenden und Ihre Daten einen Fehler haben, Sie den Fehler in Ihrem Log sehen. -Aber der Client/Benutzer sieht ihn nicht. Stattdessen erhält der Client einen „Internal Server Error“ mit einem HTTP-Statuscode `500`. +Aber der Client/Benutzer wird ihn nicht sehen. Stattdessen erhält der Client einen „Internal Server Error“ mit einem HTTP-Statuscode `500`. -Das ist, wie es sein sollte, denn wenn Sie einen Pydantic-`ValidationError` in Ihrer *Response* oder irgendwo sonst in ihrem Code haben (es sei denn, im *Request* des Clients), ist das tatsächlich ein Bug in ihrem Code. +Es sollte so sein, denn wenn Sie einen Pydantic `ValidationError` in Ihrer *Response* oder irgendwo anders in Ihrem Code haben (nicht im *Request* des Clients), ist es tatsächlich ein Fehler in Ihrem Code. -Und während Sie den Fehler beheben, sollten ihre Clients/Benutzer keinen Zugriff auf interne Informationen über den Fehler haben, da das eine Sicherheitslücke aufdecken könnte. +Und während Sie den Fehler beheben, sollten Ihre Clients/Benutzer keinen Zugriff auf interne Informationen über den Fehler haben, da das eine Sicherheitslücke aufdecken könnte. -### den `HTTPException`-Handler überschreiben +### Überschreiben des `HTTPException`-Fehlerhandlers { #override-the-httpexception-error-handler } -Genauso können Sie den `HTTPException`-Handler überschreiben. +Auf die gleiche Weise können Sie den `HTTPException`-Handler überschreiben. Zum Beispiel könnten Sie eine Klartext-Response statt JSON für diese Fehler zurückgeben wollen: -```Python hl_lines="3-4 9-11 22" -{!../../../docs_src/handling_errors/tutorial004.py!} -``` +{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} -/// note | "Technische Details" +/// note | Technische Details -Sie können auch `from starlette.responses import PlainTextResponse` verwenden. +Sie könnten auch `from starlette.responses import PlainTextResponse` verwenden. -**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, als Annehmlichkeit für Sie, den Entwickler. Die meisten verfügbaren Responses kommen aber direkt von Starlette. +**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, nur als Annehmlichkeit für Sie, den Entwickler. Aber die meisten verfügbaren Responses kommen direkt von Starlette. /// -### Den `RequestValidationError`-Body verwenden +### Verwenden des `RequestValidationError`-Bodys { #use-the-requestvalidationerror-body } Der `RequestValidationError` enthält den empfangenen `body` mit den ungültigen Daten. -Sie könnten diesen verwenden, während Sie Ihre Anwendung entwickeln, um den Body zu loggen und zu debuggen, ihn zum Benutzer zurückzugeben, usw. +Sie könnten diesen während der Entwicklung Ihrer Anwendung verwenden, um den Body zu loggen und zu debuggen, ihn an den Benutzer zurückzugeben usw. -```Python hl_lines="14" -{!../../../docs_src/handling_errors/tutorial005.py!} -``` +{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} -Jetzt versuchen Sie, einen ungültigen Artikel zu senden: +Versuchen Sie nun, einen ungültigen Artikel zu senden: ```JSON { @@ -219,7 +205,7 @@ Jetzt versuchen Sie, einen ungültigen Artikel zu senden: } ``` -Sie erhalten eine Response, die Ihnen sagt, dass die Daten ungültig sind, und welche den empfangenen Body enthält. +Sie erhalten eine Response, die Ihnen sagt, dass die Daten ungültig sind und die den empfangenen Body enthält: ```JSON hl_lines="12-15" { @@ -240,32 +226,30 @@ Sie erhalten eine Response, die Ihnen sagt, dass die Daten ungültig sind, und w } ``` -#### FastAPIs `HTTPException` vs. Starlettes `HTTPException` +#### FastAPIs `HTTPException` vs. Starlettes `HTTPException` { #fastapis-httpexception-vs-starlettes-httpexception } **FastAPI** hat seine eigene `HTTPException`. -Und **FastAPI**s `HTTPException`-Fehlerklasse erbt von Starlettes `HTTPException`-Fehlerklasse. +Und die `HTTPException`-Fehlerklasse von **FastAPI** erbt von der `HTTPException`-Fehlerklasse von Starlette. -Der einzige Unterschied besteht darin, dass **FastAPIs** `HTTPException` alles für das Feld `detail` akzeptiert, was nach JSON konvertiert werden kann, während Starlettes `HTTPException` nur Strings zulässt. +Der einzige Unterschied besteht darin, dass die `HTTPException` von **FastAPI** beliebige JSON-konvertierbare Daten für das `detail`-Feld akzeptiert, während die `HTTPException` von Starlette nur Strings dafür akzeptiert. -Sie können also weiterhin **FastAPI**s `HTTPException` wie üblich in Ihrem Code auslösen. +Sie können also weiterhin die `HTTPException` von **FastAPI** wie üblich in Ihrem Code auslösen. -Aber wenn Sie einen Exceptionhandler registrieren, registrieren Sie ihn für Starlettes `HTTPException`. +Aber wenn Sie einen Exceptionhandler registrieren, sollten Sie ihn für die `HTTPException` von Starlette registrieren. -Auf diese Weise wird Ihr Handler, wenn irgendein Teil von Starlettes internem Code, oder eine Starlette-Erweiterung, oder -Plugin eine Starlette-`HTTPException` auslöst, in der Lage sein, diese zu fangen und zu handhaben. +Auf diese Weise, wenn irgendein Teil des internen Codes von Starlette, oder eine Starlette-Erweiterung oder ein Plug-in, eine Starlette `HTTPException` auslöst, wird Ihr Handler in der Lage sein, diese abzufangen und zu handhaben. -Damit wir in diesem Beispiel beide `HTTPException`s im selben Code haben können, benennen wir Starlettes Exception um zu `StarletteHTTPException`: +Um in diesem Beispiel beide `HTTPException`s im selben Code zu haben, wird die Exception von Starlette zu `StarletteHTTPException` umbenannt: ```Python from starlette.exceptions import HTTPException as StarletteHTTPException ``` -### **FastAPI**s Exceptionhandler wiederverwenden +### Die Exceptionhandler von **FastAPI** wiederverwenden { #reuse-fastapis-exception-handlers } -Wenn Sie die Exception zusammen mit denselben Default-Exceptionhandlern von **FastAPI** verwenden möchten, können Sie die Default-Exceptionhandler von `fastapi.Exception_handlers` importieren und wiederverwenden: +Wenn Sie die Exception zusammen mit den gleichen Default-Exceptionhandlern von **FastAPI** verwenden möchten, können Sie die Default-Exceptionhandler aus `fastapi.exception_handlers` importieren und wiederverwenden: -```Python hl_lines="2-5 15 21" -{!../../../docs_src/handling_errors/tutorial006.py!} -``` +{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} -In diesem Beispiel `print`en Sie nur den Fehler mit einer sehr ausdrucksstarken Nachricht, aber Sie sehen, worauf wir hinauswollen. Sie können mit der Exception etwas machen und dann einfach die Default-Exceptionhandler wiederverwenden. +In diesem Beispiel geben Sie nur den Fehler mit einer sehr ausdrucksstarken Nachricht aus, aber Sie verstehen das Prinzip. Sie können die Exception verwenden und dann einfach die Default-Exceptionhandler wiederverwenden. diff --git a/docs/de/docs/tutorial/header-param-models.md b/docs/de/docs/tutorial/header-param-models.md new file mode 100644 index 0000000000..8c1bf61aec --- /dev/null +++ b/docs/de/docs/tutorial/header-param-models.md @@ -0,0 +1,72 @@ +# Header-Parameter-Modelle { #header-parameter-models } + +Wenn Sie eine Gruppe verwandter **Header-Parameter** haben, können Sie ein **Pydantic-Modell** erstellen, um diese zu deklarieren. + +Dadurch können Sie das **Modell an mehreren Stellen wiederverwenden** und auch Validierungen und Metadaten für alle Parameter gleichzeitig deklarieren. 😎 + +/// note | Hinweis + +Dies wird seit FastAPI Version `0.115.0` unterstützt. 🤓 + +/// + +## Header-Parameter mit einem Pydantic-Modell { #header-parameters-with-a-pydantic-model } + +Deklarieren Sie die erforderlichen **Header-Parameter** in einem **Pydantic-Modell** und dann den Parameter als `Header`: + +{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} + +**FastAPI** wird die Daten für **jedes Feld** aus den **Headern** des Request extrahieren und Ihnen das von Ihnen definierte Pydantic-Modell geben. + +## Die Dokumentation testen { #check-the-docs } + +Sie können die erforderlichen Header in der Dokumentationsoberfläche unter `/docs` sehen: + +
+ +
+ +## Zusätzliche Header verbieten { #forbid-extra-headers } + +In einigen speziellen Anwendungsfällen (wahrscheinlich nicht sehr häufig) möchten Sie möglicherweise die **Header einschränken**, die Sie erhalten möchten. + +Sie können Pydantics Modellkonfiguration verwenden, um `extra` Felder zu verbieten (`forbid`): + +{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} + +Wenn ein Client versucht, einige **zusätzliche Header** zu senden, erhält er eine **Error-Response**. + +Zum Beispiel, wenn der Client versucht, einen `tool`-Header mit einem Wert von `plumbus` zu senden, erhält er eine **Error-Response**, die ihm mitteilt, dass der Header-Parameter `tool` nicht erlaubt ist: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ] +} +``` + +## Automatische Umwandlung von Unterstrichen deaktivieren { #disable-convert-underscores } + +Ähnlich wie bei regulären Header-Parametern werden bei der Verwendung von Unterstrichen in den Parameternamen diese **automatisch in Bindestriche umgewandelt**. + +Wenn Sie beispielsweise einen Header-Parameter `save_data` im Code haben, wird der erwartete HTTP-Header `save-data` sein, und er wird auch so in der Dokumentation angezeigt. + +Falls Sie aus irgendeinem Grund diese automatische Umwandlung deaktivieren müssen, können Sie dies auch für Pydantic-Modelle für Header-Parameter tun. + +{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} + +/// warning | Achtung + +Bevor Sie `convert_underscores` auf `False` setzen, bedenken Sie, dass einige HTTP-Proxies und -Server die Verwendung von Headern mit Unterstrichen nicht zulassen. + +/// + +## Zusammenfassung { #summary } + +Sie können **Pydantic-Modelle** verwenden, um **Header** in **FastAPI** zu deklarieren. 😎 diff --git a/docs/de/docs/tutorial/header-params.md b/docs/de/docs/tutorial/header-params.md index c8c3a4c577..5c0bb3f87b 100644 --- a/docs/de/docs/tutorial/header-params.md +++ b/docs/de/docs/tutorial/header-params.md @@ -1,293 +1,79 @@ -# Header-Parameter +# Header-Parameter { #header-parameters } -So wie `Query`-, `Path`-, und `Cookie`-Parameter können Sie auch Header-Parameter definieren. +Sie können Header-Parameter genauso definieren, wie Sie `Query`-, `Path`- und `Cookie`-Parameter definieren. -## `Header` importieren +## `Header` importieren { #import-header } Importieren Sie zuerst `Header`: -//// tab | Python 3.10+ +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *} -```Python hl_lines="3" -{!> ../../../docs_src/header_params/tutorial001_an_py310.py!} -``` +## `Header`-Parameter deklarieren { #declare-header-parameters } -//// +Deklarieren Sie dann die Header-Parameter mit derselben Struktur wie bei `Path`, `Query` und `Cookie`. -//// tab | Python 3.9+ +Sie können den Defaultwert sowie alle zusätzlichen Validierungs- oder Annotationsparameter definieren: -```Python hl_lines="3" -{!> ../../../docs_src/header_params/tutorial001_an_py39.py!} -``` +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *} -//// +/// note | Technische Details -//// tab | Python 3.8+ +`Header` ist eine „Schwester“-Klasse von `Path`, `Query` und `Cookie`. Sie erbt ebenfalls von der gemeinsamen `Param`-Klasse. -```Python hl_lines="3" -{!> ../../../docs_src/header_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Aber denken Sie daran, dass bei der Nutzung von `Query`, `Path`, `Header` und anderen Importen aus `fastapi`, diese tatsächlich Funktionen sind, die spezielle Klassen zurückgeben. /// -```Python hl_lines="1" -{!> ../../../docs_src/header_params/tutorial001_py310.py!} -``` +/// info | Info -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Um Header zu deklarieren, müssen Sie `Header` verwenden, da die Parameter sonst als Query-Parameter interpretiert werden würden. /// -```Python hl_lines="3" -{!> ../../../docs_src/header_params/tutorial001.py!} -``` +## Automatische Konvertierung { #automatic-conversion } -//// +`Header` bietet etwas zusätzliche Funktionalität im Vergleich zu `Path`, `Query` und `Cookie`. -## `Header`-Parameter deklarieren +Die meisten Standard-Header sind durch ein „Bindestrich“-Zeichen getrennt, auch bekannt als „Minus-Symbol“ (`-`). -Dann deklarieren Sie Ihre Header-Parameter, auf die gleiche Weise, wie Sie auch `Path`-, `Query`-, und `Cookie`-Parameter deklarieren. +Aber eine Variable wie `user-agent` ist in Python ungültig. -Der erste Wert ist der Typ. Sie können `Header` die gehabten Extra Validierungs- und Beschreibungsparameter hinzufügen. Danach können Sie einen Defaultwert vergeben: +Daher wird `Header` standardmäßig die Zeichen des Parameter-Namens von Unterstrich (`_`) zu Bindestrich (`-`) konvertieren, um die Header zu extrahieren und zu dokumentieren. -//// tab | Python 3.10+ +Außerdem ist Groß-/Klein­schrei­bung in HTTP-Headern nicht relevant, daher können Sie sie im Standard-Python-Stil (auch bekannt als „snake_case“) deklarieren. -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial001_an_py310.py!} -``` +Sie können also `user_agent` verwenden, wie Sie es normalerweise im Python-Code tun würden, anstatt die Anfangsbuchstaben wie bei `User_Agent` großzuschreiben oder Ähnliches. -//// +Wenn Sie aus irgendeinem Grund die automatische Konvertierung von Unterstrichen zu Bindestrichen deaktivieren müssen, setzen Sie den Parameter `convert_underscores` von `Header` auf `False`: -//// tab | Python 3.9+ +{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *} -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/header_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/header_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial001.py!} -``` - -//// - -/// note | "Technische Details" - -`Header` ist eine Schwesterklasse von `Path`, `Query` und `Cookie`. Sie erbt von derselben gemeinsamen `Param`-Elternklasse. - -Aber erinnern Sie sich, dass, wenn Sie `Query`, `Path`, `Header` und andere von `fastapi` importieren, diese tatsächlich Funktionen sind, welche spezielle Klassen zurückgeben. - -/// - -/// info - -Um Header zu deklarieren, müssen Sie `Header` verwenden, da diese Parameter sonst als Query-Parameter interpretiert werden würden. - -/// - -## Automatische Konvertierung - -`Header` hat weitere Funktionalität, zusätzlich zu der, die `Path`, `Query` und `Cookie` bereitstellen. - -Die meisten Standard-Header benutzen als Trennzeichen einen Bindestrich, auch bekannt als das „Minus-Symbol“ (`-`). - -Aber eine Variable wie `user-agent` ist in Python nicht gültig. - -Darum wird `Header` standardmäßig in Parameternamen den Unterstrich (`_`) zu einem Bindestrich (`-`) konvertieren. - -HTTP-Header sind außerdem unabhängig von Groß-/Kleinschreibung, darum können Sie sie mittels der Standard-Python-Schreibweise deklarieren (auch bekannt als "snake_case"). - -Sie können also `user_agent` schreiben, wie Sie es normalerweise in Python-Code machen würden, statt etwa die ersten Buchstaben groß zu schreiben, wie in `User_Agent`. - -Wenn Sie aus irgendeinem Grund das automatische Konvertieren von Unterstrichen zu Bindestrichen abschalten möchten, setzen Sie den Parameter `convert_underscores` auf `False`. - -//// tab | Python 3.10+ - -```Python hl_lines="10" -{!> ../../../docs_src/header_params/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11" -{!> ../../../docs_src/header_params/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12" -{!> ../../../docs_src/header_params/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8" -{!> ../../../docs_src/header_params/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/header_params/tutorial002.py!} -``` - -//// - -/// warning | "Achtung" +/// warning | Achtung Bevor Sie `convert_underscores` auf `False` setzen, bedenken Sie, dass manche HTTP-Proxys und Server die Verwendung von Headern mit Unterstrichen nicht erlauben. /// -## Doppelte Header +## Doppelte Header { #duplicate-headers } -Es ist möglich, doppelte Header zu empfangen. Also den gleichen Header mit unterschiedlichen Werten. +Es ist möglich, doppelte Header zu empfangen. Damit ist gemeint, denselben Header mit mehreren Werten. -Sie können solche Fälle deklarieren, indem Sie in der Typdeklaration eine Liste verwenden. +Sie können solche Fälle definieren, indem Sie in der Typdeklaration eine Liste verwenden. -Sie erhalten dann alle Werte von diesem doppelten Header als Python-`list`e. +Sie erhalten dann alle Werte von diesem doppelten Header als Python-`list`. -Um zum Beispiel einen Header `X-Token` zu deklarieren, der mehrmals vorkommen kann, schreiben Sie: +Um beispielsweise einen `X-Token`-Header zu deklarieren, der mehrmals vorkommen kann, können Sie schreiben: -//// tab | Python 3.10+ +{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/header_params/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/header_params/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial003.py!} -``` - -//// - -Wenn Sie mit einer *Pfadoperation* kommunizieren, die zwei HTTP-Header sendet, wie: +Wenn Sie mit dieser *Pfadoperation* kommunizieren und zwei HTTP-Header senden, wie: ``` X-Token: foo X-Token: bar ``` -Dann wäre die Response: +Dann wäre die Response: ```JSON { @@ -298,8 +84,8 @@ Dann wäre die Response: } ``` -## Zusammenfassung +## Zusammenfassung { #recap } -Deklarieren Sie Header mittels `Header`, auf die gleiche Weise wie bei `Query`, `Path` und `Cookie`. +Deklarieren Sie Header mit `Header`, wobei Sie dasselbe gängige Muster wie bei `Query`, `Path` und `Cookie` verwenden. -Machen Sie sich keine Sorgen um Unterstriche in ihren Variablen, **FastAPI** wird sich darum kümmern, diese zu konvertieren. +Und machen Sie sich keine Sorgen um Unterstriche in Ihren Variablen, **FastAPI** wird sich darum kümmern, sie zu konvertieren. diff --git a/docs/de/docs/tutorial/index.md b/docs/de/docs/tutorial/index.md index c15d0b0bd3..70a6b6a082 100644 --- a/docs/de/docs/tutorial/index.md +++ b/docs/de/docs/tutorial/index.md @@ -1,83 +1,95 @@ -# Tutorial – Benutzerhandbuch +# Tutorial – Benutzerhandbuch { #tutorial-user-guide } -Dieses Tutorial zeigt Ihnen Schritt für Schritt, wie Sie **FastAPI** und die meisten seiner Funktionen verwenden können. +Dieses Tutorial zeigt Ihnen Schritt für Schritt, wie Sie **FastAPI** mit den meisten seiner Funktionen verwenden können. -Jeder Abschnitt baut schrittweise auf den vorhergehenden auf. Diese Abschnitte sind aber nach einzelnen Themen gegliedert, sodass Sie direkt zu einem bestimmten Thema übergehen können, um Ihre speziellen API-Anforderungen zu lösen. +Jeder Abschnitt baut schrittweise auf den vorhergehenden auf, ist jedoch in einzelne Themen gegliedert, sodass Sie direkt zu einem bestimmten Thema übergehen können, um Ihre spezifischen API-Anforderungen zu lösen. -Außerdem dienen diese als zukünftige Referenz. +Es ist auch so gestaltet, dass es als zukünftige Referenz dient, sodass Sie jederzeit zurückkommen und genau das sehen, was Sie benötigen. -Dadurch können Sie jederzeit zurückkommen und sehen genau das, was Sie benötigen. +## Den Code ausführen { #run-the-code } -## Den Code ausführen +Alle Codeblöcke können kopiert und direkt verwendet werden (es sind tatsächlich getestete Python-Dateien). -Alle Codeblöcke können kopiert und direkt verwendet werden (da es sich um getestete Python-Dateien handelt). - -Um eines der Beispiele auszuführen, kopieren Sie den Code in eine Datei `main.py`, und starten Sie `uvicorn` mit: +Um eines der Beispiele auszuführen, kopieren Sie den Code in eine Datei `main.py` und starten Sie `fastapi dev` mit:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py -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 Starting development server 🚀 + + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with + the following code: + + from main import app + + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C + to quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. ```
-Es wird **ausdrücklich empfohlen**, dass Sie den Code schreiben oder kopieren, ihn bearbeiten und lokal ausführen. +Es wird **dringend empfohlen**, den Code zu schreiben oder zu kopieren, ihn zu bearbeiten und lokal auszuführen. Die Verwendung in Ihrem eigenen Editor zeigt Ihnen die Vorteile von FastAPI am besten, wenn Sie sehen, wie wenig Code Sie schreiben müssen, all die Typprüfungen, die automatische Vervollständigung usw. --- -## FastAPI installieren +## FastAPI installieren { #install-fastapi } -Der erste Schritt besteht aus der Installation von FastAPI. +Der erste Schritt besteht darin, FastAPI zu installieren. -Für dieses Tutorial empfiehlt es sich, FastAPI mit allen optionalen Abhängigkeiten und Funktionen zu installieren: +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und dann **FastAPI installieren**:
```console -$ pip install "fastapi[all]" +$ pip install "fastapi[standard]" ---> 100% ```
-... das beinhaltet auch `uvicorn`, welchen Sie als Server verwenden können, der ihren Code ausführt. +/// note | Hinweis -/// note | "Hinweis" +Wenn Sie mit `pip install "fastapi[standard]"` installieren, werden einige optionale Standard-Abhängigkeiten mit installiert, einschließlich `fastapi-cloud-cli`, welches Ihnen das Deployment in der FastAPI Cloud ermöglicht. -Sie können die einzelnen Teile auch separat installieren. +Wenn Sie diese optionalen Abhängigkeiten nicht haben möchten, können Sie stattdessen `pip install fastapi` installieren. -Das folgende würden Sie wahrscheinlich tun, wenn Sie Ihre Anwendung in der Produktion einsetzen: - -``` -pip install fastapi -``` - -Installieren Sie auch `uvicorn` als Server: - -``` -pip install "uvicorn[standard]" -``` - -Das gleiche gilt für jede der optionalen Abhängigkeiten, die Sie verwenden möchten. +Wenn Sie die Standard-Abhängigkeiten, aber ohne das `fastapi-cloud-cli` installieren möchten, können Sie mit `pip install "fastapi[standard-no-fastapi-cloud-cli]"` installieren. /// -## Handbuch für fortgeschrittene Benutzer +## Handbuch für fortgeschrittene Benutzer { #advanced-user-guide } -Es gibt auch ein **Handbuch für fortgeschrittene Benutzer**, welches Sie später nach diesem **Tutorial – Benutzerhandbuch** lesen können. +Es gibt auch ein **Handbuch für fortgeschrittene Benutzer**, das Sie nach diesem **Tutorial – Benutzerhandbuch** lesen können. -Das **Handbuch für fortgeschrittene Benutzer** baut auf diesem Tutorial auf, verwendet dieselben Konzepte und bringt Ihnen einige zusätzliche Funktionen bei. +Das **Handbuch für fortgeschrittene Benutzer** baut hierauf auf, verwendet dieselben Konzepte und bringt Ihnen einige zusätzliche Funktionen bei. -Allerdings sollten Sie zuerst das **Tutorial – Benutzerhandbuch** lesen (was Sie hier gerade tun). +Sie sollten jedoch zuerst das **Tutorial – Benutzerhandbuch** lesen (was Sie gerade tun). -Die Dokumentation ist so konzipiert, dass Sie mit dem **Tutorial – Benutzerhandbuch** eine vollständige Anwendung erstellen können und diese dann je nach Bedarf mit einigen der zusätzlichen Ideen aus dem **Handbuch für fortgeschrittene Benutzer** vervollständigen können. +Es ist so konzipiert, dass Sie mit dem **Tutorial – Benutzerhandbuch** eine vollständige Anwendung erstellen können und diese dann je nach Bedarf mit einigen der zusätzlichen Ideen aus dem **Handbuch für fortgeschrittene Benutzer** erweitern können. diff --git a/docs/de/docs/tutorial/metadata.md b/docs/de/docs/tutorial/metadata.md index 3ab56ff3ef..44d02e6d89 100644 --- a/docs/de/docs/tutorial/metadata.md +++ b/docs/de/docs/tutorial/metadata.md @@ -1,10 +1,10 @@ -# Metadaten und URLs der Dokumentationen +# Metadaten und Dokumentations-URLs { #metadata-and-docs-urls } -Sie können mehrere Metadaten-Einstellungen für Ihre **FastAPI**-Anwendung konfigurieren. +Sie können mehrere Metadaten-Konfigurationen in Ihrer **FastAPI**-Anwendung anpassen. -## Metadaten für die API +## Metadaten für die API { #metadata-for-api } -Sie können die folgenden Felder festlegen, welche in der OpenAPI-Spezifikation und den Benutzeroberflächen der automatischen API-Dokumentation verwendet werden: +Sie können die folgenden Felder festlegen, die in der OpenAPI-Spezifikation und in den Benutzeroberflächen der automatischen API-Dokumentation verwendet werden: | Parameter | Typ | Beschreibung | |------------|------|-------------| @@ -13,18 +13,16 @@ Sie können die folgenden Felder festlegen, welche in der OpenAPI-Spezifikation | `description` | `str` | Eine kurze Beschreibung der API. Kann Markdown verwenden. | | `version` | `string` | Die Version der API. Das ist die Version Ihrer eigenen Anwendung, nicht die von OpenAPI. Zum Beispiel `2.5.0`. | | `terms_of_service` | `str` | Eine URL zu den Nutzungsbedingungen für die API. Falls angegeben, muss es sich um eine URL handeln. | -| `contact` | `dict` | Die Kontaktinformationen für die verfügbar gemachte API. Kann mehrere Felder enthalten.
contact-Felder
ParameterTypBeschreibung
namestrDer identifizierende Name der Kontaktperson/Organisation.
urlstrDie URL, die auf die Kontaktinformationen verweist. MUSS im Format einer URL vorliegen.
emailstrDie E-Mail-Adresse der Kontaktperson/Organisation. MUSS im Format einer E-Mail-Adresse vorliegen.
| -| `license_info` | `dict` | Die Lizenzinformationen für die verfügbar gemachte API. Kann mehrere Felder enthalten.
license_info-Felder
ParameterTypBeschreibung
namestrERFORDERLICH (wenn eine license_info festgelegt ist). Der für die API verwendete Lizenzname.
identifierstrEin SPDX-Lizenzausdruck für die API. Das Feld identifier und das Feld url schließen sich gegenseitig aus. Verfügbar seit OpenAPI 3.1.0, FastAPI 0.99.0.
urlstrEine URL zur Lizenz, die für die API verwendet wird. MUSS im Format einer URL vorliegen.
| +| `contact` | `dict` | Die Kontaktinformationen für die freigegebene API. Kann mehrere Felder enthalten.
contact-Felder
ParameterTypBeschreibung
namestrDer identifizierende Name der Kontaktperson/Organisation.
urlstrDie URL, die auf die Kontaktinformationen verweist. MUSS im Format einer URL vorliegen.
emailstrDie E-Mail-Adresse der Kontaktperson/Organisation. MUSS im Format einer E-Mail-Adresse vorliegen.
| +| `license_info` | `dict` | Die Lizenzinformationen für die freigegebene API. Kann mehrere Felder enthalten.
license_info-Felder
ParameterTypBeschreibung
namestrERFORDERLICH (wenn eine license_info festgelegt ist). Der für die API verwendete Lizenzname.
identifierstrEin SPDX-Lizenzausdruck für die API. Das Feld identifier und das Feld url schließen sich gegenseitig aus. Verfügbar seit OpenAPI 3.1.0, FastAPI 0.99.0.
urlstrEine URL zur Lizenz, die für die API verwendet wird. MUSS im Format einer URL vorliegen.
| Sie können diese wie folgt setzen: -```Python hl_lines="3-16 19-32" -{!../../../docs_src/metadata/tutorial001.py!} -``` +{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *} -/// tip | "Tipp" +/// tip | Tipp -Sie können Markdown in das Feld `description` schreiben und es wird in der Ausgabe gerendert. +Sie können Markdown im Feld `description` verwenden, und es wird in der Ausgabe gerendert. /// @@ -32,75 +30,69 @@ Mit dieser Konfiguration würde die automatische API-Dokumentation wie folgt aus -## Lizenz-ID +## Lizenzkennung { #license-identifier } Seit OpenAPI 3.1.0 und FastAPI 0.99.0 können Sie die `license_info` auch mit einem `identifier` anstelle einer `url` festlegen. Zum Beispiel: -```Python hl_lines="31" -{!../../../docs_src/metadata/tutorial001_1.py!} -``` +{* ../../docs_src/metadata/tutorial001_1.py hl[31] *} -## Metadaten für Tags +## Metadaten für Tags { #metadata-for-tags } -Sie können mit dem Parameter `openapi_tags` auch zusätzliche Metadaten für die verschiedenen Tags hinzufügen, die zum Gruppieren Ihrer Pfadoperationen verwendet werden. +Sie können auch zusätzliche Metadaten für die verschiedenen Tags hinzufügen, die zum Gruppieren Ihrer Pfadoperationen verwendet werden, mit dem Parameter `openapi_tags`. -Es wird eine Liste benötigt, die für jedes Tag ein Dict enthält. +Er nimmt eine Liste entgegen, die für jeden Tag ein Dictionary enthält. -Jedes Dict kann Folgendes enthalten: +Jedes Dictionary kann Folgendes enthalten: * `name` (**erforderlich**): ein `str` mit demselben Tag-Namen, den Sie im Parameter `tags` in Ihren *Pfadoperationen* und `APIRouter`n verwenden. * `description`: ein `str` mit einer kurzen Beschreibung für das Tag. Sie kann Markdown enthalten und wird in der Benutzeroberfläche der Dokumentation angezeigt. * `externalDocs`: ein `dict`, das externe Dokumentation beschreibt mit: - * `description`: ein `str` mit einer kurzen Beschreibung für die externe Dokumentation. - * `url` (**erforderlich**): ein `str` mit der URL für die externe Dokumentation. + * `description`: ein `str` mit einer kurzen Beschreibung für die externe Dokumentation. + * `url` (**erforderlich**): ein `str` mit der URL für die externe Dokumentation. -### Metadaten für Tags erstellen +### Metadaten für Tags erstellen { #create-metadata-for-tags } -Versuchen wir das an einem Beispiel mit Tags für `users` und `items`. +Versuchen wir es mit einem Beispiel mit Tags für `users` und `items`. -Erstellen Sie Metadaten für Ihre Tags und übergeben Sie sie an den Parameter `openapi_tags`: +Erstellen Sie Metadaten für Ihre Tags und übergeben Sie diese an den Parameter `openapi_tags`: -```Python hl_lines="3-16 18" -{!../../../docs_src/metadata/tutorial004.py!} -``` +{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} -Beachten Sie, dass Sie Markdown in den Beschreibungen verwenden können. Beispielsweise wird „login“ in Fettschrift (**login**) und „fancy“ in Kursivschrift (_fancy_) angezeigt. +Beachten Sie, dass Sie Markdown innerhalb der Beschreibungen verwenden können. Zum Beispiel wird „login“ in Fettschrift (**login**) und „fancy“ in Kursivschrift (_fancy_) angezeigt. -/// tip | "Tipp" +/// tip | Tipp Sie müssen nicht für alle von Ihnen verwendeten Tags Metadaten hinzufügen. /// -### Ihre Tags verwenden +### Ihre Tags verwenden { #use-your-tags } Verwenden Sie den Parameter `tags` mit Ihren *Pfadoperationen* (und `APIRouter`n), um diese verschiedenen Tags zuzuweisen: -```Python hl_lines="21 26" -{!../../../docs_src/metadata/tutorial004.py!} -``` +{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} -/// info +/// info | Info Lesen Sie mehr zu Tags unter [Pfadoperation-Konfiguration](path-operation-configuration.md#tags){.internal-link target=_blank}. /// -### Die Dokumentation anschauen +### Die Dokumentation testen { #check-the-docs } Wenn Sie nun die Dokumentation ansehen, werden dort alle zusätzlichen Metadaten angezeigt: -### Reihenfolge der Tags +### Reihenfolge der Tags { #order-of-tags } -Die Reihenfolge der Tag-Metadaten-Dicts definiert auch die Reihenfolge, in der diese in der Benutzeroberfläche der Dokumentation angezeigt werden. +Die Reihenfolge der Tag-Metadaten-Dictionarys definiert auch die Reihenfolge, in der diese in der Benutzeroberfläche der Dokumentation angezeigt werden. -Auch wenn beispielsweise `users` im Alphabet nach `items` kommt, wird es vor diesen angezeigt, da wir seine Metadaten als erstes Dict der Liste hinzugefügt haben. +Auch wenn beispielsweise `users` im Alphabet nach `items` kommt, wird es vor diesen angezeigt, da wir deren Metadaten als erstes Dictionary der Liste hinzugefügt haben. -## OpenAPI-URL +## OpenAPI-URL { #openapi-url } Standardmäßig wird das OpenAPI-Schema unter `/openapi.json` bereitgestellt. @@ -108,25 +100,21 @@ Sie können das aber mit dem Parameter `openapi_url` konfigurieren. Um beispielsweise festzulegen, dass es unter `/api/v1/openapi.json` bereitgestellt wird: -```Python hl_lines="3" -{!../../../docs_src/metadata/tutorial002.py!} -``` +{* ../../docs_src/metadata/tutorial002.py hl[3] *} Wenn Sie das OpenAPI-Schema vollständig deaktivieren möchten, können Sie `openapi_url=None` festlegen, wodurch auch die Dokumentationsbenutzeroberflächen deaktiviert werden, die es verwenden. -## URLs der Dokumentationen +## Dokumentations-URLs { #docs-urls } Sie können die beiden enthaltenen Dokumentationsbenutzeroberflächen konfigurieren: * **Swagger UI**: bereitgestellt unter `/docs`. - * Sie können deren URL mit dem Parameter `docs_url` festlegen. - * Sie können sie deaktivieren, indem Sie `docs_url=None` festlegen. + * Sie können deren URL mit dem Parameter `docs_url` festlegen. + * Sie können sie deaktivieren, indem Sie `docs_url=None` festlegen. * **ReDoc**: bereitgestellt unter `/redoc`. - * Sie können deren URL mit dem Parameter `redoc_url` festlegen. - * Sie können sie deaktivieren, indem Sie `redoc_url=None` festlegen. + * Sie können deren URL mit dem Parameter `redoc_url` festlegen. + * Sie können sie deaktivieren, indem Sie `redoc_url=None` festlegen. Um beispielsweise Swagger UI so einzustellen, dass sie unter `/documentation` bereitgestellt wird, und ReDoc zu deaktivieren: -```Python hl_lines="3" -{!../../../docs_src/metadata/tutorial003.py!} -``` +{* ../../docs_src/metadata/tutorial003.py hl[3] *} diff --git a/docs/de/docs/tutorial/middleware.md b/docs/de/docs/tutorial/middleware.md index 62a0d1613f..6410deba1a 100644 --- a/docs/de/docs/tutorial/middleware.md +++ b/docs/de/docs/tutorial/middleware.md @@ -1,8 +1,8 @@ -# Middleware +# Middleware { #middleware } Sie können Middleware zu **FastAPI**-Anwendungen hinzufügen. -Eine „Middleware“ ist eine Funktion, die mit jedem **Request** arbeitet, bevor er von einer bestimmten *Pfadoperation* verarbeitet wird. Und auch mit jeder **Response**, bevor sie zurückgegeben wird. +Eine „Middleware“ ist eine Funktion, die mit jedem **Request** arbeitet, bevor er von einer bestimmten *Pfadoperation* verarbeitet wird. Und auch mit jeder **Response**, bevor sie zurückgegeben wird. * Sie nimmt jeden **Request** entgegen, der an Ihre Anwendung gesendet wird. * Sie kann dann etwas mit diesem **Request** tun oder beliebigen Code ausführen. @@ -11,15 +11,15 @@ Eine „Middleware“ ist eine Funktion, die mit jedem **Request** arbeitet, bev * Sie kann etwas mit dieser **Response** tun oder beliebigen Code ausführen. * Dann gibt sie die **Response** zurück. -/// note | "Technische Details" +/// note | Technische Details Wenn Sie Abhängigkeiten mit `yield` haben, wird der Exit-Code *nach* der Middleware ausgeführt. -Wenn es Hintergrundaufgaben gab (später dokumentiert), werden sie *nach* allen Middlewares ausgeführt. +Wenn es Hintergrundtasks gab (dies wird später im [Hintergrundtasks](background-tasks.md){.internal-link target=_blank}-Abschnitt behandelt), werden sie *nach* allen Middlewares ausgeführt. /// -## Erstellung einer Middleware +## Eine Middleware erstellen { #create-a-middleware } Um eine Middleware zu erstellen, verwenden Sie den Dekorator `@app.middleware("http")` über einer Funktion. @@ -31,19 +31,17 @@ Die Middleware-Funktion erhält: * Dann gibt es die von der entsprechenden *Pfadoperation* generierte `response` zurück. * Sie können die `response` dann weiter modifizieren, bevor Sie sie zurückgeben. -```Python hl_lines="8-9 11 14" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} -/// tip | "Tipp" +/// tip | Tipp -Beachten Sie, dass benutzerdefinierte proprietäre Header hinzugefügt werden können. Verwenden Sie dafür das Präfix 'X-'. +Beachten Sie, dass benutzerdefinierte proprietäre Header hinzugefügt werden können unter Verwendung des `X-`-Präfixes. -Wenn Sie jedoch benutzerdefinierte Header haben, die ein Client in einem Browser sehen soll, müssen Sie sie zu Ihrer CORS-Konfigurationen ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) hinzufügen, indem Sie den Parameter `expose_headers` verwenden, der in der Starlette-CORS-Dokumentation dokumentiert ist. +Wenn Sie jedoch benutzerdefinierte Header haben, die ein Client in einem Browser sehen soll, müssen Sie sie zu Ihrer CORS-Konfiguration ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) hinzufügen, indem Sie den Parameter `expose_headers` verwenden, der in der Starlettes CORS-Dokumentation dokumentiert ist. /// -/// note | "Technische Details" +/// note | Technische Details Sie könnten auch `from starlette.requests import Request` verwenden. @@ -51,7 +49,7 @@ Sie könnten auch `from starlette.requests import Request` verwenden. /// -### Vor und nach der `response` +### Vor und nach der `response` { #before-and-after-the-response } Sie können Code hinzufügen, der mit dem `request` ausgeführt wird, bevor dieser von einer beliebigen *Pfadoperation* empfangen wird. @@ -59,12 +57,39 @@ Und auch nachdem die `response` generiert wurde, bevor sie zurückgegeben wird. Sie könnten beispielsweise einen benutzerdefinierten Header `X-Process-Time` hinzufügen, der die Zeit in Sekunden enthält, die benötigt wurde, um den Request zu verarbeiten und eine Response zu generieren: -```Python hl_lines="10 12-13" -{!../../../docs_src/middleware/tutorial001.py!} +{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} + +/// tip | Tipp + +Hier verwenden wir `time.perf_counter()` anstelle von `time.time()`, da es für diese Anwendungsfälle präziser sein kann. 🤓 + +/// + +## Ausführungsreihenfolge bei mehreren Middlewares { #multiple-middleware-execution-order } + +Wenn Sie mehrere Middlewares hinzufügen, entweder mit dem `@app.middleware()` Dekorator oder der Methode `app.add_middleware()`, umschließt jede neue Middleware die Anwendung und bildet einen Stapel. Die zuletzt hinzugefügte Middleware ist die *äußerste*, und die erste ist die *innerste*. + +Auf dem Requestpfad läuft die *äußerste* Middleware zuerst. + +Auf dem Responsepfad läuft sie zuletzt. + +Zum Beispiel: + +```Python +app.add_middleware(MiddlewareA) +app.add_middleware(MiddlewareB) ``` -## Andere Middlewares +Dies führt zu folgender Ausführungsreihenfolge: -Sie können später mehr über andere Middlewares in [Handbuch für fortgeschrittene Benutzer: Fortgeschrittene Middleware](../advanced/middleware.md){.internal-link target=_blank} lesen. +* **Request**: MiddlewareB → MiddlewareA → Route -In der nächsten Sektion erfahren Sie, wie Sie CORS mit einer Middleware behandeln können. +* **Response**: Route → MiddlewareA → MiddlewareB + +Dieses Stapelverhalten stellt sicher, dass Middlewares in einer vorhersehbaren und kontrollierbaren Reihenfolge ausgeführt werden. + +## Andere Middlewares { #other-middlewares } + +Sie können später mehr über andere Middlewares im [Handbuch für fortgeschrittene Benutzer: Fortgeschrittene Middleware](../advanced/middleware.md){.internal-link target=_blank} lesen. + +In der nächsten Sektion erfahren Sie, wie Sie CORS mit einer Middleware behandeln können. diff --git a/docs/de/docs/tutorial/path-operation-configuration.md b/docs/de/docs/tutorial/path-operation-configuration.md index 03980b7dd0..c483f4e405 100644 --- a/docs/de/docs/tutorial/path-operation-configuration.md +++ b/docs/de/docs/tutorial/path-operation-configuration.md @@ -1,48 +1,26 @@ -# Pfadoperation-Konfiguration +# Pfadoperation-Konfiguration { #path-operation-configuration } -Es gibt mehrere Konfigurations-Parameter, die Sie Ihrem *Pfadoperation-Dekorator* übergeben können. +Es gibt mehrere Parameter, die Sie Ihrem *Pfadoperation-Dekorator* übergeben können, um ihn zu konfigurieren. -/// warning | "Achtung" +/// warning | Achtung Beachten Sie, dass diese Parameter direkt dem *Pfadoperation-Dekorator* übergeben werden, nicht der *Pfadoperation-Funktion*. /// -## Response-Statuscode +## Response-Statuscode { #response-status-code } -Sie können den (HTTP-)`status_code` definieren, den die Response Ihrer *Pfadoperation* verwenden soll. +Sie können den (HTTP-)`status_code` definieren, der in der Response Ihrer *Pfadoperation* verwendet werden soll. Sie können direkt den `int`-Code übergeben, etwa `404`. -Aber falls Sie sich nicht mehr erinnern, wofür jede Nummer steht, können Sie die Abkürzungs-Konstanten in `status` verwenden: +Aber falls Sie sich nicht mehr erinnern, wofür jeder Nummerncode steht, können Sie die Abkürzungs-Konstanten in `status` verwenden: -//// tab | Python 3.10+ - -```Python hl_lines="1 15" -{!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3 17" -{!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3 17" -{!> ../../../docs_src/path_operation_configuration/tutorial001.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *} Dieser Statuscode wird in der Response verwendet und zum OpenAPI-Schema hinzugefügt. -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette import status` verwenden. @@ -50,147 +28,57 @@ Sie können auch `from starlette import status` verwenden. /// -## Tags +## Tags { #tags } -Sie können Ihrer *Pfadoperation* Tags hinzufügen, mittels des Parameters `tags`, dem eine `list`e von `str`s übergeben wird (in der Regel nur ein `str`): +Sie können Ihrer *Pfadoperation* Tags hinzufügen, indem Sie dem Parameter `tags` eine `list`e von `str`s übergeben (in der Regel nur ein `str`): -//// tab | Python 3.10+ - -```Python hl_lines="15 20 25" -{!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="17 22 27" -{!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17 22 27" -{!> ../../../docs_src/path_operation_configuration/tutorial002.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *} Diese werden zum OpenAPI-Schema hinzugefügt und von den automatischen Dokumentations-Benutzeroberflächen verwendet: -### Tags mittels Enumeration +### Tags mittels Enumeration { #tags-with-enums } -Wenn Sie eine große Anwendung haben, können sich am Ende **viele Tags** anhäufen, und Sie möchten sicherstellen, dass Sie für verwandte *Pfadoperationen* immer den **gleichen Tag** nehmen. +Wenn Sie eine große Anwendung haben, können sich am Ende **viele Tags** anhäufen, und Sie möchten sicherstellen, dass Sie für verwandte *Pfadoperationen* immer den **gleichen Tag** verwenden. In diesem Fall macht es Sinn, die Tags in einem `Enum` zu speichern. -**FastAPI** unterstützt diese genauso wie einfache Strings: +**FastAPI** unterstützt das auf die gleiche Weise wie einfache Strings: -```Python hl_lines="1 8-10 13 18" -{!../../../docs_src/path_operation_configuration/tutorial002b.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *} -## Zusammenfassung und Beschreibung +## Zusammenfassung und Beschreibung { #summary-and-description } -Sie können eine Zusammenfassung (`summary`) und eine Beschreibung (`description`) hinzufügen: +Sie können eine `summary` und eine `description` hinzufügen: -//// tab | Python 3.10+ +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} -```Python hl_lines="18-19" -{!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="20-21" -{!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20-21" -{!> ../../../docs_src/path_operation_configuration/tutorial003.py!} -``` - -//// - -## Beschreibung mittels Docstring +## Beschreibung mittels Docstring { #description-from-docstring } Da Beschreibungen oft mehrere Zeilen lang sind, können Sie die Beschreibung der *Pfadoperation* im Docstring der Funktion deklarieren, und **FastAPI** wird sie daraus auslesen. -Sie können im Docstring Markdown schreiben, es wird korrekt interpretiert und angezeigt (die Einrückung des Docstring beachtend). +Sie können Markdown im Docstring schreiben, es wird korrekt interpretiert und angezeigt (unter Berücksichtigung der Einrückung des Docstring). -//// tab | Python 3.10+ +{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} -```Python hl_lines="17-25" -{!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19-27" -{!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="19-27" -{!> ../../../docs_src/path_operation_configuration/tutorial004.py!} -``` - -//// - -In der interaktiven Dokumentation sieht das dann so aus: +Es wird in der interaktiven Dokumentation verwendet: -## Beschreibung der Response +## Beschreibung der Response { #response-description } -Die Response können Sie mit dem Parameter `response_description` beschreiben: +Sie können die Response mit dem Parameter `response_description` beschreiben: -//// tab | Python 3.10+ +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} -```Python hl_lines="19" -{!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} -``` +/// info | Info -//// - -//// tab | Python 3.9+ - -```Python hl_lines="21" -{!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/path_operation_configuration/tutorial005.py!} -``` - -//// - -/// info - -beachten Sie, dass sich `response_description` speziell auf die Response bezieht, während `description` sich generell auf die *Pfadoperation* bezieht. +Beachten Sie, dass sich `response_description` speziell auf die Response bezieht, während `description` sich generell auf die *Pfadoperation* bezieht. /// -/// check +/// check | Testen OpenAPI verlangt, dass jede *Pfadoperation* über eine Beschreibung der Response verfügt. @@ -200,15 +88,13 @@ Daher, wenn Sie keine vergeben, wird **FastAPI** automatisch eine für „Erfolg -## Eine *Pfadoperation* deprecaten +## Eine *Pfadoperation* deprecaten { #deprecate-a-path-operation } -Wenn Sie eine *Pfadoperation* als deprecated kennzeichnen möchten, ohne sie zu entfernen, fügen Sie den Parameter `deprecated` hinzu: +Wenn Sie eine *Pfadoperation* als deprecatet kennzeichnen möchten, ohne sie zu entfernen, fügen Sie den Parameter `deprecated` hinzu: -```Python hl_lines="16" -{!../../../docs_src/path_operation_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} -Sie wird in der interaktiven Dokumentation gut sichtbar als deprecated markiert werden: +Sie wird in der interaktiven Dokumentation gut sichtbar als deprecatet markiert werden: @@ -216,6 +102,6 @@ Vergleichen Sie, wie deprecatete und nicht-deprecatete *Pfadoperationen* aussehe -## Zusammenfassung +## Zusammenfassung { #recap } Sie können auf einfache Weise Metadaten für Ihre *Pfadoperationen* definieren, indem Sie den *Pfadoperation-Dekoratoren* Parameter hinzufügen. diff --git a/docs/de/docs/tutorial/path-params-numeric-validations.md b/docs/de/docs/tutorial/path-params-numeric-validations.md index 3908a0b2d5..5b74749447 100644 --- a/docs/de/docs/tutorial/path-params-numeric-validations.md +++ b/docs/de/docs/tutorial/path-params-numeric-validations.md @@ -1,383 +1,154 @@ -# Pfad-Parameter und Validierung von Zahlen +# Pfad-Parameter und Validierung von Zahlen { #path-parameters-and-numeric-validations } -So wie Sie mit `Query` für Query-Parameter zusätzliche Validierungen und Metadaten hinzufügen können, können Sie das mittels `Path` auch für Pfad-Parameter tun. +So wie Sie mit `Query` für Query-Parameter zusätzliche Validierungen und Metadaten deklarieren können, können Sie mit `Path` die gleichen Validierungen und Metadaten für Pfad-Parameter deklarieren. -## `Path` importieren +## `Path` importieren { #import-path } -Importieren Sie zuerst `Path` von `fastapi`, und importieren Sie `Annotated`. +Importieren Sie zuerst `Path` von `fastapi`, und importieren Sie `Annotated`: -//// tab | Python 3.10+ +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *} -```Python hl_lines="1 3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} -``` +/// info | Info -//// +FastAPI hat in Version 0.95.0 Unterstützung für `Annotated` hinzugefügt und es zur Verwendung empfohlen. -//// tab | Python 3.9+ +Wenn Sie eine ältere Version haben, würden Fehler angezeigt werden, wenn Sie versuchen, `Annotated` zu verwenden. -```Python hl_lines="1 3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3-4" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Stellen Sie sicher, dass Sie [FastAPI aktualisieren](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}, auf mindestens Version 0.95.1, bevor Sie `Annotated` verwenden. /// -```Python hl_lines="1" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` +## Metadaten deklarieren { #declare-metadata } -//// +Sie können dieselben Parameter wie für `Query` deklarieren. -//// tab | Python 3.8+ nicht annotiert +Um zum Beispiel einen `title`-Metadaten-Wert für den Pfad-Parameter `item_id` zu deklarieren, können Sie schreiben: -/// tip | "Tipp" +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} -Bevorzugen Sie die `Annotated`-Version, falls möglich. +/// note | Hinweis + +Ein Pfad-Parameter ist immer erforderlich, da er Teil des Pfads sein muss. Selbst wenn Sie ihn mit `None` deklarieren oder einen Defaultwert setzen, würde das nichts ändern, er wäre dennoch immer erforderlich. /// -```Python hl_lines="3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` +## Die Parameter sortieren, wie Sie möchten { #order-the-parameters-as-you-need } -//// +/// tip | Tipp -/// info - -FastAPI unterstützt (und empfiehlt die Verwendung von) `Annotated` seit Version 0.95.0. - -Wenn Sie eine ältere Version haben, werden Sie Fehler angezeigt bekommen, wenn Sie versuchen, `Annotated` zu verwenden. - -Bitte [aktualisieren Sie FastAPI](../deployment/versions.md#upgrade-der-fastapi-versionen){.internal-link target=_blank} daher mindestens zu Version 0.95.1, bevor Sie `Annotated` verwenden. +Das ist wahrscheinlich nicht so wichtig oder notwendig, wenn Sie `Annotated` verwenden. /// -## Metadaten deklarieren +Angenommen, Sie möchten den Query-Parameter `q` als erforderlichen `str` deklarieren. -Sie können die gleichen Parameter deklarieren wie für `Query`. +Und Sie müssen sonst nichts anderes für diesen Parameter deklarieren, Sie brauchen also `Query` nicht wirklich. -Um zum Beispiel einen `title`-Metadaten-Wert für den Pfad-Parameter `item_id` zu deklarieren, schreiben Sie: +Aber Sie müssen dennoch `Path` für den `item_id`-Pfad-Parameter verwenden. Und aus irgendeinem Grund möchten Sie `Annotated` nicht verwenden. -//// tab | Python 3.10+ +Python wird sich beschweren, wenn Sie einen Wert mit einem „Default“ vor einem Wert ohne „Default“ setzen. -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} -``` +Aber Sie können die Reihenfolge ändern und den Wert ohne Default (den Query-Parameter `q`) zuerst setzen. -//// - -//// tab | Python 3.9+ - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` - -//// - -/// note | "Hinweis" - -Ein Pfad-Parameter ist immer erforderlich, weil er Teil des Pfads sein muss. - -Sie sollten ihn daher mit `...` deklarieren, um ihn als erforderlich auszuzeichnen. - -Doch selbst wenn Sie ihn mit `None` deklarieren, oder einen Defaultwert setzen, bewirkt das nichts, er bleibt immer erforderlich. - -/// - -## Sortieren Sie die Parameter, wie Sie möchten - -/// tip | "Tipp" - -Wenn Sie `Annotated` verwenden, ist das folgende nicht so wichtig / nicht notwendig. - -/// - -Nehmen wir an, Sie möchten den Query-Parameter `q` als erforderlichen `str` deklarieren. - -Und Sie müssen sonst nichts anderes für den Parameter deklarieren, Sie brauchen also nicht wirklich `Query`. - -Aber Sie brauchen `Path` für den `item_id`-Pfad-Parameter. Und Sie möchten aus irgendeinem Grund nicht `Annotated` verwenden. - -Python wird sich beschweren, wenn Sie einen Parameter mit Defaultwert vor einen Parameter ohne Defaultwert setzen. - -Aber Sie können die Reihenfolge der Parameter ändern, den Query-Parameter ohne Defaultwert zuerst. - -Für **FastAPI** ist es nicht wichtig. Es erkennt die Parameter anhand ihres Namens, ihrer Typen, und ihrer Defaultwerte (`Query`, `Path`, usw.). Es kümmert sich nicht um die Reihenfolge. +Für **FastAPI** spielt es keine Rolle. Es erkennt die Parameter anhand ihrer Namen, Typen und Default-Deklarationen (`Query`, `Path`, usw.), es kümmert sich nicht um die Reihenfolge. Sie können Ihre Funktion also so deklarieren: -//// tab | Python 3.8 nicht annotiert +{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} -/// tip | "Tipp" +Aber bedenken Sie, dass Sie dieses Problem nicht haben, wenn Sie `Annotated` verwenden, da es nicht darauf ankommt, dass Sie keine Funktionsparameter-Defaultwerte für `Query()` oder `Path()` verwenden. -Bevorzugen Sie die `Annotated`-Version, falls möglich. +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} + +## Die Parameter sortieren, wie Sie möchten: Tricks { #order-the-parameters-as-you-need-tricks } + +/// tip | Tipp + +Das ist wahrscheinlich nicht so wichtig oder notwendig, wenn Sie `Annotated` verwenden. /// -```Python hl_lines="7" -{!> ../../../docs_src/path_params_numeric_validations/tutorial002.py!} -``` +Hier ist ein **kleiner Trick**, der nützlich sein kann, obwohl Sie ihn nicht oft benötigen werden. -//// +Wenn Sie: -Aber bedenken Sie, dass Sie dieses Problem nicht haben, wenn Sie `Annotated` verwenden, da Sie nicht die Funktions-Parameter-Defaultwerte für `Query()` oder `Path()` verwenden. +* den `q`-Query-Parameter sowohl ohne `Query` als auch ohne Defaultwert deklarieren +* den Pfad-Parameter `item_id` mit `Path` deklarieren +* sie in einer anderen Reihenfolge haben +* nicht `Annotated` verwenden -//// tab | Python 3.9+ +... möchten, dann hat Python eine kleine Spezial-Syntax dafür. -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py!} -``` +Übergeben Sie `*`, als den ersten Parameter der Funktion. -//// +Python wird nichts mit diesem `*` machen, aber es wird wissen, dass alle folgenden Parameter als Schlüsselwortargumente (Schlüssel-Wert-Paare) verwendet werden sollen, auch bekannt als kwargs. Selbst wenn diese keinen Defaultwert haben. -//// tab | Python 3.8+ +{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} -```Python hl_lines="9" -{!> ../../../docs_src/path_params_numeric_validations/tutorial002_an.py!} -``` +### Besser mit `Annotated` { #better-with-annotated } -//// +Bedenken Sie, dass Sie, wenn Sie `Annotated` verwenden, da Sie keine Funktionsparameter-Defaultwerte verwenden, dieses Problem nicht haben werden und wahrscheinlich nicht `*` verwenden müssen. -## Sortieren Sie die Parameter wie Sie möchten: Tricks +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} -/// tip | "Tipp" +## Validierung von Zahlen: Größer oder gleich { #number-validations-greater-than-or-equal } -Wenn Sie `Annotated` verwenden, ist das folgende nicht so wichtig / nicht notwendig. +Mit `Query` und `Path` (und anderen, die Sie später sehen werden) können Sie Zahlenbeschränkungen deklarieren. + +Hier, mit `ge=1`, muss `item_id` eine ganze Zahl sein, die „`g`reater than or `e`qual to“ (größer oder gleich) `1` ist. + +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} + +## Validierung von Zahlen: Größer und kleiner oder gleich { #number-validations-greater-than-and-less-than-or-equal } + +Das Gleiche gilt für: + +* `gt`: `g`reater `t`han (größer als) +* `le`: `l`ess than or `e`qual (kleiner oder gleich) + +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} + +## Validierung von Zahlen: Floats, größer und kleiner { #number-validations-floats-greater-than-and-less-than } + +Zahlenvalidierung funktioniert auch für `float`-Werte. + +Hier wird es wichtig, in der Lage zu sein, gt und nicht nur ge zu deklarieren. Da Sie mit dieser Option erzwingen können, dass ein Wert größer als `0` sein muss, selbst wenn er kleiner als `1` ist. + +Also wäre `0.5` ein gültiger Wert. Aber `0.0` oder `0` nicht. + +Und das Gleiche gilt für lt. + +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} + +## Zusammenfassung { #recap } + +Mit `Query`, `Path` (und anderen, die Sie noch nicht gesehen haben) können Sie Metadaten und Stringvalidierungen auf die gleichen Weisen deklarieren wie in [Query-Parameter und Stringvalidierungen](query-params-str-validations.md){.internal-link target=_blank} beschrieben. + +Und Sie können auch Zahlenvalidierungen deklarieren: + +* `gt`: `g`reater `t`han (größer als) +* `ge`: `g`reater than or `e`qual (größer oder gleich) +* `lt`: `l`ess `t`han (kleiner als) +* `le`: `l`ess than or `e`qual (kleiner oder gleich) + +/// info | Info + +`Query`, `Path`, und andere Klassen, die Sie später sehen werden, sind Unterklassen einer gemeinsamen `Param`-Klasse. + +Alle von ihnen teilen die gleichen Parameter für zusätzliche Validierung und Metadaten, die Sie gesehen haben. /// -Hier ein **kleiner Trick**, der nützlich sein kann, aber Sie werden ihn nicht oft brauchen. +/// note | Technische Details -Wenn Sie eines der folgenden Dinge tun möchten: +Wenn Sie `Query`, `Path` und andere von `fastapi` importieren, sind sie tatsächlich Funktionen. -* den `q`-Parameter ohne `Query` oder irgendeinem Defaultwert deklarieren -* den Pfad-Parameter `item_id` mittels `Path` deklarieren -* die Parameter in einer unterschiedlichen Reihenfolge haben -* `Annotated` nicht verwenden +Die, wenn sie aufgerufen werden, Instanzen von Klassen mit demselben Namen zurückgeben. -... dann hat Python eine kleine Spezial-Syntax für Sie. - -Übergeben Sie der Funktion `*` als ersten Parameter. - -Python macht nichts mit diesem `*`, aber es wird wissen, dass alle folgenden Parameter als Keyword-Argumente (Schlüssel-Wert-Paare), auch bekannt als kwargs, verwendet werden. Selbst wenn diese keinen Defaultwert haben. - -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} -``` - -### Besser mit `Annotated` - -Bedenken Sie, dass Sie, wenn Sie `Annotated` verwenden, dieses Problem nicht haben, weil Sie keine Defaultwerte für Ihre Funktionsparameter haben. Sie müssen daher wahrscheinlich auch nicht `*` verwenden. - -//// tab | Python 3.9+ - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/path_params_numeric_validations/tutorial003_an.py!} -``` - -//// - -## Validierung von Zahlen: Größer oder gleich - -Mit `Query` und `Path` (und anderen, die Sie später kennenlernen), können Sie Zahlenbeschränkungen deklarieren. - -Hier, mit `ge=1`, wird festgelegt, dass `item_id` eine Ganzzahl benötigt, die größer oder gleich `1` ist (`g`reater than or `e`qual). -//// tab | Python 3.9+ - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/path_params_numeric_validations/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8" -{!> ../../../docs_src/path_params_numeric_validations/tutorial004.py!} -``` - -//// - -## Validierung von Zahlen: Größer und kleiner oder gleich - -Das Gleiche trifft zu auf: - -* `gt`: `g`reater `t`han – größer als -* `le`: `l`ess than or `e`qual – kleiner oder gleich - -//// tab | Python 3.9+ - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/path_params_numeric_validations/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/path_params_numeric_validations/tutorial005.py!} -``` - -//// - -## Validierung von Zahlen: Floats, größer und kleiner - -Zahlenvalidierung funktioniert auch für `float`-Werte. - -Hier wird es wichtig, in der Lage zu sein, gt zu deklarieren, und nicht nur ge, da Sie hiermit bestimmen können, dass ein Wert, zum Beispiel, größer als `0` sein muss, obwohl er kleiner als `1` ist. - -`0.5` wäre also ein gültiger Wert, aber nicht `0.0` oder `0`. - -Das gleiche gilt für lt. - -//// tab | Python 3.9+ - -```Python hl_lines="13" -{!> ../../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12" -{!> ../../../docs_src/path_params_numeric_validations/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11" -{!> ../../../docs_src/path_params_numeric_validations/tutorial006.py!} -``` - -//// - -## Zusammenfassung - -Mit `Query` und `Path` (und anderen, die Sie noch nicht gesehen haben) können Sie Metadaten und Stringvalidierungen deklarieren, so wie in [Query-Parameter und Stringvalidierungen](query-params-str-validations.md){.internal-link target=_blank} beschrieben. - -Und Sie können auch Validierungen für Zahlen deklarieren: - -* `gt`: `g`reater `t`han – größer als -* `ge`: `g`reater than or `e`qual – größer oder gleich -* `lt`: `l`ess `t`han – kleiner als -* `le`: `l`ess than or `e`qual – kleiner oder gleich - -/// info - -`Query`, `Path`, und andere Klassen, die Sie später kennenlernen, sind Unterklassen einer allgemeinen `Param`-Klasse. - -Sie alle teilen die gleichen Parameter für zusätzliche Validierung und Metadaten, die Sie gesehen haben. - -/// - -/// note | "Technische Details" - -`Query`, `Path` und andere, die Sie von `fastapi` importieren, sind tatsächlich Funktionen. - -Die, wenn sie aufgerufen werden, Instanzen der Klassen mit demselben Namen zurückgeben. - -Sie importieren also `Query`, welches eine Funktion ist. Aber wenn Sie es aufrufen, gibt es eine Instanz der Klasse zurück, die auch `Query` genannt wird. +Sie importieren also `Query`, was eine Funktion ist. Und wenn Sie sie aufrufen, gibt sie eine Instanz einer Klasse zurück, die auch `Query` genannt wird. Diese Funktionen existieren (statt die Klassen direkt zu verwenden), damit Ihr Editor keine Fehlermeldungen über ihre Typen ausgibt. -Auf diese Weise können Sie Ihren Editor und Ihre Programmier-Tools verwenden, ohne besondere Einstellungen vornehmen zu müssen, um diese Fehlermeldungen stummzuschalten. +Auf diese Weise können Sie Ihren normalen Editor und Ihre Programmier-Tools verwenden, ohne besondere Einstellungen vornehmen zu müssen, um diese Fehlermeldungen stummzuschalten. /// diff --git a/docs/de/docs/tutorial/path-params.md b/docs/de/docs/tutorial/path-params.md index 2c1b691a80..1db288fb87 100644 --- a/docs/de/docs/tutorial/path-params.md +++ b/docs/de/docs/tutorial/path-params.md @@ -1,36 +1,32 @@ -# Pfad-Parameter +# Pfad-Parameter { #path-parameters } -Sie können Pfad-„Parameter“ oder -„Variablen“ mit der gleichen Syntax deklarieren, welche in Python-Format-Strings verwendet wird: +Sie können Pfad-„Parameter“ oder -„Variablen“ mit der gleichen Syntax deklarieren, welche in Python-Formatstrings verwendet wird: -```Python hl_lines="6-7" -{!../../../docs_src/path_params/tutorial001.py!} -``` +{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} Der Wert des Pfad-Parameters `item_id` wird Ihrer Funktion als das Argument `item_id` übergeben. -Wenn Sie dieses Beispiel ausführen und auf http://127.0.0.1:8000/items/foo gehen, sehen Sie als Response: +Wenn Sie dieses Beispiel ausführen und auf http://127.0.0.1:8000/items/foo gehen, sehen Sie als Response: ```JSON {"item_id":"foo"} ``` -## Pfad-Parameter mit Typen +## Pfad-Parameter mit Typen { #path-parameters-with-types } Sie können den Typ eines Pfad-Parameters in der Argumentliste der Funktion deklarieren, mit Standard-Python-Typannotationen: -```Python hl_lines="7" -{!../../../docs_src/path_params/tutorial002.py!} -``` +{* ../../docs_src/path_params/tutorial002.py hl[7] *} In diesem Fall wird `item_id` als `int` deklariert, also als Ganzzahl. -/// check +/// check | Testen Dadurch erhalten Sie Editor-Unterstützung innerhalb Ihrer Funktion, mit Fehlerprüfungen, Codevervollständigung, usw. /// -## Daten-Konversion +## Daten-Konversion { #data-conversion } Wenn Sie dieses Beispiel ausführen und Ihren Browser unter http://127.0.0.1:8000/items/3 öffnen, sehen Sie als Response: @@ -38,15 +34,15 @@ Wenn Sie dieses Beispiel ausführen und Ihren Browser unter „parsen“. +Sprich, mit dieser Typdeklaration wird **FastAPI** den Request automatisch „parsen“. /// -## Datenvalidierung +## Datenvalidierung { #data-validation } Wenn Sie aber im Browser http://127.0.0.1:8000/items/foo besuchen, erhalten Sie eine hübsche HTTP-Fehlermeldung: @@ -60,8 +56,7 @@ Wenn Sie aber im Browser http://127.0.0.1:8000/items/4.2 -/// check +/// check | Testen Sprich, mit der gleichen Python-Typdeklaration gibt Ihnen **FastAPI** Datenvalidierung. Beachten Sie, dass die Fehlermeldung auch direkt die Stelle anzeigt, wo die Validierung nicht erfolgreich war. -Das ist unglaublich hilfreich, wenn Sie Code entwickeln und debuggen, welcher mit ihrer API interagiert. +Das ist unglaublich hilfreich, wenn Sie Code entwickeln und debuggen, welcher mit Ihrer API interagiert. /// -## Dokumentation +## Dokumentation { #documentation } Wenn Sie die Seite http://127.0.0.1:8000/docs in Ihrem Browser öffnen, sehen Sie eine automatische, interaktive API-Dokumentation: -/// check +/// check | Testen Wiederum, mit dieser gleichen Python-Typdeklaration gibt Ihnen **FastAPI** eine automatische, interaktive Dokumentation (verwendet die Swagger-Benutzeroberfläche). @@ -95,7 +90,7 @@ Beachten Sie, dass der Pfad-Parameter dort als Ganzzahl deklariert ist. /// -## Nützliche Standards. Alternative Dokumentation +## Nützliche Standards, alternative Dokumentation { #standards-based-benefits-alternative-documentation } Und weil das generierte Schema vom OpenAPI-Standard kommt, gibt es viele kompatible Tools. @@ -105,15 +100,15 @@ Zum Beispiel bietet **FastAPI** selbst eine alternative API-Dokumentation (verwe Und viele weitere kompatible Tools. Inklusive Codegenerierung für viele Sprachen. -## Pydantic +## Pydantic { #pydantic } -Die ganze Datenvalidierung wird hinter den Kulissen von Pydantic durchgeführt, Sie profitieren also von dessen Vorteilen. Und Sie wissen, dass Sie in guten Händen sind. +Die ganze Datenvalidierung wird hinter den Kulissen von Pydantic durchgeführt, Sie profitieren also von dessen Vorteilen. Und Sie wissen, dass Sie in guten Händen sind. -Sie können für Typ Deklarationen auch `str`, `float`, `bool` und viele andere komplexe Datentypen verwenden. +Sie können für Typdeklarationen auch `str`, `float`, `bool` und viele andere komplexe Datentypen verwenden. Mehrere davon werden wir in den nächsten Kapiteln erkunden. -## Die Reihenfolge ist wichtig +## Die Reihenfolge ist wichtig { #order-matters } Wenn Sie *Pfadoperationen* erstellen, haben Sie manchmal einen fixen Pfad. @@ -123,97 +118,83 @@ Und Sie haben auch einen Pfad `/users/{user_id}`, um Daten über einen spezifisc Weil *Pfadoperationen* in ihrer Reihenfolge ausgewertet werden, müssen Sie sicherstellen, dass der Pfad `/users/me` vor `/users/{user_id}` deklariert wurde: -```Python hl_lines="6 11" -{!../../../docs_src/path_params/tutorial003.py!} -``` +{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} Ansonsten würde der Pfad für `/users/{user_id}` auch `/users/me` auswerten, und annehmen, dass ein Parameter `user_id` mit dem Wert `"me"` übergeben wurde. Sie können eine Pfadoperation auch nicht erneut definieren: -```Python hl_lines="6 11" -{!../../../docs_src/path_params/tutorial003b.py!} -``` +{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *} Die erste Definition wird immer verwendet werden, da ihr Pfad zuerst übereinstimmt. -## Vordefinierte Parameterwerte +## Vordefinierte Parameterwerte { #predefined-values } -Wenn Sie eine *Pfadoperation* haben, welche einen *Pfad-Parameter* hat, aber Sie wollen, dass dessen gültige Werte vordefiniert sind, können Sie ein Standard-Python `Enum` verwenden. +Wenn Sie eine *Pfadoperation* haben, welche einen *Pfad-Parameter* hat, aber Sie wollen, dass dessen gültige Werte vordefiniert sind, können Sie ein Standard-Python `Enum` verwenden. -### Erstellen Sie eine `Enum`-Klasse +### Eine `Enum`-Klasse erstellen { #create-an-enum-class } Importieren Sie `Enum` und erstellen Sie eine Unterklasse, die von `str` und `Enum` erbt. -Indem Sie von `str` erben, weiß die API Dokumentation, dass die Werte des Enums vom Typ `str` sein müssen, und wird in der Lage sein, korrekt zu rendern. +Indem Sie von `str` erben, weiß die API-Dokumentation, dass die Werte vom Typ `str` sein müssen, und wird in der Lage sein, korrekt zu rendern. Erstellen Sie dann Klassen-Attribute mit festgelegten Werten, welches die erlaubten Werte sein werden: -```Python hl_lines="1 6-9" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} -/// info +/// info | Info -Enumerationen (oder kurz Enums) gibt es in Python seit Version 3.4. +Enumerationen (oder Enums) gibt es in Python seit Version 3.4. /// -/// tip | "Tipp" +/// tip | Tipp Falls Sie sich fragen, was „AlexNet“, „ResNet“ und „LeNet“ ist, das sind Namen von Modellen für maschinelles Lernen. /// -### Deklarieren Sie einen *Pfad-Parameter* +### Einen *Pfad-Parameter* deklarieren { #declare-a-path-parameter } Dann erstellen Sie einen *Pfad-Parameter*, der als Typ die gerade erstellte Enum-Klasse hat (`ModelName`): -```Python hl_lines="16" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[16] *} -### Testen Sie es in der API-Dokumentation +### Die API-Dokumentation testen { #check-the-docs } Weil die erlaubten Werte für den *Pfad-Parameter* nun vordefiniert sind, kann die interaktive Dokumentation sie als Auswahl-Drop-Down anzeigen: -### Mit Python-*Enums* arbeiten +### Mit Python-*Enumerationen* arbeiten { #working-with-python-enumerations } -Der *Pfad-Parameter* wird ein *Member eines Enums* sein. +Der *Pfad-Parameter* wird ein *Member einer Enumeration* sein. -#### *Enum-Member* vergleichen +#### *Enumeration-Member* vergleichen { #compare-enumeration-members } -Sie können ihn mit einem Member Ihres Enums `ModelName` vergleichen: +Sie können ihn mit einem Member Ihrer Enumeration `ModelName` vergleichen: -```Python hl_lines="17" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[17] *} -#### *Enum-Wert* erhalten +#### *Enumerations-Wert* erhalten { #get-the-enumeration-value } -Den tatsächlichen Wert (in diesem Fall ein `str`) erhalten Sie via `model_name.value`, oder generell, `ihr_enum_member.value`: +Den tatsächlichen Wert (in diesem Fall ein `str`) erhalten Sie via `model_name.value`, oder generell, `your_enum_member.value`: -```Python hl_lines="20" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[20] *} -/// tip | "Tipp" +/// tip | Tipp Sie können den Wert `"lenet"` außerdem mittels `ModelName.lenet.value` abrufen. /// -#### *Enum-Member* zurückgeben +#### *Enumeration-Member* zurückgeben { #return-enumeration-members } Sie können *Enum-Member* in ihrer *Pfadoperation* zurückgeben, sogar verschachtelt in einem JSON-Body (z. B. als `dict`). Diese werden zu ihren entsprechenden Werten konvertiert (in diesem Fall Strings), bevor sie zum Client übertragen werden: -```Python hl_lines="18 21 23" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} In Ihrem Client erhalten Sie eine JSON-Response, wie etwa: @@ -224,7 +205,7 @@ In Ihrem Client erhalten Sie eine JSON-Response, wie etwa: } ``` -## Pfad Parameter die Pfade enthalten +## Pfad-Parameter, die Pfade enthalten { #path-parameters-containing-paths } Angenommen, Sie haben eine *Pfadoperation* mit einem Pfad `/files/{file_path}`. @@ -232,7 +213,7 @@ Aber `file_path` soll selbst einen *Pfad* enthalten, etwa `home/johndoe/myfile.t Sprich, die URL für diese Datei wäre etwas wie: `/files/home/johndoe/myfile.txt`. -### OpenAPI Unterstützung +### OpenAPI-Unterstützung { #openapi-support } OpenAPI bietet nicht die Möglichkeit, dass ein *Pfad-Parameter* seinerseits einen *Pfad* enthalten kann, das würde zu Szenarios führen, die schwierig zu testen und zu definieren sind. @@ -240,7 +221,7 @@ Trotzdem können Sie das in **FastAPI** tun, indem Sie eines der internen Tools Die Dokumentation würde weiterhin funktionieren, allerdings wird nicht dokumentiert werden, dass der Parameter ein Pfad sein sollte. -### Pfad Konverter +### Pfad-Konverter { #path-convertor } Mittels einer Option direkt von Starlette können Sie einen *Pfad-Parameter* deklarieren, der einen Pfad enthalten soll, indem Sie eine URL wie folgt definieren: @@ -252,11 +233,9 @@ In diesem Fall ist der Name des Parameters `file_path`. Der letzte Teil, `:path` Sie verwenden das also wie folgt: -```Python hl_lines="6" -{!../../../docs_src/path_params/tutorial004.py!} -``` +{* ../../docs_src/path_params/tutorial004.py hl[6] *} -/// tip | "Tipp" +/// tip | Tipp Der Parameter könnte einen führenden Schrägstrich (`/`) haben, wie etwa in `/home/johndoe/myfile.txt`. @@ -264,12 +243,12 @@ In dem Fall wäre die URL: `/files//home/johndoe/myfile.txt`, mit einem doppelte /// -## Zusammenfassung +## Zusammenfassung { #recap } In **FastAPI** erhalten Sie mittels kurzer, intuitiver Typdeklarationen: * Editor-Unterstützung: Fehlerprüfungen, Codevervollständigung, usw. -* Daten "parsen" +* Daten "parsen" * Datenvalidierung * API-Annotationen und automatische Dokumentation diff --git a/docs/de/docs/tutorial/query-param-models.md b/docs/de/docs/tutorial/query-param-models.md new file mode 100644 index 0000000000..7d3f2d32e8 --- /dev/null +++ b/docs/de/docs/tutorial/query-param-models.md @@ -0,0 +1,68 @@ +# Query-Parameter-Modelle { #query-parameter-models } + +Wenn Sie eine Gruppe von **Query-Parametern** haben, die miteinander in Beziehung stehen, können Sie ein **Pydantic-Modell** erstellen, um diese zu deklarieren. + +Dadurch können Sie das **Modell an mehreren Stellen wiederverwenden** und gleichzeitig Validierungen und Metadaten für alle Parameter auf einmal deklarieren. 😎 + +/// note | Hinweis + +Dies wird seit FastAPI Version `0.115.0` unterstützt. 🤓 + +/// + +## Query-Parameter mit einem Pydantic-Modell { #query-parameters-with-a-pydantic-model } + +Deklarieren Sie die benötigten **Query-Parameter** in einem **Pydantic-Modell** und dann den Parameter als `Query`: + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +**FastAPI** wird die Daten für **jedes Feld** aus den **Query-Parametern** des Request extrahieren und Ihnen das definierte Pydantic-Modell bereitstellen. + +## Die Dokumentation testen { #check-the-docs } + +Sie können die Query-Parameter in der Dokumentations-Oberfläche unter `/docs` einsehen: + +
+ +
+ +## Zusätzliche Query-Parameter verbieten { #forbid-extra-query-parameters } + +In einigen speziellen Anwendungsfällen (wahrscheinlich nicht sehr häufig) möchten Sie möglicherweise die Query-Parameter, die Sie empfangen möchten, **beschränken**. + +Sie können die Modellkonfiguration von Pydantic verwenden, um jegliche `extra` Felder zu `verbieten`: + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +Wenn ein Client versucht, einige **zusätzliche** Daten in den **Query-Parametern** zu senden, erhält er eine **Error-Response**. + +Wenn der Client beispielsweise versucht, einen `tool` Query-Parameter mit dem Wert `plumbus` zu senden, wie: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +erhält er eine **Error-Response**, die ihm mitteilt, dass der Query-Parameter `tool` nicht erlaubt ist: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## Zusammenfassung { #summary } + +Sie können **Pydantic-Modelle** verwenden, um **Query-Parameter** in **FastAPI** zu deklarieren. 😎 + +/// tip | Tipp + +Spoiler-Alarm: Sie können auch Pydantic-Modelle verwenden, um Cookies und Header zu deklarieren, aber darüber werden Sie später im Tutorial lesen. 🤫 + +/// diff --git a/docs/de/docs/tutorial/query-params-str-validations.md b/docs/de/docs/tutorial/query-params-str-validations.md index ab30fc6cf1..744160baff 100644 --- a/docs/de/docs/tutorial/query-params-str-validations.md +++ b/docs/de/docs/tutorial/query-params-str-validations.md @@ -1,83 +1,49 @@ -# Query-Parameter und Stringvalidierung +# Query-Parameter und String-Validierungen { #query-parameters-and-string-validations } -**FastAPI** erlaubt es Ihnen, Ihre Parameter zusätzlich zu validieren, und zusätzliche Informationen hinzuzufügen. +**FastAPI** ermöglicht es Ihnen, zusätzliche Informationen und Validierungen für Ihre Parameter zu deklarieren. -Nehmen wir als Beispiel die folgende Anwendung: +Nehmen wir diese Anwendung als Beispiel: -//// tab | Python 3.10+ +{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!} -``` +Der Query-Parameter `q` hat den Typ `str | None`, das bedeutet, dass er vom Typ `str` sein kann, aber auch `None`, und tatsächlich ist der Defaultwert `None`, sodass FastAPI weiß, dass er nicht erforderlich ist. -//// +/// note | Hinweis -//// tab | Python 3.8+ +FastAPI erkennt, dass der Wert von `q` nicht erforderlich ist, aufgrund des Defaultwertes `= None`. -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial001.py!} -``` - -//// - -Der Query-Parameter `q` hat den Typ `Union[str, None]` (oder `str | None` in Python 3.10), was bedeutet, er ist entweder ein `str` oder `None`. Der Defaultwert ist `None`, also weiß FastAPI, der Parameter ist nicht erforderlich. - -/// note | "Hinweis" - -FastAPI weiß nur dank des definierten Defaultwertes `=None`, dass der Wert von `q` nicht erforderlich ist - -`Union[str, None]` hingegen erlaubt ihren Editor, Sie besser zu unterstützen und Fehler zu erkennen. +Die Verwendung von `str | None` ermöglicht es Ihrem Editor, Ihnen bessere Unterstützung zu bieten und Fehler zu erkennen. /// -## Zusätzliche Validierung +## Zusätzliche Validierung { #additional-validation } -Wir werden bewirken, dass, obwohl `q` optional ist, wenn es gegeben ist, **seine Länge 50 Zeichen nicht überschreitet**. +Wir werden sicherstellen, dass, obwohl `q` optional ist, wann immer es bereitgestellt wird, **seine Länge 50 Zeichen nicht überschreitet**. -### `Query` und `Annotated` importieren +### `Query` und `Annotated` importieren { #import-query-and-annotated } -Importieren Sie zuerst: +Um dies zu erreichen, importieren Sie zuerst: * `Query` von `fastapi` -* `Annotated` von `typing` (oder von `typing_extensions` in Python unter 3.9) +* `Annotated` von `typing` -//// tab | Python 3.10+ +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} -In Python 3.9 oder darüber, ist `Annotated` Teil der Standardbibliothek, also können Sie es von `typing` importieren. +/// info | Info -```Python hl_lines="1 3" -{!> ../../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} -``` +FastAPI hat Unterstützung für `Annotated` hinzugefügt (und begonnen, es zu empfehlen) in der Version 0.95.0. -//// +Wenn Sie eine ältere Version haben, würden Sie Fehler erhalten, beim Versuch, `Annotated` zu verwenden. -//// tab | Python 3.8+ - -In Versionen unter Python 3.9 importieren Sie `Annotated` von `typing_extensions`. - -Es wird bereits mit FastAPI installiert sein. - -```Python hl_lines="3-4" -{!> ../../../docs_src/query_params_str_validations/tutorial002_an.py!} -``` - -//// - -/// info - -FastAPI unterstützt (und empfiehlt die Verwendung von) `Annotated` seit Version 0.95.0. - -Wenn Sie eine ältere Version haben, werden Sie Fehler angezeigt bekommen, wenn Sie versuchen, `Annotated` zu verwenden. - -Bitte [aktualisieren Sie FastAPI](../deployment/versions.md#upgrade-der-fastapi-versionen){.internal-link target=_blank} daher mindestens zu Version 0.95.1, bevor Sie `Annotated` verwenden. +Stellen Sie sicher, dass Sie [die FastAPI-Version aktualisieren](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}, auf mindestens Version 0.95.1, bevor Sie `Annotated` verwenden. /// -## `Annotated` im Typ des `q`-Parameters verwenden +## Verwenden von `Annotated` im Typ für den `q`-Parameter { #use-annotated-in-the-type-for-the-q-parameter } -Erinnern Sie sich, wie ich in [Einführung in Python-Typen](../python-types.md#typhinweise-mit-metadaten-annotationen){.internal-link target=_blank} sagte, dass Sie mittels `Annotated` Metadaten zu Ihren Parametern hinzufügen können? +Erinnern Sie sich, dass ich Ihnen zuvor in [Python-Typen-Intro](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank} gesagt habe, dass `Annotated` verwendet werden kann, um Metadaten zu Ihren Parametern hinzuzufügen? -Jetzt ist es an der Zeit, das mit FastAPI auszuprobieren. 🚀 +Jetzt ist es soweit, dies mit FastAPI zu verwenden. 🚀 Wir hatten diese Typannotation: @@ -97,7 +63,7 @@ q: Union[str, None] = None //// -Wir wrappen das nun in `Annotated`, sodass daraus wird: +Was wir tun werden, ist, dies mit `Annotated` zu wrappen, sodass es zu: //// tab | Python 3.10+ @@ -115,129 +81,75 @@ q: Annotated[Union[str, None]] = None //// -Beide Versionen bedeuten dasselbe: `q` ist ein Parameter, der `str` oder `None` sein kann. Standardmäßig ist er `None`. +Beide dieser Versionen bedeuten dasselbe: `q` ist ein Parameter, der ein `str` oder `None` sein kann, und standardmäßig ist er `None`. -Wenden wir uns jetzt den spannenden Dingen zu. 🎉 +Jetzt springen wir zu den spannenden Dingen. 🎉 -## `Query` zu `Annotated` im `q`-Parameter hinzufügen +## `Query` zu `Annotated` im `q`-Parameter hinzufügen { #add-query-to-annotated-in-the-q-parameter } -Jetzt, da wir `Annotated` für unsere Metadaten deklariert haben, fügen Sie `Query` hinzu, und setzen Sie den Parameter `max_length` auf `50`: +Da wir nun `Annotated` haben, in das wir mehr Informationen (in diesem Fall einige zusätzliche Validierungen) einfügen können, fügen Sie `Query` innerhalb von `Annotated` hinzu und setzen Sie den Parameter `max_length` auf `50`: -//// tab | Python 3.10+ +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} -``` +Beachten Sie, dass der Defaultwert weiterhin `None` ist, so dass der Parameter weiterhin optional ist. -//// +Aber jetzt, mit `Query(max_length=50)` innerhalb von `Annotated`, sagen wir FastAPI, dass wir eine **zusätzliche Validierung** für diesen Wert wünschen, wir wollen, dass er maximal 50 Zeichen hat. 😎 -//// tab | Python 3.8+ +/// tip | Tipp -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial002_an.py!} -``` - -//// - -Beachten Sie, dass der Defaultwert immer noch `None` ist, sodass der Parameter immer noch optional ist. - -Aber jetzt, mit `Query(max_length=50)` innerhalb von `Annotated`, sagen wir FastAPI, dass es diesen Wert aus den Query-Parametern extrahieren soll (das hätte es sowieso gemacht 🤷) und dass wir eine **zusätzliche Validierung** für diesen Wert haben wollen (darum machen wir das, um die zusätzliche Validierung zu bekommen). 😎 - -FastAPI wird nun: - -* Die Daten **validieren** und sicherstellen, dass sie nicht länger als 50 Zeichen sind -* Dem Client einen **verständlichen Fehler** anzeigen, wenn die Daten ungültig sind -* Den Parameter in der OpenAPI-Schema-*Pfadoperation* **dokumentieren** (sodass er in der **automatischen Dokumentation** angezeigt wird) - -## Alternativ (alt): `Query` als Defaultwert - -Frühere Versionen von FastAPI (vor 0.95.0) benötigten `Query` als Defaultwert des Parameters, statt es innerhalb von `Annotated` unterzubringen. Die Chance ist groß, dass Sie Quellcode sehen, der das immer noch so macht, darum erkläre ich es Ihnen. - -/// tip | "Tipp" - -Verwenden Sie für neuen Code, und wann immer möglich, `Annotated`, wie oben erklärt. Es gibt mehrere Vorteile (unten erläutert) und keine Nachteile. 🍰 +Hier verwenden wir `Query()`, weil dies ein **Query-Parameter** ist. Später werden wir andere wie `Path()`, `Body()`, `Header()`, und `Cookie()` sehen, die auch dieselben Argumente wie `Query()` akzeptieren. /// -So würden Sie `Query()` als Defaultwert Ihres Funktionsparameters verwenden, den Parameter `max_length` auf 50 gesetzt: +FastAPI wird nun: -//// tab | Python 3.10+ +* Die Daten **validieren**, um sicherzustellen, dass die Länge maximal 50 Zeichen beträgt +* Einen **klaren Fehler** für den Client anzeigen, wenn die Daten ungültig sind +* Den Parameter in der OpenAPI-Schema-*Pfadoperation* **dokumentieren** (sodass er in der **automatischen Dokumentation** angezeigt wird) -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!} -``` +## Alternative (alt): `Query` als Defaultwert { #alternative-old-query-as-the-default-value } -//// +Frühere Versionen von FastAPI (vor 0.95.0) erforderten, dass Sie `Query` als den Defaultwert Ihres Parameters verwendeten, anstatt es innerhalb von `Annotated` zu platzieren. Es besteht eine hohe Wahrscheinlichkeit, dass Sie Code sehen, der es so verwendet, also werde ich es Ihnen erklären. -//// tab | Python 3.8+ +/// tip | Tipp -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +Für neuen Code und wann immer es möglich ist, verwenden Sie `Annotated` wie oben erklärt. Es gibt mehrere Vorteile (unten erläutert) und keine Nachteile. 🍰 -//// +/// -Da wir in diesem Fall (ohne die Verwendung von `Annotated`) den Parameter-Defaultwert `None` mit `Query()` ersetzen, müssen wir nun dessen Defaultwert mit dem Parameter `Query(default=None)` deklarieren. Das dient demselben Zweck, `None` als Defaultwert für den Funktionsparameter zu setzen (zumindest für FastAPI). +So würden Sie `Query()` als den Defaultwert Ihres Funktionsparameters verwenden und den Parameter `max_length` auf 50 setzen: -Sprich: +{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} -```Python -q: Union[str, None] = Query(default=None) -``` +Da wir in diesem Fall (ohne die Verwendung von `Annotated`) den Defaultwert `None` in der Funktion durch `Query()` ersetzen müssen, müssen wir nun den Defaultwert mit dem Parameter `Query(default=None)` setzen, er erfüllt den gleichen Zweck, diesen Defaultwert zu definieren (zumindest für FastAPI). -... macht den Parameter optional, mit dem Defaultwert `None`, genauso wie: - -```Python -q: Union[str, None] = None -``` - -Und in Python 3.10 und darüber macht: +Also: ```Python q: str | None = Query(default=None) ``` -... den Parameter optional, mit dem Defaultwert `None`, genauso wie: +... macht den Parameter optional mit einem Defaultwert von `None`, genauso wie: ```Python q: str | None = None ``` -Nur, dass die `Query`-Versionen den Parameter explizit als Query-Parameter deklarieren. +Aber die `Query`-Version deklariert ihn explizit als Query-Parameter. -/// info - -Bedenken Sie, dass: +Dann können wir mehr Parameter an `Query` übergeben. In diesem Fall den `max_length`-Parameter, der auf Strings angewendet wird: ```Python -= None +q: str | None = Query(default=None, max_length=50) ``` -oder: +Dies wird die Daten validieren, einen klaren Fehler anzeigen, wenn die Daten nicht gültig sind, und den Parameter in der OpenAPI-Schema-*Pfadoperation* dokumentieren. -```Python -= Query(default=None) -``` +### `Query` als Defaultwert oder in `Annotated` { #query-as-the-default-value-or-in-annotated } -der wichtigste Teil ist, um einen Parameter optional zu machen, da dieses `None` der Defaultwert ist, und das ist es, was diesen Parameter **nicht erforderlich** macht. +Beachten Sie, dass wenn Sie `Query` innerhalb von `Annotated` verwenden, Sie den `default`-Parameter für `Query` nicht verwenden dürfen. -Der Teil mit `Union[str, None]` erlaubt es Ihrem Editor, Sie besser zu unterstützen, aber er sagt FastAPI nicht, dass dieser Parameter optional ist. - -/// - -Jetzt können wir `Query` weitere Parameter übergeben. Fangen wir mit dem `max_length` Parameter an, der auf Strings angewendet wird: - -```Python -q: Union[str, None] = Query(default=None, max_length=50) -``` - -Das wird die Daten validieren, einen verständlichen Fehler ausgeben, wenn die Daten nicht gültig sind, und den Parameter in der OpenAPI-Schema-*Pfadoperation* dokumentieren. - -### `Query` als Defaultwert oder in `Annotated` - -Bedenken Sie, dass wenn Sie `Query` innerhalb von `Annotated` benutzen, Sie den `default`-Parameter für `Query` nicht verwenden dürfen. - -Setzen Sie stattdessen den Defaultwert des Funktionsparameters, sonst wäre es inkonsistent. +Setzen Sie stattdessen den tatsächlichen Defaultwert des Funktionsparameters. Andernfalls wäre es inkonsistent. Zum Beispiel ist das nicht erlaubt: @@ -245,7 +157,7 @@ Zum Beispiel ist das nicht erlaubt: q: Annotated[str, Query(default="rick")] = "morty" ``` -... denn es wird nicht klar, ob der Defaultwert `"rick"` oder `"morty"` sein soll. +... denn es ist nicht klar, ob der Defaultwert `"rick"` oder `"morty"` sein soll. Sie würden also (bevorzugt) schreiben: @@ -253,207 +165,77 @@ Sie würden also (bevorzugt) schreiben: q: Annotated[str, Query()] = "rick" ``` -In älterem Code werden Sie auch finden: +... oder in älteren Codebasen finden Sie: ```Python q: str = Query(default="rick") ``` -### Vorzüge von `Annotated` +### Vorzüge von `Annotated` { #advantages-of-annotated } -**Es wird empfohlen, `Annotated` zu verwenden**, statt des Defaultwertes im Funktionsparameter, das ist aus mehreren Gründen **besser**: 🤓 +**Es wird empfohlen, `Annotated` zu verwenden**, anstelle des Defaultwertes in Funktionsparametern, es ist aus mehreren Gründen **besser**. 🤓 -Der **Default**wert des **Funktionsparameters** ist der **tatsächliche Default**wert, das spielt generell intuitiver mit Python zusammen. 😌 +Der **Default**wert des **Funktionsparameters** ist der **tatsächliche Default**wert, das ist in der Regel intuitiver mit Python. 😌 -Sie können die Funktion ohne FastAPI an **anderen Stellen aufrufen**, und es wird **wie erwartet funktionieren**. Wenn es einen **erforderlichen** Parameter gibt (ohne Defaultwert), und Sie führen die Funktion ohne den benötigten Parameter aus, dann wird Ihr **Editor** Sie das mit einem Fehler wissen lassen, und **Python** wird sich auch beschweren. +Sie könnten **diese gleiche Funktion** in **anderen Stellen** ohne FastAPI **aufrufen**, und es würde **wie erwartet funktionieren**. Wenn es einen **erforderlichen** Parameter gibt (ohne Defaultwert), wird Ihr **Editor** Ihnen dies mit einem Fehler mitteilen, außerdem wird **Python** sich beschweren, wenn Sie es ausführen, ohne den erforderlichen Parameter zu übergeben. -Wenn Sie aber nicht `Annotated` benutzen und stattdessen die **(alte) Variante mit einem Defaultwert**, dann müssen Sie, wenn Sie die Funktion ohne FastAPI an **anderen Stellen** aufrufen, sich daran **erinnern**, die Argumente der Funktion zu übergeben, damit es richtig funktioniert. Ansonsten erhalten Sie unerwartete Werte (z. B. `QueryInfo` oder etwas Ähnliches, statt `str`). Ihr Editor kann ihnen nicht helfen, und Python wird die Funktion ohne Beschwerden ausführen, es sei denn, die Operationen innerhalb lösen einen Fehler aus. +Wenn Sie `Annotated` nicht verwenden und stattdessen die **(alte) Defaultwert-Stilform** verwenden, müssen Sie sich daran **erinnern**, die Argumente der Funktion zu übergeben, wenn Sie diese Funktion ohne FastAPI in **anderen Stellen** aufrufen. Ansonsten sind die Werte anders als erwartet (z. B. `QueryInfo` oder etwas Ähnliches statt `str`). Ihr Editor kann Ihnen nicht helfen, und Python wird die Funktion ohne Klagen ausführen und sich nur beschweren wenn die Operationen innerhalb auf einen Fehler stoßen. -Da `Annotated` mehrere Metadaten haben kann, können Sie dieselbe Funktion auch mit anderen Tools verwenden, wie etwa Typer. 🚀 +Da `Annotated` mehr als eine Metadaten-Annotation haben kann, könnten Sie dieselbe Funktion sogar mit anderen Tools verwenden, wie z. B. Typer. 🚀 -## Mehr Validierungen hinzufügen +## Mehr Validierungen hinzufügen { #add-more-validations } -Sie können auch einen Parameter `min_length` hinzufügen: +Sie können auch einen `min_length`-Parameter hinzufügen: -//// tab | Python 3.10+ +{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial003_an_py310.py!} -``` +## Reguläre Ausdrücke hinzufügen { #add-regular-expressions } -//// +Sie können einen regulären Ausdruck `pattern` definieren, mit dem der Parameter übereinstimmen muss: -//// tab | Python 3.9+ +{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial003_an_py39.py!} -``` +Dieses spezielle Suchmuster im regulären Ausdruck überprüft, dass der erhaltene Parameterwert: -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial003.py!} -``` - -//// - -## Reguläre Ausdrücke hinzufügen - -Sie können einen Regulären Ausdruck `pattern` definieren, mit dem der Parameter übereinstimmen muss: - -//// tab | Python 3.10+ - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12" -{!> ../../../docs_src/query_params_str_validations/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial004.py!} -``` - -//// - -Dieses bestimmte reguläre Suchmuster prüft, ob der erhaltene Parameter-Wert: - -* `^`: mit den nachfolgenden Zeichen startet, keine Zeichen davor hat. +* `^`: mit den nachfolgenden Zeichen beginnt, keine Zeichen davor hat. * `fixedquery`: den exakten Text `fixedquery` hat. -* `$`: danach endet, keine weiteren Zeichen hat als `fixedquery`. +* `$`: dort endet, keine weiteren Zeichen nach `fixedquery` hat. -Wenn Sie sich verloren fühlen bei all diesen **„Regulärer Ausdruck“**-Konzepten, keine Sorge. Reguläre Ausdrücke sind für viele Menschen ein schwieriges Thema. Sie können auch ohne reguläre Ausdrücke eine ganze Menge machen. +Wenn Sie sich mit all diesen **„regulärer Ausdruck“**-Ideen verloren fühlen, keine Sorge. Sie sind ein schwieriges Thema für viele Menschen. Sie können noch viele Dinge tun, ohne reguläre Ausdrücke direkt zu benötigen. -Aber wenn Sie sie brauchen und sie lernen, wissen Sie, dass Sie sie bereits direkt in **FastAPI** verwenden können. +Aber nun wissen Sie, dass Sie sie in **FastAPI** immer dann verwenden können, wenn Sie sie brauchen. -### Pydantic v1 `regex` statt `pattern` +### Pydantic v1 `regex` statt `pattern` { #pydantic-v1-regex-instead-of-pattern } -Vor Pydantic Version 2 und vor FastAPI Version 0.100.0, war der Name des Parameters `regex` statt `pattern`, aber das ist jetzt deprecated. +Vor Pydantic Version 2 und FastAPI 0.100.0, hieß der Parameter `regex` statt `pattern`, aber das ist jetzt obsolet. Sie könnten immer noch Code sehen, der den alten Namen verwendet: -//// tab | Python 3.10+ Pydantic v1 +//// tab | Pydantic v1 -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial004_an_py310_regex.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *} //// -Beachten Sie aber, dass das deprecated ist, und zum neuen Namen `pattern` geändert werden sollte. 🤓 +Beachten Sie aber, dass das obsolet ist und auf den neuen Parameter `pattern` aktualisiert werden sollte. 🤓 -## Defaultwerte +## Defaultwerte { #default-values } -Sie können natürlich andere Defaultwerte als `None` verwenden. +Natürlich können Sie Defaultwerte verwenden, die nicht `None` sind. -Beispielsweise könnten Sie den `q` Query-Parameter so deklarieren, dass er eine `min_length` von `3` hat, und den Defaultwert `"fixedquery"`: +Nehmen wir an, Sie möchten, dass der `q` Query-Parameter eine `min_length` von `3` hat und einen Defaultwert von `"fixedquery"`: -//// tab | Python 3.9+ +{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial005_an_py39.py!} -``` +/// note | Hinweis -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Ein Defaultwert irgendeines Typs, einschließlich `None`, macht den Parameter optional (nicht erforderlich). /// -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial005.py!} -``` +## Erforderliche Parameter { #required-parameters } -//// - -/// note | "Hinweis" - -Ein Parameter ist optional (nicht erforderlich), wenn er irgendeinen Defaultwert, auch `None`, hat. - -/// - -## Erforderliche Parameter - -Wenn wir keine Validierungen oder Metadaten haben, können wir den `q` Query-Parameter erforderlich machen, indem wir einfach keinen Defaultwert deklarieren, wie in: +Wenn wir keine weiteren Validierungen oder Metadaten deklarieren müssen, können wir den `q` Query-Parameter erforderlich machen, indem wir einfach keinen Defaultwert deklarieren, wie: ```Python q: str @@ -462,252 +244,34 @@ q: str statt: ```Python -q: Union[str, None] = None +q: str | None = None ``` -Aber jetzt deklarieren wir den Parameter mit `Query`, wie in: - -//// tab | Annotiert +Aber jetzt deklarieren wir es mit `Query`, zum Beispiel so: ```Python -q: Annotated[Union[str, None], Query(min_length=3)] = None +q: Annotated[str | None, Query(min_length=3)] = None ``` -//// +Wenn Sie einen Wert als erforderlich deklarieren müssen, während Sie `Query` verwenden, deklarieren Sie einfach keinen Defaultwert: -//// tab | Nicht annotiert +{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -```Python -q: Union[str, None] = Query(default=None, min_length=3) -``` +### Erforderlich, kann `None` sein { #required-can-be-none } -//// +Sie können deklarieren, dass ein Parameter `None` akzeptieren kann, aber trotzdem erforderlich ist. Dadurch müssten Clients den Wert senden, selbst wenn der Wert `None` ist. -Wenn Sie einen Parameter erforderlich machen wollen, während Sie `Query` verwenden, deklarieren Sie ebenfalls einfach keinen Defaultwert: +Um das zu tun, können Sie deklarieren, dass `None` ein gültiger Typ ist, einfach indem Sie keinen Defaultwert deklarieren: -//// tab | Python 3.9+ +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial006_an_py39.py!} -``` +## Query-Parameter-Liste / Mehrere Werte { #query-parameter-list-multiple-values } -//// +Wenn Sie einen Query-Parameter explizit mit `Query` definieren, können Sie ihn auch so deklarieren, dass er eine Liste von Werten empfängt, oder anders gesagt, dass er mehrere Werte empfangen kann. -//// tab | Python 3.8+ +Um zum Beispiel einen Query-Parameter `q` zu deklarieren, der mehrmals in der URL vorkommen kann, schreiben Sie: -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial006.py!} -``` - -/// tip | "Tipp" - -Beachten Sie, dass, obwohl in diesem Fall `Query()` der Funktionsparameter-Defaultwert ist, wir nicht `default=None` zu `Query()` hinzufügen. - -Verwenden Sie bitte trotzdem die `Annotated`-Version. 😉 - -/// - -//// - -### Erforderlich mit Ellipse (`...`) - -Es gibt eine Alternative, die explizit deklariert, dass ein Wert erforderlich ist. Sie können als Default das Literal `...` setzen: - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial006b_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial006b_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial006b.py!} -``` - -//// - -/// info - -Falls Sie das `...` bisher noch nicht gesehen haben: Es ist ein spezieller einzelner Wert, Teil von Python und wird „Ellipsis“ genannt (Deutsch: Ellipse). - -Es wird von Pydantic und FastAPI verwendet, um explizit zu deklarieren, dass ein Wert erforderlich ist. - -/// - -Dies wird **FastAPI** wissen lassen, dass dieser Parameter erforderlich ist. - -### Erforderlich, kann `None` sein - -Sie können deklarieren, dass ein Parameter `None` akzeptiert, aber dennoch erforderlich ist. Das zwingt Clients, den Wert zu senden, selbst wenn er `None` ist. - -Um das zu machen, deklarieren Sie, dass `None` ein gültiger Typ ist, aber verwenden Sie dennoch `...` als Default: - -//// tab | Python 3.10+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial006c_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial006c_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial006c_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial006c_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial006c.py!} -``` - -//// - -/// tip | "Tipp" - -Pydantic, welches die gesamte Datenvalidierung und Serialisierung in FastAPI antreibt, hat ein spezielles Verhalten, wenn Sie `Optional` oder `Union[Something, None]` ohne Defaultwert verwenden, Sie können mehr darüber in der Pydantic-Dokumentation unter Required fields erfahren. - -/// - -/// tip | "Tipp" - -Denken Sie daran, dass Sie in den meisten Fällen, wenn etwas erforderlich ist, einfach den Defaultwert weglassen können. Sie müssen also normalerweise `...` nicht verwenden. - -/// - -## Query-Parameter-Liste / Mehrere Werte - -Wenn Sie einen Query-Parameter explizit mit `Query` auszeichnen, können Sie ihn auch eine Liste von Werten empfangen lassen, oder anders gesagt, mehrere Werte. - -Um zum Beispiel einen Query-Parameter `q` zu deklarieren, der mehrere Male in der URL vorkommen kann, schreiben Sie: - -//// tab | Python 3.10+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial011_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial011.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *} Dann, mit einer URL wie: @@ -715,9 +279,9 @@ Dann, mit einer URL wie: http://localhost:8000/items/?q=foo&q=bar ``` -bekommen Sie alle `q`-*Query-Parameter*-Werte (`foo` und `bar`) in einer Python-Liste – `list` – in ihrer *Pfadoperation-Funktion*, im Funktionsparameter `q`, überreicht. +würden Sie die mehreren `q`-*Query-Parameter*-Werte (`foo` und `bar`) in einer Python-`list` in Ihrer *Pfadoperation-Funktion* im *Funktionsparameter* `q` erhalten. -Die Response für diese URL wäre also: +So wäre die Response zu dieser URL: ```JSON { @@ -728,71 +292,29 @@ Die Response für diese URL wäre also: } ``` -/// tip | "Tipp" +/// tip | Tipp -Um einen Query-Parameter vom Typ `list` zu deklarieren, wie im Beispiel oben, müssen Sie explizit `Query` verwenden, sonst würde der Parameter als Requestbody interpretiert werden. +Um einen Query-Parameter mit einem Typ `list` zu deklarieren, wie im obigen Beispiel, müssen Sie explizit `Query` verwenden, da er andernfalls als Requestbody interpretiert würde. /// -Die interaktive API-Dokumentation wird entsprechend aktualisiert und erlaubt jetzt mehrere Werte. +Die interaktive API-Dokumentation wird entsprechend aktualisiert, um mehrere Werte zu erlauben: -### Query-Parameter-Liste / Mehrere Werte mit Defaults +### Query-Parameter-Liste / Mehrere Werte mit Defaults { #query-parameter-list-multiple-values-with-defaults } -Und Sie können auch eine Default-`list`e von Werten definieren, wenn keine übergeben werden: +Sie können auch eine Default-`list` von Werten definieren, wenn keine bereitgestellt werden: -//// tab | Python 3.9+ +{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial012_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial012_an.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial012.py!} -``` - -//// - -Wenn Sie auf: +Wenn Sie zu: ``` http://localhost:8000/items/ ``` -gehen, wird der Default für `q` verwendet: `["foo", "bar"]`, und als Response erhalten Sie: +gehen, wird der Default für `q` sein: `["foo", "bar"]`, und Ihre Response wird sein: ```JSON { @@ -803,173 +325,45 @@ gehen, wird der Default für `q` verwendet: `["foo", "bar"]`, und als Response e } ``` -#### `list` alleine verwenden +#### Nur `list` verwenden { #using-just-list } -Sie können auch `list` direkt verwenden, anstelle von `List[str]` (oder `list[str]` in Python 3.9+): +Sie können auch `list` direkt verwenden, anstelle von `list[str]`: -//// tab | Python 3.9+ +{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial013_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial013_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial013.py!} -``` - -//// - -/// note | "Hinweis" +/// note | Hinweis Beachten Sie, dass FastAPI in diesem Fall den Inhalt der Liste nicht überprüft. -Zum Beispiel würde `List[int]` überprüfen (und dokumentieren) dass die Liste Ganzzahlen enthält. `list` alleine macht das nicht. +Zum Beispiel würde `list[int]` überprüfen (und dokumentieren), dass der Inhalt der Liste Ganzzahlen sind. Aber `list` alleine würde das nicht. /// -## Deklarieren von mehr Metadaten +## Mehr Metadaten deklarieren { #declare-more-metadata } -Sie können mehr Informationen zum Parameter hinzufügen. +Sie können mehr Informationen über den Parameter hinzufügen. -Diese Informationen werden zur generierten OpenAPI hinzugefügt, und von den Dokumentations-Oberflächen und von externen Tools verwendet. +Diese Informationen werden in das generierte OpenAPI aufgenommen und von den Dokumentationsoberflächen und externen Tools verwendet. -/// note | "Hinweis" +/// note | Hinweis -Beachten Sie, dass verschiedene Tools OpenAPI möglicherweise unterschiedlich gut unterstützen. +Beachten Sie, dass verschiedene Tools möglicherweise unterschiedliche Unterstützungslevels für OpenAPI haben. -Einige könnten noch nicht alle zusätzlichen Informationen anzeigen, die Sie deklariert haben, obwohl in den meisten Fällen geplant ist, das fehlende Feature zu implementieren. +Einige davon könnten noch nicht alle zusätzlichen Informationen anzuzeigen, die Sie erklärten, obwohl in den meisten Fällen die fehlende Funktionalität bereits in der Entwicklung geplant ist. /// -Sie können einen Titel hinzufügen – `title`: +Sie können einen `title` hinzufügen: -//// tab | Python 3.10+ +{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *} -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial007_an_py310.py!} -``` +Und eine `description`: -//// +{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} -//// tab | Python 3.9+ +## Alias-Parameter { #alias-parameters } -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial007_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial007_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial007.py!} -``` - -//// - -Und eine Beschreibung – `description`: - -//// tab | Python 3.10+ - -```Python hl_lines="14" -{!> ../../../docs_src/query_params_str_validations/tutorial008_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="14" -{!> ../../../docs_src/query_params_str_validations/tutorial008_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15" -{!> ../../../docs_src/query_params_str_validations/tutorial008_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="13" -{!> ../../../docs_src/query_params_str_validations/tutorial008.py!} -``` - -//// - -## Alias-Parameter - -Stellen Sie sich vor, der Parameter soll `item-query` sein. +Stellen Sie sich vor, Sie möchten, dass der Parameter `item-query` ist. Wie in: @@ -979,187 +373,99 @@ http://127.0.0.1:8000/items/?item-query=foobaritems Aber `item-query` ist kein gültiger Name für eine Variable in Python. -Am ähnlichsten wäre `item_query`. +Der am ähnlichsten wäre `item_query`. -Aber Sie möchten dennoch exakt `item-query` verwenden. +Aber Sie benötigen dennoch, dass er genau `item-query` ist ... -Dann können Sie einen `alias` deklarieren, und dieser Alias wird verwendet, um den Parameter-Wert zu finden: +Dann können Sie ein `alias` deklarieren, und dieser Alias wird verwendet, um den Parameterwert zu finden: -//// tab | Python 3.10+ +{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial009_an_py310.py!} -``` +## Parameter als deprecatet ausweisen { #deprecating-parameters } -//// +Nehmen wir an, Ihnen gefällt dieser Parameter nicht mehr. -//// tab | Python 3.9+ +Sie müssen ihn eine Weile dort belassen, da es Clients gibt, die ihn verwenden, aber Sie möchten, dass die Dokumentation ihn klar als deprecatet anzeigt. -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial009_an_py39.py!} -``` +Dann übergeben Sie den Parameter `deprecated=True` an `Query`: -//// +{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *} -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial009_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial009.py!} -``` - -//// - -## Parameter als deprecated ausweisen - -Nehmen wir an, Sie mögen diesen Parameter nicht mehr. - -Sie müssen ihn eine Weile dort belassen, weil Clients ihn benutzen, aber Sie möchten, dass die Dokumentation klar anzeigt, dass er deprecated ist. - -In diesem Fall fügen Sie den Parameter `deprecated=True` zu `Query` hinzu. - -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/query_params_str_validations/tutorial010_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/query_params_str_validations/tutorial010_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/query_params_str_validations/tutorial010_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="16" -{!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="18" -{!> ../../../docs_src/query_params_str_validations/tutorial010.py!} -``` - -//// - -Die Dokumentation wird das so anzeigen: +Die Dokumentation wird es so anzeigen: -## Parameter von OpenAPI ausschließen +## Parameter von OpenAPI ausschließen { #exclude-parameters-from-openapi } -Um einen Query-Parameter vom generierten OpenAPI-Schema auszuschließen (und daher von automatischen Dokumentations-Systemen), setzen Sie den Parameter `include_in_schema` in `Query` auf `False`. +Um einen Query-Parameter aus dem generierten OpenAPI-Schema auszuschließen (und somit aus den automatischen Dokumentationssystemen), setzen Sie den Parameter `include_in_schema` von `Query` auf `False`: -//// tab | Python 3.10+ +{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial014_an_py310.py!} -``` +## Benutzerdefinierte Validierung { #custom-validation } -//// +Es kann Fälle geben, in denen Sie eine **benutzerdefinierte Validierung** durchführen müssen, die nicht mit den oben gezeigten Parametern durchgeführt werden kann. -//// tab | Python 3.9+ +In diesen Fällen können Sie eine **benutzerdefinierte Validierungsfunktion** verwenden, die nach der normalen Validierung angewendet wird (z. B. nach der Validierung, dass der Wert ein `str` ist). -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial014_an_py39.py!} -``` +Sie können dies mit Pydantic's `AfterValidator` innerhalb von `Annotated` erreichen. -//// +/// tip | Tipp -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial014_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Pydantic unterstützt auch `BeforeValidator` und andere. 🤓 /// -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!} -``` +Zum Beispiel überprüft dieser benutzerdefinierte Validator, ob die Artikel-ID mit `isbn-` für eine ISBN-Buchnummer oder mit `imdb-` für eine IMDB-Film-URL-ID beginnt: -//// +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} -//// tab | Python 3.8+ nicht annotiert +/// info | Info -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Dies ist verfügbar seit Pydantic Version 2 oder höher. 😎 /// -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial014.py!} -``` +/// tip | Tipp -//// +Wenn Sie irgendeine Art von Validierung durchführen müssen, die eine Kommunikation mit einer **externen Komponente** erfordert, wie z. B. einer Datenbank oder einer anderen API, sollten Sie stattdessen **FastAPI-Abhängigkeiten** verwenden. Sie werden diese später kennenlernen. -## Zusammenfassung +Diese benutzerdefinierten Validatoren sind für Dinge gedacht, die einfach mit denselben **Daten** überprüft werden können, die im Request bereitgestellt werden. -Sie können zusätzliche Validierungen und Metadaten zu ihren Parametern hinzufügen. +/// + +### Dieses Codebeispiel verstehen { #understand-that-code } + +Der wichtige Punkt ist einfach die Verwendung von **`AfterValidator` mit einer Funktion innerhalb von `Annotated`**. Fühlen Sie sich frei, diesen Teil zu überspringen. 🤸 + +--- + +Aber wenn Sie neugierig auf dieses spezielle Codebeispiel sind und immer noch Spaß haben, hier sind einige zusätzliche Details. + +#### Zeichenkette mit `value.startswith()` { #string-with-value-startswith } + +Haben Sie bemerkt? Eine Zeichenkette mit `value.startswith()` kann ein Tuple übernehmen, und es wird jeden Wert im Tuple überprüfen: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *} + +#### Ein zufälliges Item { #a-random-item } + +Mit `data.items()` erhalten wir ein iterierbares Objekt mit Tupeln, die Schlüssel und Wert für jedes Dictionary-Element enthalten. + +Wir konvertieren dieses iterierbare Objekt mit `list(data.items())` in eine richtige `list`. + +Dann können wir mit `random.choice()` einen **zufälligen Wert** aus der Liste erhalten, also bekommen wir ein Tuple mit `(id, name)`. Es wird etwas wie `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")` sein. + +Dann **weisen wir diese beiden Werte** des Tupels den Variablen `id` und `name` zu. + +Wenn der Benutzer also keine Artikel-ID bereitgestellt hat, erhält er trotzdem einen zufälligen Vorschlag. + +... wir tun all dies in einer **einzelnen einfachen Zeile**. 🤯 Lieben Sie nicht auch Python? 🐍 + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} + +## Zusammenfassung { #recap } + +Sie können zusätzliche Validierungen und Metadaten für Ihre Parameter deklarieren. Allgemeine Validierungen und Metadaten: @@ -1168,12 +474,14 @@ Allgemeine Validierungen und Metadaten: * `description` * `deprecated` -Validierungen spezifisch für Strings: +Validierungen, die spezifisch für Strings sind: * `min_length` * `max_length` * `pattern` -In diesen Beispielen haben Sie gesehen, wie Sie Validierungen für Strings hinzufügen. +Benutzerdefinierte Validierungen mit `AfterValidator`. -In den nächsten Kapiteln sehen wir, wie man Validierungen für andere Typen hinzufügt, etwa für Zahlen. +In diesen Beispielen haben Sie gesehen, wie Sie Validierungen für `str`-Werte deklarieren. + +Sehen Sie sich die nächsten Kapitel an, um zu erfahren, wie Sie Validierungen für andere Typen, wie z. B. Zahlen, deklarieren. diff --git a/docs/de/docs/tutorial/query-params.md b/docs/de/docs/tutorial/query-params.md index 136852216e..e46f31ad00 100644 --- a/docs/de/docs/tutorial/query-params.md +++ b/docs/de/docs/tutorial/query-params.md @@ -1,12 +1,10 @@ -# Query-Parameter +# Query-Parameter { #query-parameters } -Wenn Sie in ihrer Funktion Parameter deklarieren, die nicht Teil der Pfad-Parameter sind, dann werden diese automatisch als „Query“-Parameter interpretiert. +Wenn Sie in Ihrer Funktion andere Parameter deklarieren, die nicht Teil der Pfad-Parameter sind, dann werden diese automatisch als „Query“-Parameter interpretiert. -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial001.py!} -``` +{* ../../docs_src/query_params/tutorial001.py hl[9] *} -Query-Parameter (Deutsch: Abfrage-Parameter) sind die Schlüssel-Wert-Paare, die nach dem `?` in einer URL aufgelistet sind, getrennt durch `&`-Zeichen. +Die Query ist die Menge von Schlüssel-Wert-Paaren, die nach dem `?` in einer URL folgen und durch `&`-Zeichen getrennt sind. Zum Beispiel sind in der URL: @@ -21,18 +19,18 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 Da sie Teil der URL sind, sind sie „naturgemäß“ Strings. -Aber wenn Sie sie mit Python-Typen deklarieren (im obigen Beispiel als `int`), werden sie zu diesem Typ konvertiert, und gegen diesen validiert. +Aber wenn Sie sie mit Python-Typen deklarieren (im obigen Beispiel als `int`), werden sie zu diesem Typ konvertiert und gegen diesen validiert. -Die gleichen Prozesse, die für Pfad-Parameter stattfinden, werden auch auf Query-Parameter angewendet: +Die gleichen Prozesse, die für Pfad-Parameter gelten, werden auch auf Query-Parameter angewendet: * Editor Unterstützung (natürlich) -* „Parsen“ der Daten +* Daten-„Parsen“ * Datenvalidierung * Automatische Dokumentation -## Defaultwerte +## Defaultwerte { #defaults } -Da Query-Parameter nicht ein festgelegter Teil des Pfades sind, können sie optional sein und Defaultwerte haben. +Da Query-Parameter kein fester Teil eines Pfades sind, können sie optional sein und Defaultwerte haben. Im obigen Beispiel haben sie die Defaultwerte `skip=0` und `limit=10`. @@ -54,58 +52,30 @@ Aber wenn Sie zum Beispiel zu: http://127.0.0.1:8000/items/?skip=20 ``` -gehen, werden die Parameter-Werte Ihrer Funktion sein: +gehen, werden die Parameterwerte Ihrer Funktion sein: * `skip=20`: da Sie das in der URL gesetzt haben * `limit=10`: weil das der Defaultwert ist -## Optionale Parameter +## Optionale Parameter { #optional-parameters } Auf die gleiche Weise können Sie optionale Query-Parameter deklarieren, indem Sie deren Defaultwert auf `None` setzen: -//// tab | Python 3.10+ +{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial002_py310.py!} -``` +In diesem Fall wird der Funktionsparameter `q` optional und standardmäßig `None` sein. -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial002.py!} -``` - -//// - -In diesem Fall wird der Funktionsparameter `q` optional, und standardmäßig `None` sein. - -/// check +/// check | Testen Beachten Sie auch, dass **FastAPI** intelligent genug ist, um zu erkennen, dass `item_id` ein Pfad-Parameter ist und `q` keiner, daher muss letzteres ein Query-Parameter sein. /// -## Query-Parameter Typkonvertierung +## Query-Parameter Typkonvertierung { #query-parameter-type-conversion } -Sie können auch `bool`-Typen deklarieren und sie werden konvertiert: +Sie können auch `bool`-Typen deklarieren, und sie werden konvertiert: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial003.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} Wenn Sie nun zu: @@ -139,31 +109,17 @@ http://127.0.0.1:8000/items/foo?short=yes gehen, oder zu irgendeiner anderen Variante der Groß-/Kleinschreibung (Alles groß, Anfangsbuchstabe groß, usw.), dann wird Ihre Funktion den Parameter `short` mit dem `bool`-Wert `True` sehen, ansonsten mit dem Wert `False`. -## Mehrere Pfad- und Query-Parameter +## Mehrere Pfad- und Query-Parameter { #multiple-path-and-query-parameters } -Sie können mehrere Pfad-Parameter und Query-Parameter gleichzeitig deklarieren, **FastAPI** weiß, was welches ist. +Sie können mehrere Pfad-Parameter und Query-Parameter gleichzeitig deklarieren, **FastAPI** weiß, welches welcher ist. Und Sie müssen sie auch nicht in einer spezifischen Reihenfolge deklarieren. Parameter werden anhand ihres Namens erkannt: -//// tab | Python 3.10+ +{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} -```Python hl_lines="6 8" -{!> ../../../docs_src/query_params/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8 10" -{!> ../../../docs_src/query_params/tutorial004.py!} -``` - -//// - -## Erforderliche Query-Parameter +## Erforderliche Query-Parameter { #required-query-parameters } Wenn Sie einen Defaultwert für Nicht-Pfad-Parameter deklarieren (Bis jetzt haben wir nur Query-Parameter gesehen), dann ist der Parameter nicht erforderlich. @@ -171,9 +127,7 @@ Wenn Sie keinen spezifischen Wert haben wollen, sondern der Parameter einfach op Aber wenn Sie wollen, dass ein Query-Parameter erforderlich ist, vergeben Sie einfach keinen Defaultwert: -```Python hl_lines="6-7" -{!../../../docs_src/query_params/tutorial005.py!} -``` +{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} Hier ist `needy` ein erforderlicher Query-Parameter vom Typ `str`. @@ -195,8 +149,7 @@ http://127.0.0.1:8000/items/foo-item "needy" ], "msg": "Field required", - "input": null, - "url": "https://errors.pydantic.dev/2.1/v/missing" + "input": null } ] } @@ -219,21 +172,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy Und natürlich können Sie einige Parameter als erforderlich, einige mit Defaultwert, und einige als vollständig optional definieren: -//// tab | Python 3.10+ - -```Python hl_lines="8" -{!> ../../../docs_src/query_params/tutorial006_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/query_params/tutorial006.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} In diesem Fall gibt es drei Query-Parameter: @@ -241,8 +180,8 @@ In diesem Fall gibt es drei Query-Parameter: * `skip`, ein `int` mit einem Defaultwert `0`. * `limit`, ein optionales `int`. -/// tip | "Tipp" +/// tip | Tipp -Sie können auch `Enum`s verwenden, auf die gleiche Weise wie mit [Pfad-Parametern](path-params.md#vordefinierte-parameterwerte){.internal-link target=_blank}. +Sie können auch `Enum`s verwenden, auf die gleiche Weise wie mit [Pfad-Parametern](path-params.md#predefined-values){.internal-link target=_blank}. /// diff --git a/docs/de/docs/tutorial/request-files.md b/docs/de/docs/tutorial/request-files.md index cf44df5f0a..0aee898b9c 100644 --- a/docs/de/docs/tutorial/request-files.md +++ b/docs/de/docs/tutorial/request-files.md @@ -1,96 +1,44 @@ -# Dateien im Request +# Dateien im Request { #request-files } -Mit `File` können sie vom Client hochzuladende Dateien definieren. +Sie können Dateien, die vom Client hochgeladen werden, mithilfe von `File` definieren. -/// info +/// info | Info -Um hochgeladene Dateien zu empfangen, installieren Sie zuerst `python-multipart`. +Um hochgeladene Dateien zu empfangen, installieren Sie zuerst `python-multipart`. -Z. B. `pip install python-multipart`. +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und dann das Paket installieren, zum Beispiel: -Das, weil hochgeladene Dateien als „Formulardaten“ gesendet werden. +```console +$ pip install python-multipart +``` + +Das liegt daran, dass hochgeladene Dateien als „Formulardaten“ gesendet werden. /// -## `File` importieren +## `File` importieren { #import-file } Importieren Sie `File` und `UploadFile` von `fastapi`: -//// tab | Python 3.9+ +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} -```Python hl_lines="3" -{!> ../../../docs_src/request_files/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1" -{!> ../../../docs_src/request_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/request_files/tutorial001.py!} -``` - -//// - -## `File`-Parameter definieren +## `File`-Parameter definieren { #define-file-parameters } Erstellen Sie Datei-Parameter, so wie Sie es auch mit `Body` und `Form` machen würden: -//// tab | Python 3.9+ +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/request_files/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/request_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/request_files/tutorial001.py!} -``` - -//// - -/// info +/// info | Info `File` ist eine Klasse, die direkt von `Form` erbt. -Aber erinnern Sie sich, dass, wenn Sie `Query`, `Path`, `File` und andere von `fastapi` importieren, diese tatsächlich Funktionen sind, welche spezielle Klassen zurückgeben +Aber erinnern Sie sich, dass, wenn Sie `Query`, `Path`, `File` und andere von `fastapi` importieren, diese tatsächlich Funktionen sind, welche spezielle Klassen zurückgeben. /// -/// tip | "Tipp" +/// tip | Tipp -Um Dateibodys zu deklarieren, müssen Sie `File` verwenden, da diese Parameter sonst als Query-Parameter oder Body(-JSON)-Parameter interpretiert werden würden. +Um Dateibodys zu deklarieren, müssen Sie `File` verwenden, da diese Parameter sonst als Query-Parameter oder Body (JSON)-Parameter interpretiert werden würden. /// @@ -102,57 +50,29 @@ Bedenken Sie, dass das bedeutet, dass sich der gesamte Inhalt der Datei im Arbei Aber es gibt viele Fälle, in denen Sie davon profitieren, `UploadFile` zu verwenden. -## Datei-Parameter mit `UploadFile` +## Datei-Parameter mit `UploadFile` { #file-parameters-with-uploadfile } Definieren Sie einen Datei-Parameter mit dem Typ `UploadFile`: -//// tab | Python 3.9+ - -```Python hl_lines="14" -{!> ../../../docs_src/request_files/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="13" -{!> ../../../docs_src/request_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="12" -{!> ../../../docs_src/request_files/tutorial001.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} `UploadFile` zu verwenden, hat mehrere Vorzüge gegenüber `bytes`: * Sie müssen `File()` nicht als Parameter-Defaultwert verwenden. -* Es wird eine „Spool“-Datei verwendet: +* Es wird eine „gespoolte“ Datei verwendet: * Eine Datei, die bis zu einem bestimmten Größen-Limit im Arbeitsspeicher behalten wird, und wenn das Limit überschritten wird, auf der Festplatte gespeichert wird. * Das bedeutet, es wird für große Dateien wie Bilder, Videos, große Binärdateien, usw. gut funktionieren, ohne den ganzen Arbeitsspeicher aufzubrauchen. * Sie können Metadaten aus der hochgeladenen Datei auslesen. -* Es hat eine file-like `async`hrone Schnittstelle. -* Es stellt ein tatsächliches Python-`SpooledTemporaryFile`-Objekt bereit, welches Sie direkt anderen Bibliotheken übergeben können, die ein dateiartiges Objekt erwarten. +* Es hat eine dateiartige `async`hrone Schnittstelle. +* Es stellt ein tatsächliches Python-`SpooledTemporaryFile`-Objekt bereit, welches Sie direkt anderen Bibliotheken übergeben können, die ein dateiartiges Objekt erwarten. -### `UploadFile` +### `UploadFile` { #uploadfile } `UploadFile` hat die folgenden Attribute: * `filename`: Ein `str` mit dem ursprünglichen Namen der hochgeladenen Datei (z. B. `meinbild.jpg`). * `content_type`: Ein `str` mit dem Inhaltstyp (MIME-Typ / Medientyp) (z. B. `image/jpeg`). -* `file`: Ein `SpooledTemporaryFile` (ein file-like Objekt). Das ist das tatsächliche Python-Objekt, das Sie direkt anderen Funktionen oder Bibliotheken übergeben können, welche ein „file-like“-Objekt erwarten. +* `file`: Ein `SpooledTemporaryFile` (ein dateiartiges Objekt). Das ist das tatsächliche Python-Objekt, das Sie direkt anderen Funktionen oder Bibliotheken übergeben können, welche ein „file-like“-Objekt erwarten. `UploadFile` hat die folgenden `async`hronen Methoden. Sie alle rufen die entsprechenden Methoden des darunterliegenden Datei-Objekts auf (wobei intern `SpooledTemporaryFile` verwendet wird). @@ -163,7 +83,7 @@ Bevorzugen Sie die `Annotated`-Version, falls möglich. * Das ist besonders dann nützlich, wenn Sie `await myfile.read()` einmal ausführen und dann diese Inhalte erneut auslesen müssen. * `close()`: Schließt die Datei. -Da alle diese Methoden `async`hron sind, müssen Sie sie `await`en („erwarten“). +Da alle diese Methoden `async`hron sind, müssen Sie sie „await“en („erwarten“). Zum Beispiel können Sie innerhalb einer `async` *Pfadoperation-Funktion* den Inhalt wie folgt auslesen: @@ -177,133 +97,55 @@ Wenn Sie sich innerhalb einer normalen `def`-*Pfadoperation-Funktion* befinden, contents = myfile.file.read() ``` -/// note | "Technische Details zu `async`" +/// note | Technische Details zu `async` Wenn Sie die `async`-Methoden verwenden, führt **FastAPI** die Datei-Methoden in einem Threadpool aus und erwartet sie. /// -/// note | "Technische Details zu Starlette" +/// note | Technische Details zu Starlette **FastAPI**s `UploadFile` erbt direkt von **Starlette**s `UploadFile`, fügt aber ein paar notwendige Teile hinzu, um es kompatibel mit **Pydantic** und anderen Teilen von FastAPI zu machen. /// -## Was sind „Formulardaten“ +## Was sind „Formulardaten“ { #what-is-form-data } -HTML-Formulare (`
`) senden die Daten in einer „speziellen“ Kodierung zum Server, welche sich von JSON unterscheidet. +Der Weg, wie HTML-Formulare (`
`) die Daten zum Server senden, verwendet normalerweise eine „spezielle“ Kodierung für diese Daten. Diese unterscheidet sich von JSON. **FastAPI** stellt sicher, dass diese Daten korrekt ausgelesen werden, statt JSON zu erwarten. -/// note | "Technische Details" +/// note | Technische Details Daten aus Formularen werden, wenn es keine Dateien sind, normalerweise mit dem „media type“ `application/x-www-form-urlencoded` kodiert. Sollte das Formular aber Dateien enthalten, dann werden diese mit `multipart/form-data` kodiert. Wenn Sie `File` verwenden, wird **FastAPI** wissen, dass es die Dateien vom korrekten Teil des Bodys holen muss. -Wenn Sie mehr über Formularfelder und ihre Kodierungen lesen möchten, besuchen Sie die MDN-Webdokumentation für POST. +Wenn Sie mehr über diese Kodierungen und Formularfelder lesen möchten, besuchen Sie die MDN-Webdokumentation für POST. /// -/// warning | "Achtung" +/// warning | Achtung -Sie können mehrere `File`- und `Form`-Parameter in einer *Pfadoperation* deklarieren, aber Sie können nicht gleichzeitig auch `Body`-Felder deklarieren, welche Sie als JSON erwarten, da der Request den Body mittels `multipart/form-data` statt `application/json` kodiert. +Sie können mehrere `File`- und `Form`-Parameter in einer *Pfadoperation* deklarieren, aber Sie können nicht gleichzeitig auch `Body`-Felder deklarieren, welche Sie als JSON erwarten, da der Request den Body mittels `multipart/form-data` statt `application/json` kodiert. Das ist keine Limitation von **FastAPI**, sondern Teil des HTTP-Protokolls. /// -## Optionaler Datei-Upload +## Optionaler Datei-Upload { #optional-file-upload } Sie können eine Datei optional machen, indem Sie Standard-Typannotationen verwenden und den Defaultwert auf `None` setzen: -//// tab | Python 3.10+ +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02_an_py310.py!} -``` +## `UploadFile` mit zusätzlichen Metadaten { #uploadfile-with-additional-metadata } -//// +Sie können auch `File()` mit `UploadFile` verwenden, um zum Beispiel zusätzliche Metadaten zu setzen: -//// tab | Python 3.9+ +{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10 18" -{!> ../../../docs_src/request_files/tutorial001_02_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7 15" -{!> ../../../docs_src/request_files/tutorial001_02_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02.py!} -``` - -//// - -## `UploadFile` mit zusätzlichen Metadaten - -Sie können auch `File()` zusammen mit `UploadFile` verwenden, um zum Beispiel zusätzliche Metadaten zu setzen: - -//// tab | Python 3.9+ - -```Python hl_lines="9 15" -{!> ../../../docs_src/request_files/tutorial001_03_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8 14" -{!> ../../../docs_src/request_files/tutorial001_03_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7 13" -{!> ../../../docs_src/request_files/tutorial001_03.py!} -``` - -//// - -## Mehrere Datei-Uploads +## Mehrere Datei-Uploads { #multiple-file-uploads } Es ist auch möglich, mehrere Dateien gleichzeitig hochzuladen. @@ -311,108 +153,24 @@ Diese werden demselben Formularfeld zugeordnet, welches mit den Formulardaten ge Um das zu machen, deklarieren Sie eine Liste von `bytes` oder `UploadFile`s: -//// tab | Python 3.9+ +{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} -```Python hl_lines="10 15" -{!> ../../../docs_src/request_files/tutorial002_an_py39.py!} -``` +Sie erhalten, wie deklariert, eine `list` von `bytes` oder `UploadFile`s. -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11 16" -{!> ../../../docs_src/request_files/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8 13" -{!> ../../../docs_src/request_files/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10 15" -{!> ../../../docs_src/request_files/tutorial002.py!} -``` - -//// - -Sie erhalten, wie deklariert, eine `list`e von `bytes` oder `UploadFile`s. - -/// note | "Technische Details" +/// note | Technische Details Sie können auch `from starlette.responses import HTMLResponse` verwenden. -**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, als Annehmlichkeit für Sie, den Entwickler. Die meisten verfügbaren Responses kommen aber direkt von Starlette. +**FastAPI** bietet dieselben `starlette.responses` auch via `fastapi.responses` an, als Annehmlichkeit für Sie, den Entwickler. Die meisten verfügbaren Responses kommen aber direkt von Starlette. /// -### Mehrere Datei-Uploads mit zusätzlichen Metadaten +### Mehrere Datei-Uploads mit zusätzlichen Metadaten { #multiple-file-uploads-with-additional-metadata } Und so wie zuvor können Sie `File()` verwenden, um zusätzliche Parameter zu setzen, sogar für `UploadFile`: -//// tab | Python 3.9+ +{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} -```Python hl_lines="11 18-20" -{!> ../../../docs_src/request_files/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12 19-21" -{!> ../../../docs_src/request_files/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.9+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="9 16" -{!> ../../../docs_src/request_files/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="11 18" -{!> ../../../docs_src/request_files/tutorial003.py!} -``` - -//// - -## Zusammenfassung +## Zusammenfassung { #recap } Verwenden Sie `File`, `bytes` und `UploadFile`, um hochladbare Dateien im Request zu deklarieren, die als Formulardaten gesendet werden. diff --git a/docs/de/docs/tutorial/request-form-models.md b/docs/de/docs/tutorial/request-form-models.md new file mode 100644 index 0000000000..fbc6c094c7 --- /dev/null +++ b/docs/de/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# Formularmodelle { #form-models } + +Sie können **Pydantic-Modelle** verwenden, um **Formularfelder** in FastAPI zu deklarieren. + +/// info | Info + +Um Formulare zu verwenden, installieren Sie zuerst `python-multipart`. + +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und es dann installieren, zum Beispiel: + +```console +$ pip install python-multipart +``` + +/// + +/// note | Hinweis + +Dies wird seit FastAPI Version `0.113.0` unterstützt. 🤓 + +/// + +## Pydantic-Modelle für Formulare { #pydantic-models-for-forms } + +Sie müssen nur ein **Pydantic-Modell** mit den Feldern deklarieren, die Sie als **Formularfelder** erhalten möchten, und dann den Parameter als `Form` deklarieren: + +{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} + +**FastAPI** wird die Daten für **jedes Feld** aus den **Formulardaten** im Request **extrahieren** und Ihnen das von Ihnen definierte Pydantic-Modell übergeben. + +## Die Dokumentation testen { #check-the-docs } + +Sie können dies in der Dokumentations-UI unter `/docs` testen: + +
+ +
+ +## Zusätzliche Formularfelder verbieten { #forbid-extra-form-fields } + +In einigen speziellen Anwendungsfällen (wahrscheinlich nicht sehr häufig) möchten Sie möglicherweise die Formularfelder auf nur diejenigen beschränken, die im Pydantic-Modell deklariert sind, und jegliche **zusätzlichen** Felder **verbieten**. + +/// note | Hinweis + +Dies wird seit FastAPI Version `0.114.0` unterstützt. 🤓 + +/// + +Sie können die Modellkonfiguration von Pydantic verwenden, um jegliche `extra` Felder zu `verbieten`: + +{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} + +Wenn ein Client versucht, einige zusätzliche Daten zu senden, erhält er eine **Error-Response**. + +Zum Beispiel, wenn der Client versucht, folgende Formularfelder zu senden: + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +erhält er eine Error-Response, die ihm mitteilt, dass das Feld `extra` nicht erlaubt ist: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## Zusammenfassung { #summary } + +Sie können Pydantic-Modelle verwenden, um Formularfelder in FastAPI zu deklarieren. 😎 diff --git a/docs/de/docs/tutorial/request-forms-and-files.md b/docs/de/docs/tutorial/request-forms-and-files.md index e109595d10..cda38bcc2f 100644 --- a/docs/de/docs/tutorial/request-forms-and-files.md +++ b/docs/de/docs/tutorial/request-forms-and-files.md @@ -1,93 +1,41 @@ -# Formulardaten und Dateien im Request +# Formulardaten und Dateien im Request { #request-forms-and-files } Sie können gleichzeitig Dateien und Formulardaten mit `File` und `Form` definieren. -/// info +/// info | Info -Um hochgeladene Dateien und/oder Formulardaten zu empfangen, installieren Sie zuerst `python-multipart`. +Um hochgeladene Dateien und/oder Formulardaten zu empfangen, installieren Sie zuerst `python-multipart`. -Z. B. `pip install python-multipart`. +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, diese aktivieren und es dann installieren, z. B.: + +```console +$ pip install python-multipart +``` /// -## `File` und `Form` importieren +## `File` und `Form` importieren { #import-file-and-form } -//// tab | Python 3.9+ +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} -```Python hl_lines="3" -{!> ../../../docs_src/request_forms_and_files/tutorial001_an_py39.py!} -``` +## `File` und `Form`-Parameter definieren { #define-file-and-form-parameters } -//// +Erstellen Sie Datei- und Formularparameter, so wie Sie es auch mit `Body` oder `Query` machen würden: -//// tab | Python 3.8+ - -```Python hl_lines="1" -{!> ../../../docs_src/request_forms_and_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/request_forms_and_files/tutorial001.py!} -``` - -//// - -## `File` und `Form`-Parameter definieren - -Erstellen Sie Datei- und Formularparameter, so wie Sie es auch mit `Body` und `Query` machen würden: - -//// tab | Python 3.9+ - -```Python hl_lines="10-12" -{!> ../../../docs_src/request_forms_and_files/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-11" -{!> ../../../docs_src/request_forms_and_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="8" -{!> ../../../docs_src/request_forms_and_files/tutorial001.py!} -``` - -//// +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} Die Datei- und Formularfelder werden als Formulardaten hochgeladen, und Sie erhalten diese Dateien und Formularfelder. Und Sie können einige der Dateien als `bytes` und einige als `UploadFile` deklarieren. -/// warning | "Achtung" +/// warning | Achtung -Sie können mehrere `File`- und `Form`-Parameter in einer *Pfadoperation* deklarieren, aber Sie können nicht gleichzeitig auch `Body`-Felder deklarieren, welche Sie als JSON erwarten, da der Request den Body mittels `multipart/form-data` statt `application/json` kodiert. +Sie können mehrere `File`- und `Form`-Parameter in einer *Pfadoperation* deklarieren, aber Sie können nicht auch `Body`-Felder deklarieren, die Sie als JSON erwarten, da der Body des Request mittels `multipart/form-data` statt `application/json` kodiert sein wird. Das ist keine Limitation von **FastAPI**, sondern Teil des HTTP-Protokolls. /// -## Zusammenfassung +## Zusammenfassung { #recap } Verwenden Sie `File` und `Form` zusammen, wenn Sie Daten und Dateien zusammen im selben Request empfangen müssen. diff --git a/docs/de/docs/tutorial/request-forms.md b/docs/de/docs/tutorial/request-forms.md index 391788affc..5c2ace67b2 100644 --- a/docs/de/docs/tutorial/request-forms.md +++ b/docs/de/docs/tutorial/request-forms.md @@ -1,125 +1,73 @@ -# Formulardaten +# Formulardaten { #form-data } Wenn Sie Felder aus Formularen statt JSON empfangen müssen, können Sie `Form` verwenden. -/// info +/// info | Info -Um Formulare zu verwenden, installieren Sie zuerst `python-multipart`. +Um Formulare zu verwenden, installieren Sie zuerst `python-multipart`. -Z. B. `pip install python-multipart`. +Erstellen Sie unbedingt eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank}, aktivieren Sie diese und installieren Sie dann das Paket, zum Beispiel: + +```console +$ pip install python-multipart +``` /// -## `Form` importieren +## `Form` importieren { #import-form } Importieren Sie `Form` von `fastapi`: -//// tab | Python 3.9+ +{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} -```Python hl_lines="3" -{!> ../../../docs_src/request_forms/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1" -{!> ../../../docs_src/request_forms/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/request_forms/tutorial001.py!} -``` - -//// - -## `Form`-Parameter definieren +## `Form`-Parameter definieren { #define-form-parameters } Erstellen Sie Formular-Parameter, so wie Sie es auch mit `Body` und `Query` machen würden: -//// tab | Python 3.9+ +{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/request_forms/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/request_forms/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/request_forms/tutorial001.py!} -``` - -//// - -Zum Beispiel stellt eine der Möglichkeiten, die OAuth2 Spezifikation zu verwenden (genannt „password flow“), die Bedingung, einen `username` und ein `password` als Formularfelder zu senden. +Zum Beispiel stellt eine der Möglichkeiten, die OAuth2-Spezifikation zu verwenden (genannt „password flow“), die Bedingung, einen `username` und ein `password` als Formularfelder zu senden. Die Spec erfordert, dass die Felder exakt `username` und `password` genannt werden und als Formularfelder, nicht JSON, gesendet werden. Mit `Form` haben Sie die gleichen Konfigurationsmöglichkeiten wie mit `Body` (und `Query`, `Path`, `Cookie`), inklusive Validierung, Beispielen, einem Alias (z. B. `user-name` statt `username`), usw. -/// info +/// info | Info `Form` ist eine Klasse, die direkt von `Body` erbt. /// -/// tip | "Tipp" +/// tip | Tipp -Um Formularbodys zu deklarieren, verwenden Sie explizit `Form`, da diese Parameter sonst als Query-Parameter oder Body(-JSON)-Parameter interpretiert werden würden. +Um Formularbodys zu deklarieren, verwenden Sie explizit `Form`, da diese Parameter sonst als Query-Parameter oder Body (JSON)-Parameter interpretiert werden würden. /// -## Über „Formularfelder“ +## Über „Formularfelder“ { #about-form-fields } -HTML-Formulare (`
`) senden die Daten in einer „speziellen“ Kodierung zum Server, welche sich von JSON unterscheidet. +HTML-Formulare (`
`) senden die Daten in einer „speziellen“ Kodierung zum Server, die sich von JSON unterscheidet. **FastAPI** stellt sicher, dass diese Daten korrekt ausgelesen werden, statt JSON zu erwarten. -/// note | "Technische Details" +/// note | Technische Details -Daten aus Formularen werden normalerweise mit dem „media type“ `application/x-www-form-urlencoded` kodiert. +Daten aus Formularen werden normalerweise mit dem „media type“ `application/x-www-form-urlencoded` kodiert. Wenn das Formular stattdessen Dateien enthält, werden diese mit `multipart/form-data` kodiert. Im nächsten Kapitel erfahren Sie mehr über die Handhabung von Dateien. -Wenn Sie mehr über Formularfelder und ihre Kodierungen lesen möchten, besuchen Sie die MDN-Webdokumentation für POST. +Wenn Sie mehr über Formularfelder und ihre Kodierungen lesen möchten, besuchen Sie die MDN-Webdokumentation für POST. /// -/// warning | "Achtung" +/// warning | Achtung -Sie können mehrere `Form`-Parameter in einer *Pfadoperation* deklarieren, aber Sie können nicht gleichzeitig auch `Body`-Felder deklarieren, welche Sie als JSON erwarten, da der Request den Body mittels `application/x-www-form-urlencoded` statt `application/json` kodiert. +Sie können mehrere `Form`-Parameter in einer *Pfadoperation* deklarieren, aber Sie können nicht gleichzeitig auch `Body`-Felder deklarieren, welche Sie als JSON erwarten, da der Request den Body mittels `application/x-www-form-urlencoded` statt `application/json` kodiert. Das ist keine Limitation von **FastAPI**, sondern Teil des HTTP-Protokolls. /// -## Zusammenfassung +## Zusammenfassung { #recap } Verwenden Sie `Form`, um Eingabe-Parameter für Formulardaten zu deklarieren. diff --git a/docs/de/docs/tutorial/response-model.md b/docs/de/docs/tutorial/response-model.md index b480780bca..7b77125cb5 100644 --- a/docs/de/docs/tutorial/response-model.md +++ b/docs/de/docs/tutorial/response-model.md @@ -1,37 +1,15 @@ -# Responsemodell – Rückgabetyp +# Responsemodell – Rückgabetyp { #response-model-return-type } -Sie können den Typ der Response deklarieren, indem Sie den **Rückgabetyp** der *Pfadoperation* annotieren. +Sie können den Typ der Response deklarieren, indem Sie den **Rückgabetyp** der *Pfadoperation* annotieren. Hierbei können Sie **Typannotationen** genauso verwenden, wie Sie es bei Werten von Funktions-**Parametern** machen; verwenden Sie Pydantic-Modelle, Listen, Dicts und skalare Werte wie Nummern, Booleans, usw. -//// tab | Python 3.10+ - -```Python hl_lines="16 21" -{!> ../../../docs_src/response_model/tutorial001_01_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="18 23" -{!> ../../../docs_src/response_model/tutorial001_01_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18 23" -{!> ../../../docs_src/response_model/tutorial001_01.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} FastAPI wird diesen Rückgabetyp verwenden, um: * Die zurückzugebenden Daten zu **validieren**. - * Wenn die Daten ungültig sind (Sie haben z. B. ein Feld vergessen), bedeutet das, *Ihr* Anwendungscode ist fehlerhaft, er gibt nicht zurück, was er sollte, und daher wird ein Server-Error ausgegeben, statt falscher Daten. So können Sie und ihre Clients sicher sein, dass diese die erwarteten Daten, in der richtigen Form erhalten. + * Wenn die Daten ungültig sind (Sie haben z. B. ein Feld vergessen), bedeutet das, *Ihr* Anwendungscode ist fehlerhaft, er gibt nicht zurück, was er sollte, und daher wird ein Server-Error ausgegeben, statt falscher Daten. So können Sie und Ihre Clients sicher sein, dass diese die erwarteten Daten, in der richtigen Form erhalten. * In der OpenAPI *Pfadoperation* ein **JSON-Schema** für die Response hinzuzufügen. * Dieses wird von der **automatischen Dokumentation** verwendet. * Es wird auch von automatisch Client-Code-generierenden Tools verwendet. @@ -41,11 +19,11 @@ Aber am wichtigsten: * Es wird die Ausgabedaten auf das **limitieren und filtern**, was im Rückgabetyp definiert ist. * Das ist insbesondere für die **Sicherheit** wichtig, mehr dazu unten. -## `response_model`-Parameter +## `response_model`-Parameter { #response-model-parameter } Es gibt Fälle, da möchten oder müssen Sie Daten zurückgeben, die nicht genau dem entsprechen, was der Typ deklariert. -Zum Beispiel könnten Sie **ein Dict zurückgeben** wollen, oder ein Datenbank-Objekt, aber **es als Pydantic-Modell deklarieren**. Auf diese Weise übernimmt das Pydantic-Modell alle Datendokumentation, -validierung, usw. für das Objekt, welches Sie zurückgeben (z. B. ein Dict oder ein Datenbank-Objekt). +Zum Beispiel könnten Sie **ein Dictionary zurückgeben** wollen, oder ein Datenbank-Objekt, aber **es als Pydantic-Modell deklarieren**. Auf diese Weise übernimmt das Pydantic-Modell alle Datendokumentation, -validierung, usw. für das Objekt, welches Sie zurückgeben (z. B. ein Dictionary oder ein Datenbank-Objekt). Würden Sie eine hierfür eine Rückgabetyp-Annotation verwenden, dann würden Tools und Editoren (korrekterweise) Fehler ausgeben, die Ihnen sagen, dass Ihre Funktion einen Typ zurückgibt (z. B. ein Dict), der sich unterscheidet von dem, was Sie deklariert haben (z. B. ein Pydantic-Modell). @@ -59,33 +37,11 @@ Sie können `response_model` in jeder möglichen *Pfadoperation* verwenden: * `@app.delete()` * usw. -//// tab | Python 3.10+ +{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001_py310.py!} -``` +/// note | Hinweis -//// - -//// tab | Python 3.9+ - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001.py!} -``` - -//// - -/// note | "Hinweis" - -Beachten Sie, dass `response_model` ein Parameter der „Dekorator“-Methode ist (`get`, `post`, usw.). Nicht der *Pfadoperation-Funktion*, so wie die anderen Parameter. +Beachten Sie, dass `response_model` ein Parameter der „Dekorator“-Methode ist (`get`, `post`, usw.). Nicht der *Pfadoperation-Funktion*, so wie die anderen Parameter und der Body. /// @@ -93,68 +49,49 @@ Beachten Sie, dass `response_model` ein Parameter der „Dekorator“-Methode is FastAPI wird dieses `response_model` nehmen, um die Daten zu dokumentieren, validieren, usw. und auch, um **die Ausgabedaten** entsprechend der Typdeklaration **zu konvertieren und filtern**. -/// tip | "Tipp" +/// tip | Tipp -Wenn Sie in Ihrem Editor strikte Typchecks haben, mypy, usw., können Sie den Funktions-Rückgabetyp als `Any` deklarieren. +Wenn Sie in Ihrem Editor strikte Typchecks haben, mypy, usw., können Sie den Funktions-Rückgabetyp als `Any` deklarieren. So sagen Sie dem Editor, dass Sie absichtlich *irgendetwas* zurückgeben. Aber FastAPI wird trotzdem die Dokumentation, Validierung, Filterung, usw. der Daten übernehmen, via `response_model`. /// -### `response_model`-Priorität +### `response_model`-Priorität { #response-model-priority } Wenn sowohl Rückgabetyp als auch `response_model` deklariert sind, hat `response_model` die Priorität und wird von FastAPI bevorzugt verwendet. -So können Sie korrekte Typannotationen zu ihrer Funktion hinzufügen, die von ihrem Editor und Tools wie mypy verwendet werden. Und dennoch übernimmt FastAPI die Validierung und Dokumentation, usw., der Daten anhand von `response_model`. +So können Sie korrekte Typannotationen zu Ihrer Funktion hinzufügen, die von Ihrem Editor und Tools wie mypy verwendet werden. Und dennoch übernimmt FastAPI die Validierung und Dokumentation, usw., der Daten anhand von `response_model`. -Sie können auch `response_model=None` verwenden, um das Erstellen eines Responsemodells für diese *Pfadoperation* zu unterbinden. Sie könnten das tun wollen, wenn sie Dinge annotieren, die nicht gültige Pydantic-Felder sind. Ein Beispiel dazu werden Sie in einer der Abschnitte unten sehen. +Sie können auch `response_model=None` verwenden, um das Erstellen eines Responsemodells für diese *Pfadoperation* zu unterbinden. Sie könnten das tun wollen, wenn Sie Dinge annotieren, die nicht gültige Pydantic-Felder sind. Ein Beispiel dazu werden Sie in einer der Abschnitte unten sehen. -## Dieselben Eingabedaten zurückgeben +## Dieselben Eingabedaten zurückgeben { #return-the-same-input-data } Im Folgenden deklarieren wir ein `UserIn`-Modell; es enthält ein Klartext-Passwort: -//// tab | Python 3.10+ +{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} -```Python hl_lines="7 9" -{!> ../../../docs_src/response_model/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 11" -{!> ../../../docs_src/response_model/tutorial002.py!} -``` - -//// - -/// info +/// info | Info Um `EmailStr` zu verwenden, installieren Sie zuerst `email-validator`. -Z. B. `pip install email-validator` -oder `pip install pydantic[email]`. +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und es dann installieren, zum Beispiel: + +```console +$ pip install email-validator +``` + +oder mit: + +```console +$ pip install "pydantic[email]" +``` /// Wir verwenden dieses Modell, um sowohl unsere Eingabe- als auch Ausgabedaten zu deklarieren: -//// tab | Python 3.10+ - -```Python hl_lines="16" -{!> ../../../docs_src/response_model/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/response_model/tutorial002.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} Immer wenn jetzt ein Browser einen Benutzer mit Passwort erzeugt, gibt die API dasselbe Passwort in der Response zurück. @@ -162,71 +99,29 @@ Hier ist das möglicherweise kein Problem, da es derselbe Benutzer ist, der das Aber wenn wir dasselbe Modell für eine andere *Pfadoperation* verwenden, könnten wir das Passwort dieses Benutzers zu jedem Client schicken. -/// danger | "Gefahr" +/// danger | Gefahr Speichern Sie niemals das Klartext-Passwort eines Benutzers, oder versenden Sie es in einer Response wie dieser, wenn Sie sich nicht der resultierenden Gefahren bewusst sind und nicht wissen, was Sie tun. /// -## Ausgabemodell hinzufügen +## Ausgabemodell hinzufügen { #add-an-output-model } Wir können stattdessen ein Eingabemodell mit dem Klartext-Passwort, und ein Ausgabemodell ohne das Passwort erstellen: -//// tab | Python 3.10+ - -```Python hl_lines="9 11 16" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 11 16" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} Obwohl unsere *Pfadoperation-Funktion* hier denselben `user` von der Eingabe zurückgibt, der das Passwort enthält: -//// tab | Python 3.10+ - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} ... haben wir deklariert, dass `response_model` das Modell `UserOut` ist, welches das Passwort nicht enthält: -//// tab | Python 3.10+ - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} Darum wird **FastAPI** sich darum kümmern, dass alle Daten, die nicht im Ausgabemodell deklariert sind, herausgefiltert werden (mittels Pydantic). -### `response_model` oder Rückgabewert +### `response_model` oder Rückgabewert { #response-model-or-return-type } Da unsere zwei Modelle in diesem Fall unterschiedlich sind, würde, wenn wir den Rückgabewert der Funktion als `UserOut` deklarieren, der Editor sich beschweren, dass wir einen ungültigen Typ zurückgeben, weil das unterschiedliche Klassen sind. @@ -234,11 +129,11 @@ Darum müssen wir es in diesem Fall im `response_model`-Parameter deklarieren. ... aber lesen Sie weiter, um zu sehen, wie man das anders lösen kann. -## Rückgabewert und Datenfilterung +## Rückgabewert und Datenfilterung { #return-type-and-data-filtering } -Führen wir unser vorheriges Beispiel fort. Wir wollten **die Funktion mit einem Typ annotieren**, aber etwas zurückgeben, das **weniger Daten** enthält. +Führen wir unser vorheriges Beispiel fort. Wir wollten **die Funktion mit einem Typ annotieren**, aber wir wollten in der Funktion tatsächlich etwas zurückgeben, das **mehr Daten** enthält. -Wir möchten auch, dass FastAPI die Daten weiterhin, dem Responsemodell entsprechend, **filtert**. +Wir möchten, dass FastAPI die Daten weiterhin mithilfe des Responsemodells **filtert**. Selbst wenn die Funktion mehr Daten zurückgibt, soll die Response nur die Felder enthalten, die im Responsemodell deklariert sind. Im vorherigen Beispiel mussten wir den `response_model`-Parameter verwenden, weil die Klassen unterschiedlich waren. Das bedeutet aber auch, wir bekommen keine Unterstützung vom Editor und anderen Tools, die den Funktions-Rückgabewert überprüfen. @@ -246,37 +141,23 @@ Aber in den meisten Fällen, wenn wir so etwas machen, wollen wir nur, dass das Und in solchen Fällen können wir Klassen und Vererbung verwenden, um Vorteil aus den Typannotationen in der Funktion zu ziehen, was vom Editor und von Tools besser unterstützt wird, während wir gleichzeitig FastAPIs **Datenfilterung** behalten. -//// tab | Python 3.10+ - -```Python hl_lines="7-10 13-14 18" -{!> ../../../docs_src/response_model/tutorial003_01_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-13 15-16 20" -{!> ../../../docs_src/response_model/tutorial003_01.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} Damit erhalten wir Tool-Unterstützung, vom Editor und mypy, da dieser Code hinsichtlich der Typen korrekt ist, aber wir erhalten auch die Datenfilterung von FastAPI. Wie funktioniert das? Schauen wir uns das mal an. 🤓 -### Typannotationen und Tooling +### Typannotationen und Tooling { #type-annotations-and-tooling } Sehen wir uns zunächst an, wie Editor, mypy und andere Tools dies sehen würden. -`BaseUser` verfügt über die Basis-Felder. Dann erbt `UserIn` von `BaseUser` und fügt das Feld `Passwort` hinzu, sodass dass es nun alle Felder beider Modelle hat. +`BaseUser` verfügt über die Basis-Felder. Dann erbt `UserIn` von `BaseUser` und fügt das Feld `password` hinzu, sodass es nun alle Felder beider Modelle hat. Wir annotieren den Funktionsrückgabetyp als `BaseUser`, geben aber tatsächlich eine `UserIn`-Instanz zurück. Für den Editor, mypy und andere Tools ist das kein Problem, da `UserIn` eine Unterklasse von `BaseUser` ist (Salopp: `UserIn` ist ein `BaseUser`). Es handelt sich um einen *gültigen* Typ, solange irgendetwas überreicht wird, das ein `BaseUser` ist. -### FastAPI Datenfilterung +### FastAPI Datenfilterung { #fastapi-data-filtering } FastAPI seinerseits wird den Rückgabetyp sehen und sicherstellen, dass das, was zurückgegeben wird, **nur** diejenigen Felder enthält, welche im Typ deklariert sind. @@ -284,7 +165,7 @@ FastAPI macht intern mehrere Dinge mit Pydantic, um sicherzustellen, dass obige Auf diese Weise erhalten Sie das beste beider Welten: Sowohl Typannotationen mit **Tool-Unterstützung** als auch **Datenfilterung**. -## Anzeige in der Dokumentation +## Anzeige in der Dokumentation { #see-it-in-the-docs } Wenn Sie sich die automatische Dokumentation betrachten, können Sie sehen, dass Eingabe- und Ausgabemodell beide ihr eigenes JSON-Schema haben: @@ -294,57 +175,39 @@ Und beide Modelle werden auch in der interaktiven API-Dokumentation verwendet: -## Andere Rückgabetyp-Annotationen +## Andere Rückgabetyp-Annotationen { #other-return-type-annotations } Es kann Fälle geben, bei denen Sie etwas zurückgeben, das kein gültiges Pydantic-Feld ist, und Sie annotieren es in der Funktion nur, um Unterstützung von Tools zu erhalten (Editor, mypy, usw.). -### Eine Response direkt zurückgeben +### Eine Response direkt zurückgeben { #return-a-response-directly } Der häufigste Anwendungsfall ist, wenn Sie [eine Response direkt zurückgeben, wie es später im Handbuch für fortgeschrittene Benutzer erläutert wird](../advanced/response-directly.md){.internal-link target=_blank}. -```Python hl_lines="8 10-11" -{!> ../../../docs_src/response_model/tutorial003_02.py!} -``` +{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} Dieser einfache Anwendungsfall wird automatisch von FastAPI gehandhabt, weil die Annotation des Rückgabetyps die Klasse (oder eine Unterklasse von) `Response` ist. Und Tools werden auch glücklich sein, weil sowohl `RedirectResponse` als auch `JSONResponse` Unterklassen von `Response` sind, die Typannotation ist daher korrekt. -### Eine Unterklasse von Response annotieren +### Eine Unterklasse von Response annotieren { #annotate-a-response-subclass } Sie können auch eine Unterklasse von `Response` in der Typannotation verwenden. -```Python hl_lines="8-9" -{!> ../../../docs_src/response_model/tutorial003_03.py!} -``` +{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} Das wird ebenfalls funktionieren, weil `RedirectResponse` eine Unterklasse von `Response` ist, und FastAPI sich um diesen einfachen Anwendungsfall automatisch kümmert. -### Ungültige Rückgabetyp-Annotationen +### Ungültige Rückgabetyp-Annotationen { #invalid-return-type-annotations } Aber wenn Sie ein beliebiges anderes Objekt zurückgeben, das kein gültiger Pydantic-Typ ist (z. B. ein Datenbank-Objekt), und Sie annotieren es so in der Funktion, wird FastAPI versuchen, ein Pydantic-Responsemodell von dieser Typannotation zu erstellen, und scheitern. -Das gleiche wird passieren, wenn Sie eine Union mehrerer Typen haben, und einer oder mehrere sind nicht gültige Pydantic-Typen. Zum Beispiel funktioniert folgendes nicht 💥: +Das gleiche wird passieren, wenn Sie eine Union mehrerer Typen haben, und einer oder mehrere sind nicht gültige Pydantic-Typen. Zum Beispiel funktioniert folgendes nicht 💥: -//// tab | Python 3.10+ - -```Python hl_lines="8" -{!> ../../../docs_src/response_model/tutorial003_04_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/response_model/tutorial003_04.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} ... das scheitert, da die Typannotation kein Pydantic-Typ ist, und auch keine einzelne `Response`-Klasse, oder -Unterklasse, es ist eine Union (eines von beiden) von `Response` und `dict`. -### Responsemodell deaktivieren +### Responsemodell deaktivieren { #disable-response-model } Beim Beispiel oben fortsetzend, mögen Sie vielleicht die standardmäßige Datenvalidierung, -Dokumentation, -Filterung, usw., die von FastAPI durchgeführt wird, nicht haben. @@ -352,51 +215,15 @@ Aber Sie möchten dennoch den Rückgabetyp in der Funktion annotieren, um Unters In diesem Fall können Sie die Generierung des Responsemodells abschalten, indem Sie `response_model=None` setzen: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/response_model/tutorial003_05_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/response_model/tutorial003_05.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} Das bewirkt, dass FastAPI die Generierung des Responsemodells unterlässt, und damit können Sie jede gewünschte Rückgabetyp-Annotation haben, ohne dass es Ihre FastAPI-Anwendung beeinflusst. 🤓 -## Parameter für die Enkodierung des Responsemodells +## Parameter für die Enkodierung des Responsemodells { #response-model-encoding-parameters } Ihr Responsemodell könnte Defaultwerte haben, wie: -//// tab | Python 3.10+ - -```Python hl_lines="9 11-12" -{!> ../../../docs_src/response_model/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11 13-14" -{!> ../../../docs_src/response_model/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11 13-14" -{!> ../../../docs_src/response_model/tutorial004.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *} * `description: Union[str, None] = None` (oder `str | None = None` in Python 3.10) hat einen Defaultwert `None`. * `tax: float = 10.5` hat einen Defaultwert `10.5`. @@ -406,33 +233,11 @@ Aber Sie möchten diese vielleicht vom Resultat ausschließen, wenn Sie gar nich Wenn Sie zum Beispiel Modelle mit vielen optionalen Attributen in einer NoSQL-Datenbank haben, und Sie möchten nicht ellenlange JSON-Responses voller Defaultwerte senden. -### Den `response_model_exclude_unset`-Parameter verwenden +### Den `response_model_exclude_unset`-Parameter verwenden { #use-the-response-model-exclude-unset-parameter } Sie können den *Pfadoperation-Dekorator*-Parameter `response_model_exclude_unset=True` setzen: -//// tab | Python 3.10+ - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial004.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *} Die Defaultwerte werden dann nicht in der Response enthalten sein, sondern nur die tatsächlich gesetzten Werte. @@ -445,21 +250,21 @@ Wenn Sie also den Artikel mit der ID `foo` bei der *Pfadoperation* anfragen, wir } ``` -/// info +/// info | Info -In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie deprecated (aber immer noch unterstützt) und in `.model_dump()` umbenannt. +In Pydantic v1 hieß diese Methode `.dict()`, in Pydantic v2 wurde sie deprecatet (aber immer noch unterstützt) und in `.model_dump()` umbenannt. Die Beispiele hier verwenden `.dict()` für die Kompatibilität mit Pydantic v1, Sie sollten jedoch stattdessen `.model_dump()` verwenden, wenn Sie Pydantic v2 verwenden können. /// -/// info +/// info | Info FastAPI verwendet `.dict()` von Pydantic Modellen, mit dessen `exclude_unset`-Parameter, um das zu erreichen. /// -/// info +/// info | Info Sie können auch: @@ -470,9 +275,9 @@ verwenden, wie in der ../../../docs_src/response_model/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="31 37" -{!> ../../../docs_src/response_model/tutorial005.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Die Syntax `{"name", "description"}` erzeugt ein `set` mit diesen zwei Werten. @@ -553,27 +344,13 @@ Die Syntax `{"name", "description"}` erzeugt ein `set` mit diesen zwei Werten. /// -#### `list`en statt `set`s verwenden +#### `list`en statt `set`s verwenden { #using-lists-instead-of-sets } Wenn Sie vergessen, ein `set` zu verwenden, und stattdessen eine `list`e oder ein `tuple` übergeben, wird FastAPI die dennoch in ein `set` konvertieren, und es wird korrekt funktionieren: -//// tab | Python 3.10+ +{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} -```Python hl_lines="29 35" -{!> ../../../docs_src/response_model/tutorial006_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="31 37" -{!> ../../../docs_src/response_model/tutorial006.py!} -``` - -//// - -## Zusammenfassung +## Zusammenfassung { #recap } Verwenden Sie den Parameter `response_model` im *Pfadoperation-Dekorator*, um Responsemodelle zu definieren, und besonders, um private Daten herauszufiltern. diff --git a/docs/de/docs/tutorial/response-status-code.md b/docs/de/docs/tutorial/response-status-code.md index 5f96b83e4c..928003c3fc 100644 --- a/docs/de/docs/tutorial/response-status-code.md +++ b/docs/de/docs/tutorial/response-status-code.md @@ -1,6 +1,6 @@ -# Response-Statuscode +# Response-Statuscode { #response-status-code } -So wie ein Responsemodell, können Sie auch einen HTTP-Statuscode für die Response deklarieren, mithilfe des Parameters `status_code`, und zwar in jeder der *Pfadoperationen*: +Genauso wie Sie ein Responsemodell angeben können, können Sie auch den HTTP-Statuscode für die Response mit dem Parameter `status_code` in jeder der *Pfadoperationen* deklarieren: * `@app.get()` * `@app.post()` @@ -8,100 +8,94 @@ So wie ein Responsemodell, können Sie auch einen HTTP-Statuscode für die Respo * `@app.delete()` * usw. -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} -/// note | "Hinweis" +/// note | Hinweis -Beachten Sie, dass `status_code` ein Parameter der „Dekorator“-Methode ist (`get`, `post`, usw.). Nicht der *Pfadoperation-Funktion*, so wie die anderen Parameter und der Body. +Beachten Sie, dass `status_code` ein Parameter der „Dekorator“-Methode ist (`get`, `post`, usw.). Nicht der *Pfadoperation-Funktion*, wie alle anderen Parameter und der Body. /// Dem `status_code`-Parameter wird eine Zahl mit dem HTTP-Statuscode übergeben. -/// info +/// info | Info -Alternativ kann `status_code` auch ein `IntEnum` erhalten, so wie Pythons `http.HTTPStatus`. +Alternativ kann `status_code` auch ein `IntEnum` erhalten, wie etwa Pythons `http.HTTPStatus`. /// -Das wird: +Dies wird: * Diesen Statuscode mit der Response zurücksenden. -* Ihn als solchen im OpenAPI-Schema dokumentieren (und somit in den Benutzeroberflächen): +* Diesen im OpenAPI-Schema dokumentieren (und somit in den Benutzeroberflächen): -/// note | "Hinweis" +/// note | Hinweis -Einige Responsecodes (siehe nächster Abschnitt) kennzeichnen, dass die Response keinen Body hat. +Einige Responsecodes (siehe nächsten Abschnitt) kennzeichnen, dass die Response keinen Body hat. -FastAPI versteht das und wird in der OpenAPI-Dokumentation anzeigen, dass es keinen Responsebody gibt. +FastAPI erkennt dies und erstellt eine OpenAPI-Dokumentation, die zeigt, dass es keinen Responsebody gibt. /// -## Über HTTP-Statuscodes +## Über HTTP-Statuscodes { #about-http-status-codes } -/// note | "Hinweis" +/// note | Hinweis -Wenn Sie bereits wissen, was HTTP-Statuscodes sind, überspringen Sie dieses Kapitel und fahren Sie mit dem nächsten fort. +Wenn Sie bereits wissen, was HTTP-Statuscodes sind, können Sie diesen Abschnitt überspringen und mit dem nächsten fortfahren. /// -In HTTP senden Sie als Teil der Response einen aus drei Ziffern bestehenden numerischen Statuscode. +In HTTP senden Sie einen numerischen Statuscode mit 3 Ziffern als Teil der Response. -Diese Statuscodes haben einen Namen zugeordnet, um sie besser zu erkennen, aber der wichtige Teil ist die Zahl. +Diese Statuscodes haben einen zugeordneten Namen, um sie leichter zu erkennen, aber der wichtige Teil ist die Zahl. -Kurz: +Kurz gefasst: -* `100` und darüber stehen für „Information“. Diese verwenden Sie selten direkt. Responses mit diesen Statuscodes können keinen Body haben. -* **`200`** und darüber stehen für Responses, die „Successful“ („Erfolgreich“) waren. Diese verwenden Sie am häufigsten. - * `200` ist der Default-Statuscode, welcher bedeutet, alles ist „OK“. - * Ein anderes Beispiel ist `201`, „Created“ („Erzeugt“). Wird in der Regel verwendet, wenn ein neuer Datensatz in der Datenbank erzeugt wurde. - * Ein spezieller Fall ist `204`, „No Content“ („Kein Inhalt“). Diese Response wird verwendet, wenn es keinen Inhalt gibt, der zum Client zurückgeschickt wird, diese Response hat also keinen Body. -* **`300`** und darüber steht für „Redirection“ („Umleitung“). Responses mit diesen Statuscodes können einen oder keinen Body haben, mit Ausnahme von `304`, „Not Modified“ („Nicht verändert“), welche keinen haben darf. -* **`400`** und darüber stehen für „Client error“-Responses („Client-Fehler“). Auch diese verwenden Sie am häufigsten. +* `100 - 199` stehen für „Information“. Sie verwenden diese selten direkt. Responses mit diesen Statuscodes dürfen keinen Body haben. +* **`200 - 299`** stehen für „Successful“-Responses („Erfolgreich“). Diese werden Sie am häufigsten verwenden. + * `200` ist der Default-Statuscode, was bedeutet, alles ist „OK“. + * Ein weiteres Beispiel wäre `201`, „Created“ („Erzeugt“). Dieser wird üblicherweise verwendet, nachdem ein neuer Datensatz in der Datenbank erstellt wurde. + * Ein spezieller Fall ist `204`, „No Content“ („Kein Inhalt“). Diese Response wird verwendet, wenn es keinen Inhalt gibt, der an den Client zurückgeschickt werden soll, und diese Response darf daher keinen Body haben. +* **`300 - 399`** stehen für „Redirection“ („Umleitung“). Responses mit diesen Statuscodes können einen Body haben oder nicht, außer bei `304`, „Not Modified“ („Nicht verändert“), die keinen haben darf. +* **`400 - 499`** stehen für „Client error“-Responses („Client-Fehler“). Diese sind die zweithäufigsten, die Sie vermutlich verwenden werden. * Ein Beispiel ist `404`, für eine „Not Found“-Response („Nicht gefunden“). * Für allgemeine Fehler beim Client können Sie einfach `400` verwenden. -* `500` und darüber stehen für Server-Fehler. Diese verwenden Sie fast nie direkt. Wenn etwas an irgendeiner Stelle in Ihrem Anwendungscode oder im Server schiefläuft, wird automatisch einer dieser Fehler-Statuscodes zurückgegeben. +* `500 - 599` stehen für Server-Fehler. Diese verwenden Sie fast nie direkt. Wenn in Ihrem Anwendungscode oder Server etwas schiefgeht, wird automatisch einer dieser Fehler-Statuscodes zurückgegeben. -/// tip | "Tipp" +/// tip | Tipp -Um mehr über Statuscodes zu lernen, und welcher wofür verwendet wird, lesen Sie die MDN Dokumentation über HTTP-Statuscodes. +Um mehr über die einzelnen Statuscodes zu erfahren und welcher wofür verwendet wird, sehen Sie sich die MDN Dokumentation über HTTP-Statuscodes an. /// -## Abkürzung, um die Namen zu erinnern +## Abkürzung zur Erinnerung an die Namen { #shortcut-to-remember-the-names } -Schauen wir uns das vorherige Beispiel noch einmal an: +Lassen Sie uns das vorherige Beispiel noch einmal anschauen: -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} `201` ist der Statuscode für „Created“ („Erzeugt“). -Aber Sie müssen sich nicht daran erinnern, welcher dieser Codes was bedeutet. +Aber Sie müssen sich nicht merken, was jeder dieser Codes bedeutet. -Sie können die Hilfsvariablen von `fastapi.status` verwenden. +Sie können die Annehmlichkeit von Variablen aus `fastapi.status` nutzen. -```Python hl_lines="1 6" -{!../../../docs_src/response_status_code/tutorial002.py!} -``` +{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} -Diese sind nur eine Annehmlichkeit und enthalten dieselbe Nummer, aber auf diese Weise können Sie die Autovervollständigung Ihres Editors verwenden, um sie zu finden: +Diese sind nur eine Annehmlichkeit, sie enthalten dieselbe Zahl, aber so können Sie die Autovervollständigung Ihres Editors verwenden, um sie zu finden: -/// note | "Technische Details" +/// note | Technische Details -Sie können auch `from starlette import status` verwenden. +Sie könnten auch `from starlette import status` verwenden. -**FastAPI** bietet dieselben `starlette.status`-Codes auch via `fastapi.status` an, als Annehmlichkeit für Sie, den Entwickler. Sie kommen aber direkt von Starlette. +**FastAPI** bietet dieselben `starlette.status`-Codes auch via `fastapi.status` an, rein zu Ihrer Annehmlichkeit als Entwickler. Aber sie stammen direkt von Starlette. /// -## Den Defaultwert ändern +## Den Defaultwert ändern { #changing-the-default } -Später sehen Sie, im [Handbuch für fortgeschrittene Benutzer](../advanced/response-change-status-code.md){.internal-link target=_blank}, wie Sie einen anderen Statuscode zurückgeben können, als den Default, den Sie hier deklarieren. +Später im [Handbuch für fortgeschrittene Benutzer](../advanced/response-change-status-code.md){.internal-link target=_blank} werden Sie sehen, wie Sie einen anderen Statuscode zurückgeben können, als den Default, den Sie hier deklarieren. diff --git a/docs/de/docs/tutorial/schema-extra-example.md b/docs/de/docs/tutorial/schema-extra-example.md index 07b4bb7596..e2ffed292e 100644 --- a/docs/de/docs/tutorial/schema-extra-example.md +++ b/docs/de/docs/tutorial/schema-extra-example.md @@ -1,42 +1,22 @@ -# Beispiel-Request-Daten deklarieren +# Beispiel-Request-Daten deklarieren { #declare-request-example-data } -Sie können Beispiele für die Daten deklarieren, die Ihre Anwendung empfangen kann. +Sie können Beispiele für die Daten deklarieren, die Ihre App empfangen kann. Hier sind mehrere Möglichkeiten, das zu tun. -## Zusätzliche JSON-Schemadaten in Pydantic-Modellen +## Zusätzliche JSON-Schemadaten in Pydantic-Modellen { #extra-json-schema-data-in-pydantic-models } Sie können `examples` („Beispiele“) für ein Pydantic-Modell deklarieren, welche dem generierten JSON-Schema hinzugefügt werden. -//// tab | Python 3.10+ Pydantic v2 +//// tab | Pydantic v2 -```Python hl_lines="13-24" -{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} //// -//// tab | Python 3.10+ Pydantic v1 +//// tab | Pydantic v1 -```Python hl_lines="13-23" -{!> ../../../docs_src/schema_extra_example/tutorial001_py310_pv1.py!} -``` - -//// - -//// tab | Python 3.8+ Pydantic v2 - -```Python hl_lines="15-26" -{!> ../../../docs_src/schema_extra_example/tutorial001.py!} -``` - -//// - -//// tab | Python 3.8+ Pydantic v1 - -```Python hl_lines="15-25" -{!> ../../../docs_src/schema_extra_example/tutorial001_pv1.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} //// @@ -44,7 +24,7 @@ Diese zusätzlichen Informationen werden unverändert zum für dieses Modell aus //// tab | Pydantic v2 -In Pydantic Version 2 würden Sie das Attribut `model_config` verwenden, das ein `dict` akzeptiert, wie beschrieben in Pydantic-Dokumentation: Configuration. +In Pydantic Version 2 würden Sie das Attribut `model_config` verwenden, das ein `dict` akzeptiert, wie beschrieben in Pydantic-Dokumentation: Configuration. Sie können `json_schema_extra` setzen, mit einem `dict`, das alle zusätzlichen Daten enthält, die im generierten JSON-Schema angezeigt werden sollen, einschließlich `examples`. @@ -58,7 +38,7 @@ Sie können `schema_extra` setzen, mit einem `dict`, das alle zusätzlichen Date //// -/// tip | "Tipp" +/// tip | Tipp Mit derselben Technik können Sie das JSON-Schema erweitern und Ihre eigenen benutzerdefinierten Zusatzinformationen hinzufügen. @@ -66,37 +46,23 @@ Sie könnten das beispielsweise verwenden, um Metadaten für eine Frontend-Benut /// -/// info +/// info | Info OpenAPI 3.1.0 (verwendet seit FastAPI 0.99.0) hat Unterstützung für `examples` hinzugefügt, was Teil des **JSON Schema** Standards ist. -Zuvor unterstützte es nur das Schlüsselwort `example` mit einem einzigen Beispiel. Dieses wird weiterhin von OpenAPI 3.1.0 unterstützt, ist jedoch deprecated und nicht Teil des JSON Schema Standards. Wir empfehlen Ihnen daher, von `example` nach `examples` zu migrieren. 🤓 +Zuvor unterstützte es nur das Schlüsselwort `example` mit einem einzigen Beispiel. Dieses wird weiterhin von OpenAPI 3.1.0 unterstützt, ist jedoch deprecatet und nicht Teil des JSON Schema Standards. Wir empfehlen Ihnen daher, von `example` nach `examples` zu migrieren. 🤓 Mehr erfahren Sie am Ende dieser Seite. /// -## Zusätzliche Argumente für `Field` +## Zusätzliche Argumente für `Field` { #field-additional-arguments } Wenn Sie `Field()` mit Pydantic-Modellen verwenden, können Sie ebenfalls zusätzliche `examples` deklarieren: -//// tab | Python 3.10+ +{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} -```Python hl_lines="2 8-11" -{!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 10-13" -{!> ../../../docs_src/schema_extra_example/tutorial002.py!} -``` - -//// - -## `examples` im JSON-Schema – OpenAPI +## `examples` im JSON-Schema – OpenAPI { #examples-in-json-schema-openapi } Bei Verwendung von: @@ -110,129 +76,29 @@ Bei Verwendung von: können Sie auch eine Gruppe von `examples` mit zusätzlichen Informationen deklarieren, die zu ihren **JSON-Schemas** innerhalb von **OpenAPI** hinzugefügt werden. -### `Body` mit `examples` +### `Body` mit `examples` { #body-with-examples } Hier übergeben wir `examples`, welches ein einzelnes Beispiel für die in `Body()` erwarteten Daten enthält: -//// tab | Python 3.10+ +{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} -```Python hl_lines="22-29" -{!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="22-29" -{!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23-30" -{!> ../../../docs_src/schema_extra_example/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="18-25" -{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="20-27" -{!> ../../../docs_src/schema_extra_example/tutorial003.py!} -``` - -//// - -### Beispiel in der Dokumentations-Benutzeroberfläche +### Beispiel in der Dokumentations-Benutzeroberfläche { #example-in-the-docs-ui } Mit jeder der oben genannten Methoden würde es in `/docs` so aussehen: -### `Body` mit mehreren `examples` +### `Body` mit mehreren `examples` { #body-with-multiple-examples } Sie können natürlich auch mehrere `examples` übergeben: -//// tab | Python 3.10+ - -```Python hl_lines="23-38" -{!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="23-38" -{!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24-39" -{!> ../../../docs_src/schema_extra_example/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19-34" -{!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="21-36" -{!> ../../../docs_src/schema_extra_example/tutorial004.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} Wenn Sie das tun, werden die Beispiele Teil des internen **JSON-Schemas** für diese Body-Daten. -Während dies geschrieben wird, unterstützt Swagger UI, das für die Anzeige der Dokumentations-Benutzeroberfläche zuständige Tool, jedoch nicht die Anzeige mehrerer Beispiele für die Daten in **JSON Schema**. Aber lesen Sie unten für einen Workaround weiter. +Während dies geschrieben wird, unterstützt Swagger UI, das für die Anzeige der Dokumentations-Benutzeroberfläche zuständige Tool, jedoch nicht die Anzeige mehrerer Beispiele für die Daten in **JSON Schema**. Aber lesen Sie unten für einen Workaround weiter. -### OpenAPI-spezifische `examples` +### OpenAPI-spezifische `examples` { #openapi-specific-examples } Schon bevor **JSON Schema** `examples` unterstützte, unterstützte OpenAPI ein anderes Feld, das auch `examples` genannt wurde. @@ -240,11 +106,11 @@ Diese **OpenAPI-spezifischen** `examples` finden sich in einem anderen Abschnitt Und Swagger UI unterstützt dieses spezielle Feld `examples` schon seit einiger Zeit. Sie können es also verwenden, um verschiedene **Beispiele in der Benutzeroberfläche der Dokumentation anzuzeigen**. -Das Format dieses OpenAPI-spezifischen Felds `examples` ist ein `dict` mit **mehreren Beispielen** (anstelle einer `list`e), jedes mit zusätzlichen Informationen, die auch zu **OpenAPI** hinzugefügt werden. +Das Format dieses OpenAPI-spezifischen Felds `examples` ist ein `dict` mit **mehreren Beispielen** (anstelle einer `list`), jedes mit zusätzlichen Informationen, die auch zu **OpenAPI** hinzugefügt werden. Dies erfolgt nicht innerhalb jedes in OpenAPI enthaltenen JSON-Schemas, sondern außerhalb, in der *Pfadoperation*. -### Verwendung des Parameters `openapi_examples` +### Verwendung des Parameters `openapi_examples` { #using-the-openapi-examples-parameter } Sie können die OpenAPI-spezifischen `examples` in FastAPI mit dem Parameter `openapi_examples` deklarieren, für: @@ -256,7 +122,7 @@ Sie können die OpenAPI-spezifischen `examples` in FastAPI mit dem Parameter `op * `Form()` * `File()` -Die Schlüssel des `dict` identifizieren jedes Beispiel, und jeder Wert (`"value"`) ist ein weiteres `dict`. +Die Schlüssel des `dict` identifizieren jedes Beispiel, und jeder Wert ist ein weiteres `dict`. Jedes spezifische Beispiel-`dict` in den `examples` kann Folgendes enthalten: @@ -267,67 +133,17 @@ Jedes spezifische Beispiel-`dict` in den `examples` kann Folgendes enthalten: Sie können es so verwenden: -//// tab | Python 3.10+ +{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} -```Python hl_lines="23-49" -{!> ../../../docs_src/schema_extra_example/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="23-49" -{!> ../../../docs_src/schema_extra_example/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24-50" -{!> ../../../docs_src/schema_extra_example/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19-45" -{!> ../../../docs_src/schema_extra_example/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="21-47" -{!> ../../../docs_src/schema_extra_example/tutorial005.py!} -``` - -//// - -### OpenAPI-Beispiele in der Dokumentations-Benutzeroberfläche +### OpenAPI-Beispiele in der Dokumentations-Benutzeroberfläche { #openapi-examples-in-the-docs-ui } Wenn `openapi_examples` zu `Body()` hinzugefügt wird, würde `/docs` so aussehen: -## Technische Details +## Technische Details { #technical-details } -/// tip | "Tipp" +/// tip | Tipp Wenn Sie bereits **FastAPI** Version **0.99.0 oder höher** verwenden, können Sie diese Details wahrscheinlich **überspringen**. @@ -337,7 +153,7 @@ Sie können dies als eine kurze **Geschichtsstunde** zu OpenAPI und JSON Schema /// -/// warning | "Achtung" +/// warning | Achtung Dies sind sehr technische Details zu den Standards **JSON Schema** und **OpenAPI**. @@ -361,23 +177,23 @@ OpenAPI fügte auch die Felder `example` und `examples` zu anderen Teilen der Sp * `File()` * `Form()` -/// info +/// info | Info Dieser alte, OpenAPI-spezifische `examples`-Parameter heißt seit FastAPI `0.103.0` jetzt `openapi_examples`. /// -### JSON Schemas Feld `examples` +### JSON Schemas Feld `examples` { #json-schemas-examples-field } Aber dann fügte JSON Schema ein `examples`-Feld zu einer neuen Version der Spezifikation hinzu. Und dann basierte das neue OpenAPI 3.1.0 auf der neuesten Version (JSON Schema 2020-12), die dieses neue Feld `examples` enthielt. -Und jetzt hat dieses neue `examples`-Feld Vorrang vor dem alten (und benutzerdefinierten) `example`-Feld, im Singular, das jetzt deprecated ist. +Und jetzt hat dieses neue `examples`-Feld Vorrang vor dem alten (und benutzerdefinierten) `example`-Feld, im Singular, das jetzt deprecatet ist. -Dieses neue `examples`-Feld in JSON Schema ist **nur eine `list`e** von Beispielen, kein Dict mit zusätzlichen Metadaten wie an den anderen Stellen in OpenAPI (oben beschrieben). +Dieses neue `examples`-Feld in JSON Schema ist **nur eine `list`** von Beispielen, kein Dict mit zusätzlichen Metadaten wie an den anderen Stellen in OpenAPI (oben beschrieben). -/// info +/// info | Info Selbst, nachdem OpenAPI 3.1.0 veröffentlicht wurde, mit dieser neuen, einfacheren Integration mit JSON Schema, unterstützte Swagger UI, das Tool, das die automatische Dokumentation bereitstellt, eine Zeit lang OpenAPI 3.1.0 nicht (das tut es seit Version 5.0.0 🎉). @@ -385,7 +201,7 @@ Aus diesem Grund verwendeten Versionen von FastAPI vor 0.99.0 immer noch Version /// -### Pydantic- und FastAPI-`examples` +### Pydantic- und FastAPI-`examples` { #pydantic-and-fastapi-examples } Wenn Sie `examples` innerhalb eines Pydantic-Modells hinzufügen, indem Sie `schema_extra` oder `Field(examples=["something"])` verwenden, wird dieses Beispiel dem **JSON-Schema** für dieses Pydantic-Modell hinzugefügt. @@ -395,14 +211,14 @@ In Versionen von FastAPI vor 0.99.0 (0.99.0 und höher verwenden das neuere Open Aber jetzt, da FastAPI 0.99.0 und höher, OpenAPI 3.1.0 verwendet, das JSON Schema 2020-12 verwendet, und Swagger UI 5.0.0 und höher, ist alles konsistenter und die Beispiele sind in JSON Schema enthalten. -### Swagger-Benutzeroberfläche und OpenAPI-spezifische `examples`. +### Swagger-Benutzeroberfläche und OpenAPI-spezifische `examples` { #swagger-ui-and-openapi-specific-examples } Da die Swagger-Benutzeroberfläche derzeit nicht mehrere JSON Schema Beispiele unterstützt (Stand: 26.08.2023), hatten Benutzer keine Möglichkeit, mehrere Beispiele in der Dokumentation anzuzeigen. Um dieses Problem zu lösen, hat FastAPI `0.103.0` **Unterstützung** für die Deklaration desselben alten **OpenAPI-spezifischen** `examples`-Felds mit dem neuen Parameter `openapi_examples` hinzugefügt. 🤓 -### Zusammenfassung +### Zusammenfassung { #summary } Ich habe immer gesagt, dass ich Geschichte nicht so sehr mag ... und jetzt schauen Sie mich an, wie ich „Technikgeschichte“-Unterricht gebe. 😅 -Kurz gesagt: **Upgraden Sie auf FastAPI 0.99.0 oder höher**, und die Dinge sind viel **einfacher, konsistenter und intuitiver**, und Sie müssen nicht alle diese historischen Details kennen. 😎 +Kurz gesagt: **Aktualisieren Sie auf FastAPI 0.99.0 oder höher**, und die Dinge sind viel **einfacher, konsistenter und intuitiver**, und Sie müssen nicht alle diese historischen Details kennen. 😎 diff --git a/docs/de/docs/tutorial/security/first-steps.md b/docs/de/docs/tutorial/security/first-steps.md index 6bc42cf6c3..20fcd0c007 100644 --- a/docs/de/docs/tutorial/security/first-steps.md +++ b/docs/de/docs/tutorial/security/first-steps.md @@ -1,8 +1,8 @@ -# Sicherheit – Erste Schritte +# Sicherheit – Erste Schritte { #security-first-steps } Stellen wir uns vor, dass Sie Ihre **Backend**-API auf einer Domain haben. -Und Sie haben ein **Frontend** auf einer anderen Domain oder in einem anderen Pfad derselben Domain (oder in einer mobilen Anwendung). +Und Sie haben ein **Frontend** auf einer anderen Domain oder in einem anderen Pfad derselben Domain (oder in einer Mobile-Anwendung). Und Sie möchten eine Möglichkeit haben, dass sich das Frontend mithilfe eines **Benutzernamens** und eines **Passworts** beim Backend authentisieren kann. @@ -12,53 +12,33 @@ Aber ersparen wir Ihnen die Zeit, die gesamte lange Spezifikation zu lesen, nur Lassen Sie uns die von **FastAPI** bereitgestellten Tools verwenden, um Sicherheit zu gewährleisten. -## Wie es aussieht +## Wie es aussieht { #how-it-looks } Lassen Sie uns zunächst einfach den Code verwenden und sehen, wie er funktioniert, und dann kommen wir zurück, um zu verstehen, was passiert. -## `main.py` erstellen +## `main.py` erstellen { #create-main-py } Kopieren Sie das Beispiel in eine Datei `main.py`: -//// tab | Python 3.9+ +{* ../../docs_src/security/tutorial001_an_py39.py *} -```Python -{!> ../../../docs_src/security/tutorial001_an_py39.py!} +## Ausführen { #run-it } + +/// info | Info + +Das Paket `python-multipart` wird automatisch mit **FastAPI** installiert, wenn Sie den Befehl `pip install "fastapi[standard]"` ausführen. + +Wenn Sie jedoch den Befehl `pip install fastapi` verwenden, ist das Paket `python-multipart` nicht standardmäßig enthalten. + +Um es manuell zu installieren, stellen Sie sicher, dass Sie eine [Virtuelle Umgebung](../../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und es dann mit: + +```console +$ pip install python-multipart ``` -//// +installieren. -//// tab | Python 3.8+ - -```Python -{!> ../../../docs_src/security/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python -{!> ../../../docs_src/security/tutorial001.py!} -``` - -//// - -## Ausführen - -/// info - -Um hochgeladene Dateien zu empfangen, installieren Sie zuerst `python-multipart`. - -Z. B. `pip install python-multipart`. - -Das, weil **OAuth2** „Formulardaten“ zum Senden von `username` und `password` verwendet. +Das liegt daran, dass **OAuth2** „Formulardaten“ zum Senden von `username` und `password` verwendet. /// @@ -67,14 +47,14 @@ Führen Sie das Beispiel aus mit:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-## Überprüfen +## Es testen { #check-it } Gehen Sie zu der interaktiven Dokumentation unter: http://127.0.0.1:8000/docs. @@ -82,7 +62,7 @@ Sie werden etwa Folgendes sehen: -/// check | "Authorize-Button!" +/// check | Authorize-Button! Sie haben bereits einen glänzenden, neuen „Authorize“-Button. @@ -94,7 +74,7 @@ Und wenn Sie darauf klicken, erhalten Sie ein kleines Anmeldeformular zur Eingab -/// note | "Hinweis" +/// note | Hinweis Es spielt keine Rolle, was Sie in das Formular eingeben, es wird noch nicht funktionieren. Wir kommen dahin. @@ -108,7 +88,7 @@ Es kann von Anwendungen und Systemen Dritter verwendet werden. Und es kann auch von Ihnen selbst verwendet werden, um dieselbe Anwendung zu debuggen, zu prüfen und zu testen. -## Der `password`-Flow +## Der `password`-Flow { #the-password-flow } Lassen Sie uns nun etwas zurückgehen und verstehen, was das alles ist. @@ -131,16 +111,16 @@ Betrachten wir es also aus dieser vereinfachten Sicht: * Der Benutzer klickt im Frontend, um zu einem anderen Abschnitt der Frontend-Web-Anwendung zu gelangen. * Das Frontend muss weitere Daten von der API abrufen. * Es benötigt jedoch eine Authentifizierung für diesen bestimmten Endpunkt. - * Um sich also bei unserer API zu authentifizieren, sendet es einen Header `Authorization` mit dem Wert `Bearer` plus dem Token. + * Um sich also bei unserer API zu authentifizieren, sendet es einen Header `Authorization` mit dem Wert `Bearer ` plus dem Token. * Wenn der Token `foobar` enthielte, wäre der Inhalt des `Authorization`-Headers: `Bearer foobar`. -## **FastAPI**s `OAuth2PasswordBearer` +## **FastAPI**s `OAuth2PasswordBearer` { #fastapis-oauth2passwordbearer } **FastAPI** bietet mehrere Tools auf unterschiedlichen Abstraktionsebenen zur Implementierung dieser Sicherheitsfunktionen. In diesem Beispiel verwenden wir **OAuth2** mit dem **Password**-Flow und einem **Bearer**-Token. Wir machen das mit der Klasse `OAuth2PasswordBearer`. -/// info +/// info | Info Ein „Bearer“-Token ist nicht die einzige Option. @@ -154,37 +134,9 @@ In dem Fall gibt Ihnen **FastAPI** ebenfalls die Tools, die Sie zum Erstellen br Wenn wir eine Instanz der Klasse `OAuth2PasswordBearer` erstellen, übergeben wir den Parameter `tokenUrl`. Dieser Parameter enthält die URL, die der Client (das Frontend, das im Browser des Benutzers ausgeführt wird) verwendet, wenn er den `username` und das `password` sendet, um einen Token zu erhalten. -//// tab | Python 3.9+ +{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} -```Python hl_lines="8" -{!> ../../../docs_src/security/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7" -{!> ../../../docs_src/security/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="6" -{!> ../../../docs_src/security/tutorial001.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Hier bezieht sich `tokenUrl="token"` auf eine relative URL `token`, die wir noch nicht erstellt haben. Da es sich um eine relative URL handelt, entspricht sie `./token`. @@ -198,7 +150,7 @@ Dieser Parameter erstellt nicht diesen Endpunkt / diese *Pfadoperation*, sondern Wir werden demnächst auch die eigentliche Pfadoperation erstellen. -/// info +/// info | Info Wenn Sie ein sehr strenger „Pythonista“ sind, missfällt Ihnen möglicherweise die Schreibweise des Parameternamens `tokenUrl` anstelle von `token_url`. @@ -216,45 +168,17 @@ oauth2_scheme(some, parameters) Es kann also mit `Depends` verwendet werden. -### Verwendung +### Verwenden { #use-it } Jetzt können Sie dieses `oauth2_scheme` als Abhängigkeit `Depends` übergeben. -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/security/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/security/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/security/tutorial001.py!} -``` - -//// +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} Diese Abhängigkeit stellt einen `str` bereit, der dem Parameter `token` der *Pfadoperation-Funktion* zugewiesen wird. **FastAPI** weiß, dass es diese Abhängigkeit verwenden kann, um ein „Sicherheitsschema“ im OpenAPI-Schema (und der automatischen API-Dokumentation) zu definieren. -/// info | "Technische Details" +/// info | Technische Details **FastAPI** weiß, dass es die Klasse `OAuth2PasswordBearer` (deklariert in einer Abhängigkeit) verwenden kann, um das Sicherheitsschema in OpenAPI zu definieren, da es von `fastapi.security.oauth2.OAuth2` erbt, das wiederum von `fastapi.security.base.SecurityBase` erbt. @@ -262,11 +186,11 @@ Alle Sicherheits-Werkzeuge, die in OpenAPI integriert sind (und die automatische /// -## Was es macht +## Was es macht { #what-it-does } -FastAPI wird im Request nach diesem `Authorization`-Header suchen, prüfen, ob der Wert `Bearer` plus ein Token ist, und den Token als `str` zurückgeben. +FastAPI wird im Request nach diesem `Authorization`-Header suchen, prüfen, ob der Wert `Bearer ` plus ein Token ist, und den Token als `str` zurückgeben. -Wenn es keinen `Authorization`-Header sieht, oder der Wert keinen `Bearer`-Token hat, antwortet es direkt mit einem 401-Statuscode-Error (`UNAUTHORIZED`). +Wenn es keinen `Authorization`-Header sieht, oder der Wert keinen `Bearer `-Token hat, antwortet es direkt mit einem 401-Statuscode-Error (`UNAUTHORIZED`). Sie müssen nicht einmal prüfen, ob der Token existiert, um einen Fehler zurückzugeben. Seien Sie sicher, dass Ihre Funktion, wenn sie ausgeführt wird, ein `str` in diesem Token enthält. @@ -276,6 +200,6 @@ Sie können das bereits in der interaktiven Dokumentation ausprobieren: Wir überprüfen im Moment noch nicht die Gültigkeit des Tokens, aber das ist bereits ein Anfang. -## Zusammenfassung +## Zusammenfassung { #recap } -Mit nur drei oder vier zusätzlichen Zeilen haben Sie also bereits eine primitive Form der Sicherheit. +Mit nur drei oder vier zusätzlichen Zeilen haben Sie so bereits eine primitive Form der Sicherheit. diff --git a/docs/de/docs/tutorial/security/get-current-user.md b/docs/de/docs/tutorial/security/get-current-user.md index 8a68deeeff..e32e366698 100644 --- a/docs/de/docs/tutorial/security/get-current-user.md +++ b/docs/de/docs/tutorial/security/get-current-user.md @@ -1,100 +1,22 @@ -# Aktuellen Benutzer abrufen +# Aktuellen Benutzer abrufen { #get-current-user } Im vorherigen Kapitel hat das Sicherheitssystem (das auf dem Dependency Injection System basiert) der *Pfadoperation-Funktion* einen `token` vom Typ `str` überreicht: -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/security/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/security/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/security/tutorial001.py!} -``` - -//// +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} Aber das ist immer noch nicht so nützlich. Lassen wir es uns den aktuellen Benutzer überreichen. -## Ein Benutzermodell erstellen +## Ein Benutzermodell erstellen { #create-a-user-model } Erstellen wir zunächst ein Pydantic-Benutzermodell. So wie wir Pydantic zum Deklarieren von Bodys verwenden, können wir es auch überall sonst verwenden: -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *} -```Python hl_lines="5 12-16" -{!> ../../../docs_src/security/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="5 12-16" -{!> ../../../docs_src/security/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="5 13-17" -{!> ../../../docs_src/security/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="3 10-14" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="5 12-16" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -## Eine `get_current_user`-Abhängigkeit erstellen +## Eine `get_current_user`-Abhängigkeit erstellen { #create-a-get-current-user-dependency } Erstellen wir eine Abhängigkeit `get_current_user`. @@ -104,183 +26,33 @@ Erinnern Sie sich, dass Abhängigkeiten Unterabhängigkeiten haben können? So wie wir es zuvor in der *Pfadoperation* direkt gemacht haben, erhält unsere neue Abhängigkeit `get_current_user` von der Unterabhängigkeit `oauth2_scheme` einen `token` vom Typ `str`: -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *} -```Python hl_lines="25" -{!> ../../../docs_src/security/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="25" -{!> ../../../docs_src/security/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="26" -{!> ../../../docs_src/security/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="23" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="25" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -## Den Benutzer holen +## Den Benutzer abrufen { #get-the-user } `get_current_user` wird eine von uns erstellte (gefakte) Hilfsfunktion verwenden, welche einen Token vom Typ `str` entgegennimmt und unser Pydantic-`User`-Modell zurückgibt: -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *} -```Python hl_lines="19-22 26-27" -{!> ../../../docs_src/security/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19-22 26-27" -{!> ../../../docs_src/security/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20-23 27-28" -{!> ../../../docs_src/security/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="17-20 24-25" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="19-22 26-27" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -## Den aktuellen Benutzer einfügen +## Den aktuellen Benutzer einfügen { #inject-the-current-user } Und jetzt können wir wiederum `Depends` mit unserem `get_current_user` in der *Pfadoperation* verwenden: -//// tab | Python 3.10+ - -```Python hl_lines="31" -{!> ../../../docs_src/security/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="31" -{!> ../../../docs_src/security/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="32" -{!> ../../../docs_src/security/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="29" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="31" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} Beachten Sie, dass wir als Typ von `current_user` das Pydantic-Modell `User` deklarieren. Das wird uns innerhalb der Funktion bei Codevervollständigung und Typprüfungen helfen. -/// tip | "Tipp" +/// tip | Tipp -Sie erinnern sich vielleicht, dass Requestbodys ebenfalls mit Pydantic-Modellen deklariert werden. +Sie erinnern sich vielleicht, dass Requestbodys ebenfalls mit Pydantic-Modellen deklariert werden. Weil Sie `Depends` verwenden, wird **FastAPI** hier aber nicht verwirrt. /// -/// check +/// check | Testen Die Art und Weise, wie dieses System von Abhängigkeiten konzipiert ist, ermöglicht es uns, verschiedene Abhängigkeiten (verschiedene „Dependables“) zu haben, die alle ein `User`-Modell zurückgeben. @@ -288,7 +60,7 @@ Wir sind nicht darauf beschränkt, nur eine Abhängigkeit zu haben, die diesen T /// -## Andere Modelle +## Andere Modelle { #other-models } Sie können jetzt den aktuellen Benutzer direkt in den *Pfadoperation-Funktionen* abrufen und die Sicherheitsmechanismen auf **Dependency Injection** Ebene handhaben, mittels `Depends`. @@ -304,7 +76,7 @@ Sie haben eigentlich keine Benutzer, die sich bei Ihrer Anwendung anmelden, sond Verwenden Sie einfach jede Art von Modell, jede Art von Klasse, jede Art von Datenbank, die Sie für Ihre Anwendung benötigen. **FastAPI** deckt das alles mit seinem Dependency Injection System ab. -## Codegröße +## Codegröße { #code-size } Dieses Beispiel mag ausführlich erscheinen. Bedenken Sie, dass wir Sicherheit, Datenmodelle, Hilfsfunktionen und *Pfadoperationen* in derselben Datei vermischen. @@ -320,59 +92,9 @@ Und alle (oder beliebige Teile davon) können Vorteil ziehen aus der Wiederverwe Und alle diese Tausenden von *Pfadoperationen* können nur drei Zeilen lang sein: -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} -```Python hl_lines="30-32" -{!> ../../../docs_src/security/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="30-32" -{!> ../../../docs_src/security/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="31-33" -{!> ../../../docs_src/security/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="28-30" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="30-32" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -## Zusammenfassung +## Zusammenfassung { #recap } Sie können jetzt den aktuellen Benutzer direkt in Ihrer *Pfadoperation-Funktion* abrufen. diff --git a/docs/de/docs/tutorial/security/index.md b/docs/de/docs/tutorial/security/index.md index ad09273610..39b0b93c98 100644 --- a/docs/de/docs/tutorial/security/index.md +++ b/docs/de/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# Sicherheit +# Sicherheit { #security } Es gibt viele Wege, Sicherheit, Authentifizierung und Autorisierung zu handhaben. @@ -10,11 +10,11 @@ In vielen Frameworks und Systemen erfordert allein die Handhabung von Sicherheit Aber schauen wir uns zunächst ein paar kleine Konzepte an. -## In Eile? +## In Eile? { #in-a-hurry } Wenn Ihnen diese Begriffe egal sind und Sie einfach *jetzt* Sicherheit mit Authentifizierung basierend auf Benutzername und Passwort hinzufügen müssen, fahren Sie mit den nächsten Kapiteln fort. -## OAuth2 +## OAuth2 { #oauth2 } OAuth2 ist eine Spezifikation, die verschiedene Möglichkeiten zur Handhabung von Authentifizierung und Autorisierung definiert. @@ -22,9 +22,9 @@ Es handelt sich um eine recht umfangreiche Spezifikation, und sie deckt mehrere Sie umfasst Möglichkeiten zur Authentifizierung mithilfe eines „Dritten“ („third party“). -Das ist es, was alle diese „Login mit Facebook, Google, Twitter, GitHub“-Systeme unter der Haube verwenden. +Das ist es, was alle diese „Login mit Facebook, Google, X (Twitter), GitHub“-Systeme unter der Haube verwenden. -### OAuth 1 +### OAuth 1 { #oauth-1 } Es gab ein OAuth 1, das sich stark von OAuth2 unterscheidet und komplexer ist, da es direkte Spezifikationen enthält, wie die Kommunikation verschlüsselt wird. @@ -32,13 +32,13 @@ Heutzutage ist es nicht sehr populär und wird kaum verwendet. OAuth2 spezifiziert nicht, wie die Kommunikation verschlüsselt werden soll, sondern erwartet, dass Ihre Anwendung mit HTTPS bereitgestellt wird. -/// tip | "Tipp" +/// tip | Tipp Im Abschnitt über **Deployment** erfahren Sie, wie Sie HTTPS mithilfe von Traefik und Let's Encrypt kostenlos einrichten. /// -## OpenID Connect +## OpenID Connect { #openid-connect } OpenID Connect ist eine weitere Spezifikation, die auf **OAuth2** basiert. @@ -48,7 +48,7 @@ Beispielsweise verwendet der Google Login OpenID Connect (welches seinerseits OA Aber der Facebook Login unterstützt OpenID Connect nicht. Es hat seine eigene Variante von OAuth2. -### OpenID (nicht „OpenID Connect“) +### OpenID (nicht „OpenID Connect“) { #openid-not-openid-connect } Es gab auch eine „OpenID“-Spezifikation. Sie versuchte das Gleiche zu lösen wie **OpenID Connect**, basierte aber nicht auf OAuth2. @@ -56,7 +56,7 @@ Es handelte sich also um ein komplett zusätzliches System. Heutzutage ist es nicht sehr populär und wird kaum verwendet. -## OpenAPI +## OpenAPI { #openapi } OpenAPI (früher bekannt als Swagger) ist die offene Spezifikation zum Erstellen von APIs (jetzt Teil der Linux Foundation). @@ -75,11 +75,11 @@ OpenAPI definiert die folgenden Sicherheitsschemas: * Einem Header. * Einem Cookie. * `http`: Standard-HTTP-Authentifizierungssysteme, einschließlich: - * `bearer`: ein Header `Authorization` mit dem Wert `Bearer` plus einem Token. Dies wird von OAuth2 geerbt. + * `bearer`: ein Header `Authorization` mit dem Wert `Bearer ` plus einem Token. Dies wird von OAuth2 geerbt. * HTTP Basic Authentication. * HTTP Digest, usw. * `oauth2`: Alle OAuth2-Methoden zum Umgang mit Sicherheit (genannt „Flows“). - * Mehrere dieser Flows eignen sich zum Aufbau eines OAuth 2.0-Authentifizierungsanbieters (wie Google, Facebook, Twitter, GitHub usw.): + * Mehrere dieser Flows eignen sich zum Aufbau eines OAuth 2.0-Authentifizierungsanbieters (wie Google, Facebook, X (Twitter), GitHub usw.): * `implicit` * `clientCredentials` * `authorizationCode` @@ -89,15 +89,15 @@ OpenAPI definiert die folgenden Sicherheitsschemas: * Diese automatische Erkennung ist es, die in der OpenID Connect Spezifikation definiert ist. -/// tip | "Tipp" +/// tip | Tipp -Auch die Integration anderer Authentifizierungs-/Autorisierungsanbieter wie Google, Facebook, Twitter, GitHub, usw. ist möglich und relativ einfach. +Auch die Integration anderer Authentifizierungs-/Autorisierungsanbieter wie Google, Facebook, X (Twitter), GitHub, usw. ist möglich und relativ einfach. Das komplexeste Problem besteht darin, einen Authentifizierungs-/Autorisierungsanbieter wie solche aufzubauen, aber **FastAPI** reicht Ihnen die Tools, das einfach zu erledigen, während Ihnen die schwere Arbeit abgenommen wird. /// -## **FastAPI** Tools +## **FastAPI** Tools { #fastapi-utilities } FastAPI stellt für jedes dieser Sicherheitsschemas im Modul `fastapi.security` verschiedene Tools bereit, die die Verwendung dieser Sicherheitsmechanismen vereinfachen. diff --git a/docs/de/docs/tutorial/security/oauth2-jwt.md b/docs/de/docs/tutorial/security/oauth2-jwt.md index 88f0dbe42e..0d5ce587ba 100644 --- a/docs/de/docs/tutorial/security/oauth2-jwt.md +++ b/docs/de/docs/tutorial/security/oauth2-jwt.md @@ -1,4 +1,4 @@ -# OAuth2 mit Password (und Hashing), Bearer mit JWT-Tokens +# OAuth2 mit Passwort (und Hashing), Bearer mit JWT-Tokens { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } Da wir nun über den gesamten Sicherheitsablauf verfügen, machen wir die Anwendung tatsächlich sicher, indem wir JWT-Tokens und sicheres Passwort-Hashing verwenden. @@ -6,7 +6,7 @@ Diesen Code können Sie tatsächlich in Ihrer Anwendung verwenden, die Passwort- Wir bauen auf dem vorherigen Kapitel auf. -## Über JWT +## Über JWT { #about-jwt } JWT bedeutet „JSON Web Tokens“. @@ -26,33 +26,31 @@ Nach einer Woche läuft der Token ab und der Benutzer wird nicht autorisiert und Wenn Sie mit JWT-Tokens spielen und sehen möchten, wie sie funktionieren, schauen Sie sich https://jwt.io an. -## `python-jose` installieren. +## `PyJWT` installieren { #install-pyjwt } -Wir müssen `python-jose` installieren, um die JWT-Tokens in Python zu generieren und zu verifizieren: +Wir müssen `PyJWT` installieren, um die JWT-Tokens in Python zu generieren und zu verifizieren. + +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und dann `pyjwt` installieren:
```console -$ pip install "python-jose[cryptography]" +$ pip install pyjwt ---> 100% ```
-python-jose erfordert zusätzlich ein kryptografisches Backend. +/// info | Info -Hier verwenden wir das empfohlene: pyca/cryptography. +Wenn Sie planen, digitale Signaturalgorithmen wie RSA oder ECDSA zu verwenden, sollten Sie die Kryptografie-Abhängigkeit `pyjwt[crypto]` installieren. -/// tip | "Tipp" - -Dieses Tutorial verwendete zuvor PyJWT. - -Es wurde jedoch aktualisiert, stattdessen python-jose zu verwenden, da dieses alle Funktionen von PyJWT sowie einige Extras bietet, die Sie später möglicherweise benötigen, wenn Sie Integrationen mit anderen Tools erstellen. +Weitere Informationen finden Sie in der PyJWT-Installationsdokumentation. /// -## Passwort-Hashing +## Passwort-Hashing { #password-hashing } „Hashing“ bedeutet: Konvertieren eines Inhalts (in diesem Fall eines Passworts) in eine Folge von Bytes (ein schlichter String), die wie Kauderwelsch aussieht. @@ -60,35 +58,35 @@ Immer wenn Sie genau den gleichen Inhalt (genau das gleiche Passwort) übergeben Sie können jedoch nicht vom Kauderwelsch zurück zum Passwort konvertieren. -### Warum Passwort-Hashing verwenden? +### Warum Passwort-Hashing verwenden { #why-use-password-hashing } Wenn Ihre Datenbank gestohlen wird, hat der Dieb nicht die Klartext-Passwörter Ihrer Benutzer, sondern nur die Hashes. Der Dieb kann also nicht versuchen, die gleichen Passwörter in einem anderen System zu verwenden (da viele Benutzer überall das gleiche Passwort verwenden, wäre dies gefährlich). -## `passlib` installieren +## `pwdlib` installieren { #install-pwdlib } -PassLib ist ein großartiges Python-Package, um Passwort-Hashes zu handhaben. +pwdlib ist ein großartiges Python-Package, um Passwort-Hashes zu handhaben. Es unterstützt viele sichere Hashing-Algorithmen und Werkzeuge, um mit diesen zu arbeiten. -Der empfohlene Algorithmus ist „Bcrypt“. +Der empfohlene Algorithmus ist „Argon2“. -Installieren Sie also PassLib mit Bcrypt: +Stellen Sie sicher, dass Sie eine [virtuelle Umgebung](../../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren, und installieren Sie dann pwdlib mit Argon2:
```console -$ pip install "passlib[bcrypt]" +$ pip install "pwdlib[argon2]" ---> 100% ```
-/// tip | "Tipp" +/// tip | Tipp -Mit `passlib` können Sie sogar konfigurieren, Passwörter zu lesen, die von **Django**, einem **Flask**-Sicherheit-Plugin, oder vielen anderen erstellt wurden. +Mit `pwdlib` können Sie sogar konfigurieren, Passwörter zu lesen, die von **Django**, einem **Flask**-Sicherheits-Plugin, oder vielen anderen erstellt wurden. So könnten Sie beispielsweise die gleichen Daten aus einer Django-Anwendung in einer Datenbank mit einer FastAPI-Anwendung teilen. Oder schrittweise eine Django-Anwendung migrieren, während Sie dieselbe Datenbank verwenden. @@ -96,17 +94,17 @@ Und Ihre Benutzer könnten sich gleichzeitig über Ihre Django-Anwendung oder Ih /// -## Die Passwörter hashen und überprüfen +## Die Passwörter hashen und überprüfen { #hash-and-verify-the-passwords } -Importieren Sie die benötigten Tools aus `passlib`. +Importieren Sie die benötigten Tools aus `pwdlib`. -Erstellen Sie einen PassLib-„Kontext“. Der wird für das Hashen und Verifizieren von Passwörtern verwendet. +Erstellen Sie eine PasswordHash-Instanz mit empfohlenen Einstellungen – sie wird für das Hashen und Verifizieren von Passwörtern verwendet. -/// tip | "Tipp" +/// tip | Tipp -Der PassLib-Kontext kann auch andere Hashing-Algorithmen verwenden, einschließlich deprecateter Alter, um etwa nur eine Verifizierung usw. zu ermöglichen. +pwdlib unterstützt auch den bcrypt-Hashing-Algorithmus, enthält jedoch keine Legacy-Algorithmen – für die Arbeit mit veralteten Hashes wird die Verwendung der Bibliothek passlib empfohlen. -Sie könnten ihn beispielsweise verwenden, um von einem anderen System (wie Django) generierte Passwörter zu lesen und zu verifizieren, aber alle neuen Passwörter mit einem anderen Algorithmus wie Bcrypt zu hashen. +Sie könnten sie beispielsweise verwenden, um von einem anderen System (wie Django) generierte Passwörter zu lesen und zu verifizieren, aber alle neuen Passwörter mit einem anderen Algorithmus wie Argon2 oder Bcrypt zu hashen. Und mit allen gleichzeitig kompatibel sein. @@ -118,65 +116,15 @@ Und eine weitere, um zu überprüfen, ob ein empfangenes Passwort mit dem gespei Und noch eine, um einen Benutzer zu authentifizieren und zurückzugeben. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} -```Python hl_lines="7 48 55-56 59-60 69-75" -{!> ../../../docs_src/security/tutorial004_an_py310.py!} -``` +/// note | Hinweis -//// - -//// tab | Python 3.9+ - -```Python hl_lines="7 48 55-56 59-60 69-75" -{!> ../../../docs_src/security/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7 49 56-57 60-61 70-76" -{!> ../../../docs_src/security/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. +Wenn Sie sich die neue (gefakte) Datenbank `fake_users_db` anschauen, sehen Sie, wie das gehashte Passwort jetzt aussieht: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`. /// -```Python hl_lines="6 47 54-55 58-59 68-74" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="7 48 55-56 59-60 69-75" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -/// note | "Hinweis" - -Wenn Sie sich die neue (gefakte) Datenbank `fake_users_db` anschauen, sehen Sie, wie das gehashte Passwort jetzt aussieht: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. - -/// - -## JWT-Token verarbeiten +## JWT-Token verarbeiten { #handle-jwt-tokens } Importieren Sie die installierten Module. @@ -200,63 +148,13 @@ Erstellen Sie eine Variable `ALGORITHM` für den Algorithmus, der zum Signieren Erstellen Sie eine Variable für das Ablaufdatum des Tokens. -Definieren Sie ein Pydantic-Modell, das im Token-Endpunkt für die Response verwendet wird. +Definieren Sie ein Pydantic-Modell, das im Token-Endpunkt für die Response verwendet wird. Erstellen Sie eine Hilfsfunktion, um einen neuen Zugriffstoken zu generieren. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} -```Python hl_lines="6 12-14 28-30 78-86" -{!> ../../../docs_src/security/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="6 12-14 28-30 78-86" -{!> ../../../docs_src/security/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="6 13-15 29-31 79-87" -{!> ../../../docs_src/security/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="5 11-13 27-29 77-85" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="6 12-14 28-30 78-86" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -## Die Abhängigkeiten aktualisieren +## Die Abhängigkeiten aktualisieren { #update-the-dependencies } Aktualisieren Sie `get_current_user`, um den gleichen Token wie zuvor zu erhalten, dieses Mal jedoch unter Verwendung von JWT-Tokens. @@ -264,117 +162,17 @@ Dekodieren Sie den empfangenen Token, validieren Sie ihn und geben Sie den aktue Wenn der Token ungültig ist, geben Sie sofort einen HTTP-Fehler zurück. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} -```Python hl_lines="89-106" -{!> ../../../docs_src/security/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="89-106" -{!> ../../../docs_src/security/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="90-107" -{!> ../../../docs_src/security/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="88-105" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="89-106" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -## Die *Pfadoperation* `/token` aktualisieren +## Die *Pfadoperation* `/token` aktualisieren { #update-the-token-path-operation } Erstellen Sie ein `timedelta` mit der Ablaufzeit des Tokens. Erstellen Sie einen echten JWT-Zugriffstoken und geben Sie ihn zurück. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} -```Python hl_lines="117-132" -{!> ../../../docs_src/security/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="117-132" -{!> ../../../docs_src/security/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="118-133" -{!> ../../../docs_src/security/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="114-129" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="115-130" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -### Technische Details zum JWT-„Subjekt“ `sub` +### Technische Details zum JWT-„Subjekt“ `sub` { #technical-details-about-the-jwt-subject-sub } Die JWT-Spezifikation besagt, dass es einen Schlüssel `sub` mit dem Subjekt des Tokens gibt. @@ -396,7 +194,7 @@ Deshalb, um ID-Kollisionen zu vermeiden, könnten Sie beim Erstellen des JWT-Tok Der wesentliche Punkt ist, dass der `sub`-Schlüssel in der gesamten Anwendung eine eindeutige Kennung haben sollte, und er sollte ein String sein. -## Es testen +## Es testen { #check-it } Führen Sie den Server aus und gehen Sie zur Dokumentation: http://127.0.0.1:8000/docs. @@ -409,11 +207,11 @@ Melden Sie sich bei der Anwendung auf die gleiche Weise wie zuvor an. Verwenden Sie die Anmeldeinformationen: Benutzername: `johndoe` -Passwort: `secret`. +Passwort: `secret` -/// check +/// check | Testen -Beachten Sie, dass im Code nirgendwo das Klartext-Passwort "`secret`" steht, wir haben nur die gehashte Version. +Beachten Sie, dass im Code nirgendwo das Klartext-Passwort „`secret`“ steht, wir haben nur die gehashte Version. /// @@ -432,17 +230,17 @@ Rufen Sie den Endpunkt `/users/me/` auf, Sie erhalten die Response: -Wenn Sie die Developer Tools öffnen, können Sie sehen, dass die gesendeten Daten nur den Token enthalten. Das Passwort wird nur bei der ersten Anfrage gesendet, um den Benutzer zu authentisieren und diesen Zugriffstoken zu erhalten, aber nicht mehr danach: +Wenn Sie die Developer Tools öffnen, können Sie sehen, dass die gesendeten Daten nur den Token enthalten. Das Passwort wird nur beim ersten Request gesendet, um den Benutzer zu authentisieren und diesen Zugriffstoken zu erhalten, aber nicht mehr danach: -/// note | "Hinweis" +/// note | Hinweis -Beachten Sie den Header `Authorization` mit einem Wert, der mit `Bearer` beginnt. +Beachten Sie den Header `Authorization` mit einem Wert, der mit `Bearer ` beginnt. /// -## Fortgeschrittene Verwendung mit `scopes` +## Fortgeschrittene Verwendung mit `scopes` { #advanced-usage-with-scopes } OAuth2 hat ein Konzept von „Scopes“. @@ -452,7 +250,7 @@ Anschließend können Sie diesen Token einem Benutzer direkt oder einem Dritten Wie Sie sie verwenden und wie sie in **FastAPI** integriert sind, erfahren Sie später im **Handbuch für fortgeschrittene Benutzer**. -## Zusammenfassung +## Zusammenfassung { #recap } Mit dem, was Sie bis hier gesehen haben, können Sie eine sichere **FastAPI**-Anwendung mithilfe von Standards wie OAuth2 und JWT einrichten. @@ -466,10 +264,10 @@ Viele Packages, die es stark vereinfachen, müssen viele Kompromisse beim Datenm Es gibt Ihnen die volle Flexibilität, diejenigen auszuwählen, die am besten zu Ihrem Projekt passen. -Und Sie können viele gut gepflegte und weit verbreitete Packages wie `passlib` und `python-jose` direkt verwenden, da **FastAPI** keine komplexen Mechanismen zur Integration externer Pakete erfordert. +Und Sie können viele gut gepflegte und weit verbreitete Packages wie `pwdlib` und `PyJWT` direkt verwenden, da **FastAPI** keine komplexen Mechanismen zur Integration externer Pakete erfordert. Aber es bietet Ihnen die Werkzeuge, um den Prozess so weit wie möglich zu vereinfachen, ohne Kompromisse bei Flexibilität, Robustheit oder Sicherheit einzugehen. Und Sie können sichere Standardprotokolle wie OAuth2 auf relativ einfache Weise verwenden und implementieren. -Im **Handbuch für fortgeschrittene Benutzer** erfahren Sie mehr darüber, wie Sie OAuth2-„Scopes“ für ein feingranuliertes Berechtigungssystem verwenden, das denselben Standards folgt. OAuth2 mit Scopes ist der Mechanismus, der von vielen großen Authentifizierungsanbietern wie Facebook, Google, GitHub, Microsoft, Twitter, usw. verwendet wird, um Drittanbieteranwendungen zu autorisieren, im Namen ihrer Benutzer mit ihren APIs zu interagieren. +Im **Handbuch für fortgeschrittene Benutzer** erfahren Sie mehr darüber, wie Sie OAuth2-„Scopes“ für ein feingranuliertes Berechtigungssystem verwenden, das denselben Standards folgt. OAuth2 mit Scopes ist der Mechanismus, der von vielen großen Authentifizierungsanbietern wie Facebook, Google, GitHub, Microsoft, X (Twitter), usw. verwendet wird, um Drittanbieteranwendungen zu autorisieren, im Namen ihrer Benutzer mit ihren APIs zu interagieren. diff --git a/docs/de/docs/tutorial/security/simple-oauth2.md b/docs/de/docs/tutorial/security/simple-oauth2.md index 3b1c4ae28a..28cb83ba93 100644 --- a/docs/de/docs/tutorial/security/simple-oauth2.md +++ b/docs/de/docs/tutorial/security/simple-oauth2.md @@ -1,8 +1,8 @@ -# Einfaches OAuth2 mit Password und Bearer +# Einfaches OAuth2 mit Password und Bearer { #simple-oauth2-with-password-and-bearer } Lassen Sie uns nun auf dem vorherigen Kapitel aufbauen und die fehlenden Teile hinzufügen, um einen vollständigen Sicherheits-Flow zu erhalten. -## `username` und `password` entgegennehmen +## `username` und `password` entgegennehmen { #get-the-username-and-password } Wir werden **FastAPIs** Sicherheits-Werkzeuge verwenden, um den `username` und das `password` entgegenzunehmen. @@ -18,7 +18,7 @@ Aber für die Login-*Pfadoperation* müssen wir diese Namen verwenden, um mit de Die Spezifikation besagt auch, dass `username` und `password` als Formulardaten gesendet werden müssen (hier also kein JSON). -### `scope` +### `scope` { #scope } Ferner sagt die Spezifikation, dass der Client ein weiteres Formularfeld "`scope`" („Geltungsbereich“) senden kann. @@ -32,7 +32,7 @@ Diese werden normalerweise verwendet, um bestimmte Sicherheitsberechtigungen zu * `instagram_basic` wird von Facebook / Instagram verwendet. * `https://www.googleapis.com/auth/drive` wird von Google verwendet. -/// info +/// info | Info In OAuth2 ist ein „Scope“ nur ein String, der eine bestimmte erforderliche Berechtigung deklariert. @@ -44,74 +44,24 @@ Für OAuth2 sind es einfach nur Strings. /// -## Code, um `username` und `password` entgegenzunehmen. +## Code, um `username` und `password` entgegenzunehmen { #code-to-get-the-username-and-password } Lassen Sie uns nun die von **FastAPI** bereitgestellten Werkzeuge verwenden, um das zu erledigen. -### `OAuth2PasswordRequestForm` +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } Importieren Sie zunächst `OAuth2PasswordRequestForm` und verwenden Sie es als Abhängigkeit mit `Depends` in der *Pfadoperation* für `/token`: -//// tab | Python 3.10+ - -```Python hl_lines="4 78" -{!> ../../../docs_src/security/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 78" -{!> ../../../docs_src/security/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 79" -{!> ../../../docs_src/security/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="2 74" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="4 76" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} `OAuth2PasswordRequestForm` ist eine Klassenabhängigkeit, die einen Formularbody deklariert mit: * Dem `username`. * Dem `password`. * Einem optionalen `scope`-Feld als langem String, bestehend aus durch Leerzeichen getrennten Strings. -* Einem optionalen `grant_type` („Art der Anmeldung“). +* Einem optionalen `grant_type`. -/// tip | "Tipp" +/// tip | Tipp Die OAuth2-Spezifikation *erfordert* tatsächlich ein Feld `grant_type` mit dem festen Wert `password`, aber `OAuth2PasswordRequestForm` erzwingt dies nicht. @@ -122,7 +72,7 @@ Wenn Sie es erzwingen müssen, verwenden Sie `OAuth2PasswordRequestFormStrict` a * Eine optionale `client_id` (benötigen wir für unser Beispiel nicht). * Ein optionales `client_secret` (benötigen wir für unser Beispiel nicht). -/// info +/// info | Info `OAuth2PasswordRequestForm` ist keine spezielle Klasse für **FastAPI**, so wie `OAuth2PasswordBearer`. @@ -134,9 +84,9 @@ Da es sich jedoch um einen häufigen Anwendungsfall handelt, wird er zur Vereinf /// -### Die Formulardaten verwenden +### Die Formulardaten verwenden { #use-the-form-data } -/// tip | "Tipp" +/// tip | Tipp Die Instanz der Klassenabhängigkeit `OAuth2PasswordRequestForm` verfügt, statt eines Attributs `scope` mit dem durch Leerzeichen getrennten langen String, über das Attribut `scopes` mit einer tatsächlichen Liste von Strings, einem für jeden gesendeten Scope. @@ -150,59 +100,9 @@ Wenn es keinen solchen Benutzer gibt, geben wir die Fehlermeldung „Incorrect u Für den Fehler verwenden wir die Exception `HTTPException`: -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} -```Python hl_lines="3 79-81" -{!> ../../../docs_src/security/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3 79-81" -{!> ../../../docs_src/security/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3 80-82" -{!> ../../../docs_src/security/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1 75-77" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="3 77-79" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -### Das Passwort überprüfen +### Das Passwort überprüfen { #check-the-password } Zu diesem Zeitpunkt liegen uns die Benutzerdaten aus unserer Datenbank vor, das Passwort haben wir jedoch noch nicht überprüft. @@ -212,7 +112,7 @@ Sie sollten niemals Klartext-Passwörter speichern, daher verwenden wir ein (gef Wenn die Passwörter nicht übereinstimmen, geben wir denselben Fehler zurück. -#### Passwort-Hashing +#### Passwort-Hashing { #password-hashing } „Hashing“ bedeutet: Konvertieren eines Inhalts (in diesem Fall eines Passworts) in eine Folge von Bytes (ein schlichter String), die wie Kauderwelsch aussieht. @@ -220,65 +120,15 @@ Immer wenn Sie genau den gleichen Inhalt (genau das gleiche Passwort) übergeben Sie können jedoch nicht vom Kauderwelsch zurück zum Passwort konvertieren. -##### Warum Passwort-Hashing verwenden? +##### Warum Passwort-Hashing verwenden? { #why-use-password-hashing } Wenn Ihre Datenbank gestohlen wird, hat der Dieb nicht die Klartext-Passwörter Ihrer Benutzer, sondern nur die Hashes. Der Dieb kann also nicht versuchen, die gleichen Passwörter in einem anderen System zu verwenden (da viele Benutzer überall das gleiche Passwort verwenden, wäre dies gefährlich). -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} -```Python hl_lines="82-85" -{!> ../../../docs_src/security/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="82-85" -{!> ../../../docs_src/security/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="83-86" -{!> ../../../docs_src/security/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="78-81" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="80-83" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -#### Über `**user_dict` +#### Über `**user_dict` { #about-user-dict } `UserInDB(**user_dict)` bedeutet: @@ -294,15 +144,15 @@ UserInDB( ) ``` -/// info +/// info | Info -Eine ausführlichere Erklärung von `**user_dict` finden Sie in [der Dokumentation für **Extra Modelle**](../extra-models.md#uber-user_indict){.internal-link target=_blank}. +Eine ausführlichere Erklärung von `**user_dict` finden Sie in [der Dokumentation für **Extra Modelle**](../extra-models.md#about-user-in-dict){.internal-link target=_blank}. /// -## Den Token zurückgeben +## Den Token zurückgeben { #return-the-token } -Die Response des `token`-Endpunkts muss ein JSON-Objekt sein. +Die Response des `token`-Endpunkts muss ein JSON-Objekt sein. Es sollte einen `token_type` haben. Da wir in unserem Fall „Bearer“-Token verwenden, sollte der Token-Typ "`bearer`" sein. @@ -310,7 +160,7 @@ Und es sollte einen `access_token` haben, mit einem String, der unseren Zugriffs In diesem einfachen Beispiel gehen wir einfach völlig unsicher vor und geben denselben `username` wie der Token zurück. -/// tip | "Tipp" +/// tip | Tipp Im nächsten Kapitel sehen Sie eine wirklich sichere Implementierung mit Passwort-Hashing und JWT-Tokens. @@ -318,59 +168,9 @@ Aber konzentrieren wir uns zunächst auf die spezifischen Details, die wir benö /// -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} -```Python hl_lines="87" -{!> ../../../docs_src/security/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="87" -{!> ../../../docs_src/security/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="88" -{!> ../../../docs_src/security/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="83" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="85" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -/// tip | "Tipp" +/// tip | Tipp Gemäß der Spezifikation sollten Sie ein JSON mit einem `access_token` und einem `token_type` zurückgeben, genau wie in diesem Beispiel. @@ -382,7 +182,7 @@ Den Rest erledigt **FastAPI** für Sie. /// -## Die Abhängigkeiten aktualisieren +## Die Abhängigkeiten aktualisieren { #update-the-dependencies } Jetzt werden wir unsere Abhängigkeiten aktualisieren. @@ -394,59 +194,9 @@ Beide Abhängigkeiten geben nur dann einen HTTP-Error zurück, wenn der Benutzer In unserem Endpunkt erhalten wir also nur dann einen Benutzer, wenn der Benutzer existiert, korrekt authentifiziert wurde und aktiv ist: -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} -```Python hl_lines="58-66 69-74 94" -{!> ../../../docs_src/security/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="58-66 69-74 94" -{!> ../../../docs_src/security/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="59-67 70-75 95" -{!> ../../../docs_src/security/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="56-64 67-70 88" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | "Tipp" - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="58-66 69-72 90" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -/// info +/// info | Info Der zusätzliche Header `WWW-Authenticate` mit dem Wert `Bearer`, den wir hier zurückgeben, ist ebenfalls Teil der Spezifikation. @@ -464,11 +214,11 @@ Das ist der Vorteil von Standards ... /// -## Es in Aktion sehen +## Es in Aktion sehen { #see-it-in-action } Öffnen Sie die interaktive Dokumentation: http://127.0.0.1:8000/docs. -### Authentifizieren +### Authentifizieren { #authenticate } Klicken Sie auf den Button „Authorize“. @@ -484,7 +234,7 @@ Nach der Authentifizierung im System sehen Sie Folgendes: -### Die eigenen Benutzerdaten ansehen +### Die eigenen Benutzerdaten ansehen { #get-your-own-user-data } Verwenden Sie nun die Operation `GET` mit dem Pfad `/users/me`. @@ -510,7 +260,7 @@ Wenn Sie auf das Schlosssymbol klicken und sich abmelden und dann den gleichen V } ``` -### Inaktiver Benutzer +### Inaktiver Benutzer { #inactive-user } Versuchen Sie es nun mit einem inaktiven Benutzer und authentisieren Sie sich mit: @@ -528,7 +278,7 @@ Sie erhalten die Fehlermeldung „Inactive user“: } ``` -## Zusammenfassung +## Zusammenfassung { #recap } Sie verfügen jetzt über die Tools, um ein vollständiges Sicherheitssystem basierend auf `username` und `password` für Ihre API zu implementieren. diff --git a/docs/de/docs/tutorial/sql-databases.md b/docs/de/docs/tutorial/sql-databases.md new file mode 100644 index 0000000000..cf9731aee6 --- /dev/null +++ b/docs/de/docs/tutorial/sql-databases.md @@ -0,0 +1,357 @@ +# SQL (Relationale) Datenbanken { #sql-relational-databases } + +**FastAPI** erfordert nicht, dass Sie eine SQL (relationale) Datenbank verwenden. Sondern Sie können **jede beliebige Datenbank** verwenden, die Sie möchten. + +Hier werden wir ein Beispiel mit SQLModel sehen. + +**SQLModel** basiert auf SQLAlchemy und Pydantic. Es wurde vom selben Autor wie **FastAPI** entwickelt, um die perfekte Ergänzung für FastAPI-Anwendungen zu sein, die **SQL-Datenbanken** verwenden müssen. + +/// tip | Tipp + +Sie könnten jede andere SQL- oder NoSQL-Datenbankbibliothek verwenden, die Sie möchten (in einigen Fällen als „ORMs“ bezeichnet), FastAPI zwingt Sie nicht, irgendetwas zu verwenden. 😎 + +/// + +Da SQLModel auf SQLAlchemy basiert, können Sie problemlos **jede von SQLAlchemy unterstützte Datenbank** verwenden (was auch bedeutet, dass sie von SQLModel unterstützt werden), wie: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server, usw. + +In diesem Beispiel verwenden wir **SQLite**, da es eine einzelne Datei verwendet und Python integrierte Unterstützung bietet. Sie können also dieses Beispiel kopieren und direkt ausführen. + +Später, für Ihre Produktionsanwendung, möchten Sie möglicherweise einen Datenbankserver wie **PostgreSQL** verwenden. + +/// tip | Tipp + +Es gibt einen offiziellen Projektgenerator mit **FastAPI** und **PostgreSQL**, einschließlich eines Frontends und weiterer Tools: https://github.com/fastapi/full-stack-fastapi-template + +/// + +Dies ist ein sehr einfaches und kurzes Tutorial. Wenn Sie mehr über Datenbanken im Allgemeinen, über SQL oder fortgeschrittenere Funktionen erfahren möchten, besuchen Sie die SQLModel-Dokumentation. + +## `SQLModel` installieren { #install-sqlmodel } + +Stellen Sie zunächst sicher, dass Sie Ihre [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} erstellen, sie aktivieren und dann `sqlmodel` installieren: + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## Die App mit einem einzelnen Modell erstellen { #create-the-app-with-a-single-model } + +Wir erstellen zuerst die einfachste erste Version der App mit einem einzigen **SQLModel**-Modell. + +Später werden wir sie verbessern, indem wir unter der Haube **mehrere Modelle** verwenden, um Sicherheit und Vielseitigkeit zu erhöhen. 🤓 + +### Modelle erstellen { #create-models } + +Importieren Sie `SQLModel` und erstellen Sie ein Datenbankmodell: + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +Die `Hero`-Klasse ist einem Pydantic-Modell sehr ähnlich (faktisch ist sie darunter tatsächlich *ein Pydantic-Modell*). + +Es gibt ein paar Unterschiede: + +* `table=True` sagt SQLModel, dass dies ein *Tabellenmodell* ist, es soll eine **Tabelle** in der SQL-Datenbank darstellen, es ist nicht nur ein *Datenmodell* (wie es jede andere reguläre Pydantic-Klasse wäre). + +* `Field(primary_key=True)` sagt SQLModel, dass die `id` der **Primärschlüssel** in der SQL-Datenbank ist (Sie können mehr über SQL-Primärschlüssel in der SQLModel-Dokumentation erfahren). + + Durch das Festlegen des Typs als `int | None` wird SQLModel wissen, dass diese Spalte ein `INTEGER` in der SQL-Datenbank sein sollte und dass sie `NULLABLE` sein sollte. + +* `Field(index=True)` sagt SQLModel, dass es einen **SQL-Index** für diese Spalte erstellen soll, was schnelleres Suchen in der Datenbank ermöglicht, wenn Daten mittels dieser Spalte gefiltert werden. + + SQLModel wird verstehen, dass etwas, das als `str` deklariert ist, eine SQL-Spalte des Typs `TEXT` (oder `VARCHAR`, abhängig von der Datenbank) sein wird. + +### Eine Engine erstellen { #create-an-engine } + +Eine SQLModel-`engine` (darunter ist es tatsächlich eine SQLAlchemy-`engine`) ist das, was die **Verbindungen** zur Datenbank hält. + +Sie hätten **ein einziges `engine`-Objekt** für Ihren gesamten Code, um sich mit derselben Datenbank zu verbinden. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +Die Verwendung von `check_same_thread=False` erlaubt FastAPI, dieselbe SQLite-Datenbank in verschiedenen Threads zu verwenden. Dies ist notwendig, da **ein einzelner Request** **mehr als einen Thread** verwenden könnte (zum Beispiel in Abhängigkeiten). + +Keine Sorge, so wie der Code strukturiert ist, werden wir später sicherstellen, dass wir **eine einzige SQLModel-*Session* pro Request** verwenden, das ist eigentlich das, was `check_same_thread` erreichen möchte. + +### Die Tabellen erstellen { #create-the-tables } + +Dann fügen wir eine Funktion hinzu, die `SQLModel.metadata.create_all(engine)` verwendet, um die **Tabellen für alle *Tabellenmodelle* zu erstellen**. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### Eine Session-Abhängigkeit erstellen { #create-a-session-dependency } + +Eine **`Session`** speichert die **Objekte im Speicher** und verfolgt alle Änderungen, die an den Daten vorgenommen werden müssen, dann **verwendet sie die `engine`**, um mit der Datenbank zu kommunizieren. + +Wir werden eine FastAPI **Abhängigkeit** mit `yield` erstellen, die eine neue `Session` für jeden Request bereitstellt. Das ist es, was sicherstellt, dass wir eine einzige Session pro Request verwenden. 🤓 + +Dann erstellen wir eine `Annotated`-Abhängigkeit `SessionDep`, um den Rest des Codes zu vereinfachen, der diese Abhängigkeit nutzen wird. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### Die Datenbanktabellen beim Start erstellen { #create-database-tables-on-startup } + +Wir werden die Datenbanktabellen erstellen, wenn die Anwendung startet. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +Hier erstellen wir die Tabellen bei einem Anwendungsstart-Event. + +Für die Produktion würden Sie wahrscheinlich ein Migrationsskript verwenden, das ausgeführt wird, bevor Sie Ihre App starten. 🤓 + +/// tip | Tipp + +SQLModel wird Migrationstools haben, die Alembic wrappen, aber im Moment können Sie Alembic direkt verwenden. + +/// + +### Einen Helden erstellen { #create-a-hero } + +Da jedes SQLModel-Modell auch ein Pydantic-Modell ist, können Sie es in denselben **Typannotationen** verwenden, die Sie für Pydantic-Modelle verwenden könnten. + +Wenn Sie beispielsweise einen Parameter vom Typ `Hero` deklarieren, wird er aus dem **JSON-Body** gelesen. + +Auf die gleiche Weise können Sie es als **Rückgabetyp** der Funktion deklarieren, und dann wird die Form der Daten in der automatischen API-Dokumentation angezeigt. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + +Hier verwenden wir die `SessionDep`-Abhängigkeit (eine `Session`), um den neuen `Hero` zur `Session`-Instanz hinzuzufügen, die Änderungen an der Datenbank zu committen, die Daten im `hero` zu aktualisieren und ihn anschließend zurückzugeben. + +### Helden lesen { #read-heroes } + +Wir können `Hero`s aus der Datenbank mit einem `select()` **lesen**. Wir können ein `limit` und `offset` hinzufügen, um die Ergebnisse zu paginieren. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### Einen Helden lesen { #read-one-hero } + +Wir können einen einzelnen `Hero` **lesen**. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### Einen Helden löschen { #delete-a-hero } + +Wir können auch einen `Hero` **löschen**. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### Die App ausführen { #run-the-app } + +Sie können die App ausführen: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Gehen Sie dann zur `/docs`-UI, Sie werden sehen, dass **FastAPI** diese **Modelle** verwendet, um die API zu **dokumentieren**, und es wird sie auch verwenden, um die Daten zu **serialisieren** und zu **validieren**. + +
+ +
+ +## Die App mit mehreren Modellen aktualisieren { #update-the-app-with-multiple-models } + +Jetzt lassen Sie uns diese App ein wenig **refaktorisieren**, um die **Sicherheit** und **Vielseitigkeit** zu erhöhen. + +Wenn Sie die vorherige App überprüfen, können Sie in der UI sehen, dass sie bis jetzt dem Client erlaubt, die `id` des zu erstellenden `Hero` zu bestimmen. 😱 + +Das sollten wir nicht zulassen, sie könnten eine `id` überschreiben, die wir bereits in der DB zugewiesen haben. Die Entscheidung über die `id` sollte vom **Backend** oder der **Datenbank** getroffen werden, **nicht vom Client**. + +Außerdem erstellen wir einen `secret_name` für den Helden, aber bisher geben wir ihn überall zurück, das ist nicht sehr **geheim** ... 😅 + +Wir werden diese Dinge beheben, indem wir ein paar **zusätzliche Modelle** hinzufügen. Hier wird SQLModel glänzen. ✨ + +### Mehrere Modelle erstellen { #create-multiple-models } + +In **SQLModel** ist jede Modellklasse, die `table=True` hat, ein **Tabellenmodell**. + +Und jede Modellklasse, die `table=True` nicht hat, ist ein **Datenmodell**, diese sind tatsächlich nur Pydantic-Modelle (mit ein paar kleinen zusätzlichen Funktionen). 🤓 + +Mit SQLModel können wir **Vererbung** verwenden, um **doppelte Felder** in allen Fällen zu **vermeiden**. + +#### `HeroBase` – die Basisklasse { #herobase-the-base-class } + +Fangen wir mit einem `HeroBase`-Modell an, das alle **Felder hat, die von allen Modellen geteilt werden**: + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` – das *Tabellenmodell* { #hero-the-table-model } + +Dann erstellen wir `Hero`, das tatsächliche *Tabellenmodell*, mit den **zusätzlichen Feldern**, die nicht immer in den anderen Modellen enthalten sind: + +* `id` +* `secret_name` + +Da `Hero` von `HeroBase` erbt, hat es **auch** die **Felder**, die in `HeroBase` deklariert sind, also sind alle Felder von `Hero`: + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` – das öffentliche *Datenmodell* { #heropublic-the-public-data-model } + +Als nächstes erstellen wir ein `HeroPublic`-Modell, das an die API-Clients **zurückgegeben** wird. + +Es hat dieselben Felder wie `HeroBase`, sodass es `secret_name` nicht enthält. + +Endlich ist die Identität unserer Helden geschützt! 🥷 + +Es deklariert auch `id: int` erneut. Indem wir dies tun, machen wir einen **Vertrag** mit den API-Clients, damit sie immer damit rechnen können, dass die `id` vorhanden ist und ein `int` ist (sie wird niemals `None` sein). + +/// tip | Tipp + +Es ist sehr nützlich für die API-Clients, wenn das Rückgabemodell sicherstellt, dass ein Wert immer verfügbar und immer `int` (nicht `None`) ist, sie können viel einfacheren Code schreiben, wenn sie diese Sicherheit haben. + +Auch **automatisch generierte Clients** werden einfachere Schnittstellen haben, damit die Entwickler, die mit Ihrer API kommunizieren, viel mehr Freude an der Arbeit mit Ihrer API haben können. 😎 + +/// + +Alle Felder in `HeroPublic` sind dieselben wie in `HeroBase`, mit `id`, das als `int` (nicht `None`) deklariert ist: + +* `id` +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` – das *Datenmodell* zum Erstellen eines Helden { #herocreate-the-data-model-to-create-a-hero } + +Nun erstellen wir ein `HeroCreate`-Modell, das die Daten der Clients **validiert**. + +Es hat dieselben Felder wie `HeroBase`, und es hat auch `secret_name`. + +Wenn die Clients **einen neuen Helden erstellen**, senden sie jetzt den `secret_name`, er wird in der Datenbank gespeichert, aber diese geheimen Namen werden den API-Clients nicht zurückgegeben. + +/// tip | Tipp + +So würden Sie **Passwörter** handhaben. Empfangen Sie sie, aber geben Sie sie nicht in der API zurück. + +Sie würden auch die Werte der Passwörter **hashen**, bevor Sie sie speichern, und sie **niemals im Klartext** speichern. + +/// + +Die Felder von `HeroCreate` sind: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` – das *Datenmodell* zum Aktualisieren eines Helden { #heroupdate-the-data-model-to-update-a-hero } + +In der vorherigen Version der App hatten wir keine Möglichkeit, einen Helden **zu aktualisieren**, aber jetzt mit **mehreren Modellen** können wir es. 🎉 + +Das `HeroUpdate`-*Datenmodell* ist etwas Besonderes, es hat **die selben Felder**, die benötigt werden, um einen neuen Helden zu erstellen, aber alle Felder sind **optional** (sie haben alle einen Defaultwert). Auf diese Weise, wenn Sie einen Helden aktualisieren, können Sie nur die Felder senden, die Sie aktualisieren möchten. + +Da sich tatsächlich **alle Felder ändern** (der Typ enthält jetzt `None` und sie haben jetzt einen Standardwert von `None`), müssen wir sie erneut **deklarieren**. + +Wir müssen wirklich nicht von `HeroBase` erben, weil wir alle Felder neu deklarieren. Ich lasse es aus Konsistenzgründen erben, aber das ist nicht notwendig. Es ist mehr eine Frage des persönlichen Geschmacks. 🤷 + +Die Felder von `HeroUpdate` sind: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### Mit `HeroCreate` erstellen und ein `HeroPublic` zurückgeben { #create-with-herocreate-and-return-a-heropublic } + +Nun, da wir **mehrere Modelle** haben, können wir die Teile der App aktualisieren, die sie verwenden. + +Wir empfangen im Request ein `HeroCreate`-*Datenmodell* und daraus erstellen wir ein `Hero`-*Tabellenmodell*. + +Dieses neue *Tabellenmodell* `Hero` wird die vom Client gesendeten Felder haben und zusätzlich eine `id`, die von der Datenbank generiert wird. + +Dann geben wir das gleiche *Tabellenmodell* `Hero` von der Funktion zurück. Aber da wir das `response_model` mit dem `HeroPublic`-*Datenmodell* deklarieren, wird **FastAPI** `HeroPublic` verwenden, um die Daten zu validieren und zu serialisieren. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | Tipp + +Jetzt verwenden wir `response_model=HeroPublic` anstelle der **Rückgabetyp-Annotation** `-> HeroPublic`, weil der Wert, den wir zurückgeben, tatsächlich *kein* `HeroPublic` ist. + +Wenn wir `-> HeroPublic` deklariert hätten, würden Ihr Editor und Linter (zu Recht) reklamieren, dass Sie ein `Hero` anstelle eines `HeroPublic` zurückgeben. + +Durch die Deklaration in `response_model` sagen wir **FastAPI**, dass es seine Aufgabe erledigen soll, ohne die Typannotationen und die Hilfe von Ihrem Editor und anderen Tools zu beeinträchtigen. + +/// + +### Helden mit `HeroPublic` lesen { #read-heroes-with-heropublic } + +Wir können dasselbe wie zuvor tun, um `Hero`s zu **lesen**, und erneut verwenden wir `response_model=list[HeroPublic]`, um sicherzustellen, dass die Daten ordnungsgemäß validiert und serialisiert werden. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### Einen einzelnen Helden mit `HeroPublic` lesen { #read-one-hero-with-heropublic } + +Wir können einen einzelnen Helden **lesen**: + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### Einen Helden mit `HeroUpdate` aktualisieren { #update-a-hero-with-heroupdate } + +Wir können einen Helden **aktualisieren**. Dafür verwenden wir eine HTTP-`PATCH`-Operation. + +Und im Code erhalten wir ein `dict` mit allen Daten, die vom Client gesendet wurden, **nur die Daten, die vom Client gesendet wurden**, unter Ausschluss von Werten, die dort nur als Defaultwerte vorhanden wären. Um dies zu tun, verwenden wir `exclude_unset=True`. Das ist der Haupttrick. 🪄 + +Dann verwenden wir `hero_db.sqlmodel_update(hero_data)`, um die `hero_db` mit den Daten aus `hero_data` zu aktualisieren. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### Einen Helden wieder löschen { #delete-a-hero-again } + +Das **Löschen** eines Helden bleibt ziemlich gleich. + +Wir werden dieses Mal nicht dem Wunsch nachgeben, alles zu refaktorisieren. 😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### Die App erneut ausführen { #run-the-app-again } + +Sie können die App erneut ausführen: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Wenn Sie zur `/docs`-API-UI gehen, werden Sie sehen, dass sie jetzt aktualisiert ist und nicht mehr erwarten wird, die `id` vom Client beim Erstellen eines Helden zu erhalten, usw. + +
+ +
+ +## Zusammenfassung { #recap } + +Sie können **SQLModel** verwenden, um mit einer SQL-Datenbank zu interagieren und den Code mit *Datenmodellen* und *Tabellenmodellen* zu vereinfachen. + +Sie können viel mehr in der **SQLModel**-Dokumentation lernen, es gibt ein längeres Mini-Tutorial zur Verwendung von SQLModel mit **FastAPI**. 🚀 diff --git a/docs/de/docs/tutorial/static-files.md b/docs/de/docs/tutorial/static-files.md index cca8cd0ea4..0c4e7c8abd 100644 --- a/docs/de/docs/tutorial/static-files.md +++ b/docs/de/docs/tutorial/static-files.md @@ -1,17 +1,15 @@ -# Statische Dateien +# Statische Dateien { #static-files } Mit `StaticFiles` können Sie statische Dateien aus einem Verzeichnis automatisch bereitstellen. -## `StaticFiles` verwenden +## `StaticFiles` verwenden { #use-staticfiles } * Importieren Sie `StaticFiles`. * „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad. -```Python hl_lines="2 6" -{!../../../docs_src/static_files/tutorial001.py!} -``` +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} -/// note | "Technische Details" +/// note | Technische Details Sie könnten auch `from starlette.staticfiles import StaticFiles` verwenden. @@ -19,7 +17,7 @@ Sie könnten auch `from starlette.staticfiles import StaticFiles` verwenden. /// -### Was ist „Mounten“? +### Was ist „Mounten“ { #what-is-mounting } „Mounten“ bedeutet das Hinzufügen einer vollständigen „unabhängigen“ Anwendung an einem bestimmten Pfad, die sich dann um die Handhabung aller Unterpfade kümmert. @@ -27,7 +25,7 @@ Dies unterscheidet sich von der Verwendung eines `APIRouter`, da eine gemountete Weitere Informationen hierzu finden Sie im [Handbuch für fortgeschrittene Benutzer](../advanced/index.md){.internal-link target=_blank}. -## Einzelheiten +## Einzelheiten { #details } Das erste `"/static"` bezieht sich auf den Unterpfad, auf dem diese „Unteranwendung“ „gemountet“ wird. Daher wird jeder Pfad, der mit `"/static"` beginnt, von ihr verarbeitet. @@ -35,8 +33,8 @@ Das `directory="static"` bezieht sich auf den Namen des Verzeichnisses, das Ihre Das `name="static"` gibt dieser Unteranwendung einen Namen, der intern von **FastAPI** verwendet werden kann. -Alle diese Parameter können anders als "`static`" lauten, passen Sie sie an die Bedürfnisse und spezifischen Details Ihrer eigenen Anwendung an. +Alle diese Parameter können anders als „`static`“ lauten, passen Sie sie an die Bedürfnisse und spezifischen Details Ihrer eigenen Anwendung an. -## Weitere Informationen +## Weitere Informationen { #more-info } -Weitere Details und Optionen finden Sie in der Dokumentation von Starlette zu statischen Dateien. +Weitere Details und Optionen finden Sie in der Dokumentation von Starlette zu statischen Dateien. diff --git a/docs/de/docs/tutorial/testing.md b/docs/de/docs/tutorial/testing.md index 43ced2aae6..9c28a2a223 100644 --- a/docs/de/docs/tutorial/testing.md +++ b/docs/de/docs/tutorial/testing.md @@ -1,18 +1,22 @@ -# Testen +# Testen { #testing } -Dank Starlette ist das Testen von **FastAPI**-Anwendungen einfach und macht Spaß. +Dank Starlette ist das Testen von **FastAPI**-Anwendungen einfach und macht Spaß. -Es basiert auf HTTPX, welches wiederum auf der Grundlage von requests konzipiert wurde, es ist also sehr vertraut und intuitiv. +Es basiert auf HTTPX, welches wiederum auf der Grundlage von Requests konzipiert wurde, es ist also sehr vertraut und intuitiv. Damit können Sie pytest direkt mit **FastAPI** verwenden. -## Verwendung von `TestClient` +## `TestClient` verwenden { #using-testclient } -/// info +/// info | Info Um `TestClient` zu verwenden, installieren Sie zunächst `httpx`. -Z. B. `pip install httpx`. +Erstellen Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank}, aktivieren Sie sie und installieren Sie es dann, z. B.: + +```console +$ pip install httpx +``` /// @@ -26,11 +30,9 @@ Verwenden Sie das `TestClient`-Objekt auf die gleiche Weise wie `httpx`. Schreiben Sie einfache `assert`-Anweisungen mit den Standard-Python-Ausdrücken, die Sie überprüfen müssen (wiederum, Standard-`pytest`). -```Python hl_lines="2 12 15-18" -{!../../../docs_src/app_testing/tutorial001.py!} -``` +{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} -/// tip | "Tipp" +/// tip | Tipp Beachten Sie, dass die Testfunktionen normal `def` und nicht `async def` sind. @@ -40,7 +42,7 @@ Dadurch können Sie `pytest` ohne Komplikationen direkt nutzen. /// -/// note | "Technische Details" +/// note | Technische Details Sie könnten auch `from starlette.testclient import TestClient` verwenden. @@ -48,19 +50,19 @@ Sie könnten auch `from starlette.testclient import TestClient` verwenden. /// -/// tip | "Tipp" +/// tip | Tipp -Wenn Sie in Ihren Tests neben dem Senden von Anfragen an Ihre FastAPI-Anwendung auch `async`-Funktionen aufrufen möchten (z. B. asynchrone Datenbankfunktionen), werfen Sie einen Blick auf die [Async-Tests](../advanced/async-tests.md){.internal-link target=_blank} im Handbuch für fortgeschrittene Benutzer. +Wenn Sie in Ihren Tests neben dem Senden von Requests an Ihre FastAPI-Anwendung auch `async`-Funktionen aufrufen möchten (z. B. asynchrone Datenbankfunktionen), werfen Sie einen Blick auf die [Async-Tests](../advanced/async-tests.md){.internal-link target=_blank} im Handbuch für fortgeschrittene Benutzer. /// -## Tests separieren +## Tests separieren { #separating-tests } In einer echten Anwendung würden Sie Ihre Tests wahrscheinlich in einer anderen Datei haben. Und Ihre **FastAPI**-Anwendung könnte auch aus mehreren Dateien/Modulen, usw. bestehen. -### **FastAPI** Anwendungsdatei +### **FastAPI** Anwendungsdatei { #fastapi-app-file } Nehmen wir an, Sie haben eine Dateistruktur wie in [Größere Anwendungen](bigger-applications.md){.internal-link target=_blank} beschrieben: @@ -74,11 +76,10 @@ Nehmen wir an, Sie haben eine Dateistruktur wie in [Größere Anwendungen](bigge In der Datei `main.py` haben Sie Ihre **FastAPI**-Anwendung: -```Python -{!../../../docs_src/app_testing/main.py!} -``` +{* ../../docs_src/app_testing/main.py *} -### Testdatei + +### Testdatei { #testing-file } Dann könnten Sie eine Datei `test_main.py` mit Ihren Tests haben. Sie könnte sich im selben Python-Package befinden (dasselbe Verzeichnis mit einer `__init__.py`-Datei): @@ -92,17 +93,16 @@ Dann könnten Sie eine Datei `test_main.py` mit Ihren Tests haben. Sie könnte s Da sich diese Datei im selben Package befindet, können Sie relative Importe verwenden, um das Objekt `app` aus dem `main`-Modul (`main.py`) zu importieren: -```Python hl_lines="3" -{!../../../docs_src/app_testing/test_main.py!} -``` +{* ../../docs_src/app_testing/test_main.py hl[3] *} + ... und haben den Code für die Tests wie zuvor. -## Testen: erweitertes Beispiel +## Testen: erweitertes Beispiel { #testing-extended-example } Nun erweitern wir dieses Beispiel und fügen weitere Details hinzu, um zu sehen, wie verschiedene Teile getestet werden. -### Erweiterte **FastAPI**-Anwendungsdatei +### Erweiterte **FastAPI**-Anwendungsdatei { #extended-fastapi-app-file } Fahren wir mit der gleichen Dateistruktur wie zuvor fort: @@ -125,7 +125,7 @@ Beide *Pfadoperationen* erfordern einen `X-Token`-Header. //// tab | Python 3.10+ ```Python -{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} +{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} ``` //// @@ -133,7 +133,7 @@ Beide *Pfadoperationen* erfordern einen `X-Token`-Header. //// tab | Python 3.9+ ```Python -{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} +{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} ``` //// @@ -141,46 +141,45 @@ Beide *Pfadoperationen* erfordern einen `X-Token`-Header. //// tab | Python 3.8+ ```Python -{!> ../../../docs_src/app_testing/app_b_an/main.py!} +{!> ../../docs_src/app_testing/app_b_an/main.py!} ``` //// //// tab | Python 3.10+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. /// ```Python -{!> ../../../docs_src/app_testing/app_b_py310/main.py!} +{!> ../../docs_src/app_testing/app_b_py310/main.py!} ``` //// //// tab | Python 3.8+ nicht annotiert -/// tip | "Tipp" +/// tip | Tipp Bevorzugen Sie die `Annotated`-Version, falls möglich. /// ```Python -{!> ../../../docs_src/app_testing/app_b/main.py!} +{!> ../../docs_src/app_testing/app_b/main.py!} ``` //// -### Erweiterte Testdatei +### Erweiterte Testdatei { #extended-testing-file } Anschließend könnten Sie `test_main.py` mit den erweiterten Tests aktualisieren: -```Python -{!> ../../../docs_src/app_testing/app_b/test_main.py!} -``` +{* ../../docs_src/app_testing/app_b/test_main.py *} + Wenn Sie möchten, dass der Client Informationen im Request übergibt und Sie nicht wissen, wie das geht, können Sie suchen (googeln), wie es mit `httpx` gemacht wird, oder sogar, wie es mit `requests` gemacht wird, da das Design von HTTPX auf dem Design von Requests basiert. @@ -189,14 +188,14 @@ Dann machen Sie in Ihren Tests einfach das gleiche. Z. B.: * Um einen *Pfad*- oder *Query*-Parameter zu übergeben, fügen Sie ihn der URL selbst hinzu. -* Um einen JSON-Body zu übergeben, übergeben Sie ein Python-Objekt (z. B. ein `dict`) an den Parameter `json`. +* Um einen JSON-Body zu übergeben, übergeben Sie ein Python-Objekt (z. B. ein `dict`) an den Parameter `json`. * Wenn Sie *Formulardaten* anstelle von JSON senden müssen, verwenden Sie stattdessen den `data`-Parameter. * Um *Header* zu übergeben, verwenden Sie ein `dict` im `headers`-Parameter. * Für *Cookies* ein `dict` im `cookies`-Parameter. Weitere Informationen zum Übergeben von Daten an das Backend (mithilfe von `httpx` oder dem `TestClient`) finden Sie in der HTTPX-Dokumentation. -/// info +/// info | Info Beachten Sie, dass der `TestClient` Daten empfängt, die nach JSON konvertiert werden können, keine Pydantic-Modelle. @@ -204,9 +203,11 @@ Wenn Sie ein Pydantic-Modell in Ihrem Test haben und dessen Daten während des T /// -## Tests ausführen +## Tests ausführen { #run-it } -Danach müssen Sie nur noch `pytest` installieren: +Danach müssen Sie nur noch `pytest` installieren. + +Erstellen Sie eine [virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank}, aktivieren Sie sie und installieren Sie es dann, z. B.:
diff --git a/docs/de/docs/virtual-environments.md b/docs/de/docs/virtual-environments.md new file mode 100644 index 0000000000..497f1b44db --- /dev/null +++ b/docs/de/docs/virtual-environments.md @@ -0,0 +1,842 @@ +# Virtuelle Umgebungen { #virtual-environments } + +Wenn Sie an Python-Projekten arbeiten, sollten Sie wahrscheinlich eine **virtuelle Umgebung** (oder einen ähnlichen Mechanismus) verwenden, um die Packages, die Sie für jedes Projekt installieren, zu isolieren. + +/// info | Info + +Wenn Sie bereits über virtuelle Umgebungen Bescheid wissen, wie man sie erstellt und verwendet, möchten Sie diesen Abschnitt vielleicht überspringen. 🤓 + +/// + +/// tip | Tipp + +Eine **virtuelle Umgebung** unterscheidet sich von einer **Umgebungsvariable**. + +Eine **Umgebungsvariable** ist eine Variable im System, die von Programmen verwendet werden kann. + +Eine **virtuelle Umgebung** ist ein Verzeichnis mit einigen Dateien darin. + +/// + +/// info | Info + +Diese Seite wird Ihnen beibringen, wie Sie **virtuelle Umgebungen** verwenden und wie sie funktionieren. + +Wenn Sie bereit sind, ein **Tool zu verwenden, das alles für Sie verwaltet** (einschließlich der Installation von Python), probieren Sie uv. + +/// + +## Ein Projekt erstellen { #create-a-project } + +Erstellen Sie zuerst ein Verzeichnis für Ihr Projekt. + +Was ich normalerweise mache, ist, dass ich ein Verzeichnis namens `code` in meinem Home/Benutzerverzeichnis erstelle. + +Und darin erstelle ich ein Verzeichnis pro Projekt. + +
+ +```console +// Gehe zum Home-Verzeichnis +$ cd +// Erstelle ein Verzeichnis für alle Ihre Code-Projekte +$ mkdir code +// Gehe in dieses Code-Verzeichnis +$ cd code +// Erstelle ein Verzeichnis für dieses Projekt +$ mkdir awesome-project +// Gehe in dieses Projektverzeichnis +$ cd awesome-project +``` + +
+ +## Eine virtuelle Umgebung erstellen { #create-a-virtual-environment } + +Wenn Sie zum **ersten Mal** an einem Python-Projekt arbeiten, erstellen Sie eine virtuelle Umgebung **innerhalb Ihres Projekts**. + +/// tip | Tipp + +Sie müssen dies nur **einmal pro Projekt** tun, nicht jedes Mal, wenn Sie daran arbeiten. + +/// + +//// tab | `venv` + +Um eine virtuelle Umgebung zu erstellen, können Sie das `venv`-Modul verwenden, das mit Python geliefert wird. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | Was dieser Befehl bedeutet + +* `python`: das Programm namens `python` verwenden +* `-m`: ein Modul als Skript aufrufen, wir geben als nächstes an, welches Modul +* `venv`: das Modul namens `venv` verwenden, das normalerweise mit Python installiert wird +* `.venv`: die virtuelle Umgebung im neuen Verzeichnis `.venv` erstellen + +/// + +//// + +//// tab | `uv` + +Wenn Sie `uv` installiert haben, können Sie es verwenden, um eine virtuelle Umgebung zu erstellen. + +
+ +```console +$ uv venv +``` + +
+ +/// tip | Tipp + +Standardmäßig erstellt `uv` eine virtuelle Umgebung in einem Verzeichnis namens `.venv`. + +Aber Sie könnten es anpassen, indem Sie ein zusätzliches Argument mit dem Verzeichnisnamen übergeben. + +/// + +//// + +Dieser Befehl erstellt eine neue virtuelle Umgebung in einem Verzeichnis namens `.venv`. + +/// details | `.venv` oder ein anderer Name + +Sie könnten die virtuelle Umgebung in einem anderen Verzeichnis erstellen, aber es ist eine Konvention, sie `.venv` zu nennen. + +/// + +## Die virtuelle Umgebung aktivieren { #activate-the-virtual-environment } + +Aktivieren Sie die neue virtuelle Umgebung, damit jeder Python-Befehl, den Sie ausführen oder jedes Paket, das Sie installieren, diese Umgebung verwendet. + +/// tip | Tipp + +Tun Sie dies **jedes Mal**, wenn Sie eine **neue Terminalsitzung** starten, um an dem Projekt zu arbeiten. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Oder wenn Sie Bash für Windows verwenden (z. B. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip | Tipp + +Jedes Mal, wenn Sie ein **neues Paket** in dieser Umgebung installieren, aktivieren Sie die Umgebung erneut. + +So stellen Sie sicher, dass, wenn Sie ein **Terminalprogramm (CLI)** verwenden, das durch dieses Paket installiert wurde, Sie das aus Ihrer virtuellen Umgebung verwenden und nicht eines, das global installiert ist, wahrscheinlich mit einer anderen Version als der, die Sie benötigen. + +/// + +## Testen, ob die virtuelle Umgebung aktiv ist { #check-the-virtual-environment-is-active } + +Testen Sie, dass die virtuelle Umgebung aktiv ist (der vorherige Befehl funktioniert hat). + +/// tip | Tipp + +Dies ist **optional**, aber es ist eine gute Möglichkeit, **zu überprüfen**, ob alles wie erwartet funktioniert und Sie die beabsichtigte virtuelle Umgebung verwenden. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +Wenn es das `python`-Binary in `.venv/bin/python` anzeigt, innerhalb Ihres Projekts (in diesem Fall `awesome-project`), dann hat es funktioniert. 🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +Wenn es das `python`-Binary in `.venv\Scripts\python` anzeigt, innerhalb Ihres Projekts (in diesem Fall `awesome-project`), dann hat es funktioniert. 🎉 + +//// + +## `pip` aktualisieren { #upgrade-pip } + +/// tip | Tipp + +Wenn Sie `uv` verwenden, würden Sie das verwenden, um Dinge zu installieren anstelle von `pip`, sodass Sie `pip` nicht aktualisieren müssen. 😎 + +/// + +Wenn Sie `pip` verwenden, um Pakete zu installieren (es wird standardmäßig mit Python geliefert), sollten Sie es auf die neueste Version **aktualisieren**. + +Viele exotische Fehler beim Installieren eines Pakets werden einfach dadurch gelöst, dass zuerst `pip` aktualisiert wird. + +/// tip | Tipp + +Normalerweise würden Sie dies **einmal** tun, unmittelbar nachdem Sie die virtuelle Umgebung erstellt haben. + +/// + +Stellen Sie sicher, dass die virtuelle Umgebung aktiv ist (mit dem obigen Befehl) und führen Sie dann aus: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## `.gitignore` hinzufügen { #add-gitignore } + +Wenn Sie **Git** verwenden (was Sie sollten), fügen Sie eine `.gitignore`-Datei hinzu, um alles in Ihrem `.venv` von Git auszuschließen. + +/// tip | Tipp + +Wenn Sie `uv` verwendet haben, um die virtuelle Umgebung zu erstellen, hat es dies bereits für Sie getan, Sie können diesen Schritt überspringen. 😎 + +/// + +/// tip | Tipp + +Tun Sie dies **einmal**, unmittelbar nachdem Sie die virtuelle Umgebung erstellt haben. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | Was dieser Befehl bedeutet + +* `echo "*"`: wird den Text `*` im Terminal „drucken“ (der nächste Teil ändert das ein wenig) +* `>`: alles, was durch den Befehl links von `>` im Terminal ausgegeben wird, sollte nicht gedruckt, sondern stattdessen in die Datei geschrieben werden, die rechts von `>` kommt +* `.gitignore`: der Name der Datei, in die der Text geschrieben werden soll + +Und `*` bedeutet für Git „alles“. Also wird alles im `.venv`-Verzeichnis ignoriert. + +Dieser Befehl erstellt eine Datei `.gitignore` mit dem Inhalt: + +```gitignore +* +``` + +/// + +## Pakete installieren { #install-packages } + +Nachdem Sie die Umgebung aktiviert haben, können Sie Pakete darin installieren. + +/// tip | Tipp + +Tun Sie dies **einmal**, wenn Sie die Pakete installieren oder aktualisieren, die Ihr Projekt benötigt. + +Wenn Sie eine Version aktualisieren oder ein neues Paket hinzufügen müssen, würden Sie **dies erneut tun**. + +/// + +### Pakete direkt installieren { #install-packages-directly } + +Wenn Sie es eilig haben und keine Datei verwenden möchten, um die Paketanforderungen Ihres Projekts zu deklarieren, können Sie sie direkt installieren. + +/// tip | Tipp + +Es ist eine (sehr) gute Idee, die Pakete und Versionen, die Ihr Programm benötigt, in einer Datei zu speichern (zum Beispiel `requirements.txt` oder `pyproject.toml`). + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Wenn Sie `uv` haben: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### Installation von `requirements.txt` { #install-from-requirements-txt } + +Wenn Sie eine `requirements.txt` haben, können Sie diese nun verwenden, um deren Pakete zu installieren. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Wenn Sie `uv` haben: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +Eine `requirements.txt` mit einigen Paketen könnte folgendermaßen aussehen: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## Ihr Programm ausführen { #run-your-program } + +Nachdem Sie die virtuelle Umgebung aktiviert haben, können Sie Ihr Programm ausführen, und es wird das Python innerhalb Ihrer virtuellen Umgebung mit den Paketen verwenden, die Sie dort installiert haben. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Ihren Editor konfigurieren { #configure-your-editor } + +Sie würden wahrscheinlich einen Editor verwenden, stellen Sie sicher, dass Sie ihn so konfigurieren, dass er dieselbe virtuelle Umgebung verwendet, die Sie erstellt haben (er wird sie wahrscheinlich automatisch erkennen), sodass Sie Autovervollständigungen und Inline-Fehler erhalten können. + +Zum Beispiel: + +* VS Code +* PyCharm + +/// tip | Tipp + +Normalerweise müssen Sie dies nur **einmal** tun, wenn Sie die virtuelle Umgebung erstellen. + +/// + +## Die virtuelle Umgebung deaktivieren { #deactivate-the-virtual-environment } + +Sobald Sie mit der Arbeit an Ihrem Projekt fertig sind, können Sie die virtuelle Umgebung **deaktivieren**. + +
+ +```console +$ deactivate +``` + +
+ +Auf diese Weise, wenn Sie `python` ausführen, wird nicht versucht, es aus dieser virtuellen Umgebung mit den dort installierten Paketen auszuführen. + +## Bereit zu arbeiten { #ready-to-work } + +Jetzt sind Sie bereit, mit Ihrem Projekt zu arbeiten. + +/// tip | Tipp + +Möchten Sie verstehen, was das alles oben bedeutet? + +Lesen Sie weiter. 👇🤓 + +/// + +## Warum virtuelle Umgebungen { #why-virtual-environments } + +Um mit FastAPI zu arbeiten, müssen Sie Python installieren. + +Danach müssen Sie FastAPI und alle anderen Pakete, die Sie verwenden möchten, **installieren**. + +Um Pakete zu installieren, würden Sie normalerweise den `pip`-Befehl verwenden, der mit Python geliefert wird (oder ähnliche Alternativen). + +Wenn Sie jedoch `pip` direkt verwenden, werden die Pakete in Ihrer **globalen Python-Umgebung** (der globalen Installation von Python) installiert. + +### Das Problem { #the-problem } + +Was ist also das Problem beim Installieren von Paketen in der globalen Python-Umgebung? + +Irgendwann werden Sie wahrscheinlich viele verschiedene Programme schreiben, die von **verschiedenen Paketen** abhängen. Und einige dieser Projekte, an denen Sie arbeiten, werden von **verschiedenen Versionen** desselben Pakets abhängen. 😱 + +Zum Beispiel könnten Sie ein Projekt namens `philosophers-stone` erstellen, dieses Programm hängt von einem anderen Paket namens **`harry`, Version `1`** ab. Also müssen Sie `harry` installieren. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|benötigt| harry-1[harry v1] +``` + +Dann erstellen Sie zu einem späteren Zeitpunkt ein weiteres Projekt namens `prisoner-of-azkaban`, und dieses Projekt hängt ebenfalls von `harry` ab, aber dieses Projekt benötigt **`harry` Version `3`**. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |benötigt| harry-3[harry v3] +``` + +Aber jetzt ist das Problem, wenn Sie die Pakete global (in der globalen Umgebung) installieren anstatt in einer lokalen **virtuellen Umgebung**, müssen Sie wählen, welche Version von `harry` zu installieren ist. + +Wenn Sie `philosophers-stone` ausführen möchten, müssen Sie zuerst `harry` Version `1` installieren, zum Beispiel mit: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +Und dann hätten Sie `harry` Version `1` in Ihrer globalen Python-Umgebung installiert. + +```mermaid +flowchart LR + subgraph global[globale Umgebung] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone-Projekt] + stone(philosophers-stone) -->|benötigt| harry-1 + end +``` + +Aber dann, wenn Sie `prisoner-of-azkaban` ausführen möchten, müssen Sie `harry` Version `1` deinstallieren und `harry` Version `3` installieren (oder einfach die Version `3` installieren, was die Version `1` automatisch deinstallieren würde). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +Und dann hätten Sie `harry` Version `3` in Ihrer globalen Python-Umgebung installiert. + +Und wenn Sie versuchen, `philosophers-stone` erneut auszuführen, besteht die Möglichkeit, dass es **nicht funktioniert**, weil es `harry` Version `1` benötigt. + +```mermaid +flowchart LR + subgraph global[globale Umgebung] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone-Projekt] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban-Projekt] + azkaban(prisoner-of-azkaban) --> |benötigt| harry-3 + end +``` + +/// tip | Tipp + +Es ist sehr üblich in Python-Paketen, alles zu versuchen, **Breaking Changes** in **neuen Versionen** zu vermeiden, aber es ist besser, auf Nummer sicher zu gehen und neue Versionen absichtlich zu installieren und wenn Sie die Tests ausführen können, sicherzustellen, dass alles korrekt funktioniert. + +/// + +Stellen Sie sich das jetzt mit **vielen** anderen **Paketen** vor, von denen alle Ihre **Projekte abhängen**. Das ist sehr schwierig zu verwalten. Und Sie würden wahrscheinlich einige Projekte mit einigen **inkompatiblen Versionen** der Pakete ausführen und nicht wissen, warum etwas nicht funktioniert. + +Darüber hinaus könnte es je nach Ihrem Betriebssystem (z. B. Linux, Windows, macOS) bereits mit installiertem Python geliefert worden sein. Und in diesem Fall hatte es wahrscheinlich einige Pakete mit bestimmten Versionen **installiert**, die von Ihrem System benötigt werden. Wenn Sie Pakete in der globalen Python-Umgebung installieren, könnten Sie einige der Programme, die mit Ihrem Betriebssystem geliefert wurden, **kaputtmachen**. + +## Wo werden Pakete installiert { #where-are-packages-installed } + +Wenn Sie Python installieren, werden einige Verzeichnisse mit einigen Dateien auf Ihrem Rechner erstellt. + +Einige dieser Verzeichnisse sind dafür zuständig, alle Pakete, die Sie installieren, aufzunehmen. + +Wenn Sie ausführen: + +
+ +```console +// Führen Sie dies jetzt nicht aus, es ist nur ein Beispiel 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +Das lädt eine komprimierte Datei mit dem FastAPI-Code herunter, normalerweise von PyPI. + +Es wird auch Dateien für andere Pakete **herunterladen**, von denen FastAPI abhängt. + +Dann wird es all diese Dateien **extrahieren** und sie in ein Verzeichnis auf Ihrem Rechner legen. + +Standardmäßig werden diese heruntergeladenen und extrahierten Dateien in das Verzeichnis gelegt, das mit Ihrer Python-Installation kommt, das ist die **globale Umgebung**. + +## Was sind virtuelle Umgebungen { #what-are-virtual-environments } + +Die Lösung für die Probleme, alle Pakete in der globalen Umgebung zu haben, besteht darin, eine **virtuelle Umgebung für jedes Projekt** zu verwenden, an dem Sie arbeiten. + +Eine virtuelle Umgebung ist ein **Verzeichnis**, sehr ähnlich zu dem globalen, in dem Sie die Pakete für ein Projekt installieren können. + +Auf diese Weise hat jedes Projekt seine eigene virtuelle Umgebung (`.venv`-Verzeichnis) mit seinen eigenen Paketen. + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone-Projekt] + stone(philosophers-stone) --->|benötigt| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban-Projekt] + azkaban(prisoner-of-azkaban) --->|benötigt| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## Was bedeutet das Aktivieren einer virtuellen Umgebung { #what-does-activating-a-virtual-environment-mean } + +Wenn Sie eine virtuelle Umgebung aktivieren, zum Beispiel mit: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Oder wenn Sie Bash für Windows verwenden (z. B. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +Dieser Befehl erstellt oder ändert einige [Umgebungsvariablen](environment-variables.md){.internal-link target=_blank}, die für die nächsten Befehle verfügbar sein werden. + +Eine dieser Variablen ist die `PATH`-Variable. + +/// tip | Tipp + +Sie können mehr über die `PATH`-Umgebungsvariable im Abschnitt [Umgebungsvariablen](environment-variables.md#path-environment-variable){.internal-link target=_blank} erfahren. + +/// + +Das Aktivieren einer virtuellen Umgebung fügt deren Pfad `.venv/bin` (auf Linux und macOS) oder `.venv\Scripts` (auf Windows) zur `PATH`-Umgebungsvariable hinzu. + +Angenommen, die `PATH`-Variable sah vor dem Aktivieren der Umgebung so aus: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +Das bedeutet, dass das System nach Programmen sucht in: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +Das bedeutet, dass das System nach Programmen sucht in: + +* `C:\Windows\System32` + +//// + +Nach dem Aktivieren der virtuellen Umgebung würde die `PATH`-Variable folgendermaßen aussehen: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Das bedeutet, dass das System nun zuerst nach Programmen sucht in: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +bevor es in den anderen Verzeichnissen sucht. + +Wenn Sie also `python` im Terminal eingeben, wird das System das Python-Programm in + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +finden und dieses verwenden. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +Das bedeutet, dass das System nun zuerst nach Programmen sucht in: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +bevor es in den anderen Verzeichnissen sucht. + +Wenn Sie also `python` im Terminal eingeben, wird das System das Python-Programm in + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +finden und dieses verwenden. + +//// + +Ein wichtiger Punkt ist, dass es den Pfad der virtuellen Umgebung am **Anfang** der `PATH`-Variable platziert. Das System wird es **vor** allen anderen verfügbaren Pythons finden. Auf diese Weise, wenn Sie `python` ausführen, wird das Python **aus der virtuellen Umgebung** verwendet anstelle eines anderen `python` (zum Beispiel, einem `python` aus einer globalen Umgebung). + +Das Aktivieren einer virtuellen Umgebung ändert auch ein paar andere Dinge, aber dies ist eines der wichtigsten Dinge, die es tut. + +## Testen einer virtuellen Umgebung { #checking-a-virtual-environment } + +Wenn Sie testen, ob eine virtuelle Umgebung aktiv ist, zum Beispiel mit: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +bedeutet das, dass das `python`-Programm, das verwendet wird, das in der **virtuellen Umgebung** ist. + +Sie verwenden `which` auf Linux und macOS und `Get-Command` in Windows PowerShell. + +So funktioniert dieser Befehl: Er wird in der `PATH`-Umgebungsvariable nachsehen und **jeden Pfad in der Reihenfolge durchgehen**, um das Programm namens `python` zu finden. Sobald er es findet, wird er Ihnen **den Pfad** zu diesem Programm anzeigen. + +Der wichtigste Punkt ist, dass, wenn Sie `python` aufrufen, genau dieses „`python`“ ausgeführt wird. + +So können Sie überprüfen, ob Sie sich in der richtigen virtuellen Umgebung befinden. + +/// tip | Tipp + +Es ist einfach, eine virtuelle Umgebung zu aktivieren, ein Python zu bekommen und dann **zu einem anderen Projekt zu wechseln**. + +Und das zweite Projekt **würde nicht funktionieren**, weil Sie das **falsche Python** verwenden, aus einer virtuellen Umgebung für ein anderes Projekt. + +Es ist nützlich, überprüfen zu können, welches `python` verwendet wird. 🤓 + +/// + +## Warum eine virtuelle Umgebung deaktivieren { #why-deactivate-a-virtual-environment } + +Zum Beispiel könnten Sie an einem Projekt `philosophers-stone` arbeiten, diese virtuelle Umgebung **aktivieren**, Pakete installieren und mit dieser Umgebung arbeiten. + +Und dann möchten Sie an **einem anderen Projekt** `prisoner-of-azkaban` arbeiten. + +Sie gehen zu diesem Projekt: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +Wenn Sie die virtuelle Umgebung für `philosophers-stone` nicht deaktivieren, wird beim Ausführen von `python` im Terminal versucht, das Python von `philosophers-stone` zu verwenden. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Fehler beim Importieren von sirius, es ist nicht installiert 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +Wenn Sie jedoch die virtuelle Umgebung deaktivieren und die neue für `prisoner-of-askaban` aktivieren, wird beim Ausführen von `python` das Python aus der virtuellen Umgebung in `prisoner-of-azkaban` verwendet. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// Sie müssen nicht im alten Verzeichnis sein, um zu deaktivieren, Sie können dies überall tun, sogar nachdem Sie zum anderen Projekt gewechselt haben 😎 +$ deactivate + +// Die virtuelle Umgebung in prisoner-of-azkaban/.venv 🚀 aktivieren +$ source .venv/bin/activate + +// Jetzt, wenn Sie python ausführen, wird das Paket sirius in dieser virtuellen Umgebung gefunden ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## Alternativen { #alternatives } + +Dies ist ein einfacher Leitfaden, um Ihnen den Einstieg zu erleichtern und Ihnen beizubringen, wie alles **unter der Haube** funktioniert. + +Es gibt viele **Alternativen** zur Verwaltung von virtuellen Umgebungen, Paketabhängigkeiten (Anforderungen), Projekten. + +Sobald Sie bereit sind und ein Tool verwenden möchten, das **das gesamte Projekt verwaltet**, Paketabhängigkeiten, virtuelle Umgebungen usw., würde ich Ihnen vorschlagen, uv auszuprobieren. + +`uv` kann viele Dinge tun, es kann: + +* **Python für Sie installieren**, einschließlich verschiedener Versionen +* Die **virtuelle Umgebung** für Ihre Projekte verwalten +* **Pakete installieren** +* Paket**abhängigkeiten und Versionen** für Ihr Projekt verwalten +* Sicherstellen, dass Sie eine **exakte** Menge an Paketen und Versionen zur Installation haben, einschließlich ihrer Abhängigkeiten, damit Sie sicher sein können, dass Sie Ihr Projekt in der Produktionsumgebung genauso ausführen können wie auf Ihrem Rechner während der Entwicklung, dies wird **Locking** genannt +* Und viele andere Dinge + +## Fazit { #conclusion } + +Wenn Sie das alles gelesen und verstanden haben, wissen Sie jetzt **viel mehr** über virtuelle Umgebungen als viele Entwickler da draußen. 🤓 + +Das Wissen über diese Details wird in Zukunft wahrscheinlich nützlich sein, wenn Sie etwas debuggen, das komplex erscheint, aber Sie werden wissen, **wie alles unter der Haube funktioniert**. 😎 diff --git a/docs/de/llm-prompt.md b/docs/de/llm-prompt.md new file mode 100644 index 0000000000..23c111d2d8 --- /dev/null +++ b/docs/de/llm-prompt.md @@ -0,0 +1,338 @@ +### Target language + +Translate to German (Deutsch). + +Language code: de. + + +### Definitions + +"hyphen" + The character «-» + Unicode U+002D (HYPHEN-MINUS) + Alternative names: hyphen, dash, minus sign + +"dash" + The character «–» + Unicode U+2013 (EN DASH) + German name: Halbgeviertstrich + + +### Grammar to use when talking to the reader + +Use the formal grammar (use «Sie» instead of «Du»). + + +### Quotes + +1) Convert neutral double quotes («"») and English double typographic quotes («“» and «”») to German double typographic quotes («„» and «“»). Convert neutral single quotes («'») and English single typographic quotes («‘» and «’») to German single typographic quotes («‚» and «‘»). Do NOT convert «`"» to «„», do NOT convert «"`» to «“». + +Examples: + + Source (English): + + ««« + "Hello world" + “Hello Universe” + "He said: 'Hello'" + “my name is ‘Nils’” + `"__main__"` + `"items"` + »»» + + Result (German): + + ««« + „Hallo Welt“ + „Hallo Universum“ + „Er sagte: ‚Hallo‘“ + „Mein Name ist ‚Nils‘“ + `"__main__"` + `"items"` + »»» + + +### Ellipsis + +1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis. + +Examples: + + Source (English): + + ««« + ...as we intended. + ...this would work: + ...etc. + others... + More to come... + »»» + + Result (German): + + ««« + ... wie wir es beabsichtigt hatten. + ... das würde funktionieren: + ... usw. + Andere ... + Später mehr ... + »»» + +2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there. + + +### Headings + +1) Translate headings using the infinite form. + +Examples: + + Source (English): + + ««« + ## Create a Project { #create-a-project } + »»» + + Translate with (German): + + ««« + ## Ein Projekt erstellen { #create-a-project } + »»» + + Do NOT translate with (German): + + ««« + ## Erstellen Sie ein Projekt { #create-a-project } + »»» + + Source (English): + + ««« + # Install Packages { #install-packages } + »»» + + Translate with (German): + + ««« + # Pakete installieren { #install-packages } + »»» + + Do NOT translate with (German): + + ««« + # Installieren Sie Pakete { #install-packages } + »»» + + Source (English): + + ««« + ### Run Your Program { #run-your-program } + »»» + + Translate with (German): + + ««« + ### Ihr Programm ausführen { #run-your-program } + »»» + + Do NOT translate with (German): + + ««« + ### Führen Sie Ihr Programm aus { #run-your-program } + »»» + +2) Make sure that the translated part of the heading does not end with a period. + +Example: + + Source (English): + + ««« + ## Another module with `APIRouter` { #another-module-with-apirouter } + »»» + + Translate with (German): + + ««« + ## Ein weiteres Modul mit `APIRouter` { #another-module-with-apirouter } + »»» + + Do NOT translate with (German) – notice the added period: + + ««« + ## Ein weiteres Modul mit `APIRouter`. { #another-module-with-apirouter } + »»» + +3) Replace occurrences of literal « - » (a space followed by a hyphen followed by a space) with « – » (a space followed by a dash followed by a space) in the translated part of the heading. + +Example: + + Source (English): + + ««« + # FastAPI in Containers - Docker { #fastapi-in-containers-docker } + »»» + + Translate with (German) – notice the dash: + + ««« + # FastAPI in Containern – Docker { #fastapi-in-containers-docker } + »»» + + Do NOT translate with (German) – notice the hyphen: + + ««« + # FastAPI in Containern - Docker { #fastapi-in-containers-docker } + »»» + +3.1) Do not apply rule 3 when there is no space before or no space after the dash. + +Example: + + Source (English): + + ««« + ## Type hints and annotations { #type-hints-and-annotations } + »»» + + Translate with (German) – use a short dash: + + ««« + ## Typhinweise und -annotationen { #type-hints-and-annotations } + »»» + + Do NOT translate with (German): + + ««« + ## Typhinweise und –annotationen { #type-hints-and-annotations } + »»» + +3.2) Do not apply rule 3 to the untranslated part of the heading inside curly brackets, which you shall not translate. + + +### German instructions, when to use and when not to use hyphens in words (written in first person, which is you) + +In der Regel versuche ich so weit wie möglich Worte zusammenzuschreiben, also ohne Bindestrich, es sei denn, es ist Konkretesding-Klassevondingen, etwa «Pydantic-Modell» (aber: «Datenbankmodell»), «Python-Modul» (aber: «Standardmodul»). Ich setze auch einen Bindestrich, wenn er die gleichen Buchstaben verbindet, etwa «Enum-Member», «Cloud-Dienst», «Template-Engine». Oder wenn das Wort sonst einfach zu lang wird, etwa, «Performance-Optimierung». Oder um etwas visuell besser zu dokumentieren, etwa «Pfadoperation-Dekorator», «Pfadoperation-Funktion». + + +### German instructions about difficult to translate technical terms (written in first person, which is you) + +Ich versuche nicht, alles einzudeutschen. Das bezieht sich besonders auf Begriffe aus dem Bereich der Programmierung. Ich wandele zwar korrekt in Großschreibung um und setze Bindestriche, wo notwendig, aber ansonsten lasse ich solch ein Wort unverändert. Beispielsweise wird aus dem englischen Wort «string» in der deutschen Übersetzung «String», aber nicht «Zeichenkette». Oder aus dem englischen Wort «request body» wird in der deutschen Übersetzung «Requestbody», aber nicht «Anfragekörper». Oder aus dem englischen «response» wird im Deutschen «Response», aber nicht «Antwort». + + +### List of English terms and their preferred German translations + +Below is a list of English terms and their preferred German translations, separated by a colon («:»). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. A term or a translation may be followed by an explanation in brackets, which explains when to translate the term this way. If a translation is preceded by «NOT», then that means: do NOT use this translation for this term. English nouns, starting with the word «the», have the German genus – «der», «die», «das» – prepended to their German translation, to help you to grammatically decline them in the translation. They are given in singular case, unless they have «(plural)» attached, which means they are given in plural case. Verbs are given in the full infinitive – starting with the word «to». + +* «/// check»: «/// check | Testen» +* «/// danger»: «/// danger | Gefahr» +* «/// info»: «/// info | Info» +* «/// note | Technical Details»: «/// note | Technische Details» +* «/// note»: «/// note | Hinweis» +* «/// tip»: «/// tip | Tipp» +* «/// warning»: «/// warning | Achtung» +* «you»: «Sie» +* «your»: «Ihr» +* «e.g»: «z. B.» +* «etc.»: «usw.» +* «ref»: «Ref.» +* «the Tutorial - User guide»: «das Tutorial – Benutzerhandbuch» +* «the Advanced User Guide»: «das Handbuch für fortgeschrittene Benutzer» +* «the SQLModel docs»: «die SQLModel-Dokumentation» +* «the docs»: «die Dokumentation» (use singular case) +* «the env var»: «die Umgebungsvariable» +* «the `PATH` environment variable»: «die `PATH`-Umgebungsvariable» +* «the `PATH`»: «der `PATH`» +* «the `requirements.txt`»: «die `requirements.txt`» +* «the API Router»: «der API-Router» +* «the Authorization-Header»: «der Autorisierungsheader» +* «the `Authorization`-Header»: «der `Authorization`-Header» +* «the background task»: «der Hintergrundtask» +* «the button»: «der Button» +* «the cloud provider»: «der Cloudanbieter» +* «the CLI»: «Das CLI» +* «the command line interface»: «Das Kommandozeileninterface» +* «the default value»: «der Defaultwert» +* «the default value»: NOT «der Standardwert» +* «the default declaration»: «die Default-Deklaration» +* «the dict»: «das Dict» +* «the dictionary»: «das Dictionary» +* «the enumeration»: «die Enumeration» +* «the enum»: «das Enum» +* «the engine»: «die Engine» +* «the error response»: «die Error-Response» +* «the event»: «das Event» +* «the exception»: «die Exception» +* «the exception handler»: «der Exceptionhandler» +* «the form model»: «das Formularmodell» +* «the form body»: «der Formularbody» +* «the header»: «der Header» +* «the headers» (plural): «die Header» +* «in headers» (plural): «in Headern» +* «the forwarded header»: «der Forwarded-Header» +* «the lifespan event»: «das Lifespan-Event» +* «the lock»: «der Lock» +* «the locking»: «das Locking» +* «the mobile application»: «die Mobile-Anwendung» +* «the model object»: «das Modellobjekt» +* «the mounting»: «das Mounten» +* «mounted»: «gemountet» +* «the origin»: «das Origin» +* «the override»: «Die Überschreibung» +* «the parameter»: «der Parameter» +* «the parameters» (plural): «die Parameter» +* «the function parameter»: «der Funktionsparameter» +* «the default parameter»: «der Defaultparameter» +* «the body parameter»: «der Body-Parameter» +* «the request body parameter»: «der Requestbody-Parameter» +* «the path parameter»: «der Pfad-Parameter» +* «the query parameter»: «der Query-Parameter» +* «the cookie parameter»: «der Cookie-Parameter» +* «the header parameter»: «der Header-Parameter» +* «the form parameter»: «der Formular-Parameter» +* «the payload»: «die Payload» +* «the performance»: NOT «die Performance» +* «the query»: «die Query» +* «the recap»: «die Zusammenfassung» +* «the request» (what the client sends to the server): «der Request» +* «the request body»: «der Requestbody» +* «the request bodies» (plural): «die Requestbodys» +* «the response» (what the server sends back to the client): «die Response» +* «the return type»: «der Rückgabetyp» +* «the return value»: «der Rückgabewert» +* «the startup» (the event of the app): «der Startup» +* «the shutdown» (the event of the app): «der Shutdown» +* «the startup event»: «das Startup-Event» +* «the shutdown event»: «das Shutdown-Event» +* «the startup» (of the server): «das Hochfahren» +* «the startup» (the company): «das Startup» +* «the SDK»: «das SDK» +* «the tag»: «der Tag» +* «the type annotation»: «die Typannotation» +* «the type hint»: «der Typhinweis» +* «the wildcard»: «die Wildcard» +* «the worker class»: «die Workerklasse» +* «the worker class»: NOT «die Arbeiterklasse» +* «the worker process»: «der Workerprozess» +* «the worker process»: NOT «der Arbeiterprozess» +* «to commit»: «committen» +* «to modify»: «ändern» +* «to serve» (an application): «bereitstellen» +* «to serve» (a response): «ausliefern» +* «to serve»: NOT «bedienen» +* «to upgrade»: «aktualisieren» +* «to wrap»: «wrappen» +* «to wrap»: NOT «hüllen» +* «`foo` as a `type`»: «`foo` vom Typ `type`» +* «`foo` as a `type`»: «`foo`, ein `type`» +* «FastAPI's X»: «FastAPIs X» +* «Starlette's Y»: «Starlettes Y» +* «X is case-sensitive»: «Groß-/Klein­schrei­bung ist relevant in X» +* «X is case-insensitive»: «Groß-/Klein­schrei­bung ist nicht relevant in X» +* «standard Python»: «Standard-Python» +* «deprecated»: «deprecatet» + + +### Other rules + +Preserve indentation. Keep emoticons. Encode in utf-8. Use Linux line breaks (LF). diff --git a/docs/em/docs/advanced/additional-responses.md b/docs/em/docs/advanced/additional-responses.md index 7a70718c5e..655fc7ab6f 100644 --- a/docs/em/docs/advanced/additional-responses.md +++ b/docs/em/docs/advanced/additional-responses.md @@ -26,9 +26,7 @@ 🖼, 📣 ➕1️⃣ 📨 ⏮️ 👔 📟 `404` & Pydantic 🏷 `Message`, 👆 💪 ✍: -```Python hl_lines="18 22" -{!../../../docs_src/additional_responses/tutorial001.py!} -``` +{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} /// note @@ -177,9 +175,7 @@ 🖼, 👆 💪 🚮 🌖 📻 🆎 `image/png`, 📣 👈 👆 *➡ 🛠️* 💪 📨 🎻 🎚 (⏮️ 📻 🆎 `application/json`) ⚖️ 🇩🇴 🖼: -```Python hl_lines="19-24 28" -{!../../../docs_src/additional_responses/tutorial002.py!} -``` +{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} /// note @@ -207,9 +203,7 @@ & 📨 ⏮️ 👔 📟 `200` 👈 ⚙️ 👆 `response_model`, ✋️ 🔌 🛃 `example`: -```Python hl_lines="20-31" -{!../../../docs_src/additional_responses/tutorial003.py!} -``` +{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} ⚫️ 🔜 🌐 🌀 & 🔌 👆 🗄, & 🎦 🛠️ 🩺: @@ -243,9 +237,7 @@ new_dict = {**old_dict, "new key": "new value"} 🖼: -```Python hl_lines="13-17 26" -{!../../../docs_src/additional_responses/tutorial004.py!} -``` +{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} ## 🌖 ℹ 🔃 🗄 📨 diff --git a/docs/em/docs/advanced/additional-status-codes.md b/docs/em/docs/advanced/additional-status-codes.md index 3f3b0aea4b..907c7e68ee 100644 --- a/docs/em/docs/advanced/additional-status-codes.md +++ b/docs/em/docs/advanced/additional-status-codes.md @@ -14,9 +14,7 @@ 🏆 👈, 🗄 `JSONResponse`, & 📨 👆 🎚 📤 🔗, ⚒ `status_code` 👈 👆 💚: -```Python hl_lines="4 25" -{!../../../docs_src/additional_status_codes/tutorial001.py!} -``` +{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *} /// warning @@ -28,7 +26,7 @@ /// -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.responses import JSONResponse`. diff --git a/docs/em/docs/advanced/advanced-dependencies.md b/docs/em/docs/advanced/advanced-dependencies.md index 22044c783c..3404c26872 100644 --- a/docs/em/docs/advanced/advanced-dependencies.md +++ b/docs/em/docs/advanced/advanced-dependencies.md @@ -18,9 +18,7 @@ 👈, 👥 📣 👩‍🔬 `__call__`: -```Python hl_lines="10" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[10] *} 👉 💼, 👉 `__call__` ⚫️❔ **FastAPI** 🔜 ⚙️ ✅ 🌖 🔢 & 🎧-🔗, & 👉 ⚫️❔ 🔜 🤙 🚶‍♀️ 💲 🔢 👆 *➡ 🛠️ 🔢* ⏪. @@ -28,9 +26,7 @@ & 🔜, 👥 💪 ⚙️ `__init__` 📣 🔢 👐 👈 👥 💪 ⚙️ "🔗" 🔗: -```Python hl_lines="7" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[7] *} 👉 💼, **FastAPI** 🏆 🚫 ⏱ 👆 ⚖️ 💅 🔃 `__init__`, 👥 🔜 ⚙️ ⚫️ 🔗 👆 📟. @@ -38,9 +34,7 @@ 👥 💪 ✍ 👐 👉 🎓 ⏮️: -```Python hl_lines="16" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[16] *} & 👈 🌌 👥 💪 "🔗" 👆 🔗, 👈 🔜 ✔️ `"bar"` 🔘 ⚫️, 🔢 `checker.fixed_content`. @@ -56,9 +50,7 @@ checker(q="somequery") ...& 🚶‍♀️ ⚫️❔ 👈 📨 💲 🔗 👆 *➡ 🛠️ 🔢* 🔢 `fixed_content_included`: -```Python hl_lines="20" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[20] *} /// tip diff --git a/docs/em/docs/advanced/async-tests.md b/docs/em/docs/advanced/async-tests.md index 324b4f68a5..283d4aa097 100644 --- a/docs/em/docs/advanced/async-tests.md +++ b/docs/em/docs/advanced/async-tests.md @@ -32,15 +32,11 @@ 📁 `main.py` 🔜 ✔️: -```Python -{!../../../docs_src/async_tests/main.py!} -``` +{* ../../docs_src/async_tests/main.py *} 📁 `test_main.py` 🔜 ✔️ 💯 `main.py`, ⚫️ 💪 👀 💖 👉 🔜: -```Python -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py *} ## 🏃 ⚫️ @@ -60,9 +56,7 @@ $ pytest 📑 `@pytest.mark.anyio` 💬 ✳ 👈 👉 💯 🔢 🔜 🤙 🔁: -```Python hl_lines="7" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[7] *} /// tip @@ -72,9 +66,7 @@ $ pytest ⤴️ 👥 💪 ✍ `AsyncClient` ⏮️ 📱, & 📨 🔁 📨 ⚫️, ⚙️ `await`. -```Python hl_lines="9-10" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} 👉 🌓: diff --git a/docs/em/docs/advanced/behind-a-proxy.md b/docs/em/docs/advanced/behind-a-proxy.md index bb65e1487e..8b14152c9d 100644 --- a/docs/em/docs/advanced/behind-a-proxy.md +++ b/docs/em/docs/advanced/behind-a-proxy.md @@ -80,7 +80,7 @@ $ uvicorn main:app --root-path /api/v1 🚥 👆 ⚙️ Hypercorn, ⚫️ ✔️ 🎛 `--root-path`. -/// note | "📡 ℹ" +/// note | 📡 ℹ 🔫 🔧 🔬 `root_path` 👉 ⚙️ 💼. @@ -94,9 +94,7 @@ $ uvicorn main:app --root-path /api/v1 📥 👥 ✅ ⚫️ 📧 🎦 🎯. -```Python hl_lines="8" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} ⤴️, 🚥 👆 ▶️ Uvicorn ⏮️: @@ -123,9 +121,7 @@ $ uvicorn main:app --root-path /api/v1 👐, 🚥 👆 🚫 ✔️ 🌌 🚚 📋 ⏸ 🎛 💖 `--root-path` ⚖️ 🌓, 👆 💪 ⚒ `root_path` 🔢 🕐❔ 🏗 👆 FastAPI 📱: -```Python hl_lines="3" -{!../../../docs_src/behind_a_proxy/tutorial002.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} 🚶‍♀️ `root_path` `FastAPI` 🔜 🌓 🚶‍♀️ `--root-path` 📋 ⏸ 🎛 Uvicorn ⚖️ Hypercorn. @@ -305,9 +301,7 @@ $ uvicorn main:app --root-path /api/v1 🖼: -```Python hl_lines="4-7" -{!../../../docs_src/behind_a_proxy/tutorial003.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} 🔜 🏗 🗄 🔗 💖: @@ -354,9 +348,7 @@ $ uvicorn main:app --root-path /api/v1 🚥 👆 🚫 💚 **FastAPI** 🔌 🏧 💽 ⚙️ `root_path`, 👆 💪 ⚙️ 🔢 `root_path_in_servers=False`: -```Python hl_lines="9" -{!../../../docs_src/behind_a_proxy/tutorial004.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} & ⤴️ ⚫️ 🏆 🚫 🔌 ⚫️ 🗄 🔗. diff --git a/docs/em/docs/advanced/custom-response.md b/docs/em/docs/advanced/custom-response.md index eec87b91bf..ab95b3e7be 100644 --- a/docs/em/docs/advanced/custom-response.md +++ b/docs/em/docs/advanced/custom-response.md @@ -30,9 +30,7 @@ ✋️ 🚥 👆 🎯 👈 🎚 👈 👆 🛬 **🎻 ⏮️ 🎻**, 👆 💪 🚶‍♀️ ⚫️ 🔗 📨 🎓 & ❎ ➕ 🌥 👈 FastAPI 🔜 ✔️ 🚶‍♀️ 👆 📨 🎚 🔘 `jsonable_encoder` ⏭ 🚶‍♀️ ⚫️ 📨 🎓. -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001b.py!} -``` +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} /// info @@ -57,9 +55,7 @@ * 🗄 `HTMLResponse`. * 🚶‍♀️ `HTMLResponse` 🔢 `response_class` 👆 *➡ 🛠️ 👨‍🎨*. -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial002.py!} -``` +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} /// info @@ -77,9 +73,7 @@ 🎏 🖼 ⚪️➡️ 🔛, 🛬 `HTMLResponse`, 💪 👀 💖: -```Python hl_lines="2 7 19" -{!../../../docs_src/custom_response/tutorial003.py!} -``` +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} /// warning @@ -103,9 +97,7 @@ 🖼, ⚫️ 💪 🕳 💖: -```Python hl_lines="7 21 23" -{!../../../docs_src/custom_response/tutorial004.py!} -``` +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} 👉 🖼, 🔢 `generate_html_response()` ⏪ 🏗 & 📨 `Response` ↩️ 🛬 🕸 `str`. @@ -121,7 +113,7 @@ ✔️ 🤯 👈 👆 💪 ⚙️ `Response` 📨 🕳 🙆, ⚖️ ✍ 🛃 🎧-🎓. -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.responses import HTMLResponse`. @@ -144,9 +136,7 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 🎚, ⚓️ 🔛 = & 🔁 = ✍ 🆎. -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} ### `HTMLResponse` @@ -156,9 +146,7 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 ✊ ✍ ⚖️ 🔢 & 📨 ✅ ✍ 📨. -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial005.py!} -``` +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} ### `JSONResponse` @@ -180,9 +168,7 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 /// -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001.py!} -``` +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} /// tip @@ -196,18 +182,14 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 👆 💪 📨 `RedirectResponse` 🔗: -```Python hl_lines="2 9" -{!../../../docs_src/custom_response/tutorial006.py!} -``` +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} --- ⚖️ 👆 💪 ⚙️ ⚫️ `response_class` 🔢: -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial006b.py!} -``` +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} 🚥 👆 👈, ⤴️ 👆 💪 📨 📛 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢. @@ -217,17 +199,13 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 👆 💪 ⚙️ `status_code` 🔢 🌀 ⏮️ `response_class` 🔢: -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial006c.py!} -``` +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} ### `StreamingResponse` ✊ 🔁 🚂 ⚖️ 😐 🚂/🎻 & 🎏 📨 💪. -```Python hl_lines="2 14" -{!../../../docs_src/custom_response/tutorial007.py!} -``` +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} #### ⚙️ `StreamingResponse` ⏮️ 📁-💖 🎚 @@ -238,7 +216,7 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 👉 🔌 📚 🗃 🔗 ⏮️ ☁ 💾, 📹 🏭, & 🎏. ```{ .python .annotate hl_lines="2 10-12 14" } -{!../../../docs_src/custom_response/tutorial008.py!} +{!../../docs_src/custom_response/tutorial008.py!} ``` 1️⃣. 👉 🚂 🔢. ⚫️ "🚂 🔢" ↩️ ⚫️ 🔌 `yield` 📄 🔘. @@ -268,15 +246,11 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 📁 📨 🔜 🔌 ☑ `Content-Length`, `Last-Modified` & `ETag` 🎚. -```Python hl_lines="2 10" -{!../../../docs_src/custom_response/tutorial009.py!} -``` +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} 👆 💪 ⚙️ `response_class` 🔢: -```Python hl_lines="2 8 10" -{!../../../docs_src/custom_response/tutorial009b.py!} -``` +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} 👉 💼, 👆 💪 📨 📁 ➡ 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢. @@ -290,9 +264,7 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 👆 💪 ✍ `CustomORJSONResponse`. 👑 👜 👆 ✔️ ✍ `Response.render(content)` 👩‍🔬 👈 📨 🎚 `bytes`: -```Python hl_lines="9-14 17" -{!../../../docs_src/custom_response/tutorial009c.py!} -``` +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} 🔜 ↩️ 🛬: @@ -318,9 +290,7 @@ FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 🖼 🔛, **FastAPI** 🔜 ⚙️ `ORJSONResponse` 🔢, 🌐 *➡ 🛠️*, ↩️ `JSONResponse`. -```Python hl_lines="2 4" -{!../../../docs_src/custom_response/tutorial010.py!} -``` +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} /// tip diff --git a/docs/em/docs/advanced/dataclasses.md b/docs/em/docs/advanced/dataclasses.md index 3f49ef07ca..4dd597262a 100644 --- a/docs/em/docs/advanced/dataclasses.md +++ b/docs/em/docs/advanced/dataclasses.md @@ -4,9 +4,7 @@ FastAPI 🏗 🔛 🔝 **Pydantic**, & 👤 ✔️ 🌏 👆 ❔ ⚙️ Pyda ✋️ FastAPI 🐕‍🦺 ⚙️ `dataclasses` 🎏 🌌: -```Python hl_lines="1 7-12 19-20" -{!../../../docs_src/dataclasses/tutorial001.py!} -``` +{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} 👉 🐕‍🦺 👏 **Pydantic**, ⚫️ ✔️ 🔗 🐕‍🦺 `dataclasses`. @@ -34,9 +32,7 @@ FastAPI 🏗 🔛 🔝 **Pydantic**, & 👤 ✔️ 🌏 👆 ❔ ⚙️ Pyda 👆 💪 ⚙️ `dataclasses` `response_model` 🔢: -```Python hl_lines="1 7-13 19" -{!../../../docs_src/dataclasses/tutorial002.py!} -``` +{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} 🎻 🔜 🔁 🗜 Pydantic 🎻. @@ -53,7 +49,7 @@ FastAPI 🏗 🔛 🔝 **Pydantic**, & 👤 ✔️ 🌏 👆 ❔ ⚙️ Pyda 👈 💼, 👆 💪 🎯 💱 🐩 `dataclasses` ⏮️ `pydantic.dataclasses`, ❔ 💧-♻: ```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } -{!../../../docs_src/dataclasses/tutorial003.py!} +{!../../docs_src/dataclasses/tutorial003.py!} ``` 1️⃣. 👥 🗄 `field` ⚪️➡️ 🐩 `dataclasses`. diff --git a/docs/em/docs/advanced/events.md b/docs/em/docs/advanced/events.md index 12c902c186..dcaac710e1 100644 --- a/docs/em/docs/advanced/events.md +++ b/docs/em/docs/advanced/events.md @@ -30,9 +30,7 @@ 👥 ✍ 🔁 🔢 `lifespan()` ⏮️ `yield` 💖 👉: -```Python hl_lines="16 19" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[16,19] *} 📥 👥 ⚖ 😥 *🕴* 🛠️ 🚚 🏷 🚮 (❌) 🏷 🔢 📖 ⏮️ 🎰 🏫 🏷 ⏭ `yield`. 👉 📟 🔜 🛠️ **⏭** 🈸 **▶️ ✊ 📨**, ⏮️ *🕴*. @@ -50,9 +48,7 @@ 🥇 👜 👀, 👈 👥 ⚖ 🔁 🔢 ⏮️ `yield`. 👉 📶 🎏 🔗 ⏮️ `yield`. -```Python hl_lines="14-19" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[14:19] *} 🥇 🍕 🔢, ⏭ `yield`, 🔜 🛠️ **⏭** 🈸 ▶️. @@ -64,9 +60,7 @@ 👈 🗜 🔢 🔘 🕳 🤙 "**🔁 🔑 👨‍💼**". -```Python hl_lines="1 13" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[1,13] *} **🔑 👨‍💼** 🐍 🕳 👈 👆 💪 ⚙️ `with` 📄, 🖼, `open()` 💪 ⚙️ 🔑 👨‍💼: @@ -88,9 +82,7 @@ async with lifespan(app): `lifespan` 🔢 `FastAPI` 📱 ✊ **🔁 🔑 👨‍💼**, 👥 💪 🚶‍♀️ 👆 🆕 `lifespan` 🔁 🔑 👨‍💼 ⚫️. -```Python hl_lines="22" -{!../../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003.py hl[22] *} ## 🎛 🎉 (😢) @@ -112,9 +104,7 @@ async with lifespan(app): 🚮 🔢 👈 🔜 🏃 ⏭ 🈸 ▶️, 📣 ⚫️ ⏮️ 🎉 `"startup"`: -```Python hl_lines="8" -{!../../../docs_src/events/tutorial001.py!} -``` +{* ../../docs_src/events/tutorial001.py hl[8] *} 👉 💼, `startup` 🎉 🐕‍🦺 🔢 🔜 🔢 🏬 "💽" ( `dict`) ⏮️ 💲. @@ -126,9 +116,7 @@ async with lifespan(app): 🚮 🔢 👈 🔜 🏃 🕐❔ 🈸 🤫 🔽, 📣 ⚫️ ⏮️ 🎉 `"shutdown"`: -```Python hl_lines="6" -{!../../../docs_src/events/tutorial002.py!} -``` +{* ../../docs_src/events/tutorial002.py hl[6] *} 📥, `shutdown` 🎉 🐕‍🦺 🔢 🔜 ✍ ✍ ⏸ `"Application shutdown"` 📁 `log.txt`. @@ -152,7 +140,7 @@ async with lifespan(app): /// info -👆 💪 ✍ 🌅 🔃 👫 🎉 🐕‍🦺 💃 🎉' 🩺. +👆 💪 ✍ 🌅 🔃 👫 🎉 🐕‍🦺 💃 🎉' 🩺. /// diff --git a/docs/em/docs/advanced/generate-clients.md b/docs/em/docs/advanced/generate-clients.md index c8e044340d..a680c9051e 100644 --- a/docs/em/docs/advanced/generate-clients.md +++ b/docs/em/docs/advanced/generate-clients.md @@ -16,21 +16,7 @@ ➡️ ▶️ ⏮️ 🙅 FastAPI 🈸: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9-11 14-15 18 19 23" -{!> ../../../docs_src/generate_clients/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="7-9 12-13 16-17 21" -{!> ../../../docs_src/generate_clients/tutorial001_py39.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial001.py hl[9:11,14:15,18,19,23] *} 👀 👈 *➡ 🛠️* 🔬 🏷 👫 ⚙️ 📨 🚀 & 📨 🚀, ⚙️ 🏷 `Item` & `ResponseMessage`. @@ -136,21 +122,7 @@ frontend-app@1.0.0 generate-client /home/user/code/frontend-app 🖼, 👆 💪 ✔️ 📄 **🏬** & ➕1️⃣ 📄 **👩‍💻**, & 👫 💪 👽 🔖: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="23 28 36" -{!> ../../../docs_src/generate_clients/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="21 26 34" -{!> ../../../docs_src/generate_clients/tutorial002_py39.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial002.py hl[23,28,36] *} ### 🏗 📕 👩‍💻 ⏮️ 🔖 @@ -197,21 +169,7 @@ FastAPI ⚙️ **😍 🆔** 🔠 *➡ 🛠️*, ⚫️ ⚙️ **🛠️ 🆔** 👆 💪 ⤴️ 🚶‍♀️ 👈 🛃 🔢 **FastAPI** `generate_unique_id_function` 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="8-9 12" -{!> ../../../docs_src/generate_clients/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="6-7 10" -{!> ../../../docs_src/generate_clients/tutorial003_py39.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial003.py hl[8:9,12] *} ### 🏗 📕 👩‍💻 ⏮️ 🛃 🛠️ 🆔 @@ -233,9 +191,7 @@ FastAPI ⚙️ **😍 🆔** 🔠 *➡ 🛠️*, ⚫️ ⚙️ **🛠️ 🆔** 👥 💪 ⏬ 🗄 🎻 📁 `openapi.json` & ⤴️ 👥 💪 **❎ 👈 🔡 🔖** ⏮️ ✍ 💖 👉: -```Python -{!../../../docs_src/generate_clients/tutorial004.py!} -``` +{* ../../docs_src/generate_clients/tutorial004.py *} ⏮️ 👈, 🛠️ 🆔 🔜 📁 ⚪️➡️ 👜 💖 `items-get_items` `get_items`, 👈 🌌 👩‍💻 🚂 💪 🏗 🙅 👩‍🔬 📛. diff --git a/docs/em/docs/advanced/middleware.md b/docs/em/docs/advanced/middleware.md index e3cc389c64..22d707062f 100644 --- a/docs/em/docs/advanced/middleware.md +++ b/docs/em/docs/advanced/middleware.md @@ -43,7 +43,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") **FastAPI** 🔌 📚 🛠️ ⚠ ⚙️ 💼, 👥 🔜 👀 ⏭ ❔ ⚙️ 👫. -/// note | "📡 ℹ" +/// note | 📡 ℹ ⏭ 🖼, 👆 💪 ⚙️ `from starlette.middleware.something import SomethingMiddleware`. @@ -57,17 +57,13 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") 🙆 📨 📨 `http` ⚖️ `ws` 🔜 ❎ 🔐 ⚖ ↩️. -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial001.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} ## `TrustedHostMiddleware` 🛠️ 👈 🌐 📨 📨 ✔️ ☑ ⚒ `Host` 🎚, ✔ 💂‍♂ 🛡 🇺🇸🔍 🦠 🎚 👊. -```Python hl_lines="2 6-8" -{!../../../docs_src/advanced_middleware/tutorial002.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} 📄 ❌ 🐕‍🦺: @@ -81,9 +77,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") 🛠️ 🔜 🍵 👯‍♂️ 🐩 & 🎥 📨. -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial003.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} 📄 ❌ 🐕‍🦺: @@ -98,4 +92,4 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") * Uvicorn `ProxyHeadersMiddleware` * 🇸🇲 -👀 🎏 💪 🛠️ ✅ 💃 🛠️ 🩺 & 🔫 👌 📇. +👀 🎏 💪 🛠️ ✅ 💃 🛠️ 🩺 & 🔫 👌 📇. diff --git a/docs/em/docs/advanced/openapi-callbacks.md b/docs/em/docs/advanced/openapi-callbacks.md index 00d54ebecd..b0a8216681 100644 --- a/docs/em/docs/advanced/openapi-callbacks.md +++ b/docs/em/docs/advanced/openapi-callbacks.md @@ -31,9 +31,7 @@ 👉 🍕 📶 😐, 🌅 📟 🎲 ⏪ 😰 👆: -```Python hl_lines="9-13 36-53" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} /// tip @@ -92,9 +90,7 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) 🥇 ✍ 🆕 `APIRouter` 👈 🔜 🔌 1️⃣ ⚖️ 🌅 ⏲. -```Python hl_lines="3 25" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} ### ✍ ⏲ *➡ 🛠️* @@ -105,9 +101,7 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) * ⚫️ 🔜 🎲 ✔️ 📄 💪 ⚫️ 🔜 📨, ✅ `body: InvoiceEvent`. * & ⚫️ 💪 ✔️ 📄 📨 ⚫️ 🔜 📨, ✅ `response_model=InvoiceEventReceived`. -```Python hl_lines="16-18 21-22 28-32" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} 📤 2️⃣ 👑 🔺 ⚪️➡️ 😐 *➡ 🛠️*: @@ -175,9 +169,7 @@ https://www.external.org/events/invoices/2expen51ve 🔜 ⚙️ 🔢 `callbacks` *👆 🛠️ ➡ 🛠️ 👨‍🎨* 🚶‍♀️ 🔢 `.routes` (👈 🤙 `list` 🛣/*➡ 🛠️*) ⚪️➡️ 👈 ⏲ 📻: -```Python hl_lines="35" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} /// tip diff --git a/docs/em/docs/advanced/path-operation-advanced-configuration.md b/docs/em/docs/advanced/path-operation-advanced-configuration.md index b684df26f5..9d9d5fa8da 100644 --- a/docs/em/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/em/docs/advanced/path-operation-advanced-configuration.md @@ -12,9 +12,7 @@ 👆 🔜 ✔️ ⚒ 💭 👈 ⚫️ 😍 🔠 🛠️. -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} ### ⚙️ *➡ 🛠️ 🔢* 📛 { @@ -22,9 +20,7 @@ 👆 🔜 ⚫️ ⏮️ ❎ 🌐 👆 *➡ 🛠️*. -```Python hl_lines="2 12-21 24" -{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *} /// tip @@ -44,9 +40,7 @@ 🚫 *➡ 🛠️* ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚙️ 🔢 `include_in_schema` & ⚒ ⚫️ `False`: -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} ## 🏧 📛 ⚪️➡️ #️⃣ @@ -56,9 +50,7 @@ ⚫️ 🏆 🚫 🎦 🆙 🧾, ✋️ 🎏 🧰 (✅ 🐉) 🔜 💪 ⚙️ 🎂. -```Python hl_lines="19-29" -{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} ## 🌖 📨 @@ -74,7 +66,7 @@ 🕐❔ 👆 📣 *➡ 🛠️* 👆 🈸, **FastAPI** 🔁 🏗 🔗 🗃 🔃 👈 *➡ 🛠️* 🔌 🗄 🔗. -/// note | "📡 ℹ" +/// note | 📡 ℹ 🗄 🔧 ⚫️ 🤙 🛠️ 🎚. @@ -100,9 +92,7 @@ 👉 `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!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} 🚥 👆 📂 🏧 🛠️ 🩺, 👆 ↔ 🔜 🎦 🆙 🔝 🎯 *➡ 🛠️*. @@ -149,9 +139,7 @@ 👆 💪 👈 ⏮️ `openapi_extra`: -```Python hl_lines="20-37 39-40" -{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[20:37,39:40] *} 👉 🖼, 👥 🚫 📣 🙆 Pydantic 🏷. 👐, 📨 💪 🚫 🎻 🎻, ⚫️ ✍ 🔗 `bytes`, & 🔢 `magic_data_reader()` 🔜 🈚 🎻 ⚫️ 🌌. @@ -165,9 +153,7 @@ 🖼, 👉 🈸 👥 🚫 ⚙️ FastAPI 🛠️ 🛠️ ⚗ 🎻 🔗 ⚪️➡️ Pydantic 🏷 🚫 🏧 🔬 🎻. 👐, 👥 📣 📨 🎚 🆎 📁, 🚫 🎻: -```Python hl_lines="17-22 24" -{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22,24] *} 👐, 👐 👥 🚫 ⚙️ 🔢 🛠️ 🛠️, 👥 ⚙️ Pydantic 🏷 ❎ 🏗 🎻 🔗 💽 👈 👥 💚 📨 📁. @@ -175,9 +161,7 @@ & ⤴️ 👆 📟, 👥 🎻 👈 📁 🎚 🔗, & ⤴️ 👥 🔄 ⚙️ 🎏 Pydantic 🏷 ✔ 📁 🎚: -```Python hl_lines="26-33" -{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} /// tip diff --git a/docs/em/docs/advanced/response-change-status-code.md b/docs/em/docs/advanced/response-change-status-code.md index 156efcc16a..4933484dd3 100644 --- a/docs/em/docs/advanced/response-change-status-code.md +++ b/docs/em/docs/advanced/response-change-status-code.md @@ -20,9 +20,7 @@ & ⤴️ 👆 💪 ⚒ `status_code` 👈 *🔀* 📨 🎚. -```Python hl_lines="1 9 12" -{!../../../docs_src/response_change_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} & ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). diff --git a/docs/em/docs/advanced/response-cookies.md b/docs/em/docs/advanced/response-cookies.md index 717fb87ce6..a6e37ad74d 100644 --- a/docs/em/docs/advanced/response-cookies.md +++ b/docs/em/docs/advanced/response-cookies.md @@ -6,9 +6,7 @@ & ⤴️ 👆 💪 ⚒ 🍪 👈 *🔀* 📨 🎚. -```Python hl_lines="1 8-9" -{!../../../docs_src/response_cookies/tutorial002.py!} -``` +{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *} & ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). @@ -26,9 +24,7 @@ ⤴️ ⚒ 🍪 ⚫️, & ⤴️ 📨 ⚫️: -```Python hl_lines="10-12" -{!../../../docs_src/response_cookies/tutorial001.py!} -``` +{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} /// tip @@ -42,7 +38,7 @@ ### 🌅 ℹ -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`. @@ -52,4 +48,4 @@ /// -👀 🌐 💪 🔢 & 🎛, ✅ 🧾 💃. +👀 🌐 💪 🔢 & 🎛, ✅ 🧾 💃. diff --git a/docs/em/docs/advanced/response-directly.md b/docs/em/docs/advanced/response-directly.md index 13ee081c2f..29819a2055 100644 --- a/docs/em/docs/advanced/response-directly.md +++ b/docs/em/docs/advanced/response-directly.md @@ -34,11 +34,9 @@ 📚 💼, 👆 💪 ⚙️ `jsonable_encoder` 🗜 👆 📊 ⏭ 🚶‍♀️ ⚫️ 📨: -```Python hl_lines="6-7 21-22" -{!../../../docs_src/response_directly/tutorial001.py!} -``` +{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.responses import JSONResponse`. @@ -56,9 +54,7 @@ 👆 💪 🚮 👆 📂 🎚 🎻, 🚮 ⚫️ `Response`, & 📨 ⚫️: -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} ## 🗒 diff --git a/docs/em/docs/advanced/response-headers.md b/docs/em/docs/advanced/response-headers.md index 27e1cdbd55..c255380d67 100644 --- a/docs/em/docs/advanced/response-headers.md +++ b/docs/em/docs/advanced/response-headers.md @@ -6,9 +6,7 @@ & ⤴️ 👆 💪 ⚒ 🎚 👈 *🔀* 📨 🎚. -```Python hl_lines="1 7-8" -{!../../../docs_src/response_headers/tutorial002.py!} -``` +{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *} & ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). @@ -24,11 +22,9 @@ ✍ 📨 🔬 [📨 📨 🔗](response-directly.md){.internal-link target=_blank} & 🚶‍♀️ 🎚 🌖 🔢: -```Python hl_lines="10-12" -{!../../../docs_src/response_headers/tutorial001.py!} -``` +{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`. @@ -42,4 +38,4 @@ ✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡. -✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 (✍ 🌅 [⚜ (✖️-🇨🇳 ℹ 🤝)](../tutorial/cors.md){.internal-link target=_blank}), ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺. +✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 (✍ 🌅 [⚜ (✖️-🇨🇳 ℹ 🤝)](../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 index 33470a7268..73736f3b35 100644 --- a/docs/em/docs/advanced/security/http-basic-auth.md +++ b/docs/em/docs/advanced/security/http-basic-auth.md @@ -20,9 +20,7 @@ * ⚫️ 📨 🎚 🆎 `HTTPBasicCredentials`: * ⚫️ 🔌 `username` & `password` 📨. -```Python hl_lines="2 6 10" -{!../../../docs_src/security/tutorial006.py!} -``` +{* ../../docs_src/security/tutorial006.py hl[2,6,10] *} 🕐❔ 👆 🔄 📂 📛 🥇 🕰 (⚖️ 🖊 "🛠️" 🔼 🩺) 🖥 🔜 💭 👆 👆 🆔 & 🔐: @@ -42,9 +40,7 @@ ⤴️ 👥 💪 ⚙️ `secrets.compare_digest()` 🚚 👈 `credentials.username` `"stanleyjobson"`, & 👈 `credentials.password` `"swordfish"`. -```Python hl_lines="1 11-21" -{!../../../docs_src/security/tutorial007.py!} -``` +{* ../../docs_src/security/tutorial007.py hl[1,11:21] *} 👉 🔜 🎏: @@ -108,6 +104,4 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": ⏮️ 🔍 👈 🎓 ❌, 📨 `HTTPException` ⏮️ 👔 📟 4️⃣0️⃣1️⃣ (🎏 📨 🕐❔ 🙅‍♂ 🎓 🚚) & 🚮 🎚 `WWW-Authenticate` ⚒ 🖥 🎦 💳 📋 🔄: -```Python hl_lines="23-27" -{!../../../docs_src/security/tutorial007.py!} -``` +{* ../../docs_src/security/tutorial007.py hl[23:27] *} diff --git a/docs/em/docs/advanced/security/oauth2-scopes.md b/docs/em/docs/advanced/security/oauth2-scopes.md index 73b2ec5406..9e3bc00587 100644 --- a/docs/em/docs/advanced/security/oauth2-scopes.md +++ b/docs/em/docs/advanced/security/oauth2-scopes.md @@ -62,9 +62,7 @@ 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 155" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:125,129:135,140,156] *} 🔜 ➡️ 📄 👈 🔀 🔁 🔁. @@ -74,9 +72,7 @@ Oauth2️⃣ 👫 🎻. `scopes` 🔢 📨 `dict` ⏮️ 🔠 ↔ 🔑 & 📛 💲: -```Python hl_lines="62-65" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[62:65] *} ↩️ 👥 🔜 📣 📚 ↔, 👫 🔜 🎦 🆙 🛠️ 🩺 🕐❔ 👆 🕹-/✔. @@ -102,9 +98,7 @@ Oauth2️⃣ 👫 🎻. /// -```Python hl_lines="155" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[156] *} ## 📣 ↔ *➡ 🛠️* & 🔗 @@ -130,11 +124,9 @@ Oauth2️⃣ 👫 🎻. /// -```Python hl_lines="4 139 168" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[4,140,169] *} -/// info | "📡 ℹ" +/// info | 📡 ℹ `Security` 🤙 🏿 `Depends`, & ⚫️ ✔️ 1️⃣ ➕ 🔢 👈 👥 🔜 👀 ⏪. @@ -158,9 +150,7 @@ Oauth2️⃣ 👫 🎻. 👉 `SecurityScopes` 🎓 🎏 `Request` (`Request` ⚙️ 🤚 📨 🎚 🔗). -```Python hl_lines="8 105" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[8,105] *} ## ⚙️ `scopes` @@ -174,9 +164,7 @@ Oauth2️⃣ 👫 🎻. 👉 ⚠, 👥 🔌 ↔ 🚚 (🚥 🙆) 🎻 👽 🚀 (⚙️ `scope_str`). 👥 🚮 👈 🎻 ⚗ ↔ `WWW-Authenticate` 🎚 (👉 🍕 🔌). -```Python hl_lines="105 107-115" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[105,107:115] *} ## ✔ `username` & 💽 💠 @@ -192,9 +180,7 @@ Oauth2️⃣ 👫 🎻. 👥 ✔ 👈 👥 ✔️ 👩‍💻 ⏮️ 👈 🆔, & 🚥 🚫, 👥 🤚 👈 🎏 ⚠ 👥 ✍ ⏭. -```Python hl_lines="46 116-127" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[46,116:128] *} ## ✔ `scopes` @@ -202,9 +188,7 @@ Oauth2️⃣ 👫 🎻. 👉, 👥 ⚙️ `security_scopes.scopes`, 👈 🔌 `list` ⏮️ 🌐 👫 ↔ `str`. -```Python hl_lines="128-134" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[129:135] *} ## 🔗 🌲 & ↔ diff --git a/docs/em/docs/advanced/settings.md b/docs/em/docs/advanced/settings.md index e84941b572..7fdd0d68a6 100644 --- a/docs/em/docs/advanced/settings.md +++ b/docs/em/docs/advanced/settings.md @@ -148,9 +148,7 @@ Hello World from Python 👆 💪 ⚙️ 🌐 🎏 🔬 ⚒ & 🧰 👆 ⚙️ Pydantic 🏷, 💖 🎏 📊 🆎 & 🌖 🔬 ⏮️ `Field()`. -```Python hl_lines="2 5-8 11" -{!../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} /// tip @@ -166,9 +164,7 @@ Hello World from Python ⤴️ 👆 💪 ⚙️ 🆕 `settings` 🎚 👆 🈸: -```Python hl_lines="18-20" -{!../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[18:20] *} ### 🏃 💽 @@ -202,15 +198,11 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app 🖼, 👆 💪 ✔️ 📁 `config.py` ⏮️: -```Python -{!../../../docs_src/settings/app01/config.py!} -``` +{* ../../docs_src/settings/app01/config.py *} & ⤴️ ⚙️ ⚫️ 📁 `main.py`: -```Python hl_lines="3 11-13" -{!../../../docs_src/settings/app01/main.py!} -``` +{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} /// tip @@ -228,9 +220,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app 👟 ⚪️➡️ ⏮️ 🖼, 👆 `config.py` 📁 💪 👀 💖: -```Python hl_lines="10" -{!../../../docs_src/settings/app02/config.py!} -``` +{* ../../docs_src/settings/app02/config.py hl[10] *} 👀 👈 🔜 👥 🚫 ✍ 🔢 👐 `settings = Settings()`. @@ -238,9 +228,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app 🔜 👥 ✍ 🔗 👈 📨 🆕 `config.Settings()`. -```Python hl_lines="5 11-12" -{!../../../docs_src/settings/app02/main.py!} -``` +{* ../../docs_src/settings/app02/main.py hl[5,11:12] *} /// tip @@ -252,17 +240,13 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app & ⤴️ 👥 💪 🚚 ⚫️ ⚪️➡️ *➡ 🛠️ 🔢* 🔗 & ⚙️ ⚫️ 🙆 👥 💪 ⚫️. -```Python hl_lines="16 18-20" -{!../../../docs_src/settings/app02/main.py!} -``` +{* ../../docs_src/settings/app02/main.py hl[16,18:20] *} ### ⚒ & 🔬 ⤴️ ⚫️ 🔜 📶 ⏩ 🚚 🎏 ⚒ 🎚 ⏮️ 🔬 🏗 🔗 🔐 `get_settings`: -```Python hl_lines="9-10 13 21" -{!../../../docs_src/settings/app02/test_main.py!} -``` +{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} 🔗 🔐 👥 ⚒ 🆕 💲 `admin_email` 🕐❔ 🏗 🆕 `Settings` 🎚, & ⤴️ 👥 📨 👈 🆕 🎚. @@ -303,9 +287,7 @@ APP_NAME="ChimichangApp" & ⤴️ ℹ 👆 `config.py` ⏮️: -```Python hl_lines="9-10" -{!../../../docs_src/settings/app03/config.py!} -``` +{* ../../docs_src/settings/app03/config.py hl[9:10] *} 📥 👥 ✍ 🎓 `Config` 🔘 👆 Pydantic `Settings` 🎓, & ⚒ `env_file` 📁 ⏮️ 🇨🇻 📁 👥 💚 ⚙️. @@ -338,9 +320,7 @@ def get_settings(): ✋️ 👥 ⚙️ `@lru_cache` 👨‍🎨 🔛 🔝, `Settings` 🎚 🔜 ✍ 🕴 🕐, 🥇 🕰 ⚫️ 🤙. 👶 👶 -```Python hl_lines="1 10" -{!../../../docs_src/settings/app03/main.py!} -``` +{* ../../docs_src/settings/app03/main.py hl[1,10] *} ⤴️ 🙆 🏁 🤙 `get_settings()` 🔗 ⏭ 📨, ↩️ 🛠️ 🔗 📟 `get_settings()` & 🏗 🆕 `Settings` 🎚, ⚫️ 🔜 📨 🎏 🎚 👈 📨 🔛 🥇 🤙, 🔄 & 🔄. diff --git a/docs/em/docs/advanced/sub-applications.md b/docs/em/docs/advanced/sub-applications.md index 1e0931f95f..7a802cd77f 100644 --- a/docs/em/docs/advanced/sub-applications.md +++ b/docs/em/docs/advanced/sub-applications.md @@ -10,9 +10,7 @@ 🥇, ✍ 👑, 🔝-🎚, **FastAPI** 🈸, & 🚮 *➡ 🛠️*: -```Python hl_lines="3 6-8" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *} ### 🎧-🈸 @@ -20,9 +18,7 @@ 👉 🎧-🈸 ➕1️⃣ 🐩 FastAPI 🈸, ✋️ 👉 1️⃣ 👈 🔜 "🗻": -```Python hl_lines="11 14-16" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *} ### 🗻 🎧-🈸 @@ -30,9 +26,7 @@ 👉 💼, ⚫️ 🔜 📌 ➡ `/subapi`: -```Python hl_lines="11 19" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *} ### ✅ 🏧 🛠️ 🩺 diff --git a/docs/em/docs/advanced/templates.md b/docs/em/docs/advanced/templates.md index c45ff47a19..2e8f562280 100644 --- a/docs/em/docs/advanced/templates.md +++ b/docs/em/docs/advanced/templates.md @@ -27,9 +27,7 @@ $ pip install jinja2 * 📣 `Request` 🔢 *➡ 🛠️* 👈 🔜 📨 📄. * ⚙️ `templates` 👆 ✍ ✍ & 📨 `TemplateResponse`, 🚶‍♀️ `request` 1️⃣ 🔑-💲 👫 Jinja2️⃣ "🔑". -```Python hl_lines="4 11 15-18" -{!../../../docs_src/templates/tutorial001.py!} -``` +{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} /// note @@ -43,7 +41,7 @@ $ pip install jinja2 /// -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.templating import Jinja2Templates`. @@ -56,7 +54,7 @@ $ pip install jinja2 ⤴️ 👆 💪 ✍ 📄 `templates/item.html` ⏮️: ```jinja hl_lines="7" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` ⚫️ 🔜 🎦 `id` ✊ ⚪️➡️ "🔑" `dict` 👆 🚶‍♀️: @@ -70,17 +68,17 @@ $ pip install jinja2 & 👆 💪 ⚙️ `url_for()` 🔘 📄, & ⚙️ ⚫️, 🖼, ⏮️ `StaticFiles` 👆 📌. ```jinja hl_lines="4" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` 👉 🖼, ⚫️ 🔜 🔗 🎚 📁 `static/styles.css` ⏮️: ```CSS hl_lines="4" -{!../../../docs_src/templates/static/styles.css!} +{!../../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 deleted file mode 100644 index 825d545a94..0000000000 --- a/docs/em/docs/advanced/testing-database.md +++ /dev/null @@ -1,101 +0,0 @@ -# 🔬 💽 - -👆 💪 ⚙️ 🎏 🔗 🔐 ⚪️➡️ [🔬 🔗 ⏮️ 🔐](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 index 8bcffcc29c..b2b4b480d0 100644 --- a/docs/em/docs/advanced/testing-dependencies.md +++ b/docs/em/docs/advanced/testing-dependencies.md @@ -28,9 +28,7 @@ & ⤴️ **FastAPI** 🔜 🤙 👈 🔐 ↩️ ⏮️ 🔗. -```Python hl_lines="28-29 32" -{!../../../docs_src/dependency_testing/tutorial001.py!} -``` +{* ../../docs_src/dependency_testing/tutorial001.py hl[28:29,32] *} /// tip diff --git a/docs/em/docs/advanced/testing-events.md b/docs/em/docs/advanced/testing-events.md index d64436eb9c..f62e9e0694 100644 --- a/docs/em/docs/advanced/testing-events.md +++ b/docs/em/docs/advanced/testing-events.md @@ -2,6 +2,4 @@ 🕐❔ 👆 💪 👆 🎉 🐕‍🦺 (`startup` & `shutdown`) 🏃 👆 💯, 👆 💪 ⚙️ `TestClient` ⏮️ `with` 📄: -```Python hl_lines="9-12 20-24" -{!../../../docs_src/app_testing/tutorial003.py!} -``` +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/em/docs/advanced/testing-websockets.md b/docs/em/docs/advanced/testing-websockets.md index 5fb1e9c340..96aa8b765d 100644 --- a/docs/em/docs/advanced/testing-websockets.md +++ b/docs/em/docs/advanced/testing-websockets.md @@ -4,12 +4,10 @@ 👉, 👆 ⚙️ `TestClient` `with` 📄, 🔗*️⃣: -```Python hl_lines="27-31" -{!../../../docs_src/app_testing/tutorial002.py!} -``` +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} /// note -🌅 ℹ, ✅ 💃 🧾 🔬 *️⃣ . +🌅 ℹ, ✅ 💃 🧾 🔬 *️⃣ . /// diff --git a/docs/em/docs/advanced/using-request-directly.md b/docs/em/docs/advanced/using-request-directly.md index edc951d963..238557e5e0 100644 --- a/docs/em/docs/advanced/using-request-directly.md +++ b/docs/em/docs/advanced/using-request-directly.md @@ -15,7 +15,7 @@ ## ℹ 🔃 `Request` 🎚 -**FastAPI** 🤙 **💃** 🔘, ⏮️ 🧽 📚 🧰 🔛 🔝, 👆 💪 ⚙️ 💃 `Request` 🎚 🔗 🕐❔ 👆 💪. +**FastAPI** 🤙 **💃** 🔘, ⏮️ 🧽 📚 🧰 🔛 🔝, 👆 💪 ⚙️ 💃 `Request` 🎚 🔗 🕐❔ 👆 💪. ⚫️ 🔜 ⛓ 👈 🚥 👆 🤚 📊 ⚪️➡️ `Request` 🎚 🔗 (🖼, ✍ 💪) ⚫️ 🏆 🚫 ✔, 🗜 ⚖️ 📄 (⏮️ 🗄, 🏧 🛠️ 👩‍💻 🔢) FastAPI. @@ -29,9 +29,7 @@ 👈 👆 💪 🔐 📨 🔗. -```Python hl_lines="1 7-8" -{!../../../docs_src/using_request_directly/tutorial001.py!} -``` +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} 📣 *➡ 🛠️ 🔢* 🔢 ⏮️ 🆎 ➖ `Request` **FastAPI** 🔜 💭 🚶‍♀️ `Request` 👈 🔢. @@ -47,9 +45,9 @@ ## `Request` 🧾 -👆 💪 ✍ 🌅 ℹ 🔃 `Request` 🎚 🛂 💃 🧾 🕸. +👆 💪 ✍ 🌅 ℹ 🔃 `Request` 🎚 🛂 💃 🧾 🕸. -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.requests import Request`. diff --git a/docs/em/docs/advanced/websockets.md b/docs/em/docs/advanced/websockets.md index 30dc3043e4..a097778c7a 100644 --- a/docs/em/docs/advanced/websockets.md +++ b/docs/em/docs/advanced/websockets.md @@ -38,19 +38,15 @@ $ pip install websockets ✋️ ⚫️ 🙅 🌌 🎯 🔛 💽-🚄 *️⃣ & ✔️ 👷 🖼: -```Python hl_lines="2 6-38 41-43" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} ## ✍ `websocket` 👆 **FastAPI** 🈸, ✍ `websocket`: -```Python hl_lines="1 46-47" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.websockets import WebSocket`. @@ -62,9 +58,7 @@ $ pip install websockets 👆 *️⃣ 🛣 👆 💪 `await` 📧 & 📨 📧. -```Python hl_lines="48-52" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} 👆 💪 📨 & 📨 💱, ✍, & 🎻 💽. @@ -115,9 +109,7 @@ $ uvicorn main:app --reload 👫 👷 🎏 🌌 🎏 FastAPI 🔗/*➡ 🛠️*: -```Python hl_lines="66-77 76-91" -{!../../../docs_src/websockets/tutorial002.py!} -``` +{* ../../docs_src/websockets/tutorial002.py hl[66:77,76:91] *} /// info @@ -162,9 +154,7 @@ $ uvicorn main:app --reload 🕐❔ *️⃣ 🔗 📪, `await websocket.receive_text()` 🔜 🤚 `WebSocketDisconnect` ⚠, ❔ 👆 💪 ⤴️ ✊ & 🍵 💖 👉 🖼. -```Python hl_lines="81-83" -{!../../../docs_src/websockets/tutorial003.py!} -``` +{* ../../docs_src/websockets/tutorial003.py hl[81:83] *} 🔄 ⚫️ 👅: @@ -192,5 +182,5 @@ Client #1596980209979 left the chat 💡 🌅 🔃 🎛, ✅ 💃 🧾: -* `WebSocket` 🎓. -* 🎓-⚓️ *️⃣ 🚚. +* `WebSocket` 🎓. +* 🎓-⚓️ *️⃣ 🚚. diff --git a/docs/em/docs/advanced/wsgi.md b/docs/em/docs/advanced/wsgi.md index 6a4ed073c2..d923347d5a 100644 --- a/docs/em/docs/advanced/wsgi.md +++ b/docs/em/docs/advanced/wsgi.md @@ -12,9 +12,7 @@ & ⤴️ 🗻 👈 🔽 ➡. -```Python hl_lines="2-3 22" -{!../../../docs_src/wsgi/tutorial001.py!} -``` +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,22] *} ## ✅ ⚫️ diff --git a/docs/em/docs/alternatives.md b/docs/em/docs/alternatives.md index 334c0de73c..4cbac7539f 100644 --- a/docs/em/docs/alternatives.md +++ b/docs/em/docs/alternatives.md @@ -36,7 +36,7 @@ /// -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** ✔️ 🏧 🛠️ 🧾 🕸 👩‍💻 🔢. @@ -56,7 +56,7 @@ 👐 🦁 🏺, ⚫️ 😑 💖 👍 🏏 🏗 🔗. ⏭ 👜 🔎 "✳ 🎂 🛠️" 🏺. -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** ◾-🛠️. ⚒ ⚫️ ⏩ 🌀 & 🏏 🧰 & 🍕 💪. @@ -98,7 +98,7 @@ def read_url(): 👀 🔀 `requests.get(...)` & `@app.get(...)`. -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** * ✔️ 🙅 & 🏋️ 🛠️. * ⚙️ 🇺🇸🔍 👩‍🔬 📛 (🛠️) 🔗, 🎯 & 🏋️ 🌌. @@ -118,7 +118,7 @@ def read_url(): 👈 ⚫️❔ 🕐❔ 💬 🔃 ⏬ 2️⃣.0️⃣ ⚫️ ⚠ 💬 "🦁", & ⏬ 3️⃣ ➕ "🗄". -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** 🛠️ & ⚙️ 📂 🐩 🛠️ 🔧, ↩️ 🛃 🔗. @@ -147,7 +147,7 @@ def read_url(): ✋️ ⚫️ ✍ ⏭ 📤 🔀 🐍 🆎 🔑. , 🔬 🔠 🔗 👆 💪 ⚙️ 🎯 🇨🇻 & 🎓 🚚 🍭. -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** ⚙️ 📟 🔬 "🔗" 👈 🚚 💽 🆎 & 🔬, 🔁. @@ -169,7 +169,7 @@ Webarg ✍ 🎏 🍭 👩‍💻. /// -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** ✔️ 🏧 🔬 📨 📨 💽. @@ -199,7 +199,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. /// -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** 🐕‍🦺 📂 🐩 🛠️, 🗄. @@ -231,7 +231,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. /// -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** 🏗 🗄 🔗 🔁, ⚪️➡️ 🎏 📟 👈 🔬 🛠️ & 🔬. @@ -251,7 +251,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. ⚫️ 💪 🚫 🍵 🔁 🏷 📶 👍. , 🚥 🎻 💪 📨 🎻 🎚 👈 ✔️ 🔘 🏑 👈 🔄 🐦 🎻 🎚, ⚫️ 🚫🔜 ☑ 📄 & ✔. -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** ⚙️ 🐍 🆎 ✔️ 👑 👨‍🎨 🐕‍🦺. @@ -263,7 +263,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. ⚫️ 🕐 🥇 📶 ⏩ 🐍 🛠️ ⚓️ 🔛 `asyncio`. ⚫️ ⚒ 📶 🎏 🏺. -/// note | "📡 ℹ" +/// note | 📡 ℹ ⚫️ ⚙️ `uvloop` ↩️ 🔢 🐍 `asyncio` ➰. 👈 ⚫️❔ ⚒ ⚫️ ⏩. @@ -271,7 +271,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. /// -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** 🔎 🌌 ✔️ 😜 🎭. @@ -287,7 +287,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. , 💽 🔬, 🛠️, & 🧾, ✔️ ⌛ 📟, 🚫 🔁. ⚖️ 👫 ✔️ 🛠️ 🛠️ 🔛 🔝 🦅, 💖 🤗. 👉 🎏 🔺 🔨 🎏 🛠️ 👈 😮 🦅 🔧, ✔️ 1️⃣ 📨 🎚 & 1️⃣ 📨 🎚 🔢. -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** 🔎 🌌 🤚 👑 🎭. @@ -313,7 +313,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. 🛣 📣 👁 🥉, ⚙️ 🔢 📣 🎏 🥉 (↩️ ⚙️ 👨‍🎨 👈 💪 🥉 ▶️️ 🔛 🔝 🔢 👈 🍵 🔗). 👉 🔐 ❔ ✳ 🔨 ⚫️ 🌘 ❔ 🏺 (& 💃) 🔨 ⚫️. ⚫️ 🎏 📟 👜 👈 📶 😆 🔗. -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** 🔬 ➕ 🔬 💽 🆎 ⚙️ "🔢" 💲 🏷 🔢. 👉 📉 👨‍🎨 🐕‍🦺, & ⚫️ 🚫 💪 Pydantic ⏭. @@ -321,7 +321,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. /// -### 🤗 +### 🤗 🤗 🕐 🥇 🛠️ 🛠️ 📄 🛠️ 🔢 🆎 ⚙️ 🐍 🆎 🔑. 👉 👑 💭 👈 😮 🎏 🧰 🎏. @@ -341,7 +341,7 @@ APISpec ✍ 🎏 🍭 👩‍💻. /// -/// check | "💭 😮 **FastAPI**" +/// check | 💭 😮 **FastAPI** 🤗 😮 🍕 APIStar, & 1️⃣ 🧰 👤 🔎 🏆 👍, 🌟 APIStar. @@ -385,7 +385,7 @@ APIStar ✍ ✡ 🇺🇸🏛. 🎏 👨 👈 ✍: /// -/// check | "😮 **FastAPI** " +/// check | 😮 **FastAPI** 🔀. @@ -409,7 +409,7 @@ Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 ⚫️ ⭐ 🍭. 👐 ⚫️ ⏩ 🌘 🍭 📇. & ⚫️ ⚓️ 🔛 🎏 🐍 🆎 🔑, 👨‍🎨 🐕‍🦺 👑. -/// check | "**FastAPI** ⚙️ ⚫️" +/// check | **FastAPI** ⚙️ ⚫️ 🍵 🌐 💽 🔬, 💽 🛠️ & 🏧 🏷 🧾 (⚓️ 🔛 🎻 🔗). @@ -417,7 +417,7 @@ Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 /// -### 💃 +### 💃 💃 💿 🔫 🛠️/🧰, ❔ 💯 🏗 ↕-🎭 ✳ 🐕‍🦺. @@ -444,7 +444,7 @@ Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 👈 1️⃣ 👑 👜 👈 **FastAPI** 🚮 🔛 🔝, 🌐 ⚓️ 🔛 🐍 🆎 🔑 (⚙️ Pydantic). 👈, ➕ 🔗 💉 ⚙️, 💂‍♂ 🚙, 🗄 🔗 ⚡, ♒️. -/// note | "📡 ℹ" +/// note | 📡 ℹ 🔫 🆕 "🐩" ➖ 🛠️ ✳ 🐚 🏉 👨‍🎓. ⚫️ 🚫 "🐍 🐩" (🇩🇬), 👐 👫 🛠️ 🔨 👈. @@ -452,7 +452,7 @@ Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 /// -/// check | "**FastAPI** ⚙️ ⚫️" +/// check | **FastAPI** ⚙️ ⚫️ 🍵 🌐 🐚 🕸 🍕. ❎ ⚒ 🔛 🔝. @@ -462,7 +462,7 @@ Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 /// -### Uvicorn +### Uvicorn Uvicorn 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool. @@ -470,7 +470,7 @@ Uvicorn 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool. ⚫️ 👍 💽 💃 & **FastAPI**. -/// check | "**FastAPI** 👍 ⚫️" +/// check | **FastAPI** 👍 ⚫️ 👑 🕸 💽 🏃 **FastAPI** 🈸. diff --git a/docs/em/docs/async.md b/docs/em/docs/async.md index ac9804f644..cde778b0ff 100644 --- a/docs/em/docs/async.md +++ b/docs/em/docs/async.md @@ -381,7 +381,7 @@ async def read_burgers(): ⏮️ ⏬ 🐍, 👆 💪 ✔️ ⚙️ 🧵 ⚖️ 🐁. ✋️ 📟 🌌 🌖 🏗 🤔, ℹ, & 💭 🔃. -⏮️ ⏬ ✳ / 🖥 🕸, 👆 🔜 ✔️ ⚙️ "⏲". ❔ ↘️ ⏲ 🔥😈. +⏮️ ⏬ ✳ / 🖥 🕸, 👆 🔜 ✔️ ⚙️ "⏲". ❔ ↘️ "⏲ 🔥😈". ## 🔁 diff --git a/docs/em/docs/contributing.md b/docs/em/docs/contributing.md deleted file mode 100644 index 3ac1afda4d..0000000000 --- a/docs/em/docs/contributing.md +++ /dev/null @@ -1,493 +0,0 @@ -# 🛠️ - 📉 - -🥇, 👆 💪 💚 👀 🔰 🌌 [ℹ FastAPI & 🤚 ℹ](help-fastapi.md){.internal-link target=_blank}. - -## 🛠️ - -🚥 👆 ⏪ 🖖 🗃 & 👆 💭 👈 👆 💪 ⏬ 🤿 📟, 📥 📄 ⚒ 🆙 👆 🌐. - -### 🕹 🌐 ⏮️ `venv` - -👆 💪 ✍ 🕹 🌐 📁 ⚙️ 🐍 `venv` 🕹: - -
- -```console -$ python -m venv env -``` - -
- -👈 🔜 ✍ 📁 `./env/` ⏮️ 🐍 💱 & ⤴️ 👆 🔜 💪 ❎ 📦 👈 ❎ 🌐. - -### 🔓 🌐 - -🔓 🆕 🌐 ⏮️: - -//// tab | 💾, 🇸🇻 - -
- -```console -$ source ./env/bin/activate -``` - -
- -//// - -//// tab | 🚪 📋 - -
- -```console -$ .\env\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | 🚪 🎉 - -⚖️ 🚥 👆 ⚙️ 🎉 🖥 (✅ 🐛 🎉): - -
- -```console -$ source ./env/Scripts/activate -``` - -
- -//// - -✅ ⚫️ 👷, ⚙️: - -//// tab | 💾, 🇸🇻, 🚪 🎉 - -
- -```console -$ which pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -//// tab | 🚪 📋 - -
- -```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 index 019703296e..bbb017277c 100644 --- a/docs/em/docs/deployment/concepts.md +++ b/docs/em/docs/deployment/concepts.md @@ -216,7 +216,7 @@ 👈 👨‍🏭 🛠️ 🔜 🕐 🏃‍♂ 👆 🈸, 👫 🔜 🎭 👑 📊 📨 **📨** & 📨 **📨**, & 👫 🔜 📐 🕳 👆 🚮 🔢 💾. - + & ↗️, 🎏 🎰 🔜 🎲 ✔️ **🎏 🛠️** 🏃 👍, ↖️ ⚪️➡️ 👆 🈸. diff --git a/docs/em/docs/deployment/https.md b/docs/em/docs/deployment/https.md index 31cf990018..6d2641a92c 100644 --- a/docs/em/docs/deployment/https.md +++ b/docs/em/docs/deployment/https.md @@ -85,7 +85,7 @@ 🏓 💽 🔜 💬 🖥 ⚙️ 🎯 **📢 📢**. 👈 🔜 📢 📢 📢 ⚙️ 👆 💽, 👈 👆 🔗 🏓 💽. - + ### 🤝 🤝 ▶️ @@ -93,7 +93,7 @@ 🥇 🍕 📻 🛠️ 🔗 🖖 👩‍💻 & 💽 & 💭 🔐 🔑 👫 🔜 ⚙️, ♒️. - + 👉 🔗 🖖 👩‍💻 & 💽 🛠️ 🤝 🔗 🤙 **🤝 🤝**. @@ -111,7 +111,7 @@ 👉 💼, ⚫️ 🔜 ⚙️ 📄 `someapp.example.com`. - + 👩‍💻 ⏪ **💙** 👨‍💼 👈 🏗 👈 🤝 📄 (👉 💼 ➡️ 🗜, ✋️ 👥 🔜 👀 🔃 👈 ⏪), ⚫️ 💪 **✔** 👈 📄 ☑. @@ -133,19 +133,19 @@ , 👩‍💻 📨 **🇺🇸🔍 📨**. 👉 🇺🇸🔍 📨 🔘 🗜 🤝 🔗. - + ### 🗜 📨 🤝 ❎ 🗳 🔜 ⚙️ 🔐 ✔ **🗜 📨**, & 🔜 📶 **✅ (🗜) 🇺🇸🔍 📨** 🛠️ 🏃 🈸 (🖼 🛠️ ⏮️ Uvicorn 🏃‍♂ FastAPI 🈸). - + ### 🇺🇸🔍 📨 🈸 🔜 🛠️ 📨 & 📨 **✅ (💽) 🇺🇸🔍 📨** 🤝 ❎ 🗳. - + ### 🇺🇸🔍 📨 @@ -153,7 +153,7 @@ ⏭, 🖥 🔜 ✔ 👈 📨 ☑ & 🗜 ⏮️ ▶️️ 🔐 🔑, ♒️. ⚫️ 🔜 ⤴️ **🗜 📨** & 🛠️ ⚫️. - + 👩‍💻 (🖥) 🔜 💭 👈 📨 👟 ⚪️➡️ ☑ 💽 ↩️ ⚫️ ⚙️ ⚛ 👫 ✔ ⚙️ **🇺🇸🔍 📄** ⏭. @@ -163,7 +163,7 @@ 🕴 1️⃣ 🛠️ 💪 🚚 🎯 📢 & ⛴ (🤝 ❎ 🗳 👆 🖼) ✋️ 🎏 🈸/🛠️ 💪 🏃 🔛 💽(Ⓜ) 💁‍♂️, 📏 👫 🚫 🔄 ⚙️ 🎏 **🌀 📢 📢 & ⛴**. - + 👈 🌌, 🤝 ❎ 🗳 💪 🍵 🇺🇸🔍 & 📄 **💗 🆔**, 💗 🈸, & ⤴️ 📶 📨 ▶️️ 🈸 🔠 💼. @@ -173,7 +173,7 @@ & ⤴️, 📤 🔜 ➕1️⃣ 📋 (💼 ⚫️ ➕1️⃣ 📋, 💼 ⚫️ 💪 🎏 🤝 ❎ 🗳) 👈 🔜 💬 ➡️ 🗜, & ♻ 📄(Ⓜ). - + **🤝 📄** **🔗 ⏮️ 🆔 📛**, 🚫 ⏮️ 📢 📢. diff --git a/docs/em/docs/deployment/manually.md b/docs/em/docs/deployment/manually.md index 8ebe00c7cf..4fa2d13e2d 100644 --- a/docs/em/docs/deployment/manually.md +++ b/docs/em/docs/deployment/manually.md @@ -4,7 +4,7 @@ 📤 3️⃣ 👑 🎛: -* Uvicorn: ↕ 🎭 🔫 💽. +* Uvicorn: ↕ 🎭 🔫 💽. * Hypercorn: 🔫 💽 🔗 ⏮️ 🇺🇸🔍/2️⃣ & 🎻 👪 🎏 ⚒. * 👸: 🔫 💽 🏗 ✳ 📻. @@ -24,7 +24,7 @@ //// tab | Uvicorn -* Uvicorn, 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool. +* Uvicorn, 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool.
diff --git a/docs/em/docs/features.md b/docs/em/docs/features.md index 13cafa72fe..ccbed0cae3 100644 --- a/docs/em/docs/features.md +++ b/docs/em/docs/features.md @@ -159,7 +159,7 @@ FastAPI 🔌 📶 ⏩ ⚙️, ✋️ 📶 🏋️ 👱📔 🔃 **FastAPI** & ➡️ 👤 & 🎏 💭 ⚫️❔ 👆 💖 ⚫️. 👶 +👱📔 🔃 **FastAPI** & ➡️ 👤 & 🎏 💭 ⚫️❔ 👆 💖 ⚫️. 👶 👤 💌 👂 🔃 ❔ **FastAPI** 💆‍♂ ⚙️, ⚫️❔ 👆 ✔️ 💖 ⚫️, ❔ 🏗/🏢 👆 ⚙️ ⚫️, ♒️. diff --git a/docs/em/docs/history-design-future.md b/docs/em/docs/history-design-future.md index 2238bec2b4..fe36643959 100644 --- a/docs/em/docs/history-design-future.md +++ b/docs/em/docs/history-design-future.md @@ -58,7 +58,7 @@ ⤴️ 👤 📉 ⚫️, ⚒ ⚫️ 🍕 🛠️ ⏮️ 🎻 🔗, 🐕‍🦺 🎏 🌌 🔬 ⚛ 📄, & 📉 👨‍🎨 🐕‍🦺 (🆎 ✅, ✍) ⚓️ 🔛 💯 📚 👨‍🎨. -⏮️ 🛠️, 👤 📉 **💃**, 🎏 🔑 📄. +⏮️ 🛠️, 👤 📉 **💃**, 🎏 🔑 📄. ## 🛠️ diff --git a/docs/em/docs/how-to/conditional-openapi.md b/docs/em/docs/how-to/conditional-openapi.md index a17ba4eec5..e47ea0c35c 100644 --- a/docs/em/docs/how-to/conditional-openapi.md +++ b/docs/em/docs/how-to/conditional-openapi.md @@ -29,9 +29,7 @@ 🖼: -```Python hl_lines="6 11" -{!../../../docs_src/conditional_openapi/tutorial001.py!} -``` +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} 📥 👥 📣 ⚒ `openapi_url` ⏮️ 🎏 🔢 `"/openapi.json"`. diff --git a/docs/em/docs/how-to/custom-request-and-route.md b/docs/em/docs/how-to/custom-request-and-route.md index 1f66c6edae..32d7fbfec5 100644 --- a/docs/em/docs/how-to/custom-request-and-route.md +++ b/docs/em/docs/how-to/custom-request-and-route.md @@ -42,9 +42,7 @@ 👈 🌌, 🎏 🛣 🎓 💪 🍵 🗜 🗜 ⚖️ 🗜 📨. -```Python hl_lines="8-15" -{!../../../docs_src/custom_request_and_route/tutorial001.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} ### ✍ 🛃 `GzipRoute` 🎓 @@ -56,11 +54,9 @@ 📥 👥 ⚙️ ⚫️ ✍ `GzipRequest` ⚪️➡️ ⏮️ 📨. -```Python hl_lines="18-26" -{!../../../docs_src/custom_request_and_route/tutorial001.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} -/// note | "📡 ℹ" +/// note | 📡 ℹ `Request` ✔️ `request.scope` 🔢, 👈 🐍 `dict` ⚗ 🗃 🔗 📨. @@ -70,7 +66,7 @@ & 👈 2️⃣ 👜, `scope` & `receive`, ⚫️❔ 💪 ✍ 🆕 `Request` 👐. -💡 🌅 🔃 `Request` ✅ 💃 🩺 🔃 📨. +💡 🌅 🔃 `Request` ✅ 💃 🩺 🔃 📨. /// @@ -96,26 +92,18 @@ 🌐 👥 💪 🍵 📨 🔘 `try`/`except` 🍫: -```Python hl_lines="13 15" -{!../../../docs_src/custom_request_and_route/tutorial002.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} 🚥 ⚠ 📉, `Request` 👐 🔜 ↔, 👥 💪 ✍ & ⚒ ⚙️ 📨 💪 🕐❔ 🚚 ❌: -```Python hl_lines="16-18" -{!../../../docs_src/custom_request_and_route/tutorial002.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} ## 🛃 `APIRoute` 🎓 📻 👆 💪 ⚒ `route_class` 🔢 `APIRouter`: -```Python hl_lines="26" -{!../../../docs_src/custom_request_and_route/tutorial003.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} 👉 🖼, *➡ 🛠️* 🔽 `router` 🔜 ⚙️ 🛃 `TimedRoute` 🎓, & 🔜 ✔️ ➕ `X-Response-Time` 🎚 📨 ⏮️ 🕰 ⚫️ ✊ 🏗 📨: -```Python hl_lines="13-20" -{!../../../docs_src/custom_request_and_route/tutorial003.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} diff --git a/docs/em/docs/how-to/extending-openapi.md b/docs/em/docs/how-to/extending-openapi.md index dc9adf80e1..c3e6c7f667 100644 --- a/docs/em/docs/how-to/extending-openapi.md +++ b/docs/em/docs/how-to/extending-openapi.md @@ -46,25 +46,19 @@ 🥇, ✍ 🌐 👆 **FastAPI** 🈸 🛎: -```Python hl_lines="1 4 7-9" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} ### 🏗 🗄 🔗 ⤴️, ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗, 🔘 `custom_openapi()` 🔢: -```Python hl_lines="2 15-20" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:20] *} ### 🔀 🗄 🔗 🔜 👆 💪 🚮 📄 ↔, ❎ 🛃 `x-logo` `info` "🎚" 🗄 🔗: -```Python hl_lines="21-23" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[21:23] *} ### 💾 🗄 🔗 @@ -74,17 +68,13 @@ ⚫️ 🔜 🏗 🕴 🕐, & ⤴️ 🎏 💾 🔗 🔜 ⚙️ ⏭ 📨. -```Python hl_lines="13-14 24-25" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,24:25] *} ### 🔐 👩‍🔬 🔜 👆 💪 ❎ `.openapi()` 👩‍🔬 ⏮️ 👆 🆕 🔢. -```Python hl_lines="28" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[28] *} ### ✅ ⚫️ diff --git a/docs/em/docs/how-to/graphql.md b/docs/em/docs/how-to/graphql.md index b8610b7675..083e9ebd28 100644 --- a/docs/em/docs/how-to/graphql.md +++ b/docs/em/docs/how-to/graphql.md @@ -35,9 +35,7 @@ 📥 🤪 🎮 ❔ 👆 💪 🛠️ 🍓 ⏮️ FastAPI: -```Python hl_lines="3 22 25-26" -{!../../../docs_src/graphql/tutorial001.py!} -``` +{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *} 👆 💪 💡 🌅 🔃 🍓 🍓 🧾. diff --git a/docs/em/docs/how-to/sql-databases-peewee.md b/docs/em/docs/how-to/sql-databases-peewee.md deleted file mode 100644 index 88b827c247..0000000000 --- a/docs/em/docs/how-to/sql-databases-peewee.md +++ /dev/null @@ -1,576 +0,0 @@ -# 🗄 (🔗) 💽 ⏮️ 🏒 - -/// 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#_7){.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/index.md b/docs/em/docs/index.md index aa7542366a..5f5fc2e393 100644 --- a/docs/em/docs/index.md +++ b/docs/em/docs/index.md @@ -12,7 +12,7 @@

- Test + Test Coverage @@ -87,13 +87,13 @@ FastAPI 🏛, ⏩ (↕-🎭), 🕸 🛠️ 🏗 🛠️ ⏮️ 🐍 3️⃣.8️ "_👤 🤭 🌕 😄 🔃 **FastAPI**. ⚫️ 🎊 ❗_" -

+
✡ 🇭🇰 - 🐍 🔢 📻 🦠 (🇦🇪)
--- "_🤙, ⚫️❔ 👆 ✔️ 🏗 👀 💎 💠 & 🇵🇱. 📚 🌌, ⚫️ ⚫️❔ 👤 💚 **🤗** - ⚫️ 🤙 😍 👀 👱 🏗 👈._" -
✡ 🗄 - 🤗 👼 (🇦🇪)
+
✡ 🗄 - 🤗 👼 (🇦🇪)
--- @@ -101,7 +101,7 @@ FastAPI 🏛, ⏩ (↕-🎭), 🕸 🛠️ 🏗 🛠️ ⏮️ 🐍 3️⃣.8️ "_👥 ✔️ 🎛 🤭 **FastAPI** 👆 **🔗** [...] 👤 💭 👆 🔜 💖 ⚫️ [...]_" -
🇱🇨 🇸🇲 - ✡ Honnibal - 💥 👲 🕴 - 🌈 👼 (🇦🇪) - (🇦🇪)
+
🇱🇨 🇸🇲 - ✡ Honnibal - 💥 👲 🕴 - 🌈 👼 (🇦🇪) - (🇦🇪)
--- @@ -125,7 +125,7 @@ FastAPI 🏛, ⏩ (↕-🎭), 🕸 🛠️ 🏗 🛠️ ⏮️ 🐍 3️⃣.8️ FastAPI 🧍 🔛 ⌚ 🐘: -* 💃 🕸 🍕. +* 💃 🕸 🍕. * Pydantic 📊 🍕. ## 👷‍♂ @@ -140,7 +140,7 @@ $ pip install "fastapi[standard]"
-👆 🔜 💪 🔫 💽, 🏭 ✅ Uvicorn ⚖️ Hypercorn. +👆 🔜 💪 🔫 💽, 🏭 ✅ Uvicorn ⚖️ Hypercorn.
@@ -463,7 +463,7 @@ item: Item ⚙️ FastAPI / 💃: -* uvicorn - 💽 👈 📐 & 🍦 👆 🈸. +* uvicorn - 💽 👈 📐 & 🍦 👆 🈸. * orjson - ✔ 🚥 👆 💚 ⚙️ `ORJSONResponse`. * ujson - ✔ 🚥 👆 💚 ⚙️ `UJSONResponse`. diff --git a/docs/em/docs/python-types.md b/docs/em/docs/python-types.md index 202c90f94c..d2af23bb9a 100644 --- a/docs/em/docs/python-types.md +++ b/docs/em/docs/python-types.md @@ -23,7 +23,7 @@ ➡️ ▶️ ⏮️ 🙅 🖼: ```Python -{!../../../docs_src/python_types/tutorial001.py!} +{!../../docs_src/python_types/tutorial001.py!} ``` 🤙 👉 📋 🔢: @@ -39,7 +39,7 @@ John Doe * 🔢 👫 ⏮️ 🚀 🖕. ```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial001.py!} +{!../../docs_src/python_types/tutorial001.py!} ``` ### ✍ ⚫️ @@ -83,7 +83,7 @@ John Doe 👈 "🆎 🔑": ```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial002.py!} +{!../../docs_src/python_types/tutorial002.py!} ``` 👈 🚫 🎏 📣 🔢 💲 💖 🔜 ⏮️: @@ -113,7 +113,7 @@ John Doe ✅ 👉 🔢, ⚫️ ⏪ ✔️ 🆎 🔑: ```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial003.py!} +{!../../docs_src/python_types/tutorial003.py!} ``` ↩️ 👨‍🎨 💭 🆎 🔢, 👆 🚫 🕴 🤚 🛠️, 👆 🤚 ❌ ✅: @@ -123,7 +123,7 @@ John Doe 🔜 👆 💭 👈 👆 ✔️ 🔧 ⚫️, 🗜 `age` 🎻 ⏮️ `str(age)`: ```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial004.py!} +{!../../docs_src/python_types/tutorial004.py!} ``` ## 📣 🆎 @@ -144,7 +144,7 @@ John Doe * `bytes` ```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial005.py!} +{!../../docs_src/python_types/tutorial005.py!} ``` ### 💊 🆎 ⏮️ 🆎 🔢 @@ -172,7 +172,7 @@ John Doe ⚪️➡️ `typing`, 🗄 `List` (⏮️ 🔠 `L`): ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial006.py!} +{!> ../../docs_src/python_types/tutorial006.py!} ``` 📣 🔢, ⏮️ 🎏 ❤ (`:`) ❕. @@ -182,7 +182,7 @@ John Doe 📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜: ```Python hl_lines="4" -{!> ../../../docs_src/python_types/tutorial006.py!} +{!> ../../docs_src/python_types/tutorial006.py!} ``` //// @@ -196,7 +196,7 @@ John Doe 📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜: ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial006_py39.py!} +{!> ../../docs_src/python_types/tutorial006_py39.py!} ``` //// @@ -234,7 +234,7 @@ John Doe //// tab | 🐍 3️⃣.6️⃣ & 🔛 ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial007.py!} +{!> ../../docs_src/python_types/tutorial007.py!} ``` //// @@ -242,7 +242,7 @@ John Doe //// tab | 🐍 3️⃣.9️⃣ & 🔛 ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial007_py39.py!} +{!> ../../docs_src/python_types/tutorial007_py39.py!} ``` //// @@ -263,7 +263,7 @@ John Doe //// tab | 🐍 3️⃣.6️⃣ & 🔛 ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial008.py!} +{!> ../../docs_src/python_types/tutorial008.py!} ``` //// @@ -271,7 +271,7 @@ John Doe //// tab | 🐍 3️⃣.9️⃣ & 🔛 ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial008_py39.py!} +{!> ../../docs_src/python_types/tutorial008_py39.py!} ``` //// @@ -293,7 +293,7 @@ John Doe //// tab | 🐍 3️⃣.6️⃣ & 🔛 ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial008b.py!} +{!> ../../docs_src/python_types/tutorial008b.py!} ``` //// @@ -301,7 +301,7 @@ John Doe //// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial008b_py310.py!} +{!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` //// @@ -315,7 +315,7 @@ John Doe 🐍 3️⃣.6️⃣ & 🔛 (✅ 🐍 3️⃣.1️⃣0️⃣) 👆 💪 📣 ⚫️ 🏭 & ⚙️ `Optional` ⚪️➡️ `typing` 🕹. ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009.py!} ``` ⚙️ `Optional[str]` ↩️ `str` 🔜 ➡️ 👨‍🎨 ℹ 👆 🔍 ❌ 🌐❔ 👆 💪 🤔 👈 💲 🕧 `str`, 🕐❔ ⚫️ 💪 🤙 `None` 💁‍♂️. @@ -327,7 +327,7 @@ John Doe //// tab | 🐍 3️⃣.6️⃣ & 🔛 ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial009.py!} +{!> ../../docs_src/python_types/tutorial009.py!} ``` //// @@ -335,7 +335,7 @@ John Doe //// tab | 🐍 3️⃣.6️⃣ & 🔛 - 🎛 ```Python hl_lines="1 4" -{!> ../../../docs_src/python_types/tutorial009b.py!} +{!> ../../docs_src/python_types/tutorial009b.py!} ``` //// @@ -343,7 +343,7 @@ John Doe //// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 ```Python hl_lines="1" -{!> ../../../docs_src/python_types/tutorial009_py310.py!} +{!> ../../docs_src/python_types/tutorial009_py310.py!} ``` //// @@ -364,7 +364,7 @@ John Doe 🖼, ➡️ ✊ 👉 🔢: ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009c.py!} +{!../../docs_src/python_types/tutorial009c.py!} ``` 🔢 `name` 🔬 `Optional[str]`, ✋️ ⚫️ **🚫 📦**, 👆 🚫🔜 🤙 🔢 🍵 🔢: @@ -382,7 +382,7 @@ say_hi(name=None) # This works, None is valid 🎉 👍 📰, 🕐 👆 🔛 🐍 3️⃣.1️⃣0️⃣ 👆 🏆 🚫 ✔️ 😟 🔃 👈, 👆 🔜 💪 🎯 ⚙️ `|` 🔬 🇪🇺 🆎: ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009c_py310.py!} +{!../../docs_src/python_types/tutorial009c_py310.py!} ``` & ⤴️ 👆 🏆 🚫 ✔️ 😟 🔃 📛 💖 `Optional` & `Union`. 👶 @@ -446,13 +446,13 @@ say_hi(name=None) # This works, None is valid 🎉 ➡️ 💬 👆 ✔️ 🎓 `Person`, ⏮️ 📛: ```Python hl_lines="1-3" -{!../../../docs_src/python_types/tutorial010.py!} +{!../../docs_src/python_types/tutorial010.py!} ``` ⤴️ 👆 💪 📣 🔢 🆎 `Person`: ```Python hl_lines="6" -{!../../../docs_src/python_types/tutorial010.py!} +{!../../docs_src/python_types/tutorial010.py!} ``` & ⤴️, 🔄, 👆 🤚 🌐 👨‍🎨 🐕‍🦺: @@ -476,7 +476,7 @@ say_hi(name=None) # This works, None is valid 🎉 //// tab | 🐍 3️⃣.6️⃣ & 🔛 ```Python -{!> ../../../docs_src/python_types/tutorial011.py!} +{!> ../../docs_src/python_types/tutorial011.py!} ``` //// @@ -484,7 +484,7 @@ say_hi(name=None) # This works, None is valid 🎉 //// tab | 🐍 3️⃣.9️⃣ & 🔛 ```Python -{!> ../../../docs_src/python_types/tutorial011_py39.py!} +{!> ../../docs_src/python_types/tutorial011_py39.py!} ``` //// @@ -492,7 +492,7 @@ say_hi(name=None) # This works, None is valid 🎉 //// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 ```Python -{!> ../../../docs_src/python_types/tutorial011_py310.py!} +{!> ../../docs_src/python_types/tutorial011_py310.py!} ``` //// diff --git a/docs/em/docs/tutorial/background-tasks.md b/docs/em/docs/tutorial/background-tasks.md index 1d17a0e4ea..4cbcbc710d 100644 --- a/docs/em/docs/tutorial/background-tasks.md +++ b/docs/em/docs/tutorial/background-tasks.md @@ -15,9 +15,7 @@ 🥇, 🗄 `BackgroundTasks` & 🔬 🔢 👆 *➡ 🛠️ 🔢* ⏮️ 🆎 📄 `BackgroundTasks`: -```Python hl_lines="1 13" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} **FastAPI** 🔜 ✍ 🎚 🆎 `BackgroundTasks` 👆 & 🚶‍♀️ ⚫️ 👈 🔢. @@ -33,17 +31,13 @@ & ✍ 🛠️ 🚫 ⚙️ `async` & `await`, 👥 🔬 🔢 ⏮️ 😐 `def`: -```Python hl_lines="6-9" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} ## 🚮 🖥 📋 🔘 👆 *➡ 🛠️ 🔢*, 🚶‍♀️ 👆 📋 🔢 *🖥 📋* 🎚 ⏮️ 👩‍🔬 `.add_task()`: -```Python hl_lines="14" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} `.add_task()` 📨 ❌: @@ -57,21 +51,7 @@ **FastAPI** 💭 ⚫️❔ 🔠 💼 & ❔ 🏤-⚙️ 🎏 🎚, 👈 🌐 🖥 📋 🔗 👯‍♂️ & 🏃 🖥 ⏮️: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="11 13 20 23" -{!> ../../../docs_src/background_tasks/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *} 👉 🖼, 📧 🔜 ✍ `log.txt` 📁 *⏮️* 📨 📨. @@ -81,7 +61,7 @@ ## 📡 ℹ -🎓 `BackgroundTasks` 👟 🔗 ⚪️➡️ `starlette.background`. +🎓 `BackgroundTasks` 👟 🔗 ⚪️➡️ `starlette.background`. ⚫️ 🗄/🔌 🔗 🔘 FastAPI 👈 👆 💪 🗄 ⚫️ ⚪️➡️ `fastapi` & ❎ 😫 🗄 🎛 `BackgroundTask` (🍵 `s` 🔚) ⚪️➡️ `starlette.background`. @@ -89,7 +69,7 @@ ⚫️ 💪 ⚙️ `BackgroundTask` 😞 FastAPI, ✋️ 👆 ✔️ ✍ 🎚 👆 📟 & 📨 💃 `Response` 🔌 ⚫️. -👆 💪 👀 🌖 ℹ 💃 🛂 🩺 🖥 📋. +👆 💪 👀 🌖 ℹ 💃 🛂 🩺 🖥 📋. ## ⚠ @@ -97,8 +77,6 @@ 👫 😑 🚚 🌖 🏗 📳, 📧/👨‍🏭 📤 👨‍💼, 💖 ✳ ⚖️ ✳, ✋️ 👫 ✔ 👆 🏃 🖥 📋 💗 🛠️, & ✴️, 💗 💽. -👀 🖼, ✅ [🏗 🚂](../project-generation.md){.internal-link target=_blank}, 👫 🌐 🔌 🥒 ⏪ 📶. - ✋️ 🚥 👆 💪 🔐 🔢 & 🎚 ⚪️➡️ 🎏 **FastAPI** 📱, ⚖️ 👆 💪 🎭 🤪 🖥 📋 (💖 📨 📧 📨), 👆 💪 🎯 ⚙️ `BackgroundTasks`. ## 🌃 diff --git a/docs/em/docs/tutorial/bigger-applications.md b/docs/em/docs/tutorial/bigger-applications.md index 693a75d28d..78a321ae6a 100644 --- a/docs/em/docs/tutorial/bigger-applications.md +++ b/docs/em/docs/tutorial/bigger-applications.md @@ -52,7 +52,7 @@ from app.routers import items * 📤 📁 `app/internal/` ⏮️ ➕1️⃣ 📁 `__init__.py`, ⚫️ ➕1️⃣ "🐍 📦": `app.internal`. * & 📁 `app/internal/admin.py` ➕1️⃣ 🔁: `app.internal.admin`. - + 🎏 📁 📊 ⏮️ 🏤: @@ -86,7 +86,7 @@ from app.routers import items 👆 🗄 ⚫️ & ✍ "👐" 🎏 🌌 👆 🔜 ⏮️ 🎓 `FastAPI`: ```Python hl_lines="1 3" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` ### *➡ 🛠️* ⏮️ `APIRouter` @@ -96,7 +96,7 @@ from app.routers import items ⚙️ ⚫️ 🎏 🌌 👆 🔜 ⚙️ `FastAPI` 🎓: ```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` 👆 💪 💭 `APIRouter` "🐩 `FastAPI`" 🎓. @@ -122,7 +122,7 @@ from app.routers import items 👥 🔜 🔜 ⚙️ 🙅 🔗 ✍ 🛃 `X-Token` 🎚: ```Python hl_lines="1 4-6" title="app/dependencies.py" -{!../../../docs_src/bigger_applications/app/dependencies.py!} +{!../../docs_src/bigger_applications/app/dependencies.py!} ``` /// tip @@ -156,7 +156,7 @@ from app.routers import items , ↩️ ❎ 🌐 👈 🔠 *➡ 🛠️*, 👥 💪 🚮 ⚫️ `APIRouter`. ```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` ➡ 🔠 *➡ 🛠️* ✔️ ▶️ ⏮️ `/`, 💖: @@ -217,7 +217,7 @@ async def read_item(item_id: str): 👥 ⚙️ ⚖ 🗄 ⏮️ `..` 🔗: ```Python hl_lines="3" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` #### ❔ ⚖ 🗄 👷 @@ -244,7 +244,7 @@ from .dependencies import get_token_header 💭 ❔ 👆 📱/📁 📊 👀 💖: - + --- @@ -290,7 +290,7 @@ that 🔜 ⛓: ✋️ 👥 💪 🚮 _🌅_ `tags` 👈 🔜 ✔ 🎯 *➡ 🛠️*, & ➕ `responses` 🎯 👈 *➡ 🛠️*: ```Python hl_lines="30-31" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` /// tip @@ -318,7 +318,7 @@ that 🔜 ⛓: & 👥 💪 📣 [🌐 🔗](dependencies/global-dependencies.md){.internal-link target=_blank} 👈 🔜 🌀 ⏮️ 🔗 🔠 `APIRouter`: ```Python hl_lines="1 3 7" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` ### 🗄 `APIRouter` @@ -326,7 +326,7 @@ that 🔜 ⛓: 🔜 👥 🗄 🎏 🔁 👈 ✔️ `APIRouter`Ⓜ: ```Python hl_lines="5" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` 📁 `app/routers/users.py` & `app/routers/items.py` 🔁 👈 🍕 🎏 🐍 📦 `app`, 👥 💪 ⚙️ 👁 ❣ `.` 🗄 👫 ⚙️ "⚖ 🗄". @@ -391,7 +391,7 @@ from .routers.users import router , 💪 ⚙️ 👯‍♂️ 👫 🎏 📁, 👥 🗄 🔁 🔗: ```Python hl_lines="5" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` ### 🔌 `APIRouter`Ⓜ `users` & `items` @@ -399,7 +399,7 @@ from .routers.users import router 🔜, ➡️ 🔌 `router`Ⓜ ⚪️➡️ 🔁 `users` & `items`: ```Python hl_lines="10-11" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` /// info @@ -414,7 +414,7 @@ from .routers.users import router ⚫️ 🔜 🔌 🌐 🛣 ⚪️➡️ 👈 📻 🍕 ⚫️. -/// note | "📡 ℹ" +/// note | 📡 ℹ ⚫️ 🔜 🤙 🔘 ✍ *➡ 🛠️* 🔠 *➡ 🛠️* 👈 📣 `APIRouter`. @@ -441,7 +441,7 @@ from .routers.users import router 👉 🖼 ⚫️ 🔜 💎 🙅. ✋️ ➡️ 💬 👈 ↩️ ⚫️ 💰 ⏮️ 🎏 🏗 🏢, 👥 🚫🔜 🔀 ⚫️ & 🚮 `prefix`, `dependencies`, `tags`, ♒️. 🔗 `APIRouter`: ```Python hl_lines="3" title="app/internal/admin.py" -{!../../../docs_src/bigger_applications/app/internal/admin.py!} +{!../../docs_src/bigger_applications/app/internal/admin.py!} ``` ✋️ 👥 💚 ⚒ 🛃 `prefix` 🕐❔ ✅ `APIRouter` 👈 🌐 🚮 *➡ 🛠️* ▶️ ⏮️ `/admin`, 👥 💚 🔐 ⚫️ ⏮️ `dependencies` 👥 ⏪ ✔️ 👉 🏗, & 👥 💚 🔌 `tags` & `responses`. @@ -449,7 +449,7 @@ from .routers.users import router 👥 💪 📣 🌐 👈 🍵 ✔️ 🔀 ⏮️ `APIRouter` 🚶‍♀️ 👈 🔢 `app.include_router()`: ```Python hl_lines="14-17" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` 👈 🌌, ⏮️ `APIRouter` 🔜 🚧 ⚗, 👥 💪 💰 👈 🎏 `app/internal/admin.py` 📁 ⏮️ 🎏 🏗 🏢. @@ -472,12 +472,12 @@ from .routers.users import router 📥 👥 ⚫️... 🎦 👈 👥 💪 🤷: ```Python hl_lines="21-23" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` & ⚫️ 🔜 👷 ☑, 👯‍♂️ ⏮️ 🌐 🎏 *➡ 🛠️* 🚮 ⏮️ `app.include_router()`. -/// info | "📶 📡 ℹ" +/// info | 📶 📡 ℹ **🗒**: 👉 📶 📡 ℹ 👈 👆 🎲 💪 **🚶**. diff --git a/docs/em/docs/tutorial/body-fields.md b/docs/em/docs/tutorial/body-fields.md index c5a04daeb0..f202284b5b 100644 --- a/docs/em/docs/tutorial/body-fields.md +++ b/docs/em/docs/tutorial/body-fields.md @@ -6,21 +6,7 @@ 🥇, 👆 ✔️ 🗄 ⚫️: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="2" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body_fields/tutorial001.py hl[4] *} /// warning @@ -32,25 +18,11 @@ 👆 💪 ⤴️ ⚙️ `Field` ⏮️ 🏷 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="9-12" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body_fields/tutorial001.py hl[11:14] *} `Field` 👷 🎏 🌌 `Query`, `Path` & `Body`, ⚫️ ✔️ 🌐 🎏 🔢, ♒️. -/// note | "📡 ℹ" +/// note | 📡 ℹ 🤙, `Query`, `Path` & 🎏 👆 🔜 👀 ⏭ ✍ 🎚 🏿 ⚠ `Param` 🎓, ❔ ⚫️ 🏿 Pydantic `FieldInfo` 🎓. diff --git a/docs/em/docs/tutorial/body-multiple-params.md b/docs/em/docs/tutorial/body-multiple-params.md index c2a9a224d5..3a2f2bd547 100644 --- a/docs/em/docs/tutorial/body-multiple-params.md +++ b/docs/em/docs/tutorial/body-multiple-params.md @@ -8,21 +8,7 @@ & 👆 💪 📣 💪 🔢 📦, ⚒ 🔢 `None`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="19-21" -{!> ../../../docs_src/body_multiple_params/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17-19" -{!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial001.py hl[19:21] *} /// note @@ -45,21 +31,7 @@ ✋️ 👆 💪 📣 💗 💪 🔢, ✅ `item` & `user`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *} 👉 💼, **FastAPI** 🔜 👀 👈 📤 🌅 🌘 1️⃣ 💪 🔢 🔢 (2️⃣ 🔢 👈 Pydantic 🏷). @@ -100,21 +72,7 @@ ✋️ 👆 💪 💡 **FastAPI** 😥 ⚫️ ➕1️⃣ 💪 🔑 ⚙️ `Body`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial003.py hl[22] *} 👉 💼, **FastAPI** 🔜 ⌛ 💪 💖: @@ -154,21 +112,7 @@ q: str | None = None 🖼: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="26" -{!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *} /// info @@ -190,21 +134,7 @@ item: Item = Body(embed=True) : -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="15" -{!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *} 👉 💼 **FastAPI** 🔜 ⌛ 💪 💖: diff --git a/docs/em/docs/tutorial/body-nested-models.md b/docs/em/docs/tutorial/body-nested-models.md index 23114540aa..6c8d5a6100 100644 --- a/docs/em/docs/tutorial/body-nested-models.md +++ b/docs/em/docs/tutorial/body-nested-models.md @@ -6,21 +6,7 @@ 👆 💪 🔬 🔢 🏾. 🖼, 🐍 `list`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *} 👉 🔜 ⚒ `tags` 📇, 👐 ⚫️ 🚫 📣 🆎 🔣 📇. @@ -34,9 +20,7 @@ ✋️ 🐍 ⏬ ⏭ 3️⃣.9️⃣ (3️⃣.6️⃣ & 🔛), 👆 🥇 💪 🗄 `List` ⚪️➡️ 🐩 🐍 `typing` 🕹: -```Python hl_lines="1" -{!> ../../../docs_src/body_nested_models/tutorial002.py!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} ### 📣 `list` ⏮️ 🆎 🔢 @@ -65,29 +49,7 @@ my_list: List[str] , 👆 🖼, 👥 💪 ⚒ `tags` 🎯 "📇 🎻": -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *} ## ⚒ 🆎 @@ -97,29 +59,7 @@ my_list: List[str] ⤴️ 👥 💪 📣 `tags` ⚒ 🎻: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 14" -{!> ../../../docs_src/body_nested_models/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *} ⏮️ 👉, 🚥 👆 📨 📨 ⏮️ ❎ 📊, ⚫️ 🔜 🗜 ⚒ 😍 🏬. @@ -141,57 +81,13 @@ my_list: List[str] 🖼, 👥 💪 🔬 `Image` 🏷: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9-11" -{!> ../../../docs_src/body_nested_models/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="9-11" -{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7-9" -{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *} ### ⚙️ 📊 🆎 & ⤴️ 👥 💪 ⚙️ ⚫️ 🆎 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *} 👉 🔜 ⛓ 👈 **FastAPI** 🔜 ⌛ 💪 🎏: @@ -224,29 +120,7 @@ my_list: List[str] 🖼, `Image` 🏷 👥 ✔️ `url` 🏑, 👥 💪 📣 ⚫️ ↩️ `str`, Pydantic `HttpUrl`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="4 10" -{!> ../../../docs_src/body_nested_models/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="4 10" -{!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="2 8" -{!> ../../../docs_src/body_nested_models/tutorial005_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *} 🎻 🔜 ✅ ☑ 📛, & 📄 🎻 🔗 / 🗄 ✅. @@ -254,29 +128,7 @@ my_list: List[str] 👆 💪 ⚙️ Pydantic 🏷 🏾 `list`, `set`, ♒️: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial006.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/body_nested_models/tutorial006_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *} 👉 🔜 ⌛ (🗜, ✔, 📄, ♒️) 🎻 💪 💖: @@ -314,29 +166,7 @@ my_list: List[str] 👆 💪 🔬 🎲 🙇 🐦 🏷: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9 14 20 23 27" -{!> ../../../docs_src/body_nested_models/tutorial007.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="9 14 20 23 27" -{!> ../../../docs_src/body_nested_models/tutorial007_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7 12 18 21 25" -{!> ../../../docs_src/body_nested_models/tutorial007_py310.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *} /// info @@ -360,21 +190,7 @@ images: list[Image] : -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="15" -{!> ../../../docs_src/body_nested_models/tutorial008.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="13" -{!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *} ## 👨‍🎨 🐕‍🦺 🌐 @@ -404,21 +220,7 @@ images: list[Image] 👉 💼, 👆 🔜 🚫 🙆 `dict` 📏 ⚫️ ✔️ `int` 🔑 ⏮️ `float` 💲: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/body_nested_models/tutorial009.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/body_nested_models/tutorial009_py39.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial009.py hl[9] *} /// tip diff --git a/docs/em/docs/tutorial/body-updates.md b/docs/em/docs/tutorial/body-updates.md index 97501eb6da..7e2fbfaf79 100644 --- a/docs/em/docs/tutorial/body-updates.md +++ b/docs/em/docs/tutorial/body-updates.md @@ -6,29 +6,7 @@ 👆 💪 ⚙️ `jsonable_encoder` 🗜 🔢 💽 📊 👈 💪 🏪 🎻 (✅ ⏮️ ☁ 💽). 🖼, 🏭 `datetime` `str`. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="30-35" -{!> ../../../docs_src/body_updates/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="30-35" -{!> ../../../docs_src/body_updates/tutorial001_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="28-33" -{!> ../../../docs_src/body_updates/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body_updates/tutorial001.py hl[30:35] *} `PUT` ⚙️ 📨 💽 👈 🔜 ❎ ♻ 💽. @@ -76,29 +54,7 @@ ⤴️ 👆 💪 ⚙️ 👉 🏗 `dict` ⏮️ 🕴 💽 👈 ⚒ (📨 📨), 🚫 🔢 💲: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="34" -{!> ../../../docs_src/body_updates/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="34" -{!> ../../../docs_src/body_updates/tutorial002_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="32" -{!> ../../../docs_src/body_updates/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/body_updates/tutorial002.py hl[34] *} ### ⚙️ Pydantic `update` 🔢 @@ -106,29 +62,7 @@ 💖 `stored_item_model.copy(update=update_data)`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="35" -{!> ../../../docs_src/body_updates/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="35" -{!> ../../../docs_src/body_updates/tutorial002_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="33" -{!> ../../../docs_src/body_updates/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/body_updates/tutorial002.py hl[35] *} ### 🍕 ℹ 🌃 @@ -145,29 +79,7 @@ * 🖊 💽 👆 💽. * 📨 ℹ 🏷. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="30-37" -{!> ../../../docs_src/body_updates/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="30-37" -{!> ../../../docs_src/body_updates/tutorial002_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="28-35" -{!> ../../../docs_src/body_updates/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/body_updates/tutorial002.py hl[30:37] *} /// tip diff --git a/docs/em/docs/tutorial/body.md b/docs/em/docs/tutorial/body.md index 79d8e716f6..09e1d7ccaa 100644 --- a/docs/em/docs/tutorial/body.md +++ b/docs/em/docs/tutorial/body.md @@ -22,21 +22,7 @@ 🥇, 👆 💪 🗄 `BaseModel` ⚪️➡️ `pydantic`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="4" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="2" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body/tutorial001.py hl[4] *} ## ✍ 👆 💽 🏷 @@ -44,21 +30,7 @@ ⚙️ 🐩 🐍 🆎 🌐 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="7-11" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="5-9" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body/tutorial001.py hl[7:11] *} 🎏 🕐❔ 📣 🔢 🔢, 🕐❔ 🏷 🔢 ✔️ 🔢 💲, ⚫️ 🚫 ✔. ⏪, ⚫️ ✔. ⚙️ `None` ⚒ ⚫️ 📦. @@ -86,21 +58,7 @@ 🚮 ⚫️ 👆 *➡ 🛠️*, 📣 ⚫️ 🎏 🌌 👆 📣 ➡ & 🔢 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/body/tutorial001.py hl[18] *} ...& 📣 🚮 🆎 🏷 👆 ✍, `Item`. @@ -167,21 +125,7 @@ 🔘 🔢, 👆 💪 🔐 🌐 🔢 🏷 🎚 🔗: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="21" -{!> ../../../docs_src/body/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="19" -{!> ../../../docs_src/body/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/body/tutorial002.py hl[21] *} ## 📨 💪 ➕ ➡ 🔢 @@ -189,21 +133,7 @@ **FastAPI** 🔜 🤔 👈 🔢 🔢 👈 🏏 ➡ 🔢 🔜 **✊ ⚪️➡️ ➡**, & 👈 🔢 🔢 👈 📣 Pydantic 🏷 🔜 **✊ ⚪️➡️ 📨 💪**. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="17-18" -{!> ../../../docs_src/body/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="15-16" -{!> ../../../docs_src/body/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/body/tutorial003.py hl[17:18] *} ## 📨 💪 ➕ ➡ ➕ 🔢 🔢 @@ -211,21 +141,7 @@ **FastAPI** 🔜 🤔 🔠 👫 & ✊ 📊 ⚪️➡️ ☑ 🥉. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/body/tutorial004.py hl[18] *} 🔢 🔢 🔜 🤔 ⏩: diff --git a/docs/em/docs/tutorial/cookie-params.md b/docs/em/docs/tutorial/cookie-params.md index 8919990283..4699fe2a54 100644 --- a/docs/em/docs/tutorial/cookie-params.md +++ b/docs/em/docs/tutorial/cookie-params.md @@ -6,21 +6,7 @@ 🥇 🗄 `Cookie`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/cookie_params/tutorial001.py hl[3] *} ## 📣 `Cookie` 🔢 @@ -28,23 +14,9 @@ 🥇 💲 🔢 💲, 👆 💪 🚶‍♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 +{* ../../docs_src/cookie_params/tutorial001.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` - -//// - -/// note | "📡 ℹ" +/// note | 📡 ℹ `Cookie` "👭" 🎓 `Path` & `Query`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓. diff --git a/docs/em/docs/tutorial/cors.md b/docs/em/docs/tutorial/cors.md index 690b8973a2..44ab4adc59 100644 --- a/docs/em/docs/tutorial/cors.md +++ b/docs/em/docs/tutorial/cors.md @@ -46,9 +46,7 @@ * 🎯 🇺🇸🔍 👩‍🔬 (`POST`, `PUT`) ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`. * 🎯 🇺🇸🔍 🎚 ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`. -```Python hl_lines="2 6-11 13-19" -{!../../../docs_src/cors/tutorial001.py!} -``` +{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} 🔢 🔢 ⚙️ `CORSMiddleware` 🛠️ 🚫 🔢, 👆 🔜 💪 🎯 🛠️ 🎯 🇨🇳, 👩‍🔬, ⚖️ 🎚, ✔ 🖥 ✔ ⚙️ 👫 ✖️-🆔 🔑. @@ -78,7 +76,7 @@ 🌖 ℹ 🔃 , ✅ 🦎 ⚜ 🧾. -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.middleware.cors import CORSMiddleware`. diff --git a/docs/em/docs/tutorial/debugging.md b/docs/em/docs/tutorial/debugging.md index abef2a50cd..97e61a763f 100644 --- a/docs/em/docs/tutorial/debugging.md +++ b/docs/em/docs/tutorial/debugging.md @@ -6,9 +6,7 @@ 👆 FastAPI 🈸, 🗄 & 🏃 `uvicorn` 🔗: -```Python hl_lines="1 15" -{!../../../docs_src/debugging/tutorial001.py!} -``` +{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} ### 🔃 `__name__ == "__main__"` diff --git a/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md index f14239b0f8..41938bc7b8 100644 --- a/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md @@ -6,21 +6,7 @@ ⏮️ 🖼, 👥 🛬 `dict` ⚪️➡️ 👆 🔗 ("☑"): -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001.py hl[9] *} ✋️ ⤴️ 👥 🤚 `dict` 🔢 `commons` *➡ 🛠️ 🔢*. @@ -83,57 +69,15 @@ fluffy = Cat(name="Mr Fluffy") ⤴️, 👥 💪 🔀 🔗 "☑" `common_parameters` ⚪️➡️ 🔛 🎓 `CommonQueryParams`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="9-13" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002.py hl[11:15] *} 💸 🙋 `__init__` 👩‍🔬 ⚙️ ✍ 👐 🎓: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002.py hl[12] *} ...⚫️ ✔️ 🎏 🔢 👆 ⏮️ `common_parameters`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="6" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001.py hl[9] *} 📚 🔢 ⚫️❔ **FastAPI** 🔜 ⚙️ "❎" 🔗. @@ -149,21 +93,7 @@ fluffy = Cat(name="Mr Fluffy") 🔜 👆 💪 📣 👆 🔗 ⚙️ 👉 🎓. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002.py hl[19] *} **FastAPI** 🤙 `CommonQueryParams` 🎓. 👉 ✍ "👐" 👈 🎓 & 👐 🔜 🚶‍♀️ 🔢 `commons` 👆 🔢. @@ -203,21 +133,7 @@ commons = Depends(CommonQueryParams) ...: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial003.py hl[19] *} ✋️ 📣 🆎 💡 👈 🌌 👆 👨‍🎨 🔜 💭 ⚫️❔ 🔜 🚶‍♀️ 🔢 `commons`, & ⤴️ ⚫️ 💪 ℹ 👆 ⏮️ 📟 🛠️, 🆎 ✅, ♒️: @@ -251,21 +167,7 @@ commons: CommonQueryParams = Depends() 🎏 🖼 🔜 ⤴️ 👀 💖: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial004.py hl[19] *} ...& **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 index bf267e0561..ab144a497b 100644 --- a/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,9 +14,7 @@ ⚫️ 🔜 `list` `Depends()`: -```Python hl_lines="17" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[17] *} 👉 🔗 🔜 🛠️/❎ 🎏 🌌 😐 🔗. ✋️ 👫 💲 (🚥 👫 📨 🙆) 🏆 🚫 🚶‍♀️ 👆 *➡ 🛠️ 🔢*. @@ -46,17 +44,13 @@ 👫 💪 📣 📨 📄 (💖 🎚) ⚖️ 🎏 🎧-🔗: -```Python hl_lines="6 11" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *} ### 🤚 ⚠ 👫 🔗 💪 `raise` ⚠, 🎏 😐 🔗: -```Python hl_lines="8 13" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *} ### 📨 💲 @@ -64,9 +58,7 @@ , 👆 💪 🏤-⚙️ 😐 🔗 (👈 📨 💲) 👆 ⏪ ⚙️ 👱 🙆, & ✋️ 💲 🏆 🚫 ⚙️, 🔗 🔜 🛠️: -```Python hl_lines="9 14" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *} ## 🔗 👪 *➡ 🛠️* diff --git a/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md index 5998d06dff..1b37b1cf29 100644 --- a/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md @@ -10,7 +10,7 @@ FastAPI 🐕‍🦺 🔗 👈 🔑 👨‍💼. @@ -224,9 +212,7 @@ with open("./somefile.txt") as f: 👆 💪 ⚙️ 👫 🔘 **FastAPI** 🔗 ⏮️ `yield` ⚙️ `with` ⚖️ `async with` 📄 🔘 🔗 🔢: -```Python hl_lines="1-9 13" -{!../../../docs_src/dependencies/tutorial010.py!} -``` +{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *} /// tip diff --git a/docs/em/docs/tutorial/dependencies/global-dependencies.md b/docs/em/docs/tutorial/dependencies/global-dependencies.md index 81759d0e83..5a22e5f1c8 100644 --- a/docs/em/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/em/docs/tutorial/dependencies/global-dependencies.md @@ -6,9 +6,7 @@ 👈 💼, 👫 🔜 ✔ 🌐 *➡ 🛠️* 🈸: -```Python hl_lines="15" -{!../../../docs_src/dependencies/tutorial012.py!} -``` +{* ../../docs_src/dependencies/tutorial012.py hl[15] *} & 🌐 💭 📄 🔃 [❎ `dependencies` *➡ 🛠️ 👨‍🎨*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} ✔, ✋️ 👉 💼, 🌐 *➡ 🛠️* 📱. diff --git a/docs/em/docs/tutorial/dependencies/index.md b/docs/em/docs/tutorial/dependencies/index.md index efb4ee672b..ce87d9ee48 100644 --- a/docs/em/docs/tutorial/dependencies/index.md +++ b/docs/em/docs/tutorial/dependencies/index.md @@ -31,21 +31,7 @@ ⚫️ 🔢 👈 💪 ✊ 🌐 🎏 🔢 👈 *➡ 🛠️ 🔢* 💪 ✊: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="8-11" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="6-7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001.py hl[8:11] *} 👈 ⚫️. @@ -67,41 +53,13 @@ ### 🗄 `Depends` -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001.py hl[3] *} ### 📣 🔗, "⚓️" 🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️. ⏮️ 👆 *➡ 🛠️ 🔢* 🔢, ⚙️ `Depends` ⏮️ 🆕 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="15 20" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="11 16" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001.py hl[15,20] *} 👐 👆 ⚙️ `Depends` 🔢 👆 🔢 🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️, `Depends` 👷 👄 🎏. diff --git a/docs/em/docs/tutorial/dependencies/sub-dependencies.md b/docs/em/docs/tutorial/dependencies/sub-dependencies.md index 02b33ccd7c..6d622e952f 100644 --- a/docs/em/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/em/docs/tutorial/dependencies/sub-dependencies.md @@ -10,21 +10,7 @@ 👆 💪 ✍ 🥇 🔗 ("☑") 💖: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="8-9" -{!> ../../../docs_src/dependencies/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="6-7" -{!> ../../../docs_src/dependencies/tutorial005_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial005.py hl[8:9] *} ⚫️ 📣 📦 🔢 🔢 `q` `str`, & ⤴️ ⚫️ 📨 ⚫️. @@ -34,21 +20,7 @@ ⤴️ 👆 💪 ✍ ➕1️⃣ 🔗 🔢 ("☑") 👈 🎏 🕰 📣 🔗 🚮 👍 (⚫️ "⚓️" 💁‍♂️): -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="13" -{!> ../../../docs_src/dependencies/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial005_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial005.py hl[13] *} ➡️ 🎯 🔛 🔢 📣: @@ -61,21 +33,7 @@ ⤴️ 👥 💪 ⚙️ 🔗 ⏮️: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="22" -{!> ../../../docs_src/dependencies/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial005_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial005.py hl[22] *} /// info diff --git a/docs/em/docs/tutorial/encoder.md b/docs/em/docs/tutorial/encoder.md index 314f5b3247..ad05f701e2 100644 --- a/docs/em/docs/tutorial/encoder.md +++ b/docs/em/docs/tutorial/encoder.md @@ -20,21 +20,7 @@ ⚫️ 📨 🎚, 💖 Pydantic 🏷, & 📨 🎻 🔗 ⏬: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="5 22" -{!> ../../../docs_src/encoder/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="4 21" -{!> ../../../docs_src/encoder/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/encoder/tutorial001.py hl[5,22] *} 👉 🖼, ⚫️ 🔜 🗜 Pydantic 🏷 `dict`, & `datetime` `str`. diff --git a/docs/em/docs/tutorial/extra-data-types.md b/docs/em/docs/tutorial/extra-data-types.md index cbe1110799..f15a74b4a3 100644 --- a/docs/em/docs/tutorial/extra-data-types.md +++ b/docs/em/docs/tutorial/extra-data-types.md @@ -55,36 +55,8 @@ 📥 🖼 *➡ 🛠️* ⏮️ 🔢 ⚙️ 🔛 🆎. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 3 12-16" -{!> ../../../docs_src/extra_data_types/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1 2 11-15" -{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/extra_data_types/tutorial001.py hl[1,3,12:16] *} 🗒 👈 🔢 🔘 🔢 ✔️ 👫 🐠 💽 🆎, & 👆 💪, 🖼, 🎭 😐 📅 🎭, 💖: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="18-19" -{!> ../../../docs_src/extra_data_types/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17-18" -{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/extra_data_types/tutorial001.py hl[18:19] *} diff --git a/docs/em/docs/tutorial/extra-models.md b/docs/em/docs/tutorial/extra-models.md index 4703c71234..19ab5b7986 100644 --- a/docs/em/docs/tutorial/extra-models.md +++ b/docs/em/docs/tutorial/extra-models.md @@ -20,21 +20,7 @@ 📥 🏢 💭 ❔ 🏷 💪 👀 💖 ⏮️ 👫 🔐 🏑 & 🥉 🌐❔ 👫 ⚙️: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" -{!> ../../../docs_src/extra_models/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" -{!> ../../../docs_src/extra_models/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/extra_models/tutorial001.py hl[9,11,16,22,24,29:30,33:35,40:41] *} ### 🔃 `**user_in.dict()` @@ -168,21 +154,7 @@ UserInDB( 👈 🌌, 👥 💪 📣 🔺 🖖 🏷 (⏮️ 🔢 `password`, ⏮️ `hashed_password` & 🍵 🔐): -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9 15-16 19-20 23-24" -{!> ../../../docs_src/extra_models/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7 13-14 17-18 21-22" -{!> ../../../docs_src/extra_models/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/extra_models/tutorial002.py hl[9,15:16,19:20,23:24] *} ## `Union` ⚖️ `anyOf` @@ -198,21 +170,7 @@ UserInDB( /// -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 14-15 18-20 33" -{!> ../../../docs_src/extra_models/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1 14-15 18-20 33" -{!> ../../../docs_src/extra_models/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/extra_models/tutorial003.py hl[1,14:15,18:20,33] *} ### `Union` 🐍 3️⃣.1️⃣0️⃣ @@ -234,21 +192,7 @@ some_variable: PlaneItem | CarItem 👈, ⚙️ 🐩 🐍 `typing.List` (⚖️ `list` 🐍 3️⃣.9️⃣ & 🔛): -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 20" -{!> ../../../docs_src/extra_models/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/extra_models/tutorial004_py39.py!} -``` - -//// +{* ../../docs_src/extra_models/tutorial004.py hl[1,20] *} ## 📨 ⏮️ ❌ `dict` @@ -258,21 +202,7 @@ some_variable: PlaneItem | CarItem 👉 💼, 👆 💪 ⚙️ `typing.Dict` (⚖️ `dict` 🐍 3️⃣.9️⃣ & 🔛): -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 8" -{!> ../../../docs_src/extra_models/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="6" -{!> ../../../docs_src/extra_models/tutorial005_py39.py!} -``` - -//// +{* ../../docs_src/extra_models/tutorial005.py hl[1,8] *} ## 🌃 diff --git a/docs/em/docs/tutorial/first-steps.md b/docs/em/docs/tutorial/first-steps.md index 158189e6ee..f9bb3fb756 100644 --- a/docs/em/docs/tutorial/first-steps.md +++ b/docs/em/docs/tutorial/first-steps.md @@ -2,9 +2,7 @@ 🙅 FastAPI 📁 💪 👀 💖 👉: -```Python -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py *} 📁 👈 📁 `main.py`. @@ -133,25 +131,21 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ### 🔁 1️⃣: 🗄 `FastAPI` -```Python hl_lines="1" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[1] *} `FastAPI` 🐍 🎓 👈 🚚 🌐 🛠️ 👆 🛠️. -/// note | "📡 ℹ" +/// note | 📡 ℹ `FastAPI` 🎓 👈 😖 🔗 ⚪️➡️ `Starlette`. -👆 💪 ⚙️ 🌐 💃 🛠️ ⏮️ `FastAPI` 💁‍♂️. +👆 💪 ⚙️ 🌐 💃 🛠️ ⏮️ `FastAPI` 💁‍♂️. /// ### 🔁 2️⃣: ✍ `FastAPI` "👐" -```Python hl_lines="3" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[3] *} 📥 `app` 🔢 🔜 "👐" 🎓 `FastAPI`. @@ -171,9 +165,7 @@ $ uvicorn main:app --reload 🚥 👆 ✍ 👆 📱 💖: -```Python hl_lines="3" -{!../../../docs_src/first_steps/tutorial002.py!} -``` +{* ../../docs_src/first_steps/tutorial002.py hl[3] *} & 🚮 ⚫️ 📁 `main.py`, ⤴️ 👆 🔜 🤙 `uvicorn` 💖: @@ -250,16 +242,14 @@ https://example.com/items/foo #### 🔬 *➡ 🛠️ 👨‍🎨* -```Python hl_lines="6" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[6] *} `@app.get("/")` 💬 **FastAPI** 👈 🔢 ▶️️ 🔛 🈚 🚚 📨 👈 🚶: * ➡ `/` * ⚙️ get 🛠️ -/// info | "`@decorator` ℹ" +/// info | `@decorator` ℹ 👈 `@something` ❕ 🐍 🤙 "👨‍🎨". @@ -306,9 +296,7 @@ https://example.com/items/foo * **🛠️**: `get`. * **🔢**: 🔢 🔛 "👨‍🎨" (🔛 `@app.get("/")`). -```Python hl_lines="7" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[7] *} 👉 🐍 🔢. @@ -320,9 +308,7 @@ https://example.com/items/foo 👆 💪 🔬 ⚫️ 😐 🔢 ↩️ `async def`: -```Python hl_lines="7" -{!../../../docs_src/first_steps/tutorial003.py!} -``` +{* ../../docs_src/first_steps/tutorial003.py hl[7] *} /// note @@ -332,9 +318,7 @@ https://example.com/items/foo ### 🔁 5️⃣: 📨 🎚 -```Python hl_lines="8" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[8] *} 👆 💪 📨 `dict`, `list`, ⭐ 💲 `str`, `int`, ♒️. diff --git a/docs/em/docs/tutorial/handling-errors.md b/docs/em/docs/tutorial/handling-errors.md index ed32ab53a2..6d72775976 100644 --- a/docs/em/docs/tutorial/handling-errors.md +++ b/docs/em/docs/tutorial/handling-errors.md @@ -25,9 +25,7 @@ ### 🗄 `HTTPException` -```Python hl_lines="1" -{!../../../docs_src/handling_errors/tutorial001.py!} -``` +{* ../../docs_src/handling_errors/tutorial001.py hl[1] *} ### 🤚 `HTTPException` 👆 📟 @@ -41,9 +39,7 @@ 👉 🖼, 🕐❔ 👩‍💻 📨 🏬 🆔 👈 🚫 🔀, 🤚 ⚠ ⏮️ 👔 📟 `404`: -```Python hl_lines="11" -{!../../../docs_src/handling_errors/tutorial001.py!} -``` +{* ../../docs_src/handling_errors/tutorial001.py hl[11] *} ### 📉 📨 @@ -81,13 +77,11 @@ ✋️ 💼 👆 💪 ⚫️ 🏧 😐, 👆 💪 🚮 🛃 🎚: -```Python hl_lines="14" -{!../../../docs_src/handling_errors/tutorial002.py!} -``` +{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} ## ❎ 🛃 ⚠ 🐕‍🦺 -👆 💪 🚮 🛃 ⚠ 🐕‍🦺 ⏮️ 🎏 ⚠ 🚙 ⚪️➡️ 💃. +👆 💪 🚮 🛃 ⚠ 🐕‍🦺 ⏮️ 🎏 ⚠ 🚙 ⚪️➡️ 💃. ➡️ 💬 👆 ✔️ 🛃 ⚠ `UnicornException` 👈 👆 (⚖️ 🗃 👆 ⚙️) 💪 `raise`. @@ -95,9 +89,7 @@ 👆 💪 🚮 🛃 ⚠ 🐕‍🦺 ⏮️ `@app.exception_handler()`: -```Python hl_lines="5-7 13-18 24" -{!../../../docs_src/handling_errors/tutorial003.py!} -``` +{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *} 📥, 🚥 👆 📨 `/unicorns/yolo`, *➡ 🛠️* 🔜 `raise` `UnicornException`. @@ -109,7 +101,7 @@ {"message": "Oops! yolo did something. There goes a rainbow..."} ``` -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.requests import Request` & `from starlette.responses import JSONResponse`. @@ -135,9 +127,7 @@ ⚠ 🐕‍🦺 🔜 📨 `Request` & ⚠. -```Python hl_lines="2 14-16" -{!../../../docs_src/handling_errors/tutorial004.py!} -``` +{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} 🔜, 🚥 👆 🚶 `/items/foo`, ↩️ 💆‍♂ 🔢 🎻 ❌ ⏮️: @@ -188,11 +178,9 @@ path -> item_id 🖼, 👆 💪 💚 📨 ✅ ✍ 📨 ↩️ 🎻 👫 ❌: -```Python hl_lines="3-4 9-11 22" -{!../../../docs_src/handling_errors/tutorial004.py!} -``` +{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.responses import PlainTextResponse`. @@ -206,9 +194,7 @@ path -> item_id 👆 💪 ⚙️ ⚫️ ⏪ 🛠️ 👆 📱 🕹 💪 & ℹ ⚫️, 📨 ⚫️ 👩‍💻, ♒️. -```Python hl_lines="14" -{!../../../docs_src/handling_errors/tutorial005.py!} -``` +{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} 🔜 🔄 📨 ❌ 🏬 💖: @@ -266,8 +252,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException 🚥 👆 💚 ⚙️ ⚠ ⤴️ ⏮️ 🎏 🔢 ⚠ 🐕‍🦺 ⚪️➡️ **FastAPI**, 👆 💪 🗄 & 🏤-⚙️ 🔢 ⚠ 🐕‍🦺 ⚪️➡️ `fastapi.exception_handlers`: -```Python hl_lines="2-5 15 21" -{!../../../docs_src/handling_errors/tutorial006.py!} -``` +{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} 👉 🖼 👆 `print`😅 ❌ ⏮️ 📶 🎨 📧, ✋️ 👆 🤚 💭. 👆 💪 ⚙️ ⚠ & ⤴️ 🏤-⚙️ 🔢 ⚠ 🐕‍🦺. diff --git a/docs/em/docs/tutorial/header-params.md b/docs/em/docs/tutorial/header-params.md index 82583c7c33..fa5e3a22b4 100644 --- a/docs/em/docs/tutorial/header-params.md +++ b/docs/em/docs/tutorial/header-params.md @@ -6,21 +6,7 @@ 🥇 🗄 `Header`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3" -{!> ../../../docs_src/header_params/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../../docs_src/header_params/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/header_params/tutorial001.py hl[3] *} ## 📣 `Header` 🔢 @@ -28,23 +14,9 @@ 🥇 💲 🔢 💲, 👆 💪 🚶‍♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 +{* ../../docs_src/header_params/tutorial001.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/header_params/tutorial001_py310.py!} -``` - -//// - -/// note | "📡 ℹ" +/// note | 📡 ℹ `Header` "👭" 🎓 `Path`, `Query` & `Cookie`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓. @@ -74,21 +46,7 @@ 🚥 🤔 👆 💪 ❎ 🏧 🛠️ 🎦 🔠, ⚒ 🔢 `convert_underscores` `Header` `False`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/header_params/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="8" -{!> ../../../docs_src/header_params/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/header_params/tutorial002.py hl[10] *} /// warning @@ -106,29 +64,7 @@ 🖼, 📣 🎚 `X-Token` 👈 💪 😑 🌅 🌘 🕐, 👆 💪 ✍: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/header_params/tutorial003_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/header_params/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/header_params/tutorial003.py hl[9] *} 🚥 👆 🔗 ⏮️ 👈 *➡ 🛠️* 📨 2️⃣ 🇺🇸🔍 🎚 💖: diff --git a/docs/em/docs/tutorial/metadata.md b/docs/em/docs/tutorial/metadata.md index 6caeed4cd0..eaf605de1e 100644 --- a/docs/em/docs/tutorial/metadata.md +++ b/docs/em/docs/tutorial/metadata.md @@ -17,9 +17,7 @@ 👆 💪 ⚒ 👫 ⏩: -```Python hl_lines="3-16 19-31" -{!../../../docs_src/metadata/tutorial001.py!} -``` +{* ../../docs_src/metadata/tutorial001.py hl[3:16,19:31] *} /// tip @@ -51,9 +49,7 @@ ✍ 🗃 👆 🔖 & 🚶‍♀️ ⚫️ `openapi_tags` 🔢: -```Python hl_lines="3-16 18" -{!../../../docs_src/metadata/tutorial004.py!} -``` +{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} 👀 👈 👆 💪 ⚙️ ✍ 🔘 📛, 🖼 "💳" 🔜 🎦 🦁 (**💳**) & "🎀" 🔜 🎦 ❕ (_🎀_). @@ -67,9 +63,7 @@ ⚙️ `tags` 🔢 ⏮️ 👆 *➡ 🛠️* (& `APIRouter`Ⓜ) 🛠️ 👫 🎏 🔖: -```Python hl_lines="21 26" -{!../../../docs_src/metadata/tutorial004.py!} -``` +{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} /// info @@ -97,9 +91,7 @@ 🖼, ⚒ ⚫️ 🍦 `/api/v1/openapi.json`: -```Python hl_lines="3" -{!../../../docs_src/metadata/tutorial002.py!} -``` +{* ../../docs_src/metadata/tutorial002.py hl[3] *} 🚥 👆 💚 ❎ 🗄 🔗 🍕 👆 💪 ⚒ `openapi_url=None`, 👈 🔜 ❎ 🧾 👩‍💻 🔢 👈 ⚙️ ⚫️. @@ -116,6 +108,4 @@ 🖼, ⚒ 🦁 🎚 🍦 `/documentation` & ❎ 📄: -```Python hl_lines="3" -{!../../../docs_src/metadata/tutorial003.py!} -``` +{* ../../docs_src/metadata/tutorial003.py hl[3] *} diff --git a/docs/em/docs/tutorial/middleware.md b/docs/em/docs/tutorial/middleware.md index b9bb12e005..c77b105544 100644 --- a/docs/em/docs/tutorial/middleware.md +++ b/docs/em/docs/tutorial/middleware.md @@ -11,7 +11,7 @@ * ⚫️ 💪 🕳 👈 **📨** ⚖️ 🏃 🙆 💪 📟. * ⤴️ ⚫️ 📨 **📨**. -/// note | "📡 ℹ" +/// note | 📡 ℹ 🚥 👆 ✔️ 🔗 ⏮️ `yield`, 🚪 📟 🔜 🏃 *⏮️* 🛠️. @@ -31,19 +31,17 @@ * ⤴️ ⚫️ 📨 `response` 🏗 🔗 *➡ 🛠️*. * 👆 💪 ⤴️ 🔀 🌅 `response` ⏭ 🛬 ⚫️. -```Python hl_lines="8-9 11 14" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} /// tip ✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡. -✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 ([⚜ (✖️-🇨🇳 ℹ 🤝)](cors.md){.internal-link target=_blank}) ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺. +✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 ([⚜ (✖️-🇨🇳 ℹ 🤝)](cors.md){.internal-link target=_blank}) ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺. /// -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.requests import Request`. @@ -59,9 +57,7 @@ 🖼, 👆 💪 🚮 🛃 🎚 `X-Process-Time` ⚗ 🕰 🥈 👈 ⚫️ ✊ 🛠️ 📨 & 🏗 📨: -```Python hl_lines="10 12-13" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} ## 🎏 🛠️ diff --git a/docs/em/docs/tutorial/path-operation-configuration.md b/docs/em/docs/tutorial/path-operation-configuration.md index 1979bed2b2..c6030c0890 100644 --- a/docs/em/docs/tutorial/path-operation-configuration.md +++ b/docs/em/docs/tutorial/path-operation-configuration.md @@ -16,33 +16,11 @@ ✋️ 🚥 👆 🚫 💭 ⚫️❔ 🔠 🔢 📟, 👆 💪 ⚙️ ⌨ 📉 `status`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3 17" -{!> ../../../docs_src/path_operation_configuration/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="3 17" -{!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1 15" -{!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *} 👈 👔 📟 🔜 ⚙️ 📨 & 🔜 🚮 🗄 🔗. -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette import status`. @@ -54,29 +32,7 @@ 👆 💪 🚮 🔖 👆 *➡ 🛠️*, 🚶‍♀️ 🔢 `tags` ⏮️ `list` `str` (🛎 1️⃣ `str`): -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="17 22 27" -{!> ../../../docs_src/path_operation_configuration/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="17 22 27" -{!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="15 20 25" -{!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *} 👫 🔜 🚮 🗄 🔗 & ⚙️ 🏧 🧾 🔢: @@ -90,37 +46,13 @@ **FastAPI** 🐕‍🦺 👈 🎏 🌌 ⏮️ ✅ 🎻: -```Python hl_lines="1 8-10 13 18" -{!../../../docs_src/path_operation_configuration/tutorial002b.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *} ## 📄 & 📛 👆 💪 🚮 `summary` & `description`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="20-21" -{!> ../../../docs_src/path_operation_configuration/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="20-21" -{!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="18-19" -{!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *} ## 📛 ⚪️➡️ #️⃣ @@ -128,29 +60,7 @@ 👆 💪 ✍ #️⃣ , ⚫️ 🔜 🔬 & 🖥 ☑ (✊ 🔘 🏧 #️⃣ 📐). -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="19-27" -{!> ../../../docs_src/path_operation_configuration/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="19-27" -{!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17-25" -{!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} ⚫️ 🔜 ⚙️ 🎓 🩺: @@ -160,29 +70,7 @@ 👆 💪 ✔ 📨 📛 ⏮️ 🔢 `response_description`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="21" -{!> ../../../docs_src/path_operation_configuration/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="21" -{!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="19" -{!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *} /// info @@ -204,9 +92,7 @@ 🚥 👆 💪 ™ *➡ 🛠️* 😢, ✋️ 🍵 ❎ ⚫️, 🚶‍♀️ 🔢 `deprecated`: -```Python hl_lines="16" -{!../../../docs_src/path_operation_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} ⚫️ 🔜 🎯 ™ 😢 🎓 🩺: diff --git a/docs/em/docs/tutorial/path-params-numeric-validations.md b/docs/em/docs/tutorial/path-params-numeric-validations.md index a7952984cd..b45e0557b6 100644 --- a/docs/em/docs/tutorial/path-params-numeric-validations.md +++ b/docs/em/docs/tutorial/path-params-numeric-validations.md @@ -6,21 +6,7 @@ 🥇, 🗄 `Path` ⚪️➡️ `fastapi`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[3] *} ## 📣 🗃 @@ -28,21 +14,7 @@ 🖼, 📣 `title` 🗃 💲 ➡ 🔢 `item_id` 👆 💪 🆎: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="8" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *} /// note @@ -70,9 +42,7 @@ , 👆 💪 📣 👆 🔢: -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} ## ✔ 🔢 👆 💪, 🎱 @@ -82,9 +52,7 @@ 🐍 🏆 🚫 🕳 ⏮️ 👈 `*`, ✋️ ⚫️ 🔜 💭 👈 🌐 📄 🔢 🔜 🤙 🇨🇻 ❌ (🔑-💲 👫), 💭 kwargs. 🚥 👫 🚫 ✔️ 🔢 💲. -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} ## 🔢 🔬: 👑 🌘 ⚖️ 🌓 @@ -92,9 +60,7 @@ 📥, ⏮️ `ge=1`, `item_id` 🔜 💪 🔢 🔢 "`g`🅾 🌘 ⚖️ `e`🅾" `1`. -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} ## 🔢 🔬: 🌘 🌘 & 🌘 🌘 ⚖️ 🌓 @@ -103,9 +69,7 @@ * `gt`: `g`🅾 `t`👲 * `le`: `l`👭 🌘 ⚖️ `e`🅾 -```Python hl_lines="9" -{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *} ## 🔢 🔬: 🎈, 🌘 🌘 & 🌘 🌘 @@ -117,9 +81,7 @@ & 🎏 lt. -```Python hl_lines="11" -{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} ## 🌃 @@ -140,7 +102,7 @@ /// -/// note | "📡 ℹ" +/// note | 📡 ℹ 🕐❔ 👆 🗄 `Query`, `Path` & 🎏 ⚪️➡️ `fastapi`, 👫 🤙 🔢. diff --git a/docs/em/docs/tutorial/path-params.md b/docs/em/docs/tutorial/path-params.md index e0d51a1df9..a914dc9050 100644 --- a/docs/em/docs/tutorial/path-params.md +++ b/docs/em/docs/tutorial/path-params.md @@ -2,9 +2,7 @@ 👆 💪 📣 ➡ "🔢" ⚖️ "🔢" ⏮️ 🎏 ❕ ⚙️ 🐍 📁 🎻: -```Python hl_lines="6-7" -{!../../../docs_src/path_params/tutorial001.py!} -``` +{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} 💲 ➡ 🔢 `item_id` 🔜 🚶‍♀️ 👆 🔢 ❌ `item_id`. @@ -18,9 +16,7 @@ 👆 💪 📣 🆎 ➡ 🔢 🔢, ⚙️ 🐩 🐍 🆎 ✍: -```Python hl_lines="7" -{!../../../docs_src/path_params/tutorial002.py!} -``` +{* ../../docs_src/path_params/tutorial002.py hl[7] *} 👉 💼, `item_id` 📣 `int`. @@ -121,17 +117,13 @@ ↩️ *➡ 🛠️* 🔬 ✔, 👆 💪 ⚒ 💭 👈 ➡ `/users/me` 📣 ⏭ 1️⃣ `/users/{user_id}`: -```Python hl_lines="6 11" -{!../../../docs_src/path_params/tutorial003.py!} -``` +{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} ⏪, ➡ `/users/{user_id}` 🔜 🏏 `/users/me`, "💭" 👈 ⚫️ 📨 🔢 `user_id` ⏮️ 💲 `"me"`. ➡, 👆 🚫🔜 ↔ ➡ 🛠️: -```Python hl_lines="6 11" -{!../../../docs_src/path_params/tutorial003b.py!} -``` +{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *} 🥇 🕐 🔜 🕧 ⚙️ ↩️ ➡ 🏏 🥇. @@ -147,9 +139,7 @@ ⤴️ ✍ 🎓 🔢 ⏮️ 🔧 💲, ❔ 🔜 💪 ☑ 💲: -```Python hl_lines="1 6-9" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} /// info @@ -167,9 +157,7 @@ ⤴️ ✍ *➡ 🔢* ⏮️ 🆎 ✍ ⚙️ 🔢 🎓 👆 ✍ (`ModelName`): -```Python hl_lines="16" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[16] *} ### ✅ 🩺 @@ -185,17 +173,13 @@ 👆 💪 🔬 ⚫️ ⏮️ *🔢 👨‍🎓* 👆 ✍ 🔢 `ModelName`: -```Python hl_lines="17" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[17] *} #### 🤚 *🔢 💲* 👆 💪 🤚 ☑ 💲 ( `str` 👉 💼) ⚙️ `model_name.value`, ⚖️ 🏢, `your_enum_member.value`: -```Python hl_lines="20" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[20] *} /// tip @@ -209,9 +193,7 @@ 👫 🔜 🗜 👫 🔗 💲 (🎻 👉 💼) ⏭ 🛬 👫 👩‍💻: -```Python hl_lines="18 21 23" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} 👆 👩‍💻 👆 🔜 🤚 🎻 📨 💖: @@ -250,9 +232,7 @@ , 👆 💪 ⚙️ ⚫️ ⏮️: -```Python hl_lines="6" -{!../../../docs_src/path_params/tutorial004.py!} -``` +{* ../../docs_src/path_params/tutorial004.py hl[6] *} /// tip diff --git a/docs/em/docs/tutorial/query-params-str-validations.md b/docs/em/docs/tutorial/query-params-str-validations.md index 23873155ed..fd077bf8f7 100644 --- a/docs/em/docs/tutorial/query-params-str-validations.md +++ b/docs/em/docs/tutorial/query-params-str-validations.md @@ -4,21 +4,7 @@ ➡️ ✊ 👉 🈸 🖼: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *} 🔢 🔢 `q` 🆎 `Union[str, None]` (⚖️ `str | None` 🐍 3️⃣.1️⃣0️⃣), 👈 ⛓ 👈 ⚫️ 🆎 `str` ✋️ 💪 `None`, & 👐, 🔢 💲 `None`, FastAPI 🔜 💭 ⚫️ 🚫 ✔. @@ -38,41 +24,13 @@ FastAPI 🔜 💭 👈 💲 `q` 🚫 ✔ ↩️ 🔢 💲 `= None`. 🏆 👈, 🥇 🗄 `Query` ⚪️➡️ `fastapi`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3" -{!> ../../../docs_src/query_params_str_validations/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *} ## ⚙️ `Query` 🔢 💲 & 🔜 ⚙️ ⚫️ 🔢 💲 👆 🔢, ⚒ 🔢 `max_length` 5️⃣0️⃣: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} 👥 ✔️ ❎ 🔢 💲 `None` 🔢 ⏮️ `Query()`, 👥 💪 🔜 ⚒ 🔢 💲 ⏮️ 🔢 `Query(default=None)`, ⚫️ 🍦 🎏 🎯 ⚖ 👈 🔢 💲. @@ -134,41 +92,13 @@ q: Union[str, None] = Query(default=None, max_length=50) 👆 💪 🚮 🔢 `min_length`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *} ## 🚮 🥔 🧬 👆 💪 🔬 🥔 🧬 👈 🔢 🔜 🏏: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *} 👉 🎯 🥔 🧬 ✅ 👈 📨 🔢 💲: @@ -186,9 +116,7 @@ q: Union[str, None] = Query(default=None, max_length=50) ➡️ 💬 👈 👆 💚 📣 `q` 🔢 🔢 ✔️ `min_length` `3`, & ✔️ 🔢 💲 `"fixedquery"`: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial005.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} /// note @@ -218,27 +146,7 @@ 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** 💭 👈 👉 🔢 ✔. +{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} ### ✔ ⏮️ `None` @@ -246,21 +154,7 @@ q: Union[str, None] = Query(default=None, min_length=3) 👈, 👆 💪 📣 👈 `None` ☑ 🆎 ✋️ ⚙️ `default=...`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial006c.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial006c_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *} /// tip @@ -268,49 +162,13 @@ Pydantic, ❔ ⚫️❔ 🏋️ 🌐 💽 🔬 & 🛠️ FastAPI, ✔️ /// -### ⚙️ Pydantic `Required` ↩️ ❕ (`...`) - -🚥 👆 💭 😬 ⚙️ `...`, 👆 💪 🗄 & ⚙️ `Required` ⚪️➡️ Pydantic: - -```Python hl_lines="2 8" -{!../../../docs_src/query_params_str_validations/tutorial006d.py!} -``` - -/// tip - -💭 👈 🌅 💼, 🕐❔ 🕳 🚚, 👆 💪 🎯 🚫 `default` 🔢, 👆 🛎 🚫 ✔️ ⚙️ `...` 🚫 `Required`. - -/// - ## 🔢 🔢 📇 / 💗 💲 🕐❔ 👆 🔬 🔢 🔢 🎯 ⏮️ `Query` 👆 💪 📣 ⚫️ 📨 📇 💲, ⚖️ 🙆‍♀ 🎏 🌌, 📨 💗 💲. 🖼, 📣 🔢 🔢 `q` 👈 💪 😑 💗 🕰 📛, 👆 💪 ✍: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial011.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} ⤴️, ⏮️ 📛 💖: @@ -345,21 +203,7 @@ http://localhost:8000/items/?q=foo&q=bar & 👆 💪 🔬 🔢 `list` 💲 🚥 👌 🚚: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial012.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} 🚥 👆 🚶: @@ -382,9 +226,7 @@ http://localhost:8000/items/ 👆 💪 ⚙️ `list` 🔗 ↩️ `List[str]` (⚖️ `list[str]` 🐍 3️⃣.9️⃣ ➕): -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial013.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} /// note @@ -410,39 +252,11 @@ http://localhost:8000/items/ 👆 💪 🚮 `title`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial007.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *} & `description`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="13" -{!> ../../../docs_src/query_params_str_validations/tutorial008.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="11" -{!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} ## 📛 🔢 @@ -462,21 +276,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems ⤴️ 👆 💪 📣 `alias`, & 👈 📛 ⚫️❔ 🔜 ⚙️ 🔎 🔢 💲: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial009.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} ## 😛 🔢 @@ -486,21 +286,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems ⤴️ 🚶‍♀️ 🔢 `deprecated=True` `Query`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/query_params_str_validations/tutorial010.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="16" -{!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} 🩺 🔜 🎦 ⚫️ 💖 👉: @@ -510,21 +296,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 🚫 🔢 🔢 ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚒ 🔢 `include_in_schema` `Query` `False`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/query_params_str_validations/tutorial014.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="8" -{!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial014.py hl[10] *} ## 🌃 diff --git a/docs/em/docs/tutorial/query-params.md b/docs/em/docs/tutorial/query-params.md index 9bdab9e3c1..5c8d868a9a 100644 --- a/docs/em/docs/tutorial/query-params.md +++ b/docs/em/docs/tutorial/query-params.md @@ -2,9 +2,7 @@ 🕐❔ 👆 📣 🎏 🔢 🔢 👈 🚫 🍕 ➡ 🔢, 👫 🔁 🔬 "🔢" 🔢. -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial001.py!} -``` +{* ../../docs_src/query_params/tutorial001.py hl[9] *} 🔢 ⚒ 🔑-💲 👫 👈 🚶 ⏮️ `?` 📛, 🎏 `&` 🦹. @@ -63,21 +61,7 @@ http://127.0.0.1:8000/items/?skip=20 🎏 🌌, 👆 💪 📣 📦 🔢 🔢, ⚒ 👫 🔢 `None`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial002.py hl[9] *} 👉 💼, 🔢 🔢 `q` 🔜 📦, & 🔜 `None` 🔢. @@ -91,21 +75,7 @@ http://127.0.0.1:8000/items/?skip=20 👆 💪 📣 `bool` 🆎, & 👫 🔜 🗜: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial003.py hl[9] *} 👉 💼, 🚥 👆 🚶: @@ -148,21 +118,7 @@ http://127.0.0.1:8000/items/foo?short=yes 👫 🔜 🔬 📛: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="8 10" -{!> ../../../docs_src/query_params/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="6 8" -{!> ../../../docs_src/query_params/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial004.py hl[8,10] *} ## ✔ 🔢 🔢 @@ -172,9 +128,7 @@ http://127.0.0.1:8000/items/foo?short=yes ✋️ 🕐❔ 👆 💚 ⚒ 🔢 🔢 ✔, 👆 💪 🚫 📣 🙆 🔢 💲: -```Python hl_lines="6-7" -{!../../../docs_src/query_params/tutorial005.py!} -``` +{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} 📥 🔢 🔢 `needy` ✔ 🔢 🔢 🆎 `str`. @@ -218,21 +172,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy & ↗️, 👆 💪 🔬 🔢 ✔, ✔️ 🔢 💲, & 🍕 📦: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/query_params/tutorial006.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="8" -{!> ../../../docs_src/query_params/tutorial006_py310.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial006.py hl[10] *} 👉 💼, 📤 3️⃣ 🔢 🔢: diff --git a/docs/em/docs/tutorial/request-files.md b/docs/em/docs/tutorial/request-files.md index 010aa76bf3..c3bdeafd48 100644 --- a/docs/em/docs/tutorial/request-files.md +++ b/docs/em/docs/tutorial/request-files.md @@ -16,17 +16,13 @@ 🗄 `File` & `UploadFile` ⚪️➡️ `fastapi`: -```Python hl_lines="1" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[1] *} ## 🔬 `File` 🔢 ✍ 📁 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Form`: -```Python hl_lines="7" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[7] *} /// info @@ -54,9 +50,7 @@ 🔬 📁 🔢 ⏮️ 🆎 `UploadFile`: -```Python hl_lines="12" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[12] *} ⚙️ `UploadFile` ✔️ 📚 📈 🤭 `bytes`: @@ -99,13 +93,13 @@ contents = await myfile.read() contents = myfile.file.read() ``` -/// note | "`async` 📡 ℹ" +/// note | `async` 📡 ℹ 🕐❔ 👆 ⚙️ `async` 👩‍🔬, **FastAPI** 🏃 📁 👩‍🔬 🧵 & ⌛ 👫. /// -/// note | "💃 📡 ℹ" +/// note | 💃 📡 ℹ **FastAPI**'Ⓜ `UploadFile` 😖 🔗 ⚪️➡️ **💃**'Ⓜ `UploadFile`, ✋️ 🚮 💪 🍕 ⚒ ⚫️ 🔗 ⏮️ **Pydantic** & 🎏 🍕 FastAPI. @@ -117,7 +111,7 @@ contents = myfile.file.read() **FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻. -/// note | "📡 ℹ" +/// note | 📡 ℹ 📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded` 🕐❔ ⚫️ 🚫 🔌 📁. @@ -139,29 +133,13 @@ contents = myfile.file.read() 👆 💪 ⚒ 📁 📦 ⚙️ 🐩 🆎 ✍ & ⚒ 🔢 💲 `None`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7 14" -{!> ../../../docs_src/request_files/tutorial001_02_py310.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_02.py hl[9,17] *} ## `UploadFile` ⏮️ 🌖 🗃 👆 💪 ⚙️ `File()` ⏮️ `UploadFile`, 🖼, ⚒ 🌖 🗃: -```Python hl_lines="13" -{!../../../docs_src/request_files/tutorial001_03.py!} -``` +{* ../../docs_src/request_files/tutorial001_03.py hl[13] *} ## 💗 📁 📂 @@ -171,25 +149,11 @@ contents = myfile.file.read() ⚙️ 👈, 📣 📇 `bytes` ⚖️ `UploadFile`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10 15" -{!> ../../../docs_src/request_files/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="8 13" -{!> ../../../docs_src/request_files/tutorial002_py39.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial002.py hl[10,15] *} 👆 🔜 📨, 📣, `list` `bytes` ⚖️ `UploadFile`Ⓜ. -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.responses import HTMLResponse`. @@ -201,21 +165,7 @@ contents = myfile.file.read() & 🎏 🌌 ⏭, 👆 💪 ⚙️ `File()` ⚒ 🌖 🔢, `UploadFile`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/request_files/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="16" -{!> ../../../docs_src/request_files/tutorial003_py39.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial003.py hl[18] *} ## 🌃 diff --git a/docs/em/docs/tutorial/request-forms-and-files.md b/docs/em/docs/tutorial/request-forms-and-files.md index ab39d1b949..680b1a96a5 100644 --- a/docs/em/docs/tutorial/request-forms-and-files.md +++ b/docs/em/docs/tutorial/request-forms-and-files.md @@ -12,17 +12,13 @@ ## 🗄 `File` & `Form` -```Python hl_lines="1" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *} ## 🔬 `File` & `Form` 🔢 ✍ 📁 & 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`: -```Python hl_lines="8" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *} 📁 & 📨 🏑 🔜 📂 📨 📊 & 👆 🔜 📨 📁 & 📨 🏑. diff --git a/docs/em/docs/tutorial/request-forms.md b/docs/em/docs/tutorial/request-forms.md index 74117c47d3..1cc1ea5dcb 100644 --- a/docs/em/docs/tutorial/request-forms.md +++ b/docs/em/docs/tutorial/request-forms.md @@ -14,17 +14,13 @@ 🗄 `Form` ⚪️➡️ `fastapi`: -```Python hl_lines="1" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[1] *} ## 🔬 `Form` 🔢 ✍ 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`: -```Python hl_lines="7" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[7] *} 🖼, 1️⃣ 🌌 Oauth2️⃣ 🔧 💪 ⚙️ (🤙 "🔐 💧") ⚫️ ✔ 📨 `username` & `password` 📨 🏑. @@ -50,7 +46,7 @@ **FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻. -/// note | "📡 ℹ" +/// note | 📡 ℹ 📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded`. diff --git a/docs/em/docs/tutorial/response-model.md b/docs/em/docs/tutorial/response-model.md index 9483508aa1..477376458d 100644 --- a/docs/em/docs/tutorial/response-model.md +++ b/docs/em/docs/tutorial/response-model.md @@ -4,29 +4,7 @@ 👆 💪 ⚙️ **🆎 ✍** 🎏 🌌 👆 🔜 🔢 💽 🔢 **🔢**, 👆 💪 ⚙️ Pydantic 🏷, 📇, 📖, 📊 💲 💖 🔢, 🎻, ♒️. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="18 23" -{!> ../../../docs_src/response_model/tutorial001_01.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="18 23" -{!> ../../../docs_src/response_model/tutorial001_01_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="16 21" -{!> ../../../docs_src/response_model/tutorial001_01_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial001_01.py hl[18,23] *} FastAPI 🔜 ⚙️ 👉 📨 🆎: @@ -59,29 +37,7 @@ FastAPI 🔜 ⚙️ 👉 📨 🆎: * `@app.delete()` * ♒️. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial001.py hl[17,22,24:27] *} /// note @@ -113,21 +69,7 @@ FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & ** 📥 👥 📣 `UserIn` 🏷, ⚫️ 🔜 🔌 🔢 🔐: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9 11" -{!> ../../../docs_src/response_model/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7 9" -{!> ../../../docs_src/response_model/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial002.py hl[9,11] *} /// info @@ -140,21 +82,7 @@ FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & ** & 👥 ⚙️ 👉 🏷 📣 👆 🔢 & 🎏 🏷 📣 👆 🔢: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="18" -{!> ../../../docs_src/response_model/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="16" -{!> ../../../docs_src/response_model/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial002.py hl[18] *} 🔜, 🕐❔ 🖥 🏗 👩‍💻 ⏮️ 🔐, 🛠️ 🔜 📨 🎏 🔐 📨. @@ -172,57 +100,15 @@ FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & ** 👥 💪 ↩️ ✍ 🔢 🏷 ⏮️ 🔢 🔐 & 🔢 🏷 🍵 ⚫️: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9 11 16" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="9 11 16" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *} 📥, ✋️ 👆 *➡ 🛠️ 🔢* 🛬 🎏 🔢 👩‍💻 👈 🔌 🔐: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003.py hl[24] *} ...👥 📣 `response_model` 👆 🏷 `UserOut`, 👈 🚫 🔌 🔐: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003.py hl[22] *} , **FastAPI** 🔜 ✊ 💅 🖥 👅 🌐 💽 👈 🚫 📣 🔢 🏷 (⚙️ Pydantic). @@ -246,21 +132,7 @@ FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & ** & 👈 💼, 👥 💪 ⚙️ 🎓 & 🧬 ✊ 📈 🔢 **🆎 ✍** 🤚 👍 🐕‍🦺 👨‍🎨 & 🧰, & 🤚 FastAPI **💽 🖥**. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9-13 15-16 20" -{!> ../../../docs_src/response_model/tutorial003_01.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7-10 13-14 18" -{!> ../../../docs_src/response_model/tutorial003_01_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_01.py hl[9:13,15:16,20] *} ⏮️ 👉, 👥 🤚 🏭 🐕‍🦺, ⚪️➡️ 👨‍🎨 & ✍ 👉 📟 ☑ ⚖ 🆎, ✋️ 👥 🤚 💽 🖥 ⚪️➡️ FastAPI. @@ -302,9 +174,7 @@ FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 🏆 ⚠ 💼 🔜 [🛬 📨 🔗 🔬 ⏪ 🏧 🩺](../advanced/response-directly.md){.internal-link target=_blank}. -```Python hl_lines="8 10-11" -{!> ../../../docs_src/response_model/tutorial003_02.py!} -``` +{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} 👉 🙅 💼 🍵 🔁 FastAPI ↩️ 📨 🆎 ✍ 🎓 (⚖️ 🏿) `Response`. @@ -314,9 +184,7 @@ FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 👆 💪 ⚙️ 🏿 `Response` 🆎 ✍: -```Python hl_lines="8-9" -{!> ../../../docs_src/response_model/tutorial003_03.py!} -``` +{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} 👉 🔜 👷 ↩️ `RedirectResponse` 🏿 `Response`, & FastAPI 🔜 🔁 🍵 👉 🙅 💼. @@ -326,21 +194,7 @@ FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 🎏 🔜 🔨 🚥 👆 ✔️ 🕳 💖 🇪🇺 🖖 🎏 🆎 🌐❔ 1️⃣ ⚖️ 🌅 👫 🚫 ☑ Pydantic 🆎, 🖼 👉 🔜 ❌ 👶: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="10" -{!> ../../../docs_src/response_model/tutorial003_04.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="8" -{!> ../../../docs_src/response_model/tutorial003_04_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_04.py hl[10] *} ...👉 ❌ ↩️ 🆎 ✍ 🚫 Pydantic 🆎 & 🚫 👁 `Response` 🎓 ⚖️ 🏿, ⚫️ 🇪🇺 (🙆 2️⃣) 🖖 `Response` & `dict`. @@ -352,21 +206,7 @@ FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 👉 💼, 👆 💪 ❎ 📨 🏷 ⚡ ⚒ `response_model=None`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/response_model/tutorial003_05.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="7" -{!> ../../../docs_src/response_model/tutorial003_05_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_05.py hl[9] *} 👉 🔜 ⚒ FastAPI 🚶 📨 🏷 ⚡ & 👈 🌌 👆 💪 ✔️ 🙆 📨 🆎 ✍ 👆 💪 🍵 ⚫️ 🤕 👆 FastAPI 🈸. 👶 @@ -374,29 +214,7 @@ FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 👆 📨 🏷 💪 ✔️ 🔢 💲, 💖: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="11 13-14" -{!> ../../../docs_src/response_model/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="11 13-14" -{!> ../../../docs_src/response_model/tutorial004_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="9 11-12" -{!> ../../../docs_src/response_model/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *} * `description: Union[str, None] = None` (⚖️ `str | None = None` 🐍 3️⃣.1️⃣0️⃣) ✔️ 🔢 `None`. * `tax: float = 10.5` ✔️ 🔢 `10.5`. @@ -410,29 +228,7 @@ FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 👆 💪 ⚒ *➡ 🛠️ 👨‍🎨* 🔢 `response_model_exclude_unset=True`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial004_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial004.py hl[24] *} & 👈 🔢 💲 🏆 🚫 🔌 📨, 🕴 💲 🤙 ⚒. @@ -521,21 +317,7 @@ FastAPI 🙃 🥃 (🤙, Pydantic 🙃 🥃) 🤔 👈, ✋️ `description`, `t /// -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="31 37" -{!> ../../../docs_src/response_model/tutorial005.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="29 35" -{!> ../../../docs_src/response_model/tutorial005_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial005.py hl[31,37] *} /// tip @@ -549,21 +331,7 @@ FastAPI 🙃 🥃 (🤙, Pydantic 🙃 🥃) 🤔 👈, ✋️ `description`, `t 🚥 👆 💭 ⚙️ `set` & ⚙️ `list` ⚖️ `tuple` ↩️, FastAPI 🔜 🗜 ⚫️ `set` & ⚫️ 🔜 👷 ☑: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="31 37" -{!> ../../../docs_src/response_model/tutorial006.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="29 35" -{!> ../../../docs_src/response_model/tutorial006_py310.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial006.py hl[31,37] *} ## 🌃 diff --git a/docs/em/docs/tutorial/response-status-code.md b/docs/em/docs/tutorial/response-status-code.md index 57c44777a3..413ceb9169 100644 --- a/docs/em/docs/tutorial/response-status-code.md +++ b/docs/em/docs/tutorial/response-status-code.md @@ -8,9 +8,7 @@ * `@app.delete()` * ♒️. -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} /// note @@ -76,9 +74,7 @@ FastAPI 💭 👉, & 🔜 🏭 🗄 🩺 👈 🇵🇸 📤 🙅‍♂ 📨 ➡️ 👀 ⏮️ 🖼 🔄: -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} `201` 👔 📟 "✍". @@ -86,15 +82,13 @@ FastAPI 💭 👉, & 🔜 🏭 🗄 🩺 👈 🇵🇸 📤 🙅‍♂ 📨 👆 💪 ⚙️ 🏪 🔢 ⚪️➡️ `fastapi.status`. -```Python hl_lines="1 6" -{!../../../docs_src/response_status_code/tutorial002.py!} -``` +{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} 👫 🏪, 👫 🧑‍🤝‍🧑 🎏 🔢, ✋️ 👈 🌌 👆 💪 ⚙️ 👨‍🎨 📋 🔎 👫: -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette import status`. diff --git a/docs/em/docs/tutorial/schema-extra-example.md b/docs/em/docs/tutorial/schema-extra-example.md index 8562de09cf..1bd314c513 100644 --- a/docs/em/docs/tutorial/schema-extra-example.md +++ b/docs/em/docs/tutorial/schema-extra-example.md @@ -8,21 +8,7 @@ 👆 💪 📣 `example` Pydantic 🏷 ⚙️ `Config` & `schema_extra`, 🔬 Pydantic 🩺: 🔗 🛃: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="15-23" -{!> ../../../docs_src/schema_extra_example/tutorial001.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="13-21" -{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial001.py hl[15:23] *} 👈 ➕ ℹ 🔜 🚮-🔢 **🎻 🔗** 👈 🏷, & ⚫️ 🔜 ⚙️ 🛠️ 🩺. @@ -40,21 +26,7 @@ 👆 💪 ⚙️ 👉 🚮 `example` 🔠 🏑: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="4 10-13" -{!> ../../../docs_src/schema_extra_example/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="2 8-11" -{!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial002.py hl[4,10:13] *} /// warning @@ -80,21 +52,7 @@ 📥 👥 🚶‍♀️ `example` 📊 ⌛ `Body()`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="20-25" -{!> ../../../docs_src/schema_extra_example/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="18-23" -{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial003.py hl[20:25] *} ### 🖼 🩺 🎚 @@ -115,21 +73,7 @@ * `value`: 👉 ☑ 🖼 🎦, ✅ `dict`. * `externalValue`: 🎛 `value`, 📛 ☝ 🖼. 👐 👉 5️⃣📆 🚫 🐕‍🦺 📚 🧰 `value`. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="21-47" -{!> ../../../docs_src/schema_extra_example/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="19-45" -{!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial004.py hl[21:47] *} ### 🖼 🩺 🎚 diff --git a/docs/em/docs/tutorial/security/first-steps.md b/docs/em/docs/tutorial/security/first-steps.md index 538ea7b0a6..8fb459a651 100644 --- a/docs/em/docs/tutorial/security/first-steps.md +++ b/docs/em/docs/tutorial/security/first-steps.md @@ -20,9 +20,7 @@ 📁 🖼 📁 `main.py`: -```Python -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py *} ## 🏃 ⚫️ @@ -56,7 +54,7 @@ $ uvicorn main:app --reload -/// check | "✔ 🔼 ❗" +/// check | ✔ 🔼 ❗ 👆 ⏪ ✔️ ✨ 🆕 "✔" 🔼. @@ -128,9 +126,7 @@ Oauth2️⃣ 🔧 👈 👩‍💻 ⚖️ 🛠️ 💪 🔬 💽 👈 🔓 👩 🕐❔ 👥 ✍ 👐 `OAuth2PasswordBearer` 🎓 👥 🚶‍♀️ `tokenUrl` 🔢. 👉 🔢 🔌 📛 👈 👩‍💻 (🕸 🏃 👩‍💻 🖥) 🔜 ⚙️ 📨 `username` & `password` ✔ 🤚 🤝. -```Python hl_lines="6" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[6] *} /// tip @@ -168,15 +164,13 @@ oauth2_scheme(some, parameters) 🔜 👆 💪 🚶‍♀️ 👈 `oauth2_scheme` 🔗 ⏮️ `Depends`. -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} 👉 🔗 🔜 🚚 `str` 👈 🛠️ 🔢 `token` *➡ 🛠️ 🔢*. **FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 👉 🔗 🔬 "💂‍♂ ⚖" 🗄 🔗 (& 🏧 🛠️ 🩺). -/// info | "📡 ℹ" +/// info | 📡 ℹ **FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 🎓 `OAuth2PasswordBearer` (📣 🔗) 🔬 💂‍♂ ⚖ 🗄 ↩️ ⚫️ 😖 ⚪️➡️ `fastapi.security.oauth2.OAuth2`, ❔ 🔄 😖 ⚪️➡️ `fastapi.security.base.SecurityBase`. diff --git a/docs/em/docs/tutorial/security/get-current-user.md b/docs/em/docs/tutorial/security/get-current-user.md index 15545f4278..2f4a26f352 100644 --- a/docs/em/docs/tutorial/security/get-current-user.md +++ b/docs/em/docs/tutorial/security/get-current-user.md @@ -2,9 +2,7 @@ ⏮️ 📃 💂‍♂ ⚙️ (❔ 🧢 🔛 🔗 💉 ⚙️) 🤝 *➡ 🛠️ 🔢* `token` `str`: -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} ✋️ 👈 🚫 👈 ⚠. @@ -16,21 +14,7 @@ 🎏 🌌 👥 ⚙️ Pydantic 📣 💪, 👥 💪 ⚙️ ⚫️ 🙆 🙆: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="5 12-16" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="3 10-14" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[5,12:16] *} ## ✍ `get_current_user` 🔗 @@ -42,61 +26,19 @@ 🎏 👥 🔨 ⏭ *➡ 🛠️* 🔗, 👆 🆕 🔗 `get_current_user` 🔜 📨 `token` `str` ⚪️➡️ 🎧-🔗 `oauth2_scheme`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="25" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="23" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[25] *} ## 🤚 👩‍💻 `get_current_user` 🔜 ⚙️ (❌) 🚙 🔢 👥 ✍, 👈 ✊ 🤝 `str` & 📨 👆 Pydantic `User` 🏷: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="19-22 26-27" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="17-20 24-25" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *} ## 💉 ⏮️ 👩‍💻 🔜 👥 💪 ⚙️ 🎏 `Depends` ⏮️ 👆 `get_current_user` *➡ 🛠️*: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="31" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="29" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[31] *} 👀 👈 👥 📣 🆎 `current_user` Pydantic 🏷 `User`. @@ -150,21 +92,7 @@ & 🌐 👉 💯 *➡ 🛠️* 💪 🤪 3️⃣ ⏸: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="30-32" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="28-30" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[30:32] *} ## 🌃 diff --git a/docs/em/docs/tutorial/security/oauth2-jwt.md b/docs/em/docs/tutorial/security/oauth2-jwt.md index 3ab8cc9867..ee7bc2d28a 100644 --- a/docs/em/docs/tutorial/security/oauth2-jwt.md +++ b/docs/em/docs/tutorial/security/oauth2-jwt.md @@ -118,21 +118,7 @@ $ pip install "passlib[bcrypt]" & ➕1️⃣ 1️⃣ 🔓 & 📨 👩‍💻. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="7 48 55-56 59-60 69-75" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="6 47 54-55 58-59 68-74" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial004.py hl[7,48,55:56,59:60,69:75] *} /// note @@ -168,21 +154,7 @@ $ openssl rand -hex 32 ✍ 🚙 🔢 🏗 🆕 🔐 🤝. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="6 12-14 28-30 78-86" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="5 11-13 27-29 77-85" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial004.py hl[6,12:14,28:30,78:86] *} ## ℹ 🔗 @@ -192,21 +164,7 @@ $ openssl rand -hex 32 🚥 🤝 ❌, 📨 🇺🇸🔍 ❌ ▶️️ ↖️. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="89-106" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="88-105" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial004.py hl[89:106] *} ## ℹ `/token` *➡ 🛠️* @@ -214,21 +172,7 @@ $ openssl rand -hex 32 ✍ 🎰 🥙 🔐 🤝 & 📨 ⚫️. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="115-130" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="114-129" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial004.py hl[115:130] *} ### 📡 ℹ 🔃 🥙 "📄" `sub` diff --git a/docs/em/docs/tutorial/security/simple-oauth2.md b/docs/em/docs/tutorial/security/simple-oauth2.md index 937546be88..1fd513d48d 100644 --- a/docs/em/docs/tutorial/security/simple-oauth2.md +++ b/docs/em/docs/tutorial/security/simple-oauth2.md @@ -52,21 +52,7 @@ Oauth2️⃣ 👫 🎻. 🥇, 🗄 `OAuth2PasswordRequestForm`, & ⚙️ ⚫️ 🔗 ⏮️ `Depends` *➡ 🛠️* `/token`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="4 76" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="2 74" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003.py hl[4,76] *} `OAuth2PasswordRequestForm` 🎓 🔗 👈 📣 📨 💪 ⏮️: @@ -114,21 +100,7 @@ Oauth2️⃣ 🔌 🤙 *🚚* 🏑 `grant_type` ⏮️ 🔧 💲 `password`, ✋ ❌, 👥 ⚙️ ⚠ `HTTPException`: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3 77-79" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1 75-77" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003.py hl[3,77:79] *} ### ✅ 🔐 @@ -154,21 +126,7 @@ Oauth2️⃣ 🔌 🤙 *🚚* 🏑 `grant_type` ⏮️ 🔧 💲 `password`, ✋ , 🧙‍♀ 🏆 🚫 💪 🔄 ⚙️ 👈 🎏 🔐 ➕1️⃣ ⚙️ (📚 👩‍💻 ⚙️ 🎏 🔐 🌐, 👉 🔜 ⚠). -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="80-83" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="78-81" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003.py hl[80:83] *} #### 🔃 `**user_dict` @@ -210,21 +168,7 @@ UserInDB( /// -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="85" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="83" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003.py hl[85] *} /// tip @@ -250,21 +194,7 @@ UserInDB( , 👆 🔗, 👥 🔜 🕴 🤚 👩‍💻 🚥 👩‍💻 🔀, ☑ 🔓, & 🦁: -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="58-66 69-72 90" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="55-64 67-70 88" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *} /// info diff --git a/docs/em/docs/tutorial/sql-databases.md b/docs/em/docs/tutorial/sql-databases.md deleted file mode 100644 index 2492c87087..0000000000 --- a/docs/em/docs/tutorial/sql-databases.md +++ /dev/null @@ -1,900 +0,0 @@ -# 🗄 (🔗) 💽 - -**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 *🏷*, 🖼, ⚫️ 🏆 🚫 📨 ⚪️➡️ 🛠️ 🕐❔ 👂 👩‍💻. - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="3 6-8 11-12 23-24 27-28" -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="3 6-8 11-12 23-24 27-28" -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | 🐍 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`. - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="15-17 31-34" -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="15-17 31-34" -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | 🐍 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`. - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="15 19-20 31 36-37" -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="15 19-20 31 36-37" -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | 🐍 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` ➡️ 🛠️ & ⚙️ 🌐 🎏 🍕 👥 ✍ ⏭. - -### ✍ 💽 🏓 - -📶 🙃 🌌 ✍ 💽 🏓: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="9" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -//// tab | 🐍 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` 👈 🔜 ⚙️ 👁 📨, & ⤴️ 🔐 ⚫️ 🕐 📨 🏁. - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="15-20" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -//// tab | 🐍 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#yield-httpexception){.internal-link target=_blank} - -/// - -& ⤴️, 🕐❔ ⚙️ 🔗 *➡ 🛠️ 🔢*, 👥 📣 ⚫️ ⏮️ 🆎 `Session` 👥 🗄 🔗 ⚪️➡️ 🇸🇲. - -👉 🔜 ⤴️ 🤝 👥 👍 👨‍🎨 🐕‍🦺 🔘 *➡ 🛠️ 🔢*, ↩️ 👨‍🎨 🔜 💭 👈 `db` 🔢 🆎 `Session`: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="24 32 38 47 53" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -//// tab | 🐍 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** *➡ 🛠️* 📟. - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="23-28 31-34 37-42 45-49 52-55" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -//// tab | 🐍 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#i_2){.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`: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | 🐍 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`: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -//// tab | 🐍 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` 🔠 📨, 🚮 ⚫️ 📨 & ⤴️ 🔐 ⚫️ 🕐 📨 🏁. - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="14-22" -{!> ../../../docs_src/sql_databases/sql_app/alt_main.py!} -``` - -//// - -//// tab | 🐍 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 index 3305746c29..27685c06d9 100644 --- a/docs/em/docs/tutorial/static-files.md +++ b/docs/em/docs/tutorial/static-files.md @@ -7,11 +7,9 @@ * 🗄 `StaticFiles`. * "🗻" `StaticFiles()` 👐 🎯 ➡. -```Python hl_lines="2 6" -{!../../../docs_src/static_files/tutorial001.py!} -``` +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.staticfiles import StaticFiles`. @@ -39,4 +37,4 @@ ## 🌅 ℹ -🌖 ℹ & 🎛 ✅ 💃 🩺 🔃 🎻 📁. +🌖 ℹ & 🎛 ✅ 💃 🩺 🔃 🎻 📁. diff --git a/docs/em/docs/tutorial/testing.md b/docs/em/docs/tutorial/testing.md index 75dd2d2958..2e4a531f7d 100644 --- a/docs/em/docs/tutorial/testing.md +++ b/docs/em/docs/tutorial/testing.md @@ -1,6 +1,6 @@ # 🔬 -👏 💃, 🔬 **FastAPI** 🈸 ⏩ & 😌. +👏 💃, 🔬 **FastAPI** 🈸 ⏩ & 😌. ⚫️ ⚓️ 🔛 🇸🇲, ❔ 🔄 🏗 ⚓️ 🔛 📨, ⚫️ 📶 😰 & 🏋️. @@ -26,9 +26,7 @@ ✍ 🙅 `assert` 📄 ⏮️ 🐩 🐍 🧬 👈 👆 💪 ✅ (🔄, 🐩 `pytest`). -```Python hl_lines="2 12 15-18" -{!../../../docs_src/app_testing/tutorial001.py!} -``` +{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} /// tip @@ -40,7 +38,7 @@ /// -/// note | "📡 ℹ" +/// note | 📡 ℹ 👆 💪 ⚙️ `from starlette.testclient import TestClient`. @@ -74,9 +72,7 @@ 📁 `main.py` 👆 ✔️ 👆 **FastAPI** 📱: -```Python -{!../../../docs_src/app_testing/main.py!} -``` +{* ../../docs_src/app_testing/main.py *} ### 🔬 📁 @@ -92,9 +88,7 @@ ↩️ 👉 📁 🎏 📦, 👆 💪 ⚙️ ⚖ 🗄 🗄 🎚 `app` ⚪️➡️ `main` 🕹 (`main.py`): -```Python hl_lines="3" -{!../../../docs_src/app_testing/test_main.py!} -``` +{* ../../docs_src/app_testing/test_main.py hl[3] *} ...& ✔️ 📟 💯 💖 ⏭. @@ -122,29 +116,13 @@ 👯‍♂️ *➡ 🛠️* 🚚 `X-Token` 🎚. -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python -{!> ../../../docs_src/app_testing/app_b/main.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python -{!> ../../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// +{* ../../docs_src/app_testing/app_b/main.py *} ### ↔ 🔬 📁 👆 💪 ⤴️ ℹ `test_main.py` ⏮️ ↔ 💯: -```Python -{!> ../../../docs_src/app_testing/app_b/test_main.py!} -``` +{* ../../docs_src/app_testing/app_b/test_main.py *} 🕐❔ 👆 💪 👩‍💻 🚶‍♀️ ℹ 📨 & 👆 🚫 💭 ❔, 👆 💪 🔎 (🇺🇸🔍) ❔ ⚫️ `httpx`, ⚖️ ❔ ⚫️ ⏮️ `requests`, 🇸🇲 🔧 ⚓️ 🔛 📨' 🔧. diff --git a/docs/en/data/contributors.yml b/docs/en/data/contributors.yml new file mode 100644 index 0000000000..c892d8baf0 --- /dev/null +++ b/docs/en/data/contributors.yml @@ -0,0 +1,565 @@ +tiangolo: + login: tiangolo + count: 782 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +dependabot: + login: dependabot + count: 117 + avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4 + url: https://github.com/apps/dependabot +alejsdev: + login: alejsdev + count: 52 + avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=447d12a1b347f466b35378bee4c7104cc9b2c571&v=4 + url: https://github.com/alejsdev +pre-commit-ci: + login: pre-commit-ci + count: 45 + avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4 + url: https://github.com/apps/pre-commit-ci +github-actions: + login: github-actions + count: 26 + avatarUrl: https://avatars.githubusercontent.com/in/15368?v=4 + url: https://github.com/apps/github-actions +Kludex: + login: Kludex + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex +dmontagu: + login: dmontagu + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 + url: https://github.com/dmontagu +euri10: + login: euri10 + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 + url: https://github.com/euri10 +nilslindemann: + login: nilslindemann + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 + url: https://github.com/nilslindemann +kantandane: + login: kantandane + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/3978368?u=cccc199291f991a73b1ebba5abc735a948e0bd16&v=4 + url: https://github.com/kantandane +svlandeg: + login: svlandeg + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg +zhaohan-dong: + login: zhaohan-dong + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/65422392?u=8260f8781f50248410ebfa4c9bf70e143fe5c9f2&v=4 + url: https://github.com/zhaohan-dong +YuriiMotov: + login: YuriiMotov + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov +mariacamilagl: + login: mariacamilagl + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 + url: https://github.com/mariacamilagl +handabaldeep: + login: handabaldeep + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/12239103?u=6c39ef15d14c6d5211f5dd775cc4842f8d7f2f3a&v=4 + url: https://github.com/handabaldeep +vishnuvskvkl: + login: vishnuvskvkl + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/84698110?u=8af5de0520dd4fa195f53c2850a26f57c0f6bc64&v=4 + url: https://github.com/vishnuvskvkl +alissadb: + login: alissadb + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/96190409?u=be42d85938c241be781505a5a872575be28b2906&v=4 + url: https://github.com/alissadb +alv2017: + login: alv2017 + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +wshayes: + login: wshayes + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 + url: https://github.com/wshayes +samuelcolvin: + login: samuelcolvin + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4 + url: https://github.com/samuelcolvin +waynerv: + login: waynerv + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 + url: https://github.com/waynerv +krishnamadhavan: + login: krishnamadhavan + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/31798870?u=950693b28f3ae01105fd545c046e46ca3d31ab06&v=4 + url: https://github.com/krishnamadhavan +jekirl: + login: jekirl + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4 + url: https://github.com/jekirl +hitrust: + login: hitrust + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/3360631?u=5fa1f475ad784d64eb9666bdd43cc4d285dcc773&v=4 + url: https://github.com/hitrust +ShahriyarR: + login: ShahriyarR + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/3852029?u=631b2ae59360ab380c524b32bc3d245aff1165af&v=4 + url: https://github.com/ShahriyarR +adriangb: + login: adriangb + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 + url: https://github.com/adriangb +iudeen: + login: iudeen + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=f09cdd745e5bf16138f29b42732dd57c7f02bee1&v=4 + url: https://github.com/iudeen +musicinmybrain: + login: musicinmybrain + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/6898909?u=9010312053e7141383b9bdf538036c7f37fbaba0&v=4 + url: https://github.com/musicinmybrain +philipokiokio: + login: philipokiokio + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/55271518?u=d30994d339aaaf1f6bf1b8fc810132016fbd4fdc&v=4 + url: https://github.com/philipokiokio +AlexWendland: + login: AlexWendland + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/3949212?u=c4c0c615e0ea33d00bfe16b779cf6ebc0f58071c&v=4 + url: https://github.com/AlexWendland +divums: + login: divums + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1397556?v=4 + url: https://github.com/divums +prostomarkeloff: + login: prostomarkeloff + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=6918e39a1224194ba636e897461a02a20126d7ad&v=4 + url: https://github.com/prostomarkeloff +nsidnev: + login: nsidnev + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4 + url: https://github.com/nsidnev +pawamoy: + login: pawamoy + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4 + url: https://github.com/pawamoy +patrickmckenna: + login: patrickmckenna + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/3589536?u=53aef07250d226d35e526768e26891964907b41a&v=4 + url: https://github.com/patrickmckenna +hukkin: + login: hukkin + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/3275109?u=77bb83759127965eacbfe67e2ca983066e964fde&v=4 + url: https://github.com/hukkin +marcosmmb: + login: marcosmmb + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/6181089?u=03c50eec631857d84df5232890780d00a3f76903&v=4 + url: https://github.com/marcosmmb +Serrones: + login: Serrones + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 + url: https://github.com/Serrones +uriyyo: + login: uriyyo + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/32038156?u=0c68019beb28381ce5205a838937c61e0fe3fee2&v=4 + url: https://github.com/uriyyo +andrew222651: + login: andrew222651 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/889657?u=d70187989940b085bcbfa3bedad8dbc5f3ab1fe7&v=4 + url: https://github.com/andrew222651 +rkbeatss: + login: rkbeatss + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/23391143?u=56ab6bff50be950fa8cae5cf736f2ae66e319ff3&v=4 + url: https://github.com/rkbeatss +asheux: + login: asheux + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/22955146?u=4553ebf5b5a7c7fe031a46182083aa224faba2e1&v=4 + url: https://github.com/asheux +blkst8: + login: blkst8 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=7d8a6d5f0a75a5e9a865a2527edfd48895ea27ae&v=4 + url: https://github.com/blkst8 +ghandic: + login: ghandic + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 + url: https://github.com/ghandic +TeoZosa: + login: TeoZosa + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/13070236?u=96fdae85800ef85dcfcc4b5f8281dc8778c8cb7d&v=4 + url: https://github.com/TeoZosa +graingert: + login: graingert + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4 + url: https://github.com/graingert +jaystone776: + login: jaystone776 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 + url: https://github.com/jaystone776 +zanieb: + login: zanieb + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/2586601?u=e5c86f7ff3b859e7e183187ac2b17fd6ee32b3ab&v=4 + url: https://github.com/zanieb +MicaelJarniac: + login: MicaelJarniac + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/19514231?u=158c91874ea98d6e9e6f0c6db37ee2ce60c55ff2&v=4 + url: https://github.com/MicaelJarniac +papb: + login: papb + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/20914054?u=890511fae7ea90d887e2a65ce44a1775abba38d5&v=4 + url: https://github.com/papb +tamird: + login: tamird + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1535036?v=4 + url: https://github.com/tamird +Nimitha-jagadeesha: + login: Nimitha-jagadeesha + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/58389915?v=4 + url: https://github.com/Nimitha-jagadeesha +lucaromagnoli: + login: lucaromagnoli + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/38782977?u=e66396859f493b4ddcb3a837a1b2b2039c805417&v=4 + url: https://github.com/lucaromagnoli +salmantec: + login: salmantec + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/41512228?u=443551b893ff2425c59d5d021644f098cf7c68d5&v=4 + url: https://github.com/salmantec +OCE1960: + login: OCE1960 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/45076670?u=0e9a44712b92ffa89ddfbaa83c112f3f8e1d68e2&v=4 + url: https://github.com/OCE1960 +hamidrasti: + login: hamidrasti + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/43915620?v=4 + url: https://github.com/hamidrasti +valentinDruzhinin: + login: valentinDruzhinin + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +kkinder: + login: kkinder + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1115018?u=c5e90284a9f5c5049eae1bb029e3655c7dc913ed&v=4 + url: https://github.com/kkinder +kabirkhan: + login: kabirkhan + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/13891834?u=e0eabf792376443ac853e7dca6f550db4166fe35&v=4 + url: https://github.com/kabirkhan +zamiramir: + login: zamiramir + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/40475662?v=4 + url: https://github.com/zamiramir +trim21: + login: trim21 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/13553903?u=3cadf0f02095c9621aa29df6875f53a80ca4fbfb&v=4 + url: https://github.com/trim21 +koxudaxi: + login: koxudaxi + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4 + url: https://github.com/koxudaxi +pablogamboa: + login: pablogamboa + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/12892536?u=326a57059ee0c40c4eb1b38413957236841c631b&v=4 + url: https://github.com/pablogamboa +dconathan: + login: dconathan + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/15098095?v=4 + url: https://github.com/dconathan +Jamim: + login: Jamim + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/5607572?u=9ce0b6a6d1a5124e28b3c04d8d26827ca328713a&v=4 + url: https://github.com/Jamim +svalouch: + login: svalouch + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/54674660?v=4 + url: https://github.com/svalouch +frankie567: + login: frankie567 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=f3e79acfe4ed207e15c2145161a8a9759925fcd2&v=4 + url: https://github.com/frankie567 +marier-nico: + login: marier-nico + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/30477068?u=c7df6af853c8f4163d1517814f3e9a0715c82713&v=4 + url: https://github.com/marier-nico +Dustyposa: + login: Dustyposa + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 + url: https://github.com/Dustyposa +aviramha: + login: aviramha + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/41201924?u=6883cc4fc13a7b2e60d4deddd4be06f9c5287880&v=4 + url: https://github.com/aviramha +iwpnd: + login: iwpnd + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6152183?u=ec59396e9437fff488791c5ecdf6d23f1f1ebf3a&v=4 + url: https://github.com/iwpnd +raphaelauv: + login: raphaelauv + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 + url: https://github.com/raphaelauv +windson: + login: windson + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1826682?u=8b28dcd716c46289f191f8828e01d74edd058bef&v=4 + url: https://github.com/windson +sm-Fifteen: + login: sm-Fifteen + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 + url: https://github.com/sm-Fifteen +sattosan: + login: sattosan + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4 + url: https://github.com/sattosan +michaeloliverx: + login: michaeloliverx + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/55017335?u=efb0cb6e261ff64d862fafb91ee80fc2e1f8a2ed&v=4 + url: https://github.com/michaeloliverx +voegtlel: + login: voegtlel + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/5764745?u=db8df3d70d427928ab6d7dbfc395a4a7109c1d1b&v=4 + url: https://github.com/voegtlel +HarshaLaxman: + login: HarshaLaxman + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/19939186?u=a112f38b0f6b4d4402dc8b51978b5a0b2e5c5970&v=4 + url: https://github.com/HarshaLaxman +RunningIkkyu: + login: RunningIkkyu + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 + url: https://github.com/RunningIkkyu +cassiobotaro: + login: cassiobotaro + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4 + url: https://github.com/cassiobotaro +chenl: + login: chenl + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1677651?u=c618508eaad6d596cea36c8ea784b424288f6857&v=4 + url: https://github.com/chenl +retnikt: + login: retnikt + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 + url: https://github.com/retnikt +yankeexe: + login: yankeexe + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/13623913?u=f970e66421775a8d3cdab89c0c752eaead186f6d&v=4 + url: https://github.com/yankeexe +patrickkwang: + login: patrickkwang + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1263870?u=4bf74020e15be490f19ef8322a76eec882220b96&v=4 + url: https://github.com/patrickkwang +victorphoenix3: + login: victorphoenix3 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/48182195?u=e4875bd088623cb4ddeb7be194ec54b453aff035&v=4 + url: https://github.com/victorphoenix3 +davidefiocco: + login: davidefiocco + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4547987?v=4 + url: https://github.com/davidefiocco +adriencaccia: + login: adriencaccia + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/19605940?u=9a59081f46bfc9d839886a49d5092cf572879049&v=4 + url: https://github.com/adriencaccia +jamescurtin: + login: jamescurtin + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/10189269?u=0b491fc600ca51f41cf1d95b49fa32a3eba1de57&v=4 + url: https://github.com/jamescurtin +jmriebold: + login: jmriebold + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6983392?u=4efdc97bf2422dcc7e9ff65b9ff80087c8eb2a20&v=4 + url: https://github.com/jmriebold +nukopy: + login: nukopy + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/42367320?u=6061be0bd060506f6d564a8df3ae73fab048cdfe&v=4 + url: https://github.com/nukopy +imba-tjd: + login: imba-tjd + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/24759802?u=01e901a4fe004b4b126549d3ff1c4000fe3720b5&v=4 + url: https://github.com/imba-tjd +johnthagen: + login: johnthagen + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/10340167?u=47147fc4e4db1f573bee3fe428deeacb3197bc5f&v=4 + url: https://github.com/johnthagen +paxcodes: + login: paxcodes + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/13646646?u=e7429cc7ab11211ef762f4cd3efea7db6d9ef036&v=4 + url: https://github.com/paxcodes +kaustubhgupta: + login: kaustubhgupta + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/43691873?u=8dd738718ac7ffad4ef31e86b5d780a1141c695d&v=4 + url: https://github.com/kaustubhgupta +kinuax: + login: kinuax + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/13321374?u=22dc9873d6d9f2c7e4fc44c6480c3505efb1531f&v=4 + url: https://github.com/kinuax +wakabame: + login: wakabame + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/35513518?u=41ef6b0a55076e5c540620d68fb006e386c2ddb0&v=4 + url: https://github.com/wakabame +nzig: + login: nzig + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7372858?u=e769add36ed73c778cdb136eb10bf96b1e119671&v=4 + url: https://github.com/nzig +yezz123: + login: yezz123 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=21b53ce4115062b1e20cb513e64ca0000c2ef127&v=4 + url: https://github.com/yezz123 +softwarebloat: + login: softwarebloat + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/16540684?v=4 + url: https://github.com/softwarebloat +Lancetnik: + login: Lancetnik + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/44573917?u=6eaa0cdd35259fba40a76b82e4903440cba03fa9&v=4 + url: https://github.com/Lancetnik +joakimnordling: + login: joakimnordling + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6637576?u=df5d99db9b899b399effd429f4358baaa6f7199c&v=4 + url: https://github.com/joakimnordling +yogabonito: + login: yogabonito + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7026269?v=4 + url: https://github.com/yogabonito +s111d: + login: s111d + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4954856?v=4 + url: https://github.com/s111d +estebanx64: + login: estebanx64 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 + url: https://github.com/estebanx64 +ndimares: + login: ndimares + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6267663?u=cfb27efde7a7212be8142abb6c058a1aeadb41b1&v=4 + url: https://github.com/ndimares +rabinlamadong: + login: rabinlamadong + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/170439781?v=4 + url: https://github.com/rabinlamadong +AyushSinghal1794: + login: AyushSinghal1794 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/89984761?v=4 + url: https://github.com/AyushSinghal1794 +gsheni: + login: gsheni + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/8726321?u=ee3bd9ff6320f4715d1dd9671a3d55cccb65b984&v=4 + url: https://github.com/gsheni +chailandau: + login: chailandau + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/112015853?u=2e6aaf2b1647db43834aabeae8d8282b4ec01873&v=4 + url: https://github.com/chailandau +DanielKusyDev: + login: DanielKusyDev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/36250676?u=2ea6114ff751fc48b55f231987a0e2582c6b1bd2&v=4 + url: https://github.com/DanielKusyDev +DanielYang59: + login: DanielYang59 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/80093591?u=63873f701c7c74aac83c906800a1dddc0bc8c92f&v=4 + url: https://github.com/DanielYang59 +blueswen: + login: blueswen + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1564148?u=6d6b8cc8f2b5cef715e68d6175154a8a94d518ee&v=4 + url: https://github.com/blueswen +Taoup: + login: Taoup + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/22348542?v=4 + url: https://github.com/Taoup diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml index 15f6169eef..b8a5fdb3a0 100644 --- a/docs/en/data/external_links.yml +++ b/docs/en/data/external_links.yml @@ -1,5 +1,13 @@ Articles: English: + - author: Apitally + author_link: https://apitally.io + link: https://apitally.io/blog/getting-started-with-logging-in-fastapi + title: Getting started with logging in FastAPI + - author: Balthazar Rouberol + author_link: https://balthazar-rouberol.com + link: https://blog.balthazar-rouberol.com/how-to-profile-a-fastapi-asynchronous-request + title: How to profile a FastAPI asynchronous request - author: Stephen Siegert - Neon link: https://neon.tech/blog/deploy-a-serverless-fastapi-app-with-neon-postgres-and-aws-app-runner-at-any-scale title: Deploy a Serverless FastAPI App with Neon Postgres and AWS App Runner at any scale @@ -117,11 +125,11 @@ Articles: link: https://valonjanuzaj.medium.com/deploy-a-dockerized-fastapi-application-to-aws-cc757830ba1b title: Deploy a dockerized FastAPI application to AWS - author: Amit Chaudhary - author_link: https://twitter.com/amitness + author_link: https://x.com/amitness link: https://amitness.com/2020/06/fastapi-vs-flask/ title: FastAPI for Flask Users - author: Louis Guitton - author_link: https://twitter.com/louis_guitton + author_link: https://x.com/louis_guitton link: https://guitton.co/posts/fastapi-monitoring/ title: How to monitor your FastAPI service - author: Precious Ndubueze @@ -145,7 +153,7 @@ Articles: link: https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072 title: Introducing Dispatch - author: Stavros Korokithakis - author_link: https://twitter.com/Stavros + author_link: https://x.com/Stavros link: https://www.stavros.io/posts/fastapi-with-django/ title: Using FastAPI with Django - author: Twilio @@ -153,11 +161,11 @@ Articles: link: https://www.twilio.com/blog/build-secure-twilio-webhook-python-fastapi title: Build a Secure Twilio Webhook with Python and FastAPI - author: Sebastián Ramírez (tiangolo) - author_link: https://twitter.com/tiangolo + author_link: https://x.com/tiangolo link: https://dev.to/tiangolo/build-a-web-api-from-scratch-with-fastapi-the-workshop-2ehe title: Build a web API from scratch with FastAPI - the workshop - author: Paul Sec - author_link: https://twitter.com/PaulWebSec + author_link: https://x.com/PaulWebSec link: https://paulsec.github.io/posts/fastapi_plus_zeit_serverless_fu/ title: FastAPI + Zeit.co = 🚀 - author: cuongld2 @@ -165,7 +173,7 @@ Articles: link: https://dev.to/cuongld2/build-simple-api-service-with-python-fastapi-part-1-581o title: Build simple API service with Python FastAPI — Part 1 - author: Paurakh Sharma Humagain - author_link: https://twitter.com/PaurakhSharma + author_link: https://x.com/PaurakhSharma link: https://dev.to/paurakhsharma/microservice-in-python-using-fastapi-24cc title: Microservice in Python using FastAPI - author: Guillermo Cruz @@ -177,7 +185,7 @@ Articles: link: https://www.tutlinks.com/create-and-deploy-fastapi-app-to-heroku/ title: Create and Deploy FastAPI app to Heroku without using Docker - author: Arthur Henrique - author_link: https://twitter.com/arthurheinrique + author_link: https://x.com/arthurheinrique link: https://medium.com/@arthur393/another-boilerplate-to-fastapi-azure-pipeline-ci-pytest-3c8d9a4be0bb title: 'Another Boilerplate to FastAPI: Azure Pipeline CI + Pytest' - author: Shane Soh @@ -217,7 +225,7 @@ Articles: link: https://towardsdatascience.com/how-to-deploy-a-machine-learning-model-dc51200fe8cf title: How to Deploy a Machine Learning Model - author: Johannes Gontrum - author_link: https://twitter.com/gntrm + author_link: https://x.com/gntrm link: https://medium.com/@gntrm/jwt-authentication-with-fastapi-and-aws-cognito-1333f7f2729e title: JWT Authentication with FastAPI and AWS Cognito - author: Ankush Thakur @@ -253,7 +261,7 @@ Articles: link: https://medium.com/@williamhayes/fastapi-starlette-debug-vs-prod-5f7561db3a59 title: FastAPI/Starlette debug vs prod - author: Mukul Mantosh - author_link: https://twitter.com/MantoshMukul + author_link: https://x.com/MantoshMukul link: https://www.jetbrains.com/pycharm/guide/tutorials/fastapi-aws-kubernetes/ title: Developing FastAPI Application using K8s & AWS - author: KrishNa @@ -264,13 +272,21 @@ Articles: author_link: https://devonray.com link: https://devonray.com/blog/deploying-a-fastapi-project-using-aws-lambda-aurora-cdk title: Deployment using Docker, Lambda, Aurora, CDK & GH Actions + - author: Shubhendra Kushwaha + author_link: https://www.linkedin.com/in/theshubhendra/ + link: https://theshubhendra.medium.com/mastering-soft-delete-advanced-sqlalchemy-techniques-4678f4738947 + title: 'Mastering Soft Delete: Advanced SQLAlchemy Techniques' + - author: Shubhendra Kushwaha + author_link: https://www.linkedin.com/in/theshubhendra/ + link: https://theshubhendra.medium.com/role-based-row-filtering-advanced-sqlalchemy-techniques-733e6b1328f6 + title: 'Role based row filtering: Advanced SQLAlchemy Techniques' 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 + author_link: https://x.com/_nicoax link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/ title: Inbetriebnahme eines scikit-learn-Modells mit ONNX und FastAPI - author: Felix Schürmeyer @@ -327,6 +343,10 @@ Articles: link: https://qiita.com/mtitg/items/47770e9a562dd150631d title: FastAPI|DB接続してCRUDするPython製APIサーバーを構築 Portuguese: + - author: Eduardo Mendes + author_link: https://bolha.us/@dunossauro + link: https://fastapidozero.dunossauro.com/ + title: FastAPI do ZERO - author: Jessica Temporal author_link: https://jtemporal.com/socials link: https://jtemporal.com/dicas-para-migrar-de-flask-para-fastapi-e-vice-versa/ @@ -384,14 +404,19 @@ Talks: link: https://www.youtube.com/watch?v=uZdTe8_Z6BQ title: 'PyCon AU 2023: Testing asynchronous applications with FastAPI and pytest' - author: Sebastián Ramírez (tiangolo) - author_link: https://twitter.com/tiangolo + author_link: https://x.com/tiangolo link: https://www.youtube.com/watch?v=PnpTY1f4k2U title: '[VIRTUAL] Py.Amsterdam''s flying Software Circus: Intro to FastAPI' - author: Sebastián Ramírez (tiangolo) - author_link: https://twitter.com/tiangolo + author_link: https://x.com/tiangolo link: https://www.youtube.com/watch?v=z9K5pwb0rt8 title: 'PyConBY 2020: Serve ML models easily with FastAPI' - author: Chris Withers - author_link: https://twitter.com/chriswithers13 + author_link: https://x.com/chriswithers13 link: https://www.youtube.com/watch?v=3DLwPcrE5mA title: 'PyCon UK 2019: FastAPI from the ground up' + Taiwanese: + - author: Blueswen + author_link: https://github.com/blueswen + link: https://www.youtube.com/watch?v=y3sumuoDq4w + title: 'PyCon TW 2024: 全方位強化 Python 服務可觀測性:以 FastAPI 和 Grafana Stack 為例' diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml index 5f0be61c22..7b34719b68 100644 --- a/docs/en/data/github_sponsors.yml +++ b/docs/en/data/github_sponsors.yml @@ -1,58 +1,58 @@ sponsors: -- - login: bump-sh - avatarUrl: https://avatars.githubusercontent.com/u/33217836?v=4 - url: https://github.com/bump-sh - - login: porter-dev - avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4 - url: https://github.com/porter-dev +- - login: renderinc + avatarUrl: https://avatars.githubusercontent.com/u/36424661?v=4 + url: https://github.com/renderinc - login: andrew-propelauth - avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=1188c27cb744bbec36447a2cfd4453126b2ddb5c&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=c98993dec8553c09d424ede67bbe86e5c35f48c9&v=4 url: https://github.com/andrew-propelauth - - login: zanfaruqui - avatarUrl: https://avatars.githubusercontent.com/u/104461687?v=4 - url: https://github.com/zanfaruqui - - login: Alek99 - avatarUrl: https://avatars.githubusercontent.com/u/38776361?u=bd6c163fe787c2de1a26c881598e54b67e2482dd&v=4 - url: https://github.com/Alek99 - - login: cryptapi - avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4 - url: https://github.com/cryptapi - - login: Kong - avatarUrl: https://avatars.githubusercontent.com/u/962416?v=4 - url: https://github.com/Kong - - login: codacy - avatarUrl: https://avatars.githubusercontent.com/u/1834093?v=4 - url: https://github.com/codacy + - login: blockbee-io + avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4 + url: https://github.com/blockbee-io + - login: zuplo + avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4 + url: https://github.com/zuplo + - login: coderabbitai + avatarUrl: https://avatars.githubusercontent.com/u/132028505?v=4 + url: https://github.com/coderabbitai + - login: subtotal + avatarUrl: https://avatars.githubusercontent.com/u/176449348?v=4 + url: https://github.com/subtotal + - login: railwayapp + avatarUrl: https://avatars.githubusercontent.com/u/66716858?v=4 + url: https://github.com/railwayapp - login: scalar avatarUrl: https://avatars.githubusercontent.com/u/301879?v=4 url: https://github.com/scalar -- - login: ObliviousAI - avatarUrl: https://avatars.githubusercontent.com/u/65656077?v=4 - url: https://github.com/ObliviousAI -- - login: databento - avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4 - url: https://github.com/databento +- - login: dribia + avatarUrl: https://avatars.githubusercontent.com/u/41189616?v=4 + url: https://github.com/dribia - login: svix avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4 url: https://github.com/svix - - login: deepset-ai - avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4 - url: https://github.com/deepset-ai - - login: mikeckennedy - avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=ce6165b799ea3164cb6f5ff54ea08042057442af&v=4 - url: https://github.com/mikeckennedy - - login: ndimares - avatarUrl: https://avatars.githubusercontent.com/u/6267663?u=cfb27efde7a7212be8142abb6c058a1aeadb41b1&v=4 - url: https://github.com/ndimares -- - login: takashi-yoneya - avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4 - url: https://github.com/takashi-yoneya - - login: xoflare - avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4 - url: https://github.com/xoflare - - login: marvin-robot + - login: stainless-api + avatarUrl: https://avatars.githubusercontent.com/u/88061651?v=4 + url: https://github.com/stainless-api + - login: speakeasy-api + avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4 + url: https://github.com/speakeasy-api + - login: databento + avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4 + url: https://github.com/databento + - login: permitio + avatarUrl: https://avatars.githubusercontent.com/u/71775833?v=4 + url: https://github.com/permitio +- - login: marvin-robot avatarUrl: https://avatars.githubusercontent.com/u/41086007?u=b9fcab402d0cd0aec738b6574fe60855cb0cd36d&v=4 url: https://github.com/marvin-robot + - login: mercedes-benz + avatarUrl: https://avatars.githubusercontent.com/u/34240465?v=4 + url: https://github.com/mercedes-benz + - login: Ponte-Energy-Partners + avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4 + url: https://github.com/Ponte-Energy-Partners + - login: LambdaTest-Inc + avatarUrl: https://avatars.githubusercontent.com/u/171592363?u=96606606a45fa170427206199014f2a5a2a4920b&v=4 + url: https://github.com/LambdaTest-Inc - login: BoostryJP avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4 url: https://github.com/BoostryJP @@ -62,50 +62,50 @@ sponsors: - - login: Trivie avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4 url: https://github.com/Trivie -- - login: americanair - avatarUrl: https://avatars.githubusercontent.com/u/12281813?v=4 - url: https://github.com/americanair - - login: CanoaPBC - avatarUrl: https://avatars.githubusercontent.com/u/64223768?v=4 - url: https://github.com/CanoaPBC - - login: mainframeindustries +- - login: takashi-yoneya + avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4 + url: https://github.com/takashi-yoneya + - login: Doist + avatarUrl: https://avatars.githubusercontent.com/u/2565372?v=4 + url: https://github.com/Doist +- - login: mainframeindustries avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4 url: https://github.com/mainframeindustries - - login: mangualero - avatarUrl: https://avatars.githubusercontent.com/u/3422968?u=c59272d7b5a912d6126fd6c6f17db71e20f506eb&v=4 - url: https://github.com/mangualero - - login: birkjernstrom - avatarUrl: https://avatars.githubusercontent.com/u/281715?u=4be14b43f76b4bd497b1941309bb390250b405e6&v=4 - url: https://github.com/birkjernstrom - login: yasyf avatarUrl: https://avatars.githubusercontent.com/u/709645?u=f36736b3c6a85f578886ecc42a740e7b436e7a01&v=4 url: https://github.com/yasyf +- - login: alixlahuec + avatarUrl: https://avatars.githubusercontent.com/u/29543316?u=44357eb2a93bccf30fb9d389b8befe94a3d00985&v=4 + url: https://github.com/alixlahuec - - login: primer-io avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4 url: https://github.com/primer-io - - login: povilasb - avatarUrl: https://avatars.githubusercontent.com/u/1213442?u=b11f58ed6ceea6e8297c9b310030478ebdac894d&v=4 - url: https://github.com/povilasb -- - login: jhundman - avatarUrl: https://avatars.githubusercontent.com/u/24263908?v=4 - url: https://github.com/jhundman +- - login: nilslindemann + avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 + url: https://github.com/nilslindemann - login: upciti avatarUrl: https://avatars.githubusercontent.com/u/43346262?v=4 url: https://github.com/upciti + - login: thisisfixer + avatarUrl: https://avatars.githubusercontent.com/u/14433035?u=076d52a5b7891c764904af9f462bfb45428e25df&v=4 + url: https://github.com/thisisfixer - - login: samuelcolvin avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4 url: https://github.com/samuelcolvin - - login: Kludex - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex - - login: b-rad-c - avatarUrl: https://avatars.githubusercontent.com/u/25362581?u=5bb10629f4015b62bec1f9a366675d5085551af9&v=4 - url: https://github.com/b-rad-c + - login: otosky + avatarUrl: https://avatars.githubusercontent.com/u/42260747?u=69d089387c743d89427aa4ad8740cfb34045a9e0&v=4 + url: https://github.com/otosky + - login: ramonalmeidam + avatarUrl: https://avatars.githubusercontent.com/u/45269580?u=3358750b3a5854d7c3ed77aaca7dd20a0f529d32&v=4 + url: https://github.com/ramonalmeidam + - login: roboflow + avatarUrl: https://avatars.githubusercontent.com/u/53104118?v=4 + url: https://github.com/roboflow - login: ehaca avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4 url: https://github.com/ehaca - login: raphaellaude - avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=9ae4b158c0d2cb29ebd46df6b6edb7de08a67566&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=91e1c00d9ac4f8045527e13de8050d504531cbc0&v=4 url: https://github.com/raphaellaude - login: timlrx avatarUrl: https://avatars.githubusercontent.com/u/28362229?u=9a745ca31372ee324af682715ae88ce8522f9094&v=4 @@ -113,99 +113,39 @@ sponsors: - 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: ProteinQure - avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4 - url: https://github.com/ProteinQure - - login: catherinenelson1 - avatarUrl: https://avatars.githubusercontent.com/u/11951946?u=e714b957185b8cf3d301cced7fc3ad2842122c6a&v=4 - url: https://github.com/catherinenelson1 - - login: jsoques - avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4 - url: https://github.com/jsoques - - login: joeds13 - avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4 - url: https://github.com/joeds13 - - login: dannywade - avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4 - url: https://github.com/dannywade - - login: khadrawy - avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4 - url: https://github.com/khadrawy - - login: mjohnsey - avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4 - url: https://github.com/mjohnsey - - login: ashi-agrawal - avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4 - url: https://github.com/ashi-agrawal - - login: sepsi77 - avatarUrl: https://avatars.githubusercontent.com/u/18682303?v=4 - url: https://github.com/sepsi77 - - login: wedwardbeck - avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4 - url: https://github.com/wedwardbeck - - login: RaamEEIL - avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4 - url: https://github.com/RaamEEIL - - login: anthonycepeda - avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 - url: https://github.com/anthonycepeda - - login: patricioperezv - avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4 - url: https://github.com/patricioperezv - login: kaoru0310 avatarUrl: https://avatars.githubusercontent.com/u/80977929?u=1b61d10142b490e56af932ddf08a390fae8ee94f&v=4 url: https://github.com/kaoru0310 - login: DelfinaCare avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4 url: https://github.com/DelfinaCare - - login: Eruditis - avatarUrl: https://avatars.githubusercontent.com/u/95244703?v=4 - url: https://github.com/Eruditis + - login: Karine-Bauch + avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4 + url: https://github.com/Karine-Bauch - login: jugeeem avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=ae590d79c38ac79c91b9c5caa6887d061e865a3d&v=4 url: https://github.com/jugeeem - - login: apitally - avatarUrl: https://avatars.githubusercontent.com/u/138365043?v=4 - url: https://github.com/apitally - - login: logic-automation - avatarUrl: https://avatars.githubusercontent.com/u/144732884?v=4 - url: https://github.com/logic-automation - - login: ddilidili - avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4 - url: https://github.com/ddilidili - - login: ramonalmeidam - avatarUrl: https://avatars.githubusercontent.com/u/45269580?u=3358750b3a5854d7c3ed77aaca7dd20a0f529d32&v=4 - url: https://github.com/ramonalmeidam - login: dudikbender avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=3a57542938ebfd57579a0111db2b297e606d9681&v=4 url: https://github.com/dudikbender - - login: prodhype - avatarUrl: https://avatars.githubusercontent.com/u/60444672?u=3f278cff25ea37ead487d7861d4a984795de819e&v=4 - url: https://github.com/prodhype - login: patsatsia avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4 url: https://github.com/patsatsia - - login: tcsmith - avatarUrl: https://avatars.githubusercontent.com/u/989034?u=7d8d741552b3279e8f4d3878679823a705a46f8f&v=4 - url: https://github.com/tcsmith + - login: anthonycepeda + avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 + url: https://github.com/anthonycepeda + - login: patricioperezv + avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4 + url: https://github.com/patricioperezv + - login: chickenandstats + avatarUrl: https://avatars.githubusercontent.com/u/79477966?u=ae2b894aa954070db1d7830dab99b49eba4e4567&v=4 + url: https://github.com/chickenandstats - login: dodo5522 avatarUrl: https://avatars.githubusercontent.com/u/1362607?u=9bf1e0e520cccc547c046610c468ce6115bbcf9f&v=4 url: https://github.com/dodo5522 - - login: nihpo - avatarUrl: https://avatars.githubusercontent.com/u/1841030?u=0264956d7580f7e46687a762a7baa629f84cf97c&v=4 - url: https://github.com/nihpo - login: knallgelb avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4 url: https://github.com/knallgelb - - login: johannquerne - avatarUrl: https://avatars.githubusercontent.com/u/2736484?u=9b3381546a25679913a2b08110e4373c98840821&v=4 - url: https://github.com/johannquerne - - login: Shark009 - avatarUrl: https://avatars.githubusercontent.com/u/3163309?u=0c6f4091b0eda05c44c390466199826e6dc6e431&v=4 - url: https://github.com/Shark009 - login: dblackrun avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4 url: https://github.com/dblackrun @@ -215,27 +155,36 @@ sponsors: - login: kennywakeland avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4 url: https://github.com/kennywakeland - - login: simw - avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4 - url: https://github.com/simw - - login: koconder - avatarUrl: https://avatars.githubusercontent.com/u/25068?u=582657b23622aaa3dfe68bd028a780f272f456fa&v=4 - url: https://github.com/koconder + - login: aacayaco + avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4 + url: https://github.com/aacayaco + - login: anomaly + avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4 + url: https://github.com/anomaly + - login: mj0331 + avatarUrl: https://avatars.githubusercontent.com/u/3890353?u=1c627ac1a024515b4871de5c3ebbfaa1a57f65d4&v=4 + url: https://github.com/mj0331 + - login: gorhack + avatarUrl: https://avatars.githubusercontent.com/u/4141690?u=ec119ebc4bdf00a7bc84657a71aa17834f4f27f3&v=4 + url: https://github.com/gorhack + - login: Ryandaydev + avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=679ff84cb7b988c5795a5fa583857f574a055763&v=4 + url: https://github.com/Ryandaydev + - login: vincentkoc + avatarUrl: https://avatars.githubusercontent.com/u/25068?u=fbd5b2d51142daa4bdbc21e21953a3b8b8188a4a&v=4 + url: https://github.com/vincentkoc - login: jstanden avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4 url: https://github.com/jstanden - - login: andreaso - avatarUrl: https://avatars.githubusercontent.com/u/285964?u=837265cc7562c0685f25b2d81cd9de0434fe107c&v=4 - url: https://github.com/andreaso + - login: paulcwatts + avatarUrl: https://avatars.githubusercontent.com/u/150269?u=1819e145d573b44f0ad74b87206d21cd60331d4e&v=4 + url: https://github.com/paulcwatts - login: robintw avatarUrl: https://avatars.githubusercontent.com/u/296686?v=4 url: https://github.com/robintw - 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 - login: wshayes avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 url: https://github.com/wshayes @@ -248,39 +197,33 @@ sponsors: - login: mintuhouse avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4 url: https://github.com/mintuhouse - - login: Rehket - avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4 - url: https://github.com/Rehket - - login: hiancdtrsnm - avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4 - url: https://github.com/hiancdtrsnm - - login: TrevorBenson - avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=afdd1766fdb79e04e59094cc6a54cd011ee7f686&v=4 - url: https://github.com/TrevorBenson - login: wdwinslow - avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=371272f2c69e680e0559a7b0a57385e83a5dc728&v=4 url: https://github.com/wdwinslow - - login: aacayaco - avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4 - url: https://github.com/aacayaco - - login: anomaly - avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4 - url: https://github.com/anomaly - - login: jgreys - avatarUrl: https://avatars.githubusercontent.com/u/4136890?u=096820d1ef89877d57d0f68e669ead8b0fde84df&v=4 - url: https://github.com/jgreys - - login: Ryandaydev - avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=48f68868db8886fce31a1d802c1003914c6cd7c6&v=4 - url: https://github.com/Ryandaydev + - login: jsoques + avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4 + url: https://github.com/jsoques + - login: dannywade + avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4 + url: https://github.com/dannywade + - login: khadrawy + avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4 + url: https://github.com/khadrawy + - login: mjohnsey + avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4 + url: https://github.com/mjohnsey + - login: ashi-agrawal + avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4 + url: https://github.com/ashi-agrawal + - login: RaamEEIL + avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4 + url: https://github.com/RaamEEIL - 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: ennui93 - avatarUrl: https://avatars.githubusercontent.com/u/5300907?u=5b5452725ddb391b2caaebf34e05aba873591c3a&v=4 - url: https://github.com/ennui93 - login: ternaus avatarUrl: https://avatars.githubusercontent.com/u/5481618?u=513a26b02a39e7a28d587cd37c6cc877ea368e6e&v=4 url: https://github.com/ternaus @@ -288,17 +231,35 @@ sponsors: avatarUrl: https://avatars.githubusercontent.com/u/5920492?u=208d419cf667b8ac594c82a8db01932c7e50d057&v=4 url: https://github.com/eseglem - login: FernandoCelmer - avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=d29fff3fd862fda4ca752079f13f32e84c762ea4&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=58ba6d5888fa7f355934e52db19f950e20b38162&v=4 url: https://github.com/FernandoCelmer -- - login: getsentry - avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4 - url: https://github.com/getsentry + - login: Rehket + avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4 + url: https://github.com/Rehket + - login: hiancdtrsnm + avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4 + url: https://github.com/hiancdtrsnm +- - login: manoelpqueiroz + avatarUrl: https://avatars.githubusercontent.com/u/23669137?u=b12e84b28a84369ab5b30bd5a79e5788df5a0756&v=4 + url: https://github.com/manoelpqueiroz - - login: pawamoy avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4 url: https://github.com/pawamoy - - login: SebTota - avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4 - url: https://github.com/SebTota + - login: siavashyj + avatarUrl: https://avatars.githubusercontent.com/u/43583410?u=562005ddc7901cd27a1219a118a2363817b14977&v=4 + url: https://github.com/siavashyj + - login: mobyw + avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4 + url: https://github.com/mobyw + - login: ArtyomVancyan + avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4 + url: https://github.com/ArtyomVancyan + - login: caviri + avatarUrl: https://avatars.githubusercontent.com/u/45425937?u=5f3d66ea5edea94c028c51ebf1c0f3b37e6c3db5&v=4 + url: https://github.com/caviri + - login: hgalytoby + avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=6cc9028f3db63f8f60ad21c17b1ce4b88c4e2e60&v=4 + url: https://github.com/hgalytoby - login: nisutec avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4 url: https://github.com/nisutec @@ -308,108 +269,42 @@ sponsors: - login: joerambo avatarUrl: https://avatars.githubusercontent.com/u/26282974?v=4 url: https://github.com/joerambo - - login: rlnchow - avatarUrl: https://avatars.githubusercontent.com/u/28018479?u=a93ca9cf1422b9ece155784a72d5f2fdbce7adff&v=4 - url: https://github.com/rlnchow - login: engineerjoe440 avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4 url: https://github.com/engineerjoe440 - login: bnkc avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4 url: https://github.com/bnkc - - login: DevOpsKev - avatarUrl: https://avatars.githubusercontent.com/u/36336550?u=6ccd5978fdaab06f37e22f2a14a7439341df7f67&v=4 - url: https://github.com/DevOpsKev - login: petercool - avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=81c525232bb35780945a68e88afd96bb2cdad9c4&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=75aa8c6729e6e8f85a300561c4dbeef9d65c8797&v=4 url: https://github.com/petercool - - login: JimFawkes - avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4 - url: https://github.com/JimFawkes - - login: artempronevskiy - avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4 - url: https://github.com/artempronevskiy - - login: TheR1D - avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4 - url: https://github.com/TheR1D - - login: joshuatz - avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4 - url: https://github.com/joshuatz - - login: jangia - avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4 - url: https://github.com/jangia - - login: jackleeio - avatarUrl: https://avatars.githubusercontent.com/u/20477587?u=c5184dab6d021733d10c8f975b20e391856303d6&v=4 - url: https://github.com/jackleeio - - login: shuheng-liu - avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4 - url: https://github.com/shuheng-liu - - login: pers0n4 - avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=f211a13a7b572cbbd7779b9c8d8cb428cc7ba07e&v=4 - url: https://github.com/pers0n4 - - login: curegit - avatarUrl: https://avatars.githubusercontent.com/u/37978051?u=1733c322079118c0cdc573c03d92813f50a9faec&v=4 - url: https://github.com/curegit - - login: fernandosmither - avatarUrl: https://avatars.githubusercontent.com/u/66154723?u=f79753eb207d01cca5bbb91ac62db6123e7622d1&v=4 - url: https://github.com/fernandosmither + - login: JulioPeixoto + avatarUrl: https://avatars.githubusercontent.com/u/96303574?u=27d4614350cae33653f1be35cb47c92a12627ac9&v=4 + url: https://github.com/JulioPeixoto + - login: johnl28 + avatarUrl: https://avatars.githubusercontent.com/u/54412955?u=47dd06082d1c39caa90c752eb55566e4f3813957&v=4 + url: https://github.com/johnl28 - login: PunRabbit avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4 url: https://github.com/PunRabbit - login: PelicanQ avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4 url: https://github.com/PelicanQ - - login: tahmarrrr23 - avatarUrl: https://avatars.githubusercontent.com/u/138208610?u=465a46b0ff72a74252d3e3a71ac7d2f1919cda28&v=4 - url: https://github.com/tahmarrrr23 - - login: zk-Call - avatarUrl: https://avatars.githubusercontent.com/u/147117264?v=4 - url: https://github.com/zk-Call - - login: kristiangronberg - avatarUrl: https://avatars.githubusercontent.com/u/42678548?v=4 - url: https://github.com/kristiangronberg - - login: leonardo-holguin - avatarUrl: https://avatars.githubusercontent.com/u/43093055?u=b59013d52fb6c4e0954aaaabc0882bd844985b38&v=4 - url: https://github.com/leonardo-holguin - - login: arrrrrmin - avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=36a3880a6eb29309c19e6cadbb173bafbe91deb1&v=4 - url: https://github.com/arrrrrmin - - login: mobyw - avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4 - url: https://github.com/mobyw - - login: ArtyomVancyan - avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4 - url: https://github.com/ArtyomVancyan - - login: harol97 - avatarUrl: https://avatars.githubusercontent.com/u/49042862?u=2b18e115ab73f5f09a280be2850f93c58a12e3d2&v=4 - url: https://github.com/harol97 - - login: hgalytoby - avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=62c7ff3519858423579676cd0efbd7e3f1ffe63a&v=4 - url: https://github.com/hgalytoby - - login: conservative-dude - avatarUrl: https://avatars.githubusercontent.com/u/55538308?u=f250c44942ea6e73a6bd90739b381c470c192c11&v=4 - url: https://github.com/conservative-dude - - login: Joaopcamposs - avatarUrl: https://avatars.githubusercontent.com/u/57376574?u=699d5ba5ee66af1d089df6b5e532b97169e73650&v=4 - url: https://github.com/Joaopcamposs - - login: browniebroke - avatarUrl: https://avatars.githubusercontent.com/u/861044?u=5abfca5588f3e906b31583d7ee62f6de4b68aa24&v=4 - url: https://github.com/browniebroke - login: miguelgr avatarUrl: https://avatars.githubusercontent.com/u/1484589?u=54556072b8136efa12ae3b6902032ea2a39ace4b&v=4 url: https://github.com/miguelgr - login: WillHogan - avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=8a80356e3e7d5a417157aba7ea565dabc8678327&v=4 url: https://github.com/WillHogan - login: my3 avatarUrl: https://avatars.githubusercontent.com/u/1825270?v=4 url: https://github.com/my3 - - login: leobiscassi - avatarUrl: https://avatars.githubusercontent.com/u/1977418?u=f9f82445a847ab479bd7223debd677fcac6c49a0&v=4 - url: https://github.com/leobiscassi - - login: cbonoz - avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4 - url: https://github.com/cbonoz + - login: Alisa-lisa + avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4 + url: https://github.com/Alisa-lisa + - login: moonape1226 + avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4 + url: https://github.com/moonape1226 - login: ddanier avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4 url: https://github.com/ddanier @@ -419,21 +314,48 @@ sponsors: - login: slafs avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 url: https://github.com/slafs - - login: adamghill - avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4 - url: https://github.com/adamghill - - login: eteq - avatarUrl: https://avatars.githubusercontent.com/u/346587?v=4 - url: https://github.com/eteq - - login: dmig - avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4 - url: https://github.com/dmig - - login: securancy - avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4 - url: https://github.com/securancy + - login: ceb10n + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n - login: tochikuji avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4 url: https://github.com/tochikuji + - login: xncbf + avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4 + url: https://github.com/xncbf + - login: DMantis + avatarUrl: https://avatars.githubusercontent.com/u/9536869?u=652dd0d49717803c0cbcbf44f7740e53cf2d4892&v=4 + url: https://github.com/DMantis + - 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: mntolia + avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4 + url: https://github.com/mntolia + - login: Zuzah + avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4 + url: https://github.com/Zuzah + - login: TheR1D + avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4 + url: https://github.com/TheR1D + - login: joshuatz + avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4 + url: https://github.com/joshuatz + - login: danielunderwood + avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4 + url: https://github.com/danielunderwood + - login: rangulvers + avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4 + url: https://github.com/rangulvers + - login: sdevkota + avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4 + url: https://github.com/sdevkota + - 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 @@ -443,90 +365,48 @@ sponsors: - login: harsh183 avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4 url: https://github.com/harsh183 - - login: hcristea - avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4 - url: https://github.com/hcristea - - login: moonape1226 - avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4 - url: https://github.com/moonape1226 - - login: msehnout - avatarUrl: https://avatars.githubusercontent.com/u/9369632?u=8c988f1b008a3f601385a3616f9327820f66e3a5&v=4 - url: https://github.com/msehnout - - login: xncbf - avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=2ef1ede118a72c170805f50b9ad07341fd16a354&v=4 - url: https://github.com/xncbf - - login: DMantis - avatarUrl: https://avatars.githubusercontent.com/u/9536869?v=4 - url: https://github.com/DMantis - - 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: dzoladz - avatarUrl: https://avatars.githubusercontent.com/u/10561752?u=5ee314d54aa79592c18566827ad8914debd5630d&v=4 - url: https://github.com/dzoladz - - login: Zuzah - avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4 - url: https://github.com/Zuzah - - login: Alisa-lisa - avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4 - url: https://github.com/Alisa-lisa - - login: Graeme22 - avatarUrl: https://avatars.githubusercontent.com/u/4185684?u=498182a42300d7bcd4de1215190cb17eb501136c&v=4 - url: https://github.com/Graeme22 - - login: danielunderwood - avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4 - url: https://github.com/danielunderwood - - login: rangulvers - avatarUrl: https://avatars.githubusercontent.com/u/5235430?v=4 - url: https://github.com/rangulvers - - login: sdevkota - avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4 - url: https://github.com/sdevkota - - login: brizzbuzz - avatarUrl: https://avatars.githubusercontent.com/u/5607577?u=58d5aae33bc97e52f11f334d2702e8710314b5c1&v=4 - url: https://github.com/brizzbuzz - - login: Baghdady92 - avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4 - url: https://github.com/Baghdady92 - - login: jakeecolution - avatarUrl: https://avatars.githubusercontent.com/u/5884696?u=4a7c7883fb064b593b50cb6697b54687e6f7aafe&v=4 - url: https://github.com/jakeecolution - - login: stephane-rbn - avatarUrl: https://avatars.githubusercontent.com/u/5939522?u=eb7ffe768fa3bcbcd04de14fe4a47444cc00ec4c&v=4 - url: https://github.com/stephane-rbn -- - login: danburonline - avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=94935cccfbec58083ab1e535212d54f1bf2c978a&v=4 - url: https://github.com/danburonline - - login: AliYmn - avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=0de5a262e8b4dc0a08d065f30f7a39941e246530&v=4 - url: https://github.com/AliYmn - - login: sadikkuzu - avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4 - url: https://github.com/sadikkuzu - - login: tran-hai-long - avatarUrl: https://avatars.githubusercontent.com/u/119793901?u=3b173a845dcf099b275bdc9713a69cbbc36040ce&v=4 - url: https://github.com/tran-hai-long +- - login: KOZ39 + avatarUrl: https://avatars.githubusercontent.com/u/38822500?u=9dfc0a697df1c9628f08e20dc3fb17b1afc4e5a7&v=4 + url: https://github.com/KOZ39 - login: rwxd avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4 url: https://github.com/rwxd - - login: ssbarnea - avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c2efbf6fea2737e21dfc6b1113c4edc9644e9eaa&v=4 - url: https://github.com/ssbarnea - - login: yuawn - avatarUrl: https://avatars.githubusercontent.com/u/5111198?u=5315576f3fe1a70fd2d0f02181588f4eea5d353d&v=4 - url: https://github.com/yuawn - - login: dongzhenye - avatarUrl: https://avatars.githubusercontent.com/u/5765843?u=fe420c9a4c41e5b060faaf44029f5485616b470d&v=4 - url: https://github.com/dongzhenye + - login: morzan1001 + avatarUrl: https://avatars.githubusercontent.com/u/47593005?u=c30ab7230f82a12a9b938dcb54f84a996931409a&v=4 + url: https://github.com/morzan1001 + - login: azharthegeek + avatarUrl: https://avatars.githubusercontent.com/u/51288109?u=0987b2a9f39c21ccb071b6bdce0fc60d8492f8e8&v=4 + url: https://github.com/azharthegeek + - login: Olegt0rr + avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4 + url: https://github.com/Olegt0rr + - login: larsyngvelundin + avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4 + url: https://github.com/larsyngvelundin + - login: andrecorumba + avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4 + url: https://github.com/andrecorumba + - login: ChenPu2002 + avatarUrl: https://avatars.githubusercontent.com/u/113831763?v=4 + url: https://github.com/ChenPu2002 + - login: CoderDeltaLAN + avatarUrl: https://avatars.githubusercontent.com/u/152043745?u=4ff541efffb7d134e60c5fcf2dd1e343f90bb782&v=4 + url: https://github.com/CoderDeltaLAN + - login: aghents + avatarUrl: https://avatars.githubusercontent.com/u/60949885?u=d8616ddf22cf998a712cdceefd6a0256a178fe9d&v=4 + url: https://github.com/aghents + - login: 0ne-stone + avatarUrl: https://avatars.githubusercontent.com/u/62360849?u=746dd21c34e7e06eefb11b03e8bb01aaae3c2a4f&v=4 + url: https://github.com/0ne-stone + - login: nayasinghania + avatarUrl: https://avatars.githubusercontent.com/u/74111380?u=752e99a5e139389fdc0a0677122adc08438eb076&v=4 + url: https://github.com/nayasinghania + - login: Toothwitch + avatarUrl: https://avatars.githubusercontent.com/u/1710406?u=5eebb23b46cd26e48643b9e5179536cad491c17a&v=4 + url: https://github.com/Toothwitch + - login: andreagrandi + avatarUrl: https://avatars.githubusercontent.com/u/636391?u=13d90cb8ec313593a5b71fbd4e33b78d6da736f5&v=4 + url: https://github.com/andreagrandi + - login: msserpa + avatarUrl: https://avatars.githubusercontent.com/u/6334934?u=82c4489eb1559d88d2990d60001901b14f722bbb&v=4 + url: https://github.com/msserpa diff --git a/docs/en/data/members.yml b/docs/en/data/members.yml index 0069f8c75c..7ec16e917e 100644 --- a/docs/en/data/members.yml +++ b/docs/en/data/members.yml @@ -11,9 +11,12 @@ members: - login: svlandeg avatar_url: https://avatars.githubusercontent.com/u/8796347 url: https://github.com/svlandeg -- login: estebanx64 - avatar_url: https://avatars.githubusercontent.com/u/10840422 - url: https://github.com/estebanx64 +- login: YuriiMotov + avatar_url: https://avatars.githubusercontent.com/u/109919500 + url: https://github.com/YuriiMotov - login: patrick91 avatar_url: https://avatars.githubusercontent.com/u/667029 url: https://github.com/patrick91 +- login: luzzodev + avatar_url: https://avatars.githubusercontent.com/u/27291415 + url: https://github.com/luzzodev diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 02d1779e05..2fdb21a059 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,32 +1,43 @@ maintainers: - login: tiangolo - answers: 1885 - prs: 577 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4 + answers: 1900 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo experts: +- login: tiangolo + count: 1900 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: YuriiMotov + count: 971 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov +- login: github-actions + count: 769 + avatarUrl: https://avatars.githubusercontent.com/in/15368?v=4 + url: https://github.com/apps/github-actions - login: Kludex - count: 608 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 + count: 654 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex -- login: dmontagu - count: 241 - avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 - url: https://github.com/dmontagu - login: jgould22 - count: 241 + count: 263 avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 url: https://github.com/jgould22 +- login: dmontagu + count: 240 + avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 + url: https://github.com/dmontagu - login: Mause - count: 220 + count: 219 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=29682e4b6ac7d5293742ccf818188394b9a82972&v=4 + count: 216 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 url: https://github.com/ycd - login: JarroVGIT - count: 193 + count: 190 avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 url: https://github.com/JarroVGIT - login: euri10 @@ -35,16 +46,20 @@ experts: url: https://github.com/euri10 - login: iudeen count: 128 - avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=f09cdd745e5bf16138f29b42732dd57c7f02bee1&v=4 url: https://github.com/iudeen - login: phy25 count: 126 avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 url: https://github.com/phy25 -- login: YuriiMotov - count: 104 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 - url: https://github.com/YuriiMotov +- login: JavierSanchezCastro + count: 94 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: luzzodev + count: 89 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev - login: raphaelauv count: 83 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 @@ -57,42 +72,38 @@ experts: count: 71 avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 url: https://github.com/ghandic -- login: JavierSanchezCastro - count: 64 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro +- login: n8sty + count: 67 + avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 + url: https://github.com/n8sty - login: falkben count: 59 avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 url: https://github.com/falkben -- login: n8sty - count: 56 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty -- login: acidjunk - count: 50 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk - login: yinziyan1206 - count: 49 + count: 54 avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 url: https://github.com/yinziyan1206 - login: sm-Fifteen count: 49 avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 url: https://github.com/sm-Fifteen -- login: insomnes - count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 - url: https://github.com/insomnes +- login: acidjunk + count: 49 + avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 + url: https://github.com/acidjunk +- login: adriangb + count: 46 + avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 + url: https://github.com/adriangb - login: Dustyposa count: 45 avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 url: https://github.com/Dustyposa -- login: adriangb +- login: insomnes count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 - url: https://github.com/adriangb + avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 + url: https://github.com/insomnes - login: frankie567 count: 43 avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4 @@ -101,18 +112,22 @@ experts: count: 43 avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&v=4 url: https://github.com/odiseo0 +- login: sinisaos + count: 41 + avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 + url: https://github.com/sinisaos - login: includeamin count: 40 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 url: https://github.com/includeamin -- login: chbndrhnns - count: 38 - avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 - url: https://github.com/chbndrhnns - login: STeveShary count: 37 avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 url: https://github.com/STeveShary +- login: chbndrhnns + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 + url: https://github.com/chbndrhnns - login: krishnardt count: 35 avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 @@ -123,28 +138,36 @@ experts: url: https://github.com/panla - login: prostomarkeloff count: 28 - avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=6918e39a1224194ba636e897461a02a20126d7ad&v=4 url: https://github.com/prostomarkeloff - login: hasansezertasan count: 27 avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 url: https://github.com/hasansezertasan +- login: alv2017 + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 - login: dbanty count: 26 - avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9d726785d08e50b1e1cd96505800c8ea8405bce2&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: acnebs - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/9054108?v=4 - url: https://github.com/acnebs +- login: valentinDruzhinin + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin - login: SirTelemak count: 23 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 url: https://github.com/SirTelemak +- login: connebs + count: 22 + avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=e151d5f545a3395136d711c227c22032fda67cfa&v=4 + url: https://github.com/connebs - login: nymous count: 22 avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 @@ -161,14 +184,22 @@ experts: count: 20 avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4 url: https://github.com/nsidnev -- login: ebottos94 - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/100039558?u=e2c672da5a7977fd24d87ce6ab35f8bf5b1ed9fa&v=4 - url: https://github.com/ebottos94 - login: chris-allnutt count: 20 avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4 url: https://github.com/chris-allnutt +- login: ebottos94 + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/100039558?u=8b91053b3abe4a9209375e3651e1c1ef192d884b&v=4 + url: https://github.com/ebottos94 +- login: estebanx64 + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 + url: https://github.com/estebanx64 +- login: sehraramiz + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4 + url: https://github.com/sehraramiz - login: retnikt count: 18 avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 @@ -177,10 +208,10 @@ experts: count: 18 avatarUrl: https://avatars.githubusercontent.com/u/22326718?u=31ba446ac290e23e56eea8e4f0c558aaf0b40779&v=4 url: https://github.com/zoliknemet -- login: nkhitrov +- login: caeser1996 count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4 - url: https://github.com/nkhitrov + avatarUrl: https://avatars.githubusercontent.com/u/16540232?u=05d2beb8e034d584d0a374b99d8826327bd7f614&v=4 + url: https://github.com/caeser1996 - login: Hultner count: 17 avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4 @@ -189,1170 +220,483 @@ experts: count: 17 avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4 url: https://github.com/harunyasar -- login: caeser1996 +- login: nkhitrov count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/16540232?u=05d2beb8e034d584d0a374b99d8826327bd7f614&v=4 - url: https://github.com/caeser1996 + avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=e19427d8dc296d6950e9c424adacc92d37496fe9&v=4 + url: https://github.com/nkhitrov - login: dstlny count: 16 avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 url: https://github.com/dstlny -last_month_experts: -- login: YuriiMotov - count: 29 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 - url: https://github.com/YuriiMotov -- login: killjoy1221 - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=723662989f2027755e67d200137c13c53ae154ac&v=4 - url: https://github.com/killjoy1221 -- login: Kludex - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex -- login: JavierSanchezCastro - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: hasansezertasan - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: PhysicallyActive - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/160476156?u=7a8e44f4a43d3bba636f795bb7d9476c9233b4d8&v=4 - url: https://github.com/PhysicallyActive -- login: n8sty - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty -- login: pedroconceicao - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32837064?u=5a0e6559bc391442629a28b6923790b54deb4464&v=4 - url: https://github.com/pedroconceicao -- login: PREPONDERANCE - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 - url: https://github.com/PREPONDERANCE -- login: aanchlia - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2835374?u=3c3ed29aa8b09ccaf8d66def0ce82bc2f7e5aab6&v=4 - url: https://github.com/aanchlia -- login: 0sahil - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58521386?u=ac00b731c07c712d0baa57b8b70ac8422acf183c&v=4 - url: https://github.com/0sahil -- login: jgould22 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -three_months_experts: -- login: YuriiMotov - count: 101 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 - url: https://github.com/YuriiMotov -- login: JavierSanchezCastro - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: Kludex - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex -- login: jgould22 - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -- login: killjoy1221 - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=723662989f2027755e67d200137c13c53ae154ac&v=4 - url: https://github.com/killjoy1221 -- login: hasansezertasan - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: PhysicallyActive - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/160476156?u=7a8e44f4a43d3bba636f795bb7d9476c9233b4d8&v=4 - url: https://github.com/PhysicallyActive -- login: n8sty - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty -- login: sehraramiz - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/14166324?v=4 - url: https://github.com/sehraramiz -- login: acidjunk - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk -- login: estebanx64 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=45f015f95e1c0f06df602be4ab688d4b854cc8a8&v=4 - url: https://github.com/estebanx64 -- login: PREPONDERANCE - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 - url: https://github.com/PREPONDERANCE -- login: chrisK824 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 - url: https://github.com/chrisK824 -- login: ryanisn - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/53449841?v=4 - url: https://github.com/ryanisn - login: pythonweb2 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: omarcruzpantoja - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/15116058?u=4b64c643fad49225d854e1aaecd1ffc6f9071a1b&v=4 - url: https://github.com/omarcruzpantoja -- login: mskrip - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17459600?u=10019d5c38ae3374dd4a6743b0223e56a78d4855&v=4 - url: https://github.com/mskrip -- login: pedroconceicao - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32837064?u=5a0e6559bc391442629a28b6923790b54deb4464&v=4 - url: https://github.com/pedroconceicao -- login: Jackiexiao - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/18050469?u=a2003e21a7780477ba00bf87a9abef8af58e91d1&v=4 - url: https://github.com/Jackiexiao -- login: aanchlia - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2835374?u=3c3ed29aa8b09ccaf8d66def0ce82bc2f7e5aab6&v=4 - url: https://github.com/aanchlia -- login: moreno-p - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/164261630?v=4 - url: https://github.com/moreno-p -- login: 0sahil - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58521386?u=ac00b731c07c712d0baa57b8b70ac8422acf183c&v=4 - url: https://github.com/0sahil -- login: patrick91 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/667029?u=e35958a75ac1f99c81b4bc99e22db8cd665ae7f0&v=4 - url: https://github.com/patrick91 -- login: pprunty - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58374462?u=5736576e586429abc97a803b8bcd4a6d828b8a2f&v=4 - url: https://github.com/pprunty -- login: angely-dev - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4362224?v=4 - url: https://github.com/angely-dev -- login: mastizada - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1975818?u=0751a06d7271c8bf17cb73b1b845644ab4d2c6dc&v=4 - url: https://github.com/mastizada -- login: sm-Fifteen - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 - url: https://github.com/sm-Fifteen -- login: methane - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/199592?v=4 - url: https://github.com/methane -- login: konstantinos1981 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/39465388?v=4 - url: https://github.com/konstantinos1981 -- login: druidance - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/160344534?v=4 - url: https://github.com/druidance -- login: fabianfalon - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/3700760?u=95f69e31280b17ac22299cdcd345323b142fe0af&v=4 - url: https://github.com/fabianfalon -- login: VatsalJagani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/20964366?u=43552644be05c9107c029e26d5ab3be5a1920f45&v=4 - url: https://github.com/VatsalJagani -- login: khaledadrani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/45245894?u=49ed5056426a149a5af29d385d8bd3847101d3a4&v=4 - url: https://github.com/khaledadrani -- login: ThirVondukr - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/50728601?u=167c0bd655e52817082e50979a86d2f98f95b1a3&v=4 - url: https://github.com/ThirVondukr -six_months_experts: -- login: YuriiMotov - count: 104 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 - url: https://github.com/YuriiMotov -- login: Kludex - count: 104 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex -- login: JavierSanchezCastro - count: 40 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: jgould22 - count: 40 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -- login: hasansezertasan - count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: n8sty - count: 19 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty -- login: killjoy1221 - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=723662989f2027755e67d200137c13c53ae154ac&v=4 - url: https://github.com/killjoy1221 -- login: aanchlia - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/2835374?u=3c3ed29aa8b09ccaf8d66def0ce82bc2f7e5aab6&v=4 - url: https://github.com/aanchlia -- login: estebanx64 - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=45f015f95e1c0f06df602be4ab688d4b854cc8a8&v=4 - url: https://github.com/estebanx64 -- login: PhysicallyActive - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/160476156?u=7a8e44f4a43d3bba636f795bb7d9476c9233b4d8&v=4 - url: https://github.com/PhysicallyActive -- login: dolfinus - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=a51b39001a2e5e7529b45826980becf786de2327&v=4 - url: https://github.com/dolfinus -- login: Ventura94 - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/43103937?u=ccb837005aaf212a449c374618c4339089e2f733&v=4 - url: https://github.com/Ventura94 -- login: sehraramiz - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/14166324?v=4 - url: https://github.com/sehraramiz -- login: acidjunk - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk -- login: shashstormer - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/90090313?v=4 - url: https://github.com/shashstormer -- login: GodMoonGoodman - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/29688727?u=7b251da620d999644c37c1feeb292d033eed7ad6&v=4 - url: https://github.com/GodMoonGoodman -- login: flo-at - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/564288?v=4 - url: https://github.com/flo-at -- login: PREPONDERANCE - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 - url: https://github.com/PREPONDERANCE -- login: chrisK824 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 - url: https://github.com/chrisK824 -- login: angely-dev - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/4362224?v=4 - url: https://github.com/angely-dev -- login: fmelihh - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/99879453?u=671117dba9022db2237e3da7a39cbc2efc838db0&v=4 - url: https://github.com/fmelihh -- login: ryanisn - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/53449841?v=4 - url: https://github.com/ryanisn -- login: JoshYuJump - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/5901894?u=cdbca6296ac4cdcdf6945c112a1ce8d5342839ea&v=4 - url: https://github.com/JoshYuJump -- login: pythonweb2 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: omarcruzpantoja - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/15116058?u=4b64c643fad49225d854e1aaecd1ffc6f9071a1b&v=4 - url: https://github.com/omarcruzpantoja -- login: bogdan-coman-uv - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/92912507?v=4 - url: https://github.com/bogdan-coman-uv -- login: ahmedabdou14 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=05365b155a1ff911532e8be316acfad2e0736f98&v=4 - url: https://github.com/ahmedabdou14 -- login: mskrip - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17459600?u=10019d5c38ae3374dd4a6743b0223e56a78d4855&v=4 - url: https://github.com/mskrip -- login: leonidktoto - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/159561986?v=4 - url: https://github.com/leonidktoto -- login: pedroconceicao - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32837064?u=5a0e6559bc391442629a28b6923790b54deb4464&v=4 - url: https://github.com/pedroconceicao -- login: hwong557 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/460259?u=7d2f1b33ea5bda4d8e177ab3cb924a673d53087e&v=4 - url: https://github.com/hwong557 -- login: Jackiexiao - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/18050469?u=a2003e21a7780477ba00bf87a9abef8af58e91d1&v=4 - url: https://github.com/Jackiexiao -- login: admo1 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/14835916?v=4 - url: https://github.com/admo1 -- login: binbjz - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/8213913?u=22b68b7a0d5bf5e09c02084c0f5f53d7503114cd&v=4 - url: https://github.com/binbjz -- login: nameer - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/3931725?u=6199fb065df098fc13ac0a5e649f89672b586732&v=4 - url: https://github.com/nameer -- login: moreno-p - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/164261630?v=4 - url: https://github.com/moreno-p -- login: 0sahil - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58521386?u=ac00b731c07c712d0baa57b8b70ac8422acf183c&v=4 - url: https://github.com/0sahil -- login: nymous - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 - url: https://github.com/nymous -- login: patrick91 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/667029?u=e35958a75ac1f99c81b4bc99e22db8cd665ae7f0&v=4 - url: https://github.com/patrick91 -- login: pprunty - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58374462?u=5736576e586429abc97a803b8bcd4a6d828b8a2f&v=4 - url: https://github.com/pprunty -- login: JonnyBootsNpants - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/155071540?u=2d3a72b74a2c4c8eaacdb625c7ac850369579352&v=4 - url: https://github.com/JonnyBootsNpants -- login: richin13 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/8370058?u=8e37a4cdbc78983a5f4b4847f6d1879fb39c851c&v=4 - url: https://github.com/richin13 -- login: mastizada - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1975818?u=0751a06d7271c8bf17cb73b1b845644ab4d2c6dc&v=4 - url: https://github.com/mastizada -- login: sm-Fifteen - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 - url: https://github.com/sm-Fifteen -- login: amacfie - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/889657?u=d70187989940b085bcbfa3bedad8dbc5f3ab1fe7&v=4 - url: https://github.com/amacfie -- login: garg10may - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/8787120?u=7028d2b3a2a26534c1806eb76c7425a3fac9732f&v=4 - url: https://github.com/garg10may -- login: methane - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/199592?v=4 - url: https://github.com/methane -- login: konstantinos1981 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/39465388?v=4 - url: https://github.com/konstantinos1981 -- login: druidance - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/160344534?v=4 - url: https://github.com/druidance -one_year_experts: -- login: Kludex - count: 207 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex -- login: jgould22 - count: 118 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -- login: YuriiMotov - count: 104 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 - url: https://github.com/YuriiMotov -- login: JavierSanchezCastro - count: 59 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: n8sty - count: 40 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty -- login: hasansezertasan - count: 27 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: chrisK824 count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 - url: https://github.com/chrisK824 -- login: ahmedabdou14 - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=05365b155a1ff911532e8be316acfad2e0736f98&v=4 - url: https://github.com/ahmedabdou14 -- login: arjwilliams - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/22227620?v=4 - url: https://github.com/arjwilliams -- login: killjoy1221 - count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=723662989f2027755e67d200137c13c53ae154ac&v=4 - url: https://github.com/killjoy1221 -- login: WilliamStam - count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/182800?v=4 - url: https://github.com/WilliamStam -- login: iudeen - count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 - url: https://github.com/iudeen -- login: nymous - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 - url: https://github.com/nymous -- login: aanchlia - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/2835374?u=3c3ed29aa8b09ccaf8d66def0ce82bc2f7e5aab6&v=4 - url: https://github.com/aanchlia -- login: estebanx64 - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=45f015f95e1c0f06df602be4ab688d4b854cc8a8&v=4 - url: https://github.com/estebanx64 -- login: pythonweb2 - count: 7 avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 url: https://github.com/pythonweb2 -- login: romabozhanovgithub - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/67696229?u=e4b921eef096415300425aca249348f8abb78ad7&v=4 - url: https://github.com/romabozhanovgithub -- login: PhysicallyActive - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/160476156?u=7a8e44f4a43d3bba636f795bb7d9476c9233b4d8&v=4 - url: https://github.com/PhysicallyActive -- login: mikeedjones - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/4087139?u=cc4a242896ac2fcf88a53acfaf190d0fe0a1f0c9&v=4 - url: https://github.com/mikeedjones -- login: dolfinus - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=a51b39001a2e5e7529b45826980becf786de2327&v=4 - url: https://github.com/dolfinus -- login: ebottos94 - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/100039558?u=e2c672da5a7977fd24d87ce6ab35f8bf5b1ed9fa&v=4 - url: https://github.com/ebottos94 -- login: Ventura94 - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/43103937?u=ccb837005aaf212a449c374618c4339089e2f733&v=4 - url: https://github.com/Ventura94 -- login: White-Mask - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/31826970?u=8625355dc25ddf9c85a8b2b0b9932826c4c8f44c&v=4 - url: https://github.com/White-Mask -- login: sehraramiz - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/14166324?v=4 - url: https://github.com/sehraramiz -- login: acidjunk - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk -- login: JoshYuJump - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/5901894?u=cdbca6296ac4cdcdf6945c112a1ce8d5342839ea&v=4 - url: https://github.com/JoshYuJump -- login: alex-pobeditel-2004 - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/14791483?v=4 - url: https://github.com/alex-pobeditel-2004 -- login: shashstormer - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/90090313?v=4 - url: https://github.com/shashstormer -- login: wu-clan - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/52145145?u=f8c9e5c8c259d248e1683fedf5027b4ee08a0967&v=4 - url: https://github.com/wu-clan +- login: jonatasoli + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=f601c3f111f2148bd9244c2cb3ebbd57b592e674&v=4 + url: https://github.com/jonatasoli +- login: ghost + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4 + url: https://github.com/ghost - login: abhint - count: 5 + count: 15 avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 url: https://github.com/abhint -- login: anthonycepeda - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 - url: https://github.com/anthonycepeda -- login: GodMoonGoodman - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/29688727?u=7b251da620d999644c37c1feeb292d033eed7ad6&v=4 - url: https://github.com/GodMoonGoodman -- login: flo-at - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/564288?v=4 - url: https://github.com/flo-at +last_month_experts: +- login: YuriiMotov + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov +- login: valentinDruzhinin + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin - login: yinziyan1206 count: 4 avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 url: https://github.com/yinziyan1206 -- login: amacfie - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/889657?u=d70187989940b085bcbfa3bedad8dbc5f3ab1fe7&v=4 - url: https://github.com/amacfie -- login: commonism - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/164513?v=4 - url: https://github.com/commonism -- login: dmontagu - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 - url: https://github.com/dmontagu -- login: sanzoghenzo - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/977953?v=4 - url: https://github.com/sanzoghenzo -- login: lucasgadams - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/36425095?v=4 - url: https://github.com/lucasgadams -- login: NeilBotelho - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/39030675?u=16fea2ff90a5c67b974744528a38832a6d1bb4f7&v=4 - url: https://github.com/NeilBotelho -- login: hhartzer - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/100533792?v=4 - url: https://github.com/hhartzer -- login: binbjz - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/8213913?u=22b68b7a0d5bf5e09c02084c0f5f53d7503114cd&v=4 - url: https://github.com/binbjz -- login: PREPONDERANCE - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 - url: https://github.com/PREPONDERANCE -- login: nameer - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/3931725?u=6199fb065df098fc13ac0a5e649f89672b586732&v=4 - url: https://github.com/nameer -- login: angely-dev - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/4362224?v=4 - url: https://github.com/angely-dev -- login: fmelihh - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/99879453?u=671117dba9022db2237e3da7a39cbc2efc838db0&v=4 - url: https://github.com/fmelihh -- login: ryanisn - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/53449841?v=4 - url: https://github.com/ryanisn -- login: theobouwman - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/16098190?u=dc70db88a7a99b764c9a89a6e471e0b7ca478a35&v=4 - url: https://github.com/theobouwman -- login: methane - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/199592?v=4 - url: https://github.com/methane -top_contributors: -- login: nilslindemann - count: 130 - avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 - url: https://github.com/nilslindemann -- login: jaystone776 - count: 49 - avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 - url: https://github.com/jaystone776 -- login: waynerv - count: 25 - avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 - url: https://github.com/waynerv -- login: tokusumi - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 - url: https://github.com/tokusumi -- login: SwftAlpc - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 - url: https://github.com/SwftAlpc -- login: Kludex - count: 22 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex -- login: hasansezertasan - count: 22 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: dmontagu - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 - url: https://github.com/dmontagu -- login: Xewus - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 - url: https://github.com/Xewus -- login: euri10 - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 - url: https://github.com/euri10 -- login: mariacamilagl - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 - url: https://github.com/mariacamilagl -- login: AlertRED - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 - url: https://github.com/AlertRED -- login: Smlep - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 - url: https://github.com/Smlep -- login: alejsdev - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=9ca449ad5161af12766ddd1a22988e9b14315f5c&v=4 - url: https://github.com/alejsdev -- login: hard-coders - count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 - url: https://github.com/hard-coders -- login: KaniKim - count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/19832624?u=40f8f7f3f36d5f2365ba2ad0b40693e60958ce70&v=4 - url: https://github.com/KaniKim -- login: xzmeng - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/40202897?v=4 - url: https://github.com/xzmeng -- login: Serrones - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 - url: https://github.com/Serrones -- login: rjNemo - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 - url: https://github.com/rjNemo -- login: pablocm83 - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4 - url: https://github.com/pablocm83 -- login: RunningIkkyu - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 - url: https://github.com/RunningIkkyu -- login: Alexandrhub - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 - url: https://github.com/Alexandrhub -- login: NinaHwang - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=eee6bfe9224c71193025ab7477f4f96ceaa05c62&v=4 - url: https://github.com/NinaHwang -- login: batlopes - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/33462923?u=0fb3d7acb316764616f11e4947faf080e49ad8d9&v=4 - 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=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4 - url: https://github.com/samuelcolvin -- login: Attsun1031 - 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=d2fbf412e7730183ce91686ca48d4147e1b7dc74&v=4 - url: https://github.com/ComicShrimp -- login: rostik1410 - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/11443899?u=e26a635c2ba220467b308a326a579b8ccf4a8701&v=4 - url: https://github.com/rostik1410 -- login: tamtam-fitness - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/62091034?u=8da19a6bd3d02f5d6ba30c7247d5b46c98dd1403&v=4 - url: https://github.com/tamtam-fitness -- login: jekirl - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4 - url: https://github.com/jekirl -- login: jfunez - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4 - url: https://github.com/jfunez -- login: ycd - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=29682e4b6ac7d5293742ccf818188394b9a82972&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: hitrust - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/3360631?u=5fa1f475ad784d64eb9666bdd43cc4d285dcc773&v=4 - url: https://github.com/hitrust -- login: JulianMaurin - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/63545168?u=b7d15ac865268cbefc2d739e2f23d9aeeac1a622&v=4 - url: https://github.com/JulianMaurin -- login: lsglucas - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 - url: https://github.com/lsglucas -- login: BilalAlpaslan - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 - url: https://github.com/BilalAlpaslan -- login: adriangb - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 - url: https://github.com/adriangb -- login: iudeen - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 - url: https://github.com/iudeen -- login: axel584 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 - url: https://github.com/axel584 -- login: ivan-abc - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4 - url: https://github.com/ivan-abc -- login: divums - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/1397556?v=4 - url: https://github.com/divums -- login: prostomarkeloff - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 - url: https://github.com/prostomarkeloff -- login: nsidnev - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4 - url: https://github.com/nsidnev -- login: pawamoy - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4 - url: https://github.com/pawamoy -top_reviewers: -- login: Kludex - count: 158 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex -- login: BilalAlpaslan - count: 86 - avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 - url: https://github.com/BilalAlpaslan -- login: yezz123 - count: 85 - avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=d7062cbc6eb7671d5dc9cc0e32a24ae335e0f225&v=4 - url: https://github.com/yezz123 -- login: iudeen - count: 55 - avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4 - url: https://github.com/iudeen -- login: tokusumi - count: 51 - avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 - url: https://github.com/tokusumi -- login: Xewus - count: 50 - avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 - url: https://github.com/Xewus -- login: hasansezertasan - count: 50 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: waynerv - count: 47 - avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 - url: https://github.com/waynerv -- login: Laineyzhang55 - count: 47 - avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4 - url: https://github.com/Laineyzhang55 -- login: ycd - count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=29682e4b6ac7d5293742ccf818188394b9a82972&v=4 - url: https://github.com/ycd -- login: cikay - count: 41 - avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4 - url: https://github.com/cikay -- login: alejsdev - count: 38 - avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=9ca449ad5161af12766ddd1a22988e9b14315f5c&v=4 - url: https://github.com/alejsdev -- 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=b2ea249c6b41ddf98679c8d110d0f67d4a3ebf93&v=4 - url: https://github.com/AdrianDeAnda -- login: ArcLightSlavik - count: 31 - avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 - url: https://github.com/ArcLightSlavik -- login: cassiobotaro - count: 28 - avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4 - url: https://github.com/cassiobotaro -- login: lsglucas - count: 27 - avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 - url: https://github.com/lsglucas -- login: komtaki - count: 27 - avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 - url: https://github.com/komtaki +- login: tiangolo + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: luzzodev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +three_months_experts: - login: YuriiMotov - count: 25 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 + count: 397 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 url: https://github.com/YuriiMotov -- login: Ryandaydev - count: 25 - avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=48f68868db8886fce31a1d802c1003914c6cd7c6&v=4 - url: https://github.com/Ryandaydev -- login: LorhanSohaky +- login: valentinDruzhinin count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 - url: https://github.com/LorhanSohaky -- login: dmontagu - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 - url: https://github.com/dmontagu -- login: nilslindemann - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 - url: https://github.com/nilslindemann -- login: hard-coders - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 - url: https://github.com/hard-coders -- login: rjNemo - count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 - url: https://github.com/rjNemo -- login: odiseo0 - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&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 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- login: luzzodev + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: raceychan + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 + url: https://github.com/raceychan +- login: yinziyan1206 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + url: https://github.com/yinziyan1206 +- login: DoctorJohn + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 + url: https://github.com/DoctorJohn +- login: tiangolo + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: sachinh35 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 + url: https://github.com/sachinh35 +- login: eqsdxr + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 + url: https://github.com/eqsdxr +- login: Jelle-tenB + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 + url: https://github.com/Jelle-tenB +- login: pythonweb2 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 + url: https://github.com/pythonweb2 +- login: WilliamDEdwards + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 + url: https://github.com/WilliamDEdwards +- login: Brikas + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 + url: https://github.com/Brikas +- login: purepani + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 + url: https://github.com/purepani - login: JavierSanchezCastro - count: 19 + count: 2 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 url: https://github.com/JavierSanchezCastro -- 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 - url: https://github.com/zy7y -- login: junah201 - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/75025529?u=2451c256e888fa2a06bcfc0646d09b87ddb6a945&v=4 - url: https://github.com/junah201 -- login: peidrao - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=a66902b40c13647d0ed0e573d598128240a4dd04&v=4 - url: https://github.com/peidrao -- login: yanever - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4 - url: https://github.com/yanever -- login: SwftAlpc - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 - url: https://github.com/SwftAlpc -- login: axel584 - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 - url: https://github.com/axel584 -- login: codespearhead - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/72931357?u=0fce6b82219b604d58adb614a761556425579cb5&v=4 - url: https://github.com/codespearhead -- login: Alexandrhub - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 - url: https://github.com/Alexandrhub -- login: DevDae - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4 - url: https://github.com/DevDae -- login: Aruelius - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/25380989?u=574f8cfcda3ea77a3f81884f6b26a97068e36a9d&v=4 - url: https://github.com/Aruelius -- login: OzgunCaglarArslan - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/86166426?v=4 - url: https://github.com/OzgunCaglarArslan -- login: pedabraham - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/16860088?u=abf922a7b920bf8fdb7867d8b43e091f1e796178&v=4 - url: https://github.com/pedabraham -- login: delhi09 - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 - url: https://github.com/delhi09 -- login: wdh99 - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/108172295?u=8a8fb95d5afe3e0fa33257b2aecae88d436249eb&v=4 - url: https://github.com/wdh99 -- login: sh0nk - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 - url: https://github.com/sh0nk -- 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: ivan-abc - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4 - url: https://github.com/ivan-abc -- login: AlertRED - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 - url: https://github.com/AlertRED -- login: solomein-sv - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4 - url: https://github.com/solomein-sv -top_translations_reviewers: -- login: s111d - count: 146 - avatarUrl: https://avatars.githubusercontent.com/u/4954856?v=4 - url: https://github.com/s111d -- login: Xewus - count: 128 - avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 - url: https://github.com/Xewus -- login: tokusumi - count: 104 - avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 - url: https://github.com/tokusumi -- login: hasansezertasan - count: 91 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: AlertRED - count: 70 - avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 - url: https://github.com/AlertRED -- login: Alexandrhub - count: 68 - avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 - url: https://github.com/Alexandrhub -- login: waynerv - count: 63 - avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 - url: https://github.com/waynerv -- login: hard-coders - count: 53 - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 - url: https://github.com/hard-coders -- login: Laineyzhang55 - count: 48 - avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4 - url: https://github.com/Laineyzhang55 -- login: Kludex - count: 46 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4 - url: https://github.com/Kludex -- login: komtaki +- login: TaigoFr + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 + url: https://github.com/TaigoFr +- login: Garrett-R + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 + url: https://github.com/Garrett-R +- login: jymchng + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 + url: https://github.com/jymchng +- login: davidhuser + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 + url: https://github.com/davidhuser +six_months_experts: +- login: YuriiMotov + count: 763 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov +- login: luzzodev count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 - url: https://github.com/komtaki -- login: alperiox - count: 42 - avatarUrl: https://avatars.githubusercontent.com/u/34214152?u=2c5acad3461d4dbc2d48371ba86cac56ae9b25cc&v=4 - url: https://github.com/alperiox -- login: Winand - count: 40 - avatarUrl: https://avatars.githubusercontent.com/u/53390?u=bb0e71a2fc3910a8e0ee66da67c33de40ea695f8&v=4 - url: https://github.com/Winand -- login: solomein-sv - count: 38 - avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4 - url: https://github.com/solomein-sv -- login: lsglucas - count: 36 - avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 - url: https://github.com/lsglucas -- login: SwftAlpc - count: 36 - avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 - url: https://github.com/SwftAlpc -- login: nilslindemann - count: 35 - avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 - url: https://github.com/nilslindemann -- login: rjNemo - count: 34 - avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 - url: https://github.com/rjNemo -- login: akarev0 - count: 33 - avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4 - url: https://github.com/akarev0 -- login: romashevchenko - count: 32 - avatarUrl: https://avatars.githubusercontent.com/u/132477732?v=4 - url: https://github.com/romashevchenko -- login: wdh99 - count: 31 - avatarUrl: https://avatars.githubusercontent.com/u/108172295?u=8a8fb95d5afe3e0fa33257b2aecae88d436249eb&v=4 - url: https://github.com/wdh99 -- login: LorhanSohaky - count: 30 - avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 - url: https://github.com/LorhanSohaky -- login: cassiobotaro - count: 29 - avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4 - url: https://github.com/cassiobotaro -- login: pedabraham - count: 28 - avatarUrl: https://avatars.githubusercontent.com/u/16860088?u=abf922a7b920bf8fdb7867d8b43e091f1e796178&v=4 - url: https://github.com/pedabraham -- login: Smlep - count: 28 - avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 - url: https://github.com/Smlep -- login: dedkot01 - count: 28 - avatarUrl: https://avatars.githubusercontent.com/u/26196675?u=e2966887124e67932853df4f10f86cb526edc7b0&v=4 - url: https://github.com/dedkot01 -- login: hsuanchi - count: 28 - avatarUrl: https://avatars.githubusercontent.com/u/24913710?u=0b094ae292292fee093818e37ceb645c114d2bff&v=4 - url: https://github.com/hsuanchi -- login: dpinezich - count: 28 - avatarUrl: https://avatars.githubusercontent.com/u/3204540?u=a2e1465e3ee10d537614d513589607eddefde09f&v=4 - url: https://github.com/dpinezich -- login: maoyibo - count: 27 - avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 - url: https://github.com/maoyibo -- login: 0417taehyun - count: 27 - avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4 - url: https://github.com/0417taehyun -- login: BilalAlpaslan - count: 26 - avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 - url: https://github.com/BilalAlpaslan -- login: zy7y - count: 25 - avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4 - url: https://github.com/zy7y -- login: mycaule - count: 25 - avatarUrl: https://avatars.githubusercontent.com/u/6161385?u=e3cec75bd6d938a0d73fae0dc5534d1ab2ed1b0e&v=4 - url: https://github.com/mycaule -- login: sh0nk - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 - url: https://github.com/sh0nk -- login: axel584 - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 - url: https://github.com/axel584 -- login: AGolicyn - count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/86262613?u=3c21606ab8d210a061a1673decff1e7d5592b380&v=4 - url: https://github.com/AGolicyn -- login: OzgunCaglarArslan - count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/86166426?v=4 - url: https://github.com/OzgunCaglarArslan -- login: Attsun1031 - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 - url: https://github.com/Attsun1031 -- login: ycd - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=29682e4b6ac7d5293742ccf818188394b9a82972&v=4 - url: https://github.com/ycd -- login: delhi09 - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 - url: https://github.com/delhi09 -- login: rogerbrinkmann - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/5690226?v=4 - url: https://github.com/rogerbrinkmann -- login: DevDae - count: 20 - avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4 - url: https://github.com/DevDae -- login: sattosan - count: 19 - avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4 - url: https://github.com/sattosan -- login: ComicShrimp - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=d2fbf412e7730183ce91686ca48d4147e1b7dc74&v=4 - url: https://github.com/ComicShrimp -- login: junah201 - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/75025529?u=2451c256e888fa2a06bcfc0646d09b87ddb6a945&v=4 - url: https://github.com/junah201 -- login: simatheone - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/78508673?u=1b9658d9ee0bde33f56130dd52275493ddd38690&v=4 - url: https://github.com/simatheone -- login: ivan-abc - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4 - url: https://github.com/ivan-abc + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: valentinDruzhinin + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- login: alv2017 + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: sachinh35 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 + url: https://github.com/sachinh35 +- login: yauhen-sobaleu + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4 + url: https://github.com/yauhen-sobaleu +- login: tiangolo + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo - login: JavierSanchezCastro - count: 18 + count: 6 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 url: https://github.com/JavierSanchezCastro -- login: bezaca +- login: raceychan + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 + url: https://github.com/raceychan +- login: yinziyan1206 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + url: https://github.com/yinziyan1206 +- login: DoctorJohn + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 + url: https://github.com/DoctorJohn +- login: eqsdxr + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 + url: https://github.com/eqsdxr +- login: Kludex + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex +- login: Jelle-tenB + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 + url: https://github.com/Jelle-tenB +- login: adsouza + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 + url: https://github.com/adsouza +- login: pythonweb2 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 + url: https://github.com/pythonweb2 +- login: WilliamDEdwards + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 + url: https://github.com/WilliamDEdwards +- login: Brikas + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 + url: https://github.com/Brikas +- login: purepani + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 + url: https://github.com/purepani +- login: TaigoFr + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 + url: https://github.com/TaigoFr +- login: Garrett-R + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 + url: https://github.com/Garrett-R +- login: EverStarck + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 + url: https://github.com/EverStarck +- login: henrymcl + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 + url: https://github.com/henrymcl +- login: jymchng + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 + url: https://github.com/jymchng +- login: davidhuser + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 + url: https://github.com/davidhuser +- login: PidgeyBE + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 + url: https://github.com/PidgeyBE +- login: KianAnbarestani + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 + url: https://github.com/KianAnbarestani +- login: jgould22 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: marsboy02 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/86903678?u=04cc319d6605f8d1ba3a0bed9f4f55a582719ae6&v=4 + url: https://github.com/marsboy02 +one_year_experts: +- login: YuriiMotov + count: 824 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov +- login: luzzodev + count: 89 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: Kludex + count: 50 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex +- login: sinisaos + count: 33 + avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 + url: https://github.com/sinisaos +- login: alv2017 + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: valentinDruzhinin + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- login: JavierSanchezCastro + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: jgould22 count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4 - url: https://github.com/bezaca + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: tiangolo + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: Kfir-G + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/57500876?u=a3bf923ab27bce3d1b13779a8dd22eb7675017fd&v=4 + url: https://github.com/Kfir-G +- login: sehraramiz + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4 + url: https://github.com/sehraramiz +- login: sachinh35 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 + url: https://github.com/sachinh35 +- login: yauhen-sobaleu + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4 + url: https://github.com/yauhen-sobaleu +- login: estebanx64 + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 + url: https://github.com/estebanx64 +- login: ceb10n + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +- login: yvallois + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/36999744?v=4 + url: https://github.com/yvallois +- login: raceychan + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 + url: https://github.com/raceychan +- login: yinziyan1206 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + url: https://github.com/yinziyan1206 +- login: DoctorJohn + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 + url: https://github.com/DoctorJohn +- login: n8sty + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 + url: https://github.com/n8sty +- login: pythonweb2 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 + url: https://github.com/pythonweb2 +- login: eqsdxr + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 + url: https://github.com/eqsdxr +- login: yokwejuste + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/71908316?u=4ba43bd63c169b5c015137d8916752a44001445a&v=4 + url: https://github.com/yokwejuste +- login: WilliamDEdwards + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 + url: https://github.com/WilliamDEdwards +- login: mattmess1221 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=d22ea18aa8ea688af25a45df306134d593621a44&v=4 + url: https://github.com/mattmess1221 +- login: Jelle-tenB + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 + url: https://github.com/Jelle-tenB +- login: viniciusCalcantara + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/108818737?u=80f3ec7427fa6a41d5896984d0c526432f2299fa&v=4 + url: https://github.com/viniciusCalcantara +- login: davidhuser + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 + url: https://github.com/davidhuser +- login: dbfreem + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/9778569?u=f2f1e9135b5e4f1b0c6821a548b17f97572720fc&v=4 + url: https://github.com/dbfreem +- login: SobikXexe + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/87701130?v=4 + url: https://github.com/SobikXexe +- login: pawelad + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/7062874?u=d27dc220545a8401ad21840590a97d474d7101e6&v=4 + url: https://github.com/pawelad +- login: Isuxiz + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/48672727?u=34d7b4ade252687d22a27cf53037b735b244bfc1&v=4 + url: https://github.com/Isuxiz +- login: Minibrams + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/8108085?u=b028dbc308fa8485e0e2e9402b3d03d8deb22bf9&v=4 + url: https://github.com/Minibrams +- login: adsouza + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 + url: https://github.com/adsouza +- login: Synrom + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/30272537?v=4 + url: https://github.com/Synrom +- login: gaby + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/835733?u=8c72dec16fa560bdc81113354f2ffd79ad062bde&v=4 + url: https://github.com/gaby +- login: Ale-Cas + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4 + url: https://github.com/Ale-Cas +- login: CharlesPerrotMinotHCHB + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/112571330?u=e3a666718ff5ad1d1c49d6c31358a9f80c841b30&v=4 + url: https://github.com/CharlesPerrotMinotHCHB +- login: yanggeorge + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/2434407?v=4 + url: https://github.com/yanggeorge +- login: Brikas + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 + url: https://github.com/Brikas +- login: dolfinus + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 + url: https://github.com/dolfinus +- login: slafs + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 + url: https://github.com/slafs +- login: purepani + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 + url: https://github.com/purepani +- login: ddahan + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1933516?u=1d200a620e8d6841df017e9f2bb7efb58b580f40&v=4 + url: https://github.com/ddahan +- login: TaigoFr + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 + url: https://github.com/TaigoFr +- login: Garrett-R + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 + url: https://github.com/Garrett-R +- login: jd-solanki + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/47495003?u=6e225cb42c688d0cd70e65c6baedb9f5922b1178&v=4 + url: https://github.com/jd-solanki +- login: EverStarck + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 + url: https://github.com/EverStarck +- login: henrymcl + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 + url: https://github.com/henrymcl +- login: jymchng + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 + url: https://github.com/jymchng +- login: christiansicari + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/29756552?v=4 + url: https://github.com/christiansicari +- login: JacobHayes + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/2555532?u=354a525847a276bbb4426b0c95791a8ba5970f9b&v=4 + url: https://github.com/JacobHayes +- login: iloveitaly + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 + url: https://github.com/iloveitaly +- login: iiotsrc + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/131771119?u=bcaf2559ef6266af70b151b7fda31a1ee3dbecb3&v=4 + url: https://github.com/iiotsrc +- login: PidgeyBE + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 + url: https://github.com/PidgeyBE +- login: KianAnbarestani + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 + url: https://github.com/KianAnbarestani +- login: ykaiqx + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=1eebf5ce25a8067f7bfa6251a24f667be492d9d6&v=4 + url: https://github.com/ykaiqx +- login: AliYmn + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=a77e2605e3ce6aaf6fef8ad4a7b0d32954fba47a&v=4 + url: https://github.com/AliYmn +- login: gelezo43 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/40732698?u=611f39d3c1d2f4207a590937a78c1f10eed6232c&v=4 + url: https://github.com/gelezo43 +- login: jfeaver + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1091338?u=0bcba366447d8fadad63f6705a52d128da4c7ec2&v=4 + url: https://github.com/jfeaver diff --git a/docs/en/data/skip_users.yml b/docs/en/data/skip_users.yml new file mode 100644 index 0000000000..cf24003af8 --- /dev/null +++ b/docs/en/data/skip_users.yml @@ -0,0 +1,5 @@ +- tiangolo +- codecov +- github-actions +- pre-commit-ci +- dependabot diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 8c0956ac52..7a015e4048 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -1,63 +1,63 @@ gold: - - 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://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 - - url: https://www.porter.run - title: Deploy FastAPI on AWS with a few clicks - img: https://fastapi.tiangolo.com/img/sponsors/porter.png - - url: https://bump.sh/fastapi?utm_source=fastapi&utm_medium=referral&utm_campaign=sponsor - title: Automate FastAPI documentation generation with Bump.sh - img: https://fastapi.tiangolo.com/img/sponsors/bump-sh.svg + - url: https://blockbee.io?ref=fastapi + title: BlockBee Cryptocurrency Payment Gateway + img: https://fastapi.tiangolo.com/img/sponsors/blockbee.png - url: https://github.com/scalar/scalar/?utm_source=fastapi&utm_medium=website&utm_campaign=main-badge title: "Scalar: Beautiful Open-Source API References from Swagger/OpenAPI files" img: https://fastapi.tiangolo.com/img/sponsors/scalar.svg - url: https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=mainbadge title: Auth, user management and more for your B2B product img: https://fastapi.tiangolo.com/img/sponsors/propelauth.png - - url: https://docs.withcoherence.com/configuration/frameworks/?utm_medium=advertising&utm_source=fastapi&utm_campaign=docs#fastapi-example - title: Coherence - img: https://fastapi.tiangolo.com/img/sponsors/coherence.png - - url: https://www.mongodb.com/developer/languages/python/python-quickstart-fastapi/?utm_campaign=fastapi_framework&utm_source=fastapi_sponsorship&utm_medium=web_referral - title: Simplify Full Stack Development with FastAPI & MongoDB - img: https://fastapi.tiangolo.com/img/sponsors/mongodb.png - - url: https://konghq.com/products/kong-konnect?utm_medium=referral&utm_source=github&utm_campaign=platform&utm_content=fast-api - title: Kong Konnect - API management platform - img: https://fastapi.tiangolo.com/img/sponsors/kong.png - url: https://zuplo.link/fastapi-gh - title: 'Zuplo: Scale, Protect, Document, and Monetize your FastAPI' + title: 'Zuplo: Deploy, Secure, Document, and Monetize your FastAPI' img: https://fastapi.tiangolo.com/img/sponsors/zuplo.png - - url: https://fine.dev?ref=fastapibadge - title: "Fine's AI FastAPI Workflow: Effortlessly Deploy and Integrate FastAPI into Your Project" - img: https://fastapi.tiangolo.com/img/sponsors/fine.png - url: https://liblab.com?utm_source=fastapi title: liblab - Generate SDKs from FastAPI img: https://fastapi.tiangolo.com/img/sponsors/liblab.png + - url: https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi + title: Deploy & scale any full-stack web app on Render. Focus on building apps, not infra. + img: https://fastapi.tiangolo.com/img/sponsors/render.svg + - url: https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=badge&utm_campaign=fastapi + title: Cut Code Review Time & Bugs in Half with CodeRabbit + img: https://fastapi.tiangolo.com/img/sponsors/coderabbit.png + - url: https://subtotal.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=open-source + title: The Gold Standard in Retail Account Linking + img: https://fastapi.tiangolo.com/img/sponsors/subtotal.svg + - url: https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi + title: Deploy enterprise applications at startup speed + img: https://fastapi.tiangolo.com/img/sponsors/railway.png 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://databento.com/ + - url: https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display title: Pay as you go for market data img: https://fastapi.tiangolo.com/img/sponsors/databento.svg - - url: https://speakeasy.com?utm_source=fastapi+repo&utm_medium=github+sponsorship + - url: https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship title: SDKs for your API | Speakeasy img: https://fastapi.tiangolo.com/img/sponsors/speakeasy.png - url: https://www.svix.com/ title: Svix - Webhooks as a service img: https://fastapi.tiangolo.com/img/sponsors/svix.svg - - url: https://www.codacy.com/?utm_source=github&utm_medium=sponsors&utm_id=pioneers - title: Take code reviews from hours to minutes - img: https://fastapi.tiangolo.com/img/sponsors/codacy.png - url: https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral title: Stainless | Generate best-in-class SDKs img: https://fastapi.tiangolo.com/img/sponsors/stainless.png + - url: https://www.permit.io/blog/implement-authorization-in-fastapi?utm_source=github&utm_medium=referral&utm_campaign=fastapi + title: Fine-Grained Authorization for FastAPI + img: https://fastapi.tiangolo.com/img/sponsors/permit.png + - url: https://www.interviewpal.com/?utm_source=fastapi&utm_medium=open-source&utm_campaign=dev-hiring + title: InterviewPal - AI Interview Coach for Engineers and Devs + img: https://fastapi.tiangolo.com/img/sponsors/interviewpal.png + - url: https://dribia.com/en/ + title: Dribia - Data Science within your reach + img: https://fastapi.tiangolo.com/img/sponsors/dribia.png 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://testdriven.io/courses/tdd-fastapi/ - title: Learn to build high-quality web apps with best practices - img: https://fastapi.tiangolo.com/img/sponsors/testdriven.svg + # - url: https://testdriven.io/courses/tdd-fastapi/ + # title: Learn to build high-quality web apps with best practices + # img: https://fastapi.tiangolo.com/img/sponsors/testdriven.svg + - url: https://lambdatest.com/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage + title: LambdaTest, AI-Powered Cloud-based Test Orchestration Platform + img: https://fastapi.tiangolo.com/img/sponsors/lambdatest.png + - url: https://requestly.com/fastapi + title: All-in-one platform to Test, Mock and Intercept APIs. Built for speed, privacy and offline support. + img: https://fastapi.tiangolo.com/img/sponsors/requestly.png diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index d8a41fbcb7..14f55805c6 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -29,4 +29,21 @@ logins: - andrew-propelauth - svix - zuplo-oss + - zuplo - Kong + - speakeasy-api + - jess-render + - blockbee-io + - liblaber + - render-sponsorships + - renderinc + - stainless-api + - snapit-cypher + - coderabbitai + - permitio + - LambdaTest-Inc + - dribia + - madisonredtfeldt + - railwayapp + - subtotal + - requestly diff --git a/docs/en/data/topic_repos.yml b/docs/en/data/topic_repos.yml new file mode 100644 index 0000000000..9d95fb8b1f --- /dev/null +++ b/docs/en/data/topic_repos.yml @@ -0,0 +1,495 @@ +- name: full-stack-fastapi-template + html_url: https://github.com/fastapi/full-stack-fastapi-template + stars: 38085 + owner_login: fastapi + owner_html_url: https://github.com/fastapi +- name: Hello-Python + html_url: https://github.com/mouredev/Hello-Python + stars: 32243 + owner_login: mouredev + owner_html_url: https://github.com/mouredev +- name: serve + html_url: https://github.com/jina-ai/serve + stars: 21754 + owner_login: jina-ai + owner_html_url: https://github.com/jina-ai +- name: HivisionIDPhotos + html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos + stars: 19400 + owner_login: Zeyi-Lin + owner_html_url: https://github.com/Zeyi-Lin +- name: sqlmodel + html_url: https://github.com/fastapi/sqlmodel + stars: 16859 + owner_login: fastapi + owner_html_url: https://github.com/fastapi +- name: Douyin_TikTok_Download_API + html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API + stars: 14452 + owner_login: Evil0ctal + owner_html_url: https://github.com/Evil0ctal +- name: fastapi-best-practices + html_url: https://github.com/zhanymkanov/fastapi-best-practices + stars: 13613 + owner_login: zhanymkanov + owner_html_url: https://github.com/zhanymkanov +- name: fastapi_mcp + html_url: https://github.com/tadata-org/fastapi_mcp + stars: 10624 + owner_login: tadata-org + owner_html_url: https://github.com/tadata-org +- name: awesome-fastapi + html_url: https://github.com/mjhea0/awesome-fastapi + stars: 10415 + owner_login: mjhea0 + owner_html_url: https://github.com/mjhea0 +- name: FastUI + html_url: https://github.com/pydantic/FastUI + stars: 8879 + owner_login: pydantic + owner_html_url: https://github.com/pydantic +- name: XHS-Downloader + html_url: https://github.com/JoeanAmier/XHS-Downloader + stars: 8824 + owner_login: JoeanAmier + owner_html_url: https://github.com/JoeanAmier +- name: SurfSense + html_url: https://github.com/MODSetter/SurfSense + stars: 8257 + owner_login: MODSetter + owner_html_url: https://github.com/MODSetter +- name: FileCodeBox + html_url: https://github.com/vastsa/FileCodeBox + stars: 7367 + owner_login: vastsa + owner_html_url: https://github.com/vastsa +- name: polar + html_url: https://github.com/polarsource/polar + stars: 7291 + owner_login: polarsource + owner_html_url: https://github.com/polarsource +- name: nonebot2 + html_url: https://github.com/nonebot/nonebot2 + stars: 7065 + owner_login: nonebot + owner_html_url: https://github.com/nonebot +- name: hatchet + html_url: https://github.com/hatchet-dev/hatchet + stars: 6070 + owner_login: hatchet-dev + owner_html_url: https://github.com/hatchet-dev +- name: serge + html_url: https://github.com/serge-chat/serge + stars: 5754 + owner_login: serge-chat + owner_html_url: https://github.com/serge-chat +- name: fastapi-users + html_url: https://github.com/fastapi-users/fastapi-users + stars: 5599 + owner_login: fastapi-users + owner_html_url: https://github.com/fastapi-users +- name: strawberry + html_url: https://github.com/strawberry-graphql/strawberry + stars: 4422 + owner_login: strawberry-graphql + owner_html_url: https://github.com/strawberry-graphql +- name: chatgpt-web-share + html_url: https://github.com/chatpire/chatgpt-web-share + stars: 4301 + owner_login: chatpire + owner_html_url: https://github.com/chatpire +- name: poem + html_url: https://github.com/poem-web/poem + stars: 4197 + owner_login: poem-web + owner_html_url: https://github.com/poem-web +- name: dynaconf + html_url: https://github.com/dynaconf/dynaconf + stars: 4144 + owner_login: dynaconf + owner_html_url: https://github.com/dynaconf +- name: atrilabs-engine + html_url: https://github.com/Atri-Labs/atrilabs-engine + stars: 4094 + owner_login: Atri-Labs + owner_html_url: https://github.com/Atri-Labs +- name: Kokoro-FastAPI + html_url: https://github.com/remsky/Kokoro-FastAPI + stars: 3739 + owner_login: remsky + owner_html_url: https://github.com/remsky +- name: logfire + html_url: https://github.com/pydantic/logfire + stars: 3614 + owner_login: pydantic + owner_html_url: https://github.com/pydantic +- name: LitServe + html_url: https://github.com/Lightning-AI/LitServe + stars: 3578 + owner_login: Lightning-AI + owner_html_url: https://github.com/Lightning-AI +- name: datamodel-code-generator + html_url: https://github.com/koxudaxi/datamodel-code-generator + stars: 3496 + owner_login: koxudaxi + owner_html_url: https://github.com/koxudaxi +- name: farfalle + html_url: https://github.com/rashadphz/farfalle + stars: 3459 + owner_login: rashadphz + owner_html_url: https://github.com/rashadphz +- name: fastapi-admin + html_url: https://github.com/fastapi-admin/fastapi-admin + stars: 3456 + owner_login: fastapi-admin + owner_html_url: https://github.com/fastapi-admin +- name: huma + html_url: https://github.com/danielgtaylor/huma + stars: 3447 + owner_login: danielgtaylor + owner_html_url: https://github.com/danielgtaylor +- name: tracecat + html_url: https://github.com/TracecatHQ/tracecat + stars: 3254 + owner_login: TracecatHQ + owner_html_url: https://github.com/TracecatHQ +- name: opyrator + html_url: https://github.com/ml-tooling/opyrator + stars: 3134 + owner_login: ml-tooling + owner_html_url: https://github.com/ml-tooling +- name: docarray + html_url: https://github.com/docarray/docarray + stars: 3107 + owner_login: docarray + owner_html_url: https://github.com/docarray +- name: fastapi-realworld-example-app + html_url: https://github.com/nsidnev/fastapi-realworld-example-app + stars: 2936 + owner_login: nsidnev + owner_html_url: https://github.com/nsidnev +- name: uvicorn-gunicorn-fastapi-docker + html_url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker + stars: 2804 + owner_login: tiangolo + owner_html_url: https://github.com/tiangolo +- name: best-of-web-python + html_url: https://github.com/ml-tooling/best-of-web-python + stars: 2610 + owner_login: ml-tooling + owner_html_url: https://github.com/ml-tooling +- name: mcp-context-forge + html_url: https://github.com/IBM/mcp-context-forge + stars: 2572 + owner_login: IBM + owner_html_url: https://github.com/IBM +- name: fastapi-react + html_url: https://github.com/Buuntu/fastapi-react + stars: 2451 + owner_login: Buuntu + owner_html_url: https://github.com/Buuntu +- name: RasaGPT + html_url: https://github.com/paulpierre/RasaGPT + stars: 2441 + owner_login: paulpierre + owner_html_url: https://github.com/paulpierre +- name: FastAPI-template + html_url: https://github.com/s3rius/FastAPI-template + stars: 2424 + owner_login: s3rius + owner_html_url: https://github.com/s3rius +- name: sqladmin + html_url: https://github.com/aminalaee/sqladmin + stars: 2357 + owner_login: aminalaee + owner_html_url: https://github.com/aminalaee +- name: nextpy + html_url: https://github.com/dot-agent/nextpy + stars: 2324 + owner_login: dot-agent + owner_html_url: https://github.com/dot-agent +- name: supabase-py + html_url: https://github.com/supabase/supabase-py + stars: 2236 + owner_login: supabase + owner_html_url: https://github.com/supabase +- name: 30-Days-of-Python + html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python + stars: 2210 + owner_login: codingforentrepreneurs + owner_html_url: https://github.com/codingforentrepreneurs +- name: langserve + html_url: https://github.com/langchain-ai/langserve + stars: 2171 + owner_login: langchain-ai + owner_html_url: https://github.com/langchain-ai +- name: fastapi-utils + html_url: https://github.com/fastapiutils/fastapi-utils + stars: 2164 + owner_login: fastapiutils + owner_html_url: https://github.com/fastapiutils +- name: solara + html_url: https://github.com/widgetti/solara + stars: 2102 + owner_login: widgetti + owner_html_url: https://github.com/widgetti +- name: Yuxi-Know + html_url: https://github.com/xerrors/Yuxi-Know + stars: 1995 + owner_login: xerrors + owner_html_url: https://github.com/xerrors +- name: mangum + html_url: https://github.com/Kludex/mangum + stars: 1989 + owner_login: Kludex + owner_html_url: https://github.com/Kludex +- name: python-week-2022 + html_url: https://github.com/rochacbruno/python-week-2022 + stars: 1816 + owner_login: rochacbruno + owner_html_url: https://github.com/rochacbruno +- name: agentkit + html_url: https://github.com/BCG-X-Official/agentkit + stars: 1789 + owner_login: BCG-X-Official + owner_html_url: https://github.com/BCG-X-Official +- name: manage-fastapi + html_url: https://github.com/ycd/manage-fastapi + stars: 1780 + owner_login: ycd + owner_html_url: https://github.com/ycd +- name: ormar + html_url: https://github.com/collerek/ormar + stars: 1777 + owner_login: collerek + owner_html_url: https://github.com/collerek +- name: openapi-python-client + html_url: https://github.com/openapi-generators/openapi-python-client + stars: 1707 + owner_login: openapi-generators + owner_html_url: https://github.com/openapi-generators +- name: piccolo + html_url: https://github.com/piccolo-orm/piccolo + stars: 1695 + owner_login: piccolo-orm + owner_html_url: https://github.com/piccolo-orm +- name: vue-fastapi-admin + html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin + stars: 1695 + owner_login: mizhexiaoxiao + owner_html_url: https://github.com/mizhexiaoxiao +- name: fastapi-cache + html_url: https://github.com/long2ice/fastapi-cache + stars: 1653 + owner_login: long2ice + owner_html_url: https://github.com/long2ice +- name: langchain-serve + html_url: https://github.com/jina-ai/langchain-serve + stars: 1635 + owner_login: jina-ai + owner_html_url: https://github.com/jina-ai +- name: termpair + html_url: https://github.com/cs01/termpair + stars: 1624 + owner_login: cs01 + owner_html_url: https://github.com/cs01 +- name: slowapi + html_url: https://github.com/laurentS/slowapi + stars: 1620 + owner_login: laurentS + owner_html_url: https://github.com/laurentS +- name: coronavirus-tracker-api + html_url: https://github.com/ExpDev07/coronavirus-tracker-api + stars: 1576 + owner_login: ExpDev07 + owner_html_url: https://github.com/ExpDev07 +- name: fastapi-crudrouter + html_url: https://github.com/awtkns/fastapi-crudrouter + stars: 1546 + owner_login: awtkns + owner_html_url: https://github.com/awtkns +- name: FastAPI-boilerplate + html_url: https://github.com/benavlabs/FastAPI-boilerplate + stars: 1516 + owner_login: benavlabs + owner_html_url: https://github.com/benavlabs +- name: awesome-fastapi-projects + html_url: https://github.com/Kludex/awesome-fastapi-projects + stars: 1481 + owner_login: Kludex + owner_html_url: https://github.com/Kludex +- name: fastapi-pagination + html_url: https://github.com/uriyyo/fastapi-pagination + stars: 1453 + owner_login: uriyyo + owner_html_url: https://github.com/uriyyo +- name: bracket + html_url: https://github.com/evroon/bracket + stars: 1415 + owner_login: evroon + owner_html_url: https://github.com/evroon +- name: awesome-python-resources + html_url: https://github.com/DjangoEx/awesome-python-resources + stars: 1413 + owner_login: DjangoEx + owner_html_url: https://github.com/DjangoEx +- name: fastapi-boilerplate + html_url: https://github.com/teamhide/fastapi-boilerplate + stars: 1406 + owner_login: teamhide + owner_html_url: https://github.com/teamhide +- name: budgetml + html_url: https://github.com/ebhy/budgetml + stars: 1346 + owner_login: ebhy + owner_html_url: https://github.com/ebhy +- name: fastapi-amis-admin + html_url: https://github.com/amisadmin/fastapi-amis-admin + stars: 1342 + owner_login: amisadmin + owner_html_url: https://github.com/amisadmin +- name: fastapi-langgraph-agent-production-ready-template + html_url: https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template + stars: 1334 + owner_login: wassim249 + owner_html_url: https://github.com/wassim249 +- name: fastapi-tutorial + html_url: https://github.com/liaogx/fastapi-tutorial + stars: 1303 + owner_login: liaogx + owner_html_url: https://github.com/liaogx +- name: fastapi_best_architecture + html_url: https://github.com/fastapi-practices/fastapi_best_architecture + stars: 1276 + owner_login: fastapi-practices + owner_html_url: https://github.com/fastapi-practices +- name: fastcrud + html_url: https://github.com/benavlabs/fastcrud + stars: 1272 + owner_login: benavlabs + owner_html_url: https://github.com/benavlabs +- name: fastapi-code-generator + html_url: https://github.com/koxudaxi/fastapi-code-generator + stars: 1253 + owner_login: koxudaxi + owner_html_url: https://github.com/koxudaxi +- name: prometheus-fastapi-instrumentator + html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator + stars: 1246 + owner_login: trallnag + owner_html_url: https://github.com/trallnag +- name: bolt-python + html_url: https://github.com/slackapi/bolt-python + stars: 1221 + owner_login: slackapi + owner_html_url: https://github.com/slackapi +- name: bedrock-chat + html_url: https://github.com/aws-samples/bedrock-chat + stars: 1220 + owner_login: aws-samples + owner_html_url: https://github.com/aws-samples +- name: fastapi_production_template + html_url: https://github.com/zhanymkanov/fastapi_production_template + stars: 1202 + owner_login: zhanymkanov + owner_html_url: https://github.com/zhanymkanov +- name: fastapi-scaff + html_url: https://github.com/atpuxiner/fastapi-scaff + stars: 1193 + owner_login: atpuxiner + owner_html_url: https://github.com/atpuxiner +- name: langchain-extract + html_url: https://github.com/langchain-ai/langchain-extract + stars: 1164 + owner_login: langchain-ai + owner_html_url: https://github.com/langchain-ai +- name: fastapi-alembic-sqlmodel-async + html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async + stars: 1149 + owner_login: jonra1993 + owner_html_url: https://github.com/jonra1993 +- name: odmantic + html_url: https://github.com/art049/odmantic + stars: 1133 + owner_login: art049 + owner_html_url: https://github.com/art049 +- name: restish + html_url: https://github.com/rest-sh/restish + stars: 1122 + owner_login: rest-sh + owner_html_url: https://github.com/rest-sh +- name: runhouse + html_url: https://github.com/run-house/runhouse + stars: 1047 + owner_login: run-house + owner_html_url: https://github.com/run-house +- name: flock + html_url: https://github.com/Onelevenvy/flock + stars: 1027 + owner_login: Onelevenvy + owner_html_url: https://github.com/Onelevenvy +- name: authx + html_url: https://github.com/yezz123/authx + stars: 999 + owner_login: yezz123 + owner_html_url: https://github.com/yezz123 +- name: autollm + html_url: https://github.com/viddexa/autollm + stars: 999 + owner_login: viddexa + owner_html_url: https://github.com/viddexa +- name: lanarky + html_url: https://github.com/ajndkr/lanarky + stars: 995 + owner_login: ajndkr + owner_html_url: https://github.com/ajndkr +- name: titiler + html_url: https://github.com/developmentseed/titiler + stars: 952 + owner_login: developmentseed + owner_html_url: https://github.com/developmentseed +- name: energy-forecasting + html_url: https://github.com/iusztinpaul/energy-forecasting + stars: 946 + owner_login: iusztinpaul + owner_html_url: https://github.com/iusztinpaul +- name: secure + html_url: https://github.com/TypeError/secure + stars: 944 + owner_login: TypeError + owner_html_url: https://github.com/TypeError +- name: langcorn + html_url: https://github.com/msoedov/langcorn + stars: 934 + owner_login: msoedov + owner_html_url: https://github.com/msoedov +- name: RuoYi-Vue3-FastAPI + html_url: https://github.com/insistence/RuoYi-Vue3-FastAPI + stars: 930 + owner_login: insistence + owner_html_url: https://github.com/insistence +- name: aktools + html_url: https://github.com/akfamily/aktools + stars: 916 + owner_login: akfamily + owner_html_url: https://github.com/akfamily +- name: every-pdf + html_url: https://github.com/DDULDDUCK/every-pdf + stars: 907 + owner_login: DDULDDUCK + owner_html_url: https://github.com/DDULDDUCK +- name: marker-api + html_url: https://github.com/adithya-s-k/marker-api + stars: 903 + owner_login: adithya-s-k + owner_html_url: https://github.com/adithya-s-k +- name: fastapi-observability + html_url: https://github.com/blueswen/fastapi-observability + stars: 902 + owner_login: blueswen + owner_html_url: https://github.com/blueswen +- name: fastapi-do-zero + html_url: https://github.com/dunossauro/fastapi-do-zero + stars: 900 + owner_login: dunossauro + owner_html_url: https://github.com/dunossauro diff --git a/docs/en/data/translation_reviewers.yml b/docs/en/data/translation_reviewers.yml new file mode 100644 index 0000000000..68ef01f6d5 --- /dev/null +++ b/docs/en/data/translation_reviewers.yml @@ -0,0 +1,1850 @@ +s111d: + login: s111d + count: 147 + avatarUrl: https://avatars.githubusercontent.com/u/4954856?v=4 + url: https://github.com/s111d +Xewus: + login: Xewus + count: 140 + avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 + url: https://github.com/Xewus +sodaMelon: + login: sodaMelon + count: 127 + avatarUrl: https://avatars.githubusercontent.com/u/66295123?u=be939db90f1119efee9e6110cc05066ff1f40f00&v=4 + url: https://github.com/sodaMelon +ceb10n: + login: ceb10n + count: 116 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +tokusumi: + login: tokusumi + count: 104 + avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 + url: https://github.com/tokusumi +hasansezertasan: + login: hasansezertasan + count: 95 + avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 + url: https://github.com/hasansezertasan +hard-coders: + login: hard-coders + count: 93 + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 + url: https://github.com/hard-coders +alv2017: + login: alv2017 + count: 88 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +nazarepiedady: + login: nazarepiedady + count: 86 + avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=f69ddc4ea8bda3bdfac7aa0e2ea38de282e6ee2d&v=4 + url: https://github.com/nazarepiedady +AlertRED: + login: AlertRED + count: 81 + avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 + url: https://github.com/AlertRED +Alexandrhub: + login: Alexandrhub + count: 68 + avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 + url: https://github.com/Alexandrhub +waynerv: + login: waynerv + count: 63 + avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 + url: https://github.com/waynerv +cassiobotaro: + login: cassiobotaro + count: 62 + avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4 + url: https://github.com/cassiobotaro +nilslindemann: + login: nilslindemann + count: 59 + avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 + url: https://github.com/nilslindemann +mattwang44: + login: mattwang44 + count: 59 + avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4 + url: https://github.com/mattwang44 +tiangolo: + login: tiangolo + count: 55 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +Laineyzhang55: + login: Laineyzhang55 + count: 48 + avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4 + url: https://github.com/Laineyzhang55 +Kludex: + login: Kludex + count: 47 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex +komtaki: + login: komtaki + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 + url: https://github.com/komtaki +rostik1410: + login: rostik1410 + count: 42 + avatarUrl: https://avatars.githubusercontent.com/u/11443899?u=e26a635c2ba220467b308a326a579b8ccf4a8701&v=4 + url: https://github.com/rostik1410 +svlandeg: + login: svlandeg + count: 42 + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg +alperiox: + login: alperiox + count: 42 + avatarUrl: https://avatars.githubusercontent.com/u/34214152?u=2c5acad3461d4dbc2d48371ba86cac56ae9b25cc&v=4 + url: https://github.com/alperiox +Rishat-F: + login: Rishat-F + count: 42 + avatarUrl: https://avatars.githubusercontent.com/u/66554797?v=4 + url: https://github.com/Rishat-F +Winand: + login: Winand + count: 40 + avatarUrl: https://avatars.githubusercontent.com/u/53390?u=bb0e71a2fc3910a8e0ee66da67c33de40ea695f8&v=4 + url: https://github.com/Winand +YuriiMotov: + login: YuriiMotov + count: 40 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov +solomein-sv: + login: solomein-sv + count: 38 + avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4 + url: https://github.com/solomein-sv +JavierSanchezCastro: + login: JavierSanchezCastro + count: 38 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +alejsdev: + login: alejsdev + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=447d12a1b347f466b35378bee4c7104cc9b2c571&v=4 + url: https://github.com/alejsdev +stlucasgarcia: + login: stlucasgarcia + count: 36 + avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=c22d8850e9dc396a8820766a59837f967e14f9a0&v=4 + url: https://github.com/stlucasgarcia +SwftAlpc: + login: SwftAlpc + count: 36 + avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 + url: https://github.com/SwftAlpc +timothy-jeong: + login: timothy-jeong + count: 36 + avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=db3d0cea2f5fab64d810113c5039a369699a2774&v=4 + url: https://github.com/timothy-jeong +mezgoodle: + login: mezgoodle + count: 35 + avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4 + url: https://github.com/mezgoodle +rjNemo: + login: rjNemo + count: 34 + avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 + url: https://github.com/rjNemo +codingjenny: + login: codingjenny + count: 34 + avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4 + url: https://github.com/codingjenny +akarev0: + login: akarev0 + count: 33 + avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4 + url: https://github.com/akarev0 +romashevchenko: + login: romashevchenko + count: 32 + avatarUrl: https://avatars.githubusercontent.com/u/132477732?v=4 + url: https://github.com/romashevchenko +LorhanSohaky: + login: LorhanSohaky + count: 30 + avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 + url: https://github.com/LorhanSohaky +Vincy1230: + login: Vincy1230 + count: 30 + avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4 + url: https://github.com/Vincy1230 +black-redoc: + login: black-redoc + count: 29 + avatarUrl: https://avatars.githubusercontent.com/u/18581590?u=7b6336166d0797fbbd44ea70c1c3ecadfc89af9e&v=4 + url: https://github.com/black-redoc +pedabraham: + login: pedabraham + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/16860088?u=abf922a7b920bf8fdb7867d8b43e091f1e796178&v=4 + url: https://github.com/pedabraham +Smlep: + login: Smlep + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/16785985?u=ffe99fa954c8e774ef1117e58d34aece92051e27&v=4 + url: https://github.com/Smlep +dedkot01: + login: dedkot01 + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/26196675?u=e2966887124e67932853df4f10f86cb526edc7b0&v=4 + url: https://github.com/dedkot01 +hsuanchi: + login: hsuanchi + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/24913710?u=7d25a398e478b6e63503bf6f26c54efa9e0da07b&v=4 + url: https://github.com/hsuanchi +dpinezich: + login: dpinezich + count: 28 + avatarUrl: https://avatars.githubusercontent.com/u/3204540?u=a2e1465e3ee10d537614d513589607eddefde09f&v=4 + url: https://github.com/dpinezich +maoyibo: + login: maoyibo + count: 27 + avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 + url: https://github.com/maoyibo +0417taehyun: + login: 0417taehyun + count: 27 + avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4 + url: https://github.com/0417taehyun +BilalAlpaslan: + login: BilalAlpaslan + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 + url: https://github.com/BilalAlpaslan +junah201: + login: junah201 + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/75025529?u=2451c256e888fa2a06bcfc0646d09b87ddb6a945&v=4 + url: https://github.com/junah201 +zy7y: + login: zy7y + count: 25 + avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4 + url: https://github.com/zy7y +mycaule: + login: mycaule + count: 25 + avatarUrl: https://avatars.githubusercontent.com/u/6161385?u=e3cec75bd6d938a0d73fae0dc5534d1ab2ed1b0e&v=4 + url: https://github.com/mycaule +Aruelius: + login: Aruelius + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/25380989?u=574f8cfcda3ea77a3f81884f6b26a97068e36a9d&v=4 + url: https://github.com/Aruelius +wisderfin: + login: wisderfin + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/77553770?u=9a23740d520d65dc0051cdc1ecd87f31cb900313&v=4 + url: https://github.com/wisderfin +OzgunCaglarArslan: + login: OzgunCaglarArslan + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/86166426?v=4 + url: https://github.com/OzgunCaglarArslan +sh0nk: + login: sh0nk + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 + url: https://github.com/sh0nk +axel584: + login: axel584 + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 + url: https://github.com/axel584 +DianaTrufanova: + login: DianaTrufanova + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/119067607?u=1cd55f841b68b4a187fa6d06a7dafa5f070195aa&v=4 + url: https://github.com/DianaTrufanova +AGolicyn: + login: AGolicyn + count: 21 + avatarUrl: https://avatars.githubusercontent.com/u/86262613?u=3c21606ab8d210a061a1673decff1e7d5592b380&v=4 + url: https://github.com/AGolicyn +Attsun1031: + login: Attsun1031 + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 + url: https://github.com/Attsun1031 +ycd: + login: ycd + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 + url: https://github.com/ycd +delhi09: + login: delhi09 + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 + url: https://github.com/delhi09 +rogerbrinkmann: + login: rogerbrinkmann + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/5690226?v=4 + url: https://github.com/rogerbrinkmann +DevDae: + login: DevDae + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4 + url: https://github.com/DevDae +sattosan: + login: sattosan + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4 + url: https://github.com/sattosan +yes0ng: + login: yes0ng + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/25501794?u=3aed18b0d491e0220a167a1e9e58bea3638c6707&v=4 + url: https://github.com/yes0ng +ComicShrimp: + login: ComicShrimp + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=d2fbf412e7730183ce91686ca48d4147e1b7dc74&v=4 + url: https://github.com/ComicShrimp +simatheone: + login: simatheone + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/78508673?u=1b9658d9ee0bde33f56130dd52275493ddd38690&v=4 + url: https://github.com/simatheone +ivan-abc: + login: ivan-abc + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4 + url: https://github.com/ivan-abc +Limsunoh: + login: Limsunoh + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/90311848?u=f456e0c5709fd50c8cd2898b551558eda14e5f21&v=4 + url: https://github.com/Limsunoh +SofiiaTrufanova: + login: SofiiaTrufanova + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/63260929?u=483e0b64fabc76343b3be39b7e1dcb930a95e1bb&v=4 + url: https://github.com/SofiiaTrufanova +bezaca: + login: bezaca + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4 + url: https://github.com/bezaca +lbmendes: + login: lbmendes + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/80999926?u=646619e2f07ac5a7c3f65fe7834197461a4fff9f&v=4 + url: https://github.com/lbmendes +spacesphere: + login: spacesphere + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/34628304?u=cde91f6002dd33156e1bf8005f11a7a3ed76b790&v=4 + url: https://github.com/spacesphere +panko: + login: panko + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/1569515?u=a84a5d255621ed82f8e1ca052f5f2eeb75997da2&v=4 + url: https://github.com/panko +jeison-araya: + login: jeison-araya + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/57369279?u=17001e68af7d8e5b8c343e5e9df4050f419998d5&v=4 + url: https://github.com/jeison-araya +yanever: + login: yanever + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4 + url: https://github.com/yanever +mastizada: + login: mastizada + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/1975818?u=0751a06d7271c8bf17cb73b1b845644ab4d2c6dc&v=4 + url: https://github.com/mastizada +Joao-Pedro-P-Holanda: + login: Joao-Pedro-P-Holanda + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 + url: https://github.com/Joao-Pedro-P-Holanda +JaeHyuckSa: + login: JaeHyuckSa + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/104830931?u=f3b4a2baea550f428a4c602a30ebee6721c1e3df&v=4 + url: https://github.com/JaeHyuckSa +Jedore: + login: Jedore + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/17944025?u=81d503e1c800eb666b3861ca47a3a773bbc3f539&v=4 + url: https://github.com/Jedore +kim-sangah: + login: kim-sangah + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/173775778?v=4 + url: https://github.com/kim-sangah +PandaHun: + login: PandaHun + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/13096845?u=646eba44db720e37d0dbe8e98e77ab534ea78a20&v=4 + url: https://github.com/PandaHun +dukkee: + login: dukkee + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/36825394?u=ccfd86e6a4f2d093dad6f7544cc875af67fa2df8&v=4 + url: https://github.com/dukkee +BORA040126: + login: BORA040126 + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/88664069?u=98e382727a485971e04aaa7c873d9a75a17ee3be&v=4 + url: https://github.com/BORA040126 +mattkoehne: + login: mattkoehne + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/80362153?v=4 + url: https://github.com/mattkoehne +jovicon: + login: jovicon + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4 + url: https://github.com/jovicon +izaguerreiro: + login: izaguerreiro + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4 + url: https://github.com/izaguerreiro +jburckel: + login: jburckel + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/11768758?u=044462e4130e086a0621f4abb45f0d7a289ab7fa&v=4 + url: https://github.com/jburckel +peidrao: + login: peidrao + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=64c634bb10381905038ff7faf3c8c3df47fb799a&v=4 + url: https://github.com/peidrao +impocode: + login: impocode + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/109408819?u=9cdfc5ccb31a2094c520f41b6087012fa9048982&v=4 + url: https://github.com/impocode +wesinalves: + login: wesinalves + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/13563128?u=9eb17ed50645dd684bfec47e75dba4e9772ec9c1&v=4 + url: https://github.com/wesinalves +NastasiaSaby: + login: NastasiaSaby + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/8245071?u=b3afd005f9e4bf080c219ef61a592b3a8004b764&v=4 + url: https://github.com/NastasiaSaby +oandersonmagalhaes: + login: oandersonmagalhaes + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4 + url: https://github.com/oandersonmagalhaes +mkdir700: + login: mkdir700 + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/56359329?u=3d6ea8714f5000829b60dcf7b13a75b1e73aaf47&v=4 + url: https://github.com/mkdir700 +batlopes: + login: batlopes + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/33462923?u=0fb3d7acb316764616f11e4947faf080e49ad8d9&v=4 + url: https://github.com/batlopes +joonas-yoon: + login: joonas-yoon + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/9527681?u=0166d22ef4749e617c6516e79f833cd8d73f1949&v=4 + url: https://github.com/joonas-yoon +baseplate-admin: + login: baseplate-admin + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/61817579?u=ce4c268fa949ae9a0290996e7949195302055812&v=4 + url: https://github.com/baseplate-admin +KaniKim: + login: KaniKim + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/19832624?u=296dbdd490e0eb96e3d45a2608c065603b17dc31&v=4 + url: https://github.com/KaniKim +andersonrocha0: + login: andersonrocha0 + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/22346169?u=93a1359c8c5461d894802c0cc65bcd09217e7a02&v=4 + url: https://github.com/andersonrocha0 +gitgernit: + login: gitgernit + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/129539613?u=d04f10143ab32c93f563ea14bf242d1d2bc991b0&v=4 + url: https://github.com/gitgernit +kwang1215: + login: kwang1215 + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/74170199?u=2a63ff6692119dde3f5e5693365b9fcd6f977b08&v=4 + url: https://github.com/kwang1215 +AdrianDeAnda: + login: AdrianDeAnda + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=b2ea249c6b41ddf98679c8d110d0f67d4a3ebf93&v=4 + url: https://github.com/AdrianDeAnda +blt232018: + login: blt232018 + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4 + url: https://github.com/blt232018 +NinaHwang: + login: NinaHwang + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=241f2cb6d38a2d379536608a8ea5a22ed4b1a3ea&v=4 + url: https://github.com/NinaHwang +glsglsgls: + login: glsglsgls + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/76133879?v=4 + url: https://github.com/glsglsgls +k94-ishi: + login: k94-ishi + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/32672580?u=bc7c5c07af0656be9fe4f1784a444af8d81ded89&v=4 + url: https://github.com/k94-ishi +codespearhead: + login: codespearhead + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/72931357?u=0fce6b82219b604d58adb614a761556425579cb5&v=4 + url: https://github.com/codespearhead +emrhnsyts: + login: emrhnsyts + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/42899027?u=ad26798e3f8feed2041c5dd5f87e58933d6c3283&v=4 + url: https://github.com/emrhnsyts +Lufa1u: + login: Lufa1u + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/112495876?u=087658920ed9e74311597bdd921d8d2de939d276&v=4 + url: https://github.com/Lufa1u +waketzheng: + login: waketzheng + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/35413830?u=df19e4fd5bb928e7d086e053ef26a46aad23bf84&v=4 + url: https://github.com/waketzheng +KNChiu: + login: KNChiu + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/36751646?v=4 + url: https://github.com/KNChiu +maru0123-2004: + login: maru0123-2004 + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4 + url: https://github.com/maru0123-2004 +mariacamilagl: + login: mariacamilagl + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 + url: https://github.com/mariacamilagl +ryuckel: + login: ryuckel + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/36391432?u=094eec0cfddd5013f76f31e55e56147d78b19553&v=4 + url: https://github.com/ryuckel +umitkaanusta: + login: umitkaanusta + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/53405015?v=4 + url: https://github.com/umitkaanusta +kty4119: + login: kty4119 + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4 + url: https://github.com/kty4119 +RobotToI: + login: RobotToI + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/44951382?u=e41dbc19191ce7abed86694b1a44ea0523e1c60e&v=4 + url: https://github.com/RobotToI +vitumenezes: + login: vitumenezes + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/9680878?u=05fd25cfafdc09382bf8907c37293a696c205754&v=4 + url: https://github.com/vitumenezes +fcrozetta: + login: fcrozetta + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/8006246?u=fa2a743e803de2c3a84d3ed8042faefed16c5e43&v=4 + url: https://github.com/fcrozetta +sUeharaE4: + login: sUeharaE4 + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/44468359?v=4 + url: https://github.com/sUeharaE4 +Ernilia: + login: Ernilia + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/125735800?u=13bfaac417a53fd5b5cf992efea363ca72598813&v=4 + url: https://github.com/Ernilia +socket-socket: + login: socket-socket + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/121552599?u=104df6503242e8d762fe293e7036f7260f245d49&v=4 + url: https://github.com/socket-socket +nick-cjyx9: + login: nick-cjyx9 + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/119087246?u=7227a2de948c68fb8396d5beff1ee5b0e057c42e&v=4 + url: https://github.com/nick-cjyx9 +lucasbalieiro: + login: lucasbalieiro + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=dad91601ee4f40458d691774ec439aff308344d7&v=4 + url: https://github.com/lucasbalieiro +Zhongheng-Cheng: + login: Zhongheng-Cheng + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4 + url: https://github.com/Zhongheng-Cheng +RunningIkkyu: + login: RunningIkkyu + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 + url: https://github.com/RunningIkkyu +JulianMaurin: + login: JulianMaurin + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/63545168?u=b7d15ac865268cbefc2d739e2f23d9aeeac1a622&v=4 + url: https://github.com/JulianMaurin +JeongHyeongKim: + login: JeongHyeongKim + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/26577800?u=77f060f4686f32c248907b81b16ee2b3177ca44c&v=4 + url: https://github.com/JeongHyeongKim +arthurio: + login: arthurio + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/950449?u=76b997138273ce5e1990b971c4f27c9aff979fd5&v=4 + url: https://github.com/arthurio +Lenclove: + login: Lenclove + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/32355298?u=d0065e01650c63c2b2413f42d983634b2ea85481&v=4 + url: https://github.com/Lenclove +eVery1337: + login: eVery1337 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/84917945?u=7af243f05ecfba59191199a70d8ba365c1327768&v=4 + url: https://github.com/eVery1337 +aykhans: + login: aykhans + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/88669260?u=798da457cc3276d3c6dd7fd628d0005ad8b298cc&v=4 + url: https://github.com/aykhans +riroan: + login: riroan + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/33053284?u=2d18e3771506ee874b66d6aa2b3b1107fd95c38f&v=4 + url: https://github.com/riroan +MinLee0210: + login: MinLee0210 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/57653278?u=8ca05a7efbc76048183da00da87d148b755a3ba8&v=4 + url: https://github.com/MinLee0210 +yodai-yodai: + login: yodai-yodai + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/7031039?u=4f3593f5931892b931a745cfab846eff6e9332e7&v=4 + url: https://github.com/yodai-yodai +marcelomarkus: + login: marcelomarkus + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/20115018?u=dda090ce9160ef0cd2ff69b1e5ea741283425cba&v=4 + url: https://github.com/marcelomarkus +JoaoGustavoRogel: + login: JoaoGustavoRogel + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/29525510?u=a0a91251f5e43e132608d55d28ccb8645c5ea405&v=4 + url: https://github.com/JoaoGustavoRogel +Yarous: + login: Yarous + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/61277193?u=5b462347458a373b2d599c6f416d2b75eddbffad&v=4 + url: https://github.com/Yarous +dimaqq: + login: dimaqq + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/662249?u=15313dec91bae789685e4abb3c2152251de41948&v=4 + url: https://github.com/dimaqq +julianofischer: + login: julianofischer + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/158303?u=d91662eb949d4cc7368831cf37a5cdfd90b7010c&v=4 + url: https://github.com/julianofischer +bnzone: + login: bnzone + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/39371503?u=c16f00c41d88479fa2d57b0d7d233b758eacce2d&v=4 + url: https://github.com/bnzone +shamosishen: + login: shamosishen + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/9498321?u=c83c20c79e019a0b555a125adf20fc4fb7a882c8&v=4 + url: https://github.com/shamosishen +mertssmnoglu: + login: mertssmnoglu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/61623638?u=59dd885b68ff1832f9ab3b4a4446896358c23442&v=4 + url: https://github.com/mertssmnoglu +mahone3297: + login: mahone3297 + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/1701379?u=20588ff0e456d13e8017333eb237595d11410234&v=4 + url: https://github.com/mahone3297 +KimJoonSeo: + login: KimJoonSeo + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/17760162?u=a58cdc77ae1c069a64166f7ecc4d42eecfd9a468&v=4 + url: https://github.com/KimJoonSeo +camigomezdev: + login: camigomezdev + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/16061815?u=25b5ebc042fff53fa03dc107ded10e36b1b7a5b9&v=4 + url: https://github.com/camigomezdev +minaton-ru: + login: minaton-ru + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/53541518?u=67336ca11a85493f75031508aade588dad3b9910&v=4 + url: https://github.com/minaton-ru +sungchan1: + login: sungchan1 + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/28076127?u=a816d86ef3e60450a7225f128caf9a394c9320f9&v=4 + url: https://github.com/sungchan1 +Serrones: + login: Serrones + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 + url: https://github.com/Serrones +israteneda: + login: israteneda + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/20668624?u=67574648f89019d1c73b16a6a009da659557f9e5&v=4 + url: https://github.com/israteneda +krocdort: + login: krocdort + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4 + url: https://github.com/krocdort +anthonycepeda: + login: anthonycepeda + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 + url: https://github.com/anthonycepeda +fabioueno: + login: fabioueno + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/14273852?u=edd700982b16317ac6ebfd24c47bc0029b21d360&v=4 + url: https://github.com/fabioueno +cfraboulet: + login: cfraboulet + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/62244267?u=ed0e286ba48fa1dafd64a08e50f3364b8e12df34&v=4 + url: https://github.com/cfraboulet +HiemalBeryl: + login: HiemalBeryl + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/63165207?u=276f4af2829baf28b912c718675852bfccb0e7b4&v=4 + url: https://github.com/HiemalBeryl +pablocm83: + login: pablocm83 + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4 + url: https://github.com/pablocm83 +d2a-raudenaerde: + login: d2a-raudenaerde + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/5213150?v=4 + url: https://github.com/d2a-raudenaerde +valentinDruzhinin: + login: valentinDruzhinin + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +Zerohertz: + login: Zerohertz + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/42334717?u=5ebf4d33e73b1ad373154f6cdee44f7cab4d05ba&v=4 + url: https://github.com/Zerohertz +deniscapeto: + login: deniscapeto + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/12864353?u=20c5b2300b264a585a8381acf3cef44bcfcc1ead&v=4 + url: https://github.com/deniscapeto +bsab: + login: bsab + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/9799799?u=c4a09b1abb794cd8280c4793d43d0e2eb963ecda&v=4 + url: https://github.com/bsab +ArcLightSlavik: + login: ArcLightSlavik + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 + url: https://github.com/ArcLightSlavik +Cajuteq: + login: Cajuteq + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/26676532?u=8ee0422981810e51480855de1c0d67b6b79cd3f2&v=4 + url: https://github.com/Cajuteq +emmrichard: + login: emmrichard + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/1328018?u=8114d8fc0e8e42a092e4283013a1c54b792c466b&v=4 + url: https://github.com/emmrichard +wakabame: + login: wakabame + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/35513518?u=41ef6b0a55076e5c540620d68fb006e386c2ddb0&v=4 + url: https://github.com/wakabame +mawassk: + login: mawassk + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/84179197?v=4 + url: https://github.com/mawassk +diogoduartec: + login: diogoduartec + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=7514a5f05fcbeccc62f8c5dc25879efeb1ef9335&v=4 + url: https://github.com/diogoduartec +aqcool: + login: aqcool + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/52229895?v=4 + url: https://github.com/aqcool +'1320555911': + login: '1320555911' + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/58590086?u=6d8f4fbf08d5ac72c1c895892c461c5e0b013dc3&v=4 + url: https://github.com/1320555911 +mcthesw: + login: mcthesw + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/61224072?u=82a1b106298348f060c3f4f39817e0cae5ce2b7c&v=4 + url: https://github.com/mcthesw +xzmeng: + login: xzmeng + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/40202897?v=4 + url: https://github.com/xzmeng +negadive: + login: negadive + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/47322392?u=c1be2e9b9b346b4a77d9157da2a5739ab25ce0f8&v=4 + url: https://github.com/negadive +mbroton: + login: mbroton + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/50829834?u=a48610bf1bffaa9c75d03228926e2eb08a2e24ee&v=4 + url: https://github.com/mbroton +Kirilex: + login: Kirilex + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/100281552?v=4 + url: https://github.com/Kirilex +arunppsg: + login: arunppsg + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/26398753?v=4 + url: https://github.com/arunppsg +dimastbk: + login: dimastbk + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/3132181?u=66587398d43466a1dc75c238df5f048e0afc77ed&v=4 + url: https://github.com/dimastbk +dudyaosuplayer: + login: dudyaosuplayer + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/62661898?u=7864cc5f01b1c845ae8ad49acf45dec6faca0c57&v=4 + url: https://github.com/dudyaosuplayer +talhaumer: + login: talhaumer + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/46643702?u=5d1fd7057ea9534fb3221931b809a3d750157212&v=4 + url: https://github.com/talhaumer +bankofsardine: + login: bankofsardine + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/44944207?u=0368e1b698ffab6bf29e202f9fd2dddd352429f1&v=4 + url: https://github.com/bankofsardine +Rekl0w: + login: Rekl0w + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/91488737?u=3b62b04a3e6699eab9b1eea4e88c09a39b753a17&v=4 + url: https://github.com/Rekl0w +rsip22: + login: rsip22 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/16676222?v=4 + url: https://github.com/rsip22 +jessicapaz: + login: jessicapaz + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/20428941?u=6ffdaab5a85bf77a2d8870dade5e53555f34577b&v=4 + url: https://github.com/jessicapaz +mohsen-mahmoodi: + login: mohsen-mahmoodi + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/2872586?u=3a9fc1aa16a3a0ab93a1f8550de82a940592857d&v=4 + url: https://github.com/mohsen-mahmoodi +jeesang7: + login: jeesang7 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/30719956?u=35fc8bca04d32d3c4ce085956f0636b959ba30f6&v=4 + url: https://github.com/jeesang7 +TemaSpb: + login: TemaSpb + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/20205738?u=d7dce0718720a7107803a573d628d8dd3d5c2fb4&v=4 + url: https://github.com/TemaSpb +BugLight: + login: BugLight + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/13618366?u=7d733749f80e5f7e66a434cf42aedcfc60340f43&v=4 + url: https://github.com/BugLight +0x4Dark: + login: 0x4Dark + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/7569289?v=4 + url: https://github.com/0x4Dark +Wuerike: + login: Wuerike + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/35462243?u=80c753dedf4a78db12ef66316dbdebbe6d84a2b9&v=4 + url: https://github.com/Wuerike +jvmazagao: + login: jvmazagao + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/22477816?u=2b57addf5830906bf6ae5f25cd4c8c2fa5c2d68e&v=4 + url: https://github.com/jvmazagao +cun3yt: + login: cun3yt + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/24409240?u=06abfd77786db859b0602d5369d2ae18c932c17c&v=4 + url: https://github.com/cun3yt +Mordson: + login: Mordson + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/39025897?u=b94ea96ef35bbe43bc85359cfb31d28ac16d470c&v=4 + url: https://github.com/Mordson +aminkhani: + login: aminkhani + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/51851950?u=051896c4933816bc61d11091d887f6e8dfd1d27b&v=4 + url: https://github.com/aminkhani +nifadyev: + login: nifadyev + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/36514612?u=e101da8641d5a09901d2155255a93f8ab3d9c468&v=4 + url: https://github.com/nifadyev +LaurEars: + login: LaurEars + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/4914725?v=4 + url: https://github.com/LaurEars +Chushine: + login: Chushine + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/135534400?v=4 + url: https://github.com/Chushine +ChuyuChoyeon: + login: ChuyuChoyeon + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/129537877?u=f0c76f3327817a8b86b422d62e04a34bf2827f2b&v=4 + url: https://github.com/ChuyuChoyeon +frwl404: + login: frwl404 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/42642656?u=8395a3d991d9fac86901277d76f0f70857b56ec5&v=4 + url: https://github.com/frwl404 +esrefzeki: + login: esrefzeki + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/54935247?u=193cf5a169ca05fc54995a4dceabc82c7dc6e5ea&v=4 + url: https://github.com/esrefzeki +dtleal: + login: dtleal + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/31096951?u=704664ec74ab655485e5c909b25de3fa09a922ba&v=4 + url: https://github.com/dtleal +art3xa: + login: art3xa + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/92092049?v=4 + url: https://github.com/art3xa +SamuelBFavarin: + login: SamuelBFavarin + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/17628602?u=5aac13ae492fa9a86e397a70803ac723dba2efe7&v=4 + url: https://github.com/SamuelBFavarin +takacs: + login: takacs + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/44911031?u=f6c6b70b3ba86ceb93b0f9bcab609bf9328b2305&v=4 + url: https://github.com/takacs +anton2yakovlev: + login: anton2yakovlev + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/44229180?u=bdd445ba99074b378e7298d23c4bf6d707d2c282&v=4 + url: https://github.com/anton2yakovlev +ILoveSorasakiHina: + login: ILoveSorasakiHina + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/114038930?u=3d3ed8dc3bf57e641d1b26badee5bc79ef34f25b&v=4 + url: https://github.com/ILoveSorasakiHina +devluisrodrigues: + login: devluisrodrigues + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/103431660?u=d9674a3249edc4601d2c712cdebf899918503c3a&v=4 + url: https://github.com/devluisrodrigues +11kkw: + login: 11kkw + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/21125286?v=4 + url: https://github.com/11kkw +lpdswing: + login: lpdswing + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/20874036?u=7a4fc3e4d0719e37b305deb7af234a7b63200787&v=4 + url: https://github.com/lpdswing +SepehrRasouli: + login: SepehrRasouli + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/81516241?u=3987e880c77d653dd85963302150e07bb7c0ef99&v=4 + url: https://github.com/SepehrRasouli +Zxilly: + login: Zxilly + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/31370133?u=c5359b8d9d80a7cdc23d5295d179ed90174996c8&v=4 + url: https://github.com/Zxilly +eavv: + login: eavv + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/18273429?u=c05e8b4ea62810ee7889ca049e510cdd0a66fd26&v=4 + url: https://github.com/eavv +AlexandreBiguet: + login: AlexandreBiguet + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1483079?u=ff926455cd4cab03c6c49441aa5dc2b21df3e266&v=4 + url: https://github.com/AlexandreBiguet +FelipeSilva93: + login: FelipeSilva93 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/66804965?u=e7cb4b580e46f2e04ecb4cd4d7a12acdddd3c6c1&v=4 + url: https://github.com/FelipeSilva93 +peacekimjapan: + login: peacekimjapan + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/33534175?u=e4219bcebc3773a7068cc34c3eb268ef77cec31b&v=4 + url: https://github.com/peacekimjapan +bas-baskara: + login: bas-baskara + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/41407847?u=cdabfaff7481c3323f24a76d9350393b964f2b89&v=4 + url: https://github.com/bas-baskara +odiseo0: + login: odiseo0 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&v=4 + url: https://github.com/odiseo0 +eryknn: + login: eryknn + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/87120651?v=4 + url: https://github.com/eryknn +personage-hub: + login: personage-hub + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/76659786?v=4 + url: https://github.com/personage-hub +aminalaee: + login: aminalaee + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/19784933?v=4 + url: https://github.com/aminalaee +erfan-rfmhr: + login: erfan-rfmhr + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/98986056?u=0acda1ff1df0989f3f3eb79977baa35da4cb6c8c&v=4 + url: https://github.com/erfan-rfmhr +Scorpionchiques: + login: Scorpionchiques + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/15703294?v=4 + url: https://github.com/Scorpionchiques +lordqyxz: + login: lordqyxz + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/31722468?u=974553c0ba53526d9be7e9876544283291be3b0d&v=4 + url: https://github.com/lordqyxz +heysaeid: + login: heysaeid + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/63112273?u=5397ead391319a147a18b70cc04d1a334f235ef3&v=4 + url: https://github.com/heysaeid +Yois4101: + login: Yois4101 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/119609381?v=4 + url: https://github.com/Yois4101 +tamtam-fitness: + login: tamtam-fitness + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/62091034?u=8da19a6bd3d02f5d6ba30c7247d5b46c98dd1403&v=4 + url: https://github.com/tamtam-fitness +mpmeleshko: + login: mpmeleshko + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/34425664?v=4 + url: https://github.com/mpmeleshko +SonnyYou: + login: SonnyYou + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/18657569?v=4 + url: https://github.com/SonnyYou +matiasbertani: + login: matiasbertani + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/65260383?u=d5edd86a6e2ab4fb1aab7751931fe045a963afd7&v=4 + url: https://github.com/matiasbertani +thiennc254: + login: thiennc254 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/97406628?u=1b2860679694b9a552764d0fa81dbd7a016322ec&v=4 + url: https://github.com/thiennc254 +javillegasna: + login: javillegasna + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/38879192?u=df9ab0d628f8c1f1c849db7b3c0939337f42c3f1&v=4 + url: https://github.com/javillegasna +9zimin9: + login: 9zimin9 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/174453744?v=4 + url: https://github.com/9zimin9 +ilhamfadillah: + login: ilhamfadillah + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/20577838?u=c56192cf99b55affcaad408b240259c62e633450&v=4 + url: https://github.com/ilhamfadillah +gerry-sabar: + login: gerry-sabar + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1120123?v=4 + url: https://github.com/gerry-sabar +cookie-byte217: + login: cookie-byte217 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/57880178?v=4 + url: https://github.com/cookie-byte217 +AbolfazlKameli: + login: AbolfazlKameli + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/120686133?u=e41743da3c1820efafc59c5870cacd4f4425334c&v=4 + url: https://github.com/AbolfazlKameli +tyronedamasceno: + login: tyronedamasceno + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/12273721?u=913bca6bab96d9416ad8c9874c80de0833782050&v=4 + url: https://github.com/tyronedamasceno +LikoIlya: + login: LikoIlya + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/15039930?v=4 + url: https://github.com/LikoIlya +ss-o-furda: + login: ss-o-furda + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/56111536?u=d2326baa464a3778c280ed85fd14c00f87eb1080&v=4 + url: https://github.com/ss-o-furda +Frans06: + login: Frans06 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/5842109?u=77529d5517ae80438249b1a45f2d59372a31a212&v=4 + url: https://github.com/Frans06 +Jefidev: + login: Jefidev + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/9964497?u=1da6eee587a8b425ca4afbfdfc6c3a639fe85d02&v=4 + url: https://github.com/Jefidev +Xaraxx: + login: Xaraxx + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/29824698?u=dde2e233e22bb5ca1f8bb0c6e353ccd0d06e6066&v=4 + url: https://github.com/Xaraxx +Suyoung789: + login: Suyoung789 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/31277231?u=744bd3e641413e19bfad6b06a90bb0887c3f9332&v=4 + url: https://github.com/Suyoung789 +akagaeng: + login: akagaeng + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/17076841?u=9ada2eb6a33dc705ba96d58f802c787dea3859b8&v=4 + url: https://github.com/akagaeng +phamquanganh31101998: + login: phamquanganh31101998 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/43257497?u=36fa4ee689415d869a98453083a7c4213d2136ee&v=4 + url: https://github.com/phamquanganh31101998 +peebbv6364: + login: peebbv6364 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/26784747?u=75583df215ee01a5cd2dc646aecb81e7dbd33d06&v=4 + url: https://github.com/peebbv6364 +mrparalon: + login: mrparalon + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/19637629?u=6339508ceb665717cae862a4d33816ac874cbb8f&v=4 + url: https://github.com/mrparalon +creyD: + login: creyD + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/15138480?u=51cd2873cd93807beb578af8e23975856fdbc945&v=4 + url: https://github.com/creyD +zhoonit: + login: zhoonit + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/17230883?u=698cb26dcce4770374b592aad3b7489e91c07fc6&v=4 + url: https://github.com/zhoonit +Sefank: + login: Sefank + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/12670778?u=ca16995c68a82cabc7435c54ac0564930f62dd59&v=4 + url: https://github.com/Sefank +RuslanTer: + login: RuslanTer + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/48125303?v=4 + url: https://github.com/RuslanTer +FedorGN: + login: FedorGN + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/66411909?u=22382380e7d66ee57ffbfc2ae6bd5efd0cdb672e&v=4 + url: https://github.com/FedorGN +rafsaf: + login: rafsaf + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4 + url: https://github.com/rafsaf +frnsimoes: + login: frnsimoes + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/66239468?u=fd8d408946633acc4bea057c207e6c0833871527&v=4 + url: https://github.com/frnsimoes +lieryan: + login: lieryan + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1006989?v=4 + url: https://github.com/lieryan +ValeryVal: + login: ValeryVal + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/85856176?v=4 + url: https://github.com/ValeryVal +chesstrian: + login: chesstrian + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/3923412?u=8ea9bea6cfb5e6c64dc81be65ac2a9aaf23c5d47&v=4 + url: https://github.com/chesstrian +PabloEmidio: + login: PabloEmidio + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/69937719?u=f4d04cb78da68bb93a641f0b793ff665162e712a&v=4 + url: https://github.com/PabloEmidio +PraveenNanda124: + login: PraveenNanda124 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/116082827?u=b40c4f23c191692e88f676dc3bf33fc7f315edd4&v=4 + url: https://github.com/PraveenNanda124 +guites: + login: guites + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/71985299?u=5dab5eb82b0a67fe709fc893f47a423df4de5d46&v=4 + url: https://github.com/guites +Junhyung21: + login: Junhyung21 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/138214497?u=66377988eaad4f57004decb183f396560407a73f&v=4 + url: https://github.com/Junhyung21 +rinaatt: + login: rinaatt + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/6111202?u=9f62ebd2a72879db54d0b51c07c1d1e7203a4813&v=4 + url: https://github.com/rinaatt +Slijeff: + login: Slijeff + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/31459252?u=083776331690bbcf427766071e33ac28bb8d271d&v=4 + url: https://github.com/Slijeff +GeorchW: + login: GeorchW + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/8687777?u=ae4160f1d88f32692760003f3be9b5fc40a6e00d&v=4 + url: https://github.com/GeorchW +Vlad0395: + login: Vlad0395 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/37487589?u=57dc6660b9904cc0bc59b73569bbfb1ac871a4a1&v=4 + url: https://github.com/Vlad0395 +bisibuka: + login: bisibuka + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/221887?v=4 + url: https://github.com/bisibuka +aimasheraz1: + login: aimasheraz1 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/132935019?v=4 + url: https://github.com/aimasheraz1 +whysage: + login: whysage + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/67018871?u=a05d63a1b315dcf56a4c0dda3c0ca84ce3d6c87f&v=4 + url: https://github.com/whysage +Chake9928: + login: Chake9928 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/62596047?u=7aa2c0aad46911934ce3d22f83a895d05fa54e09&v=4 + url: https://github.com/Chake9928 +qaerial: + login: qaerial + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/41595550?v=4 + url: https://github.com/qaerial +bluefish6: + login: bluefish6 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/3324881?u=d107f6d0017927191644829fb845a8ceb8ac20ee&v=4 + url: https://github.com/bluefish6 +Sion99: + login: Sion99 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/82511301?v=4 + url: https://github.com/Sion99 +nymous: + login: nymous + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 + url: https://github.com/nymous +EpsilonRationes: + login: EpsilonRationes + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/148639079?v=4 + url: https://github.com/EpsilonRationes +SametEmin: + login: SametEmin + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/115692383?u=bda9052f698e50b0df6657fb9436d07e8496fe2f&v=4 + url: https://github.com/SametEmin +fhabers21: + login: fhabers21 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/58401847?v=4 + url: https://github.com/fhabers21 +kohiry: + login: kohiry + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/57669492?u=f6ab0a062740261e882879269a41a47788c84043&v=4 + url: https://github.com/kohiry +ptt3199: + login: ptt3199 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/51350651?u=2c3d947a80283e32bf616d4c3af139a6be69680f&v=4 + url: https://github.com/ptt3199 +arynoot: + login: arynoot + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/73756088?v=4 + url: https://github.com/arynoot +GDemay: + login: GDemay + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/7033942?u=bbdcb4e2a67df4ec9caa2440362d8cebc44d65e8&v=4 + url: https://github.com/GDemay +maxscheijen: + login: maxscheijen + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/47034840?v=4 + url: https://github.com/maxscheijen +celestywang: + login: celestywang + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/184830753?v=4 + url: https://github.com/celestywang +RyaWcksn: + login: RyaWcksn + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/42831964?u=0cb4265faf3e3425a89e59b6fddd3eb2de180af0&v=4 + url: https://github.com/RyaWcksn +tienduong-21: + login: tienduong-21 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/80129618?v=4 + url: https://github.com/tienduong-21 +zbellos: + login: zbellos + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/204500646?v=4 + url: https://github.com/zbellos +Mohammad222PR: + login: Mohammad222PR + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/116789737?u=25810a5fe049d2f1618e2e7417cea011cc353ce4&v=4 + url: https://github.com/Mohammad222PR +EdmilsonRodrigues: + login: EdmilsonRodrigues + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/62777025?u=217d6f3cd6cc750bb8818a3af7726c8d74eb7c2d&v=4 + url: https://github.com/EdmilsonRodrigues +blaisep: + login: blaisep + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/254456?u=97d584b7c0a6faf583aa59975df4f993f671d121&v=4 + url: https://github.com/blaisep +SirTelemak: + login: SirTelemak + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 + url: https://github.com/SirTelemak +ovezovs: + login: ovezovs + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/44060682?u=9cb4d738b15e64157cb65afbe2e31bd0c8f3f6e6&v=4 + url: https://github.com/ovezovs +neatek: + login: neatek + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/3075678?u=3001e778e4aa0bf6d3142d09f0b9d13b2c55066f&v=4 + url: https://github.com/neatek +sprytnyk: + login: sprytnyk + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/16718258?u=4893ea96bfebfbdbde8abd9e06851eca12b01bc9&v=4 + url: https://github.com/sprytnyk +wfpinedar: + login: wfpinedar + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/5309214?u=4af7b6b3907b015699a9994d0808137dd68f7658&v=4 + url: https://github.com/wfpinedar +italopenaforte: + login: italopenaforte + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7786881?u=e64a8f24b1ba95eb82f283be8ab90892e40c5465&v=4 + url: https://github.com/italopenaforte +hackerneocom: + login: hackerneocom + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/67042948?u=ca365045bd261cec5a64059aa23cf80065148c3c&v=4 + url: https://github.com/hackerneocom +dmas-at-wiris: + login: dmas-at-wiris + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/24917162?u=0df147936a375b4b64232c650de31a227a6b59a0&v=4 + url: https://github.com/dmas-at-wiris +TorhamDev: + login: TorhamDev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/87639984?u=07e5429fbd9c5d63c5ca55a0f31ef541216f0ce6&v=4 + url: https://github.com/TorhamDev +jaystone776: + login: jaystone776 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 + url: https://github.com/jaystone776 +AaronDewes: + login: AaronDewes + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/67546953?v=4 + url: https://github.com/AaronDewes +kunansy: + login: kunansy + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/20476946?u=d8321cd00787d5ee29bfdd8ff6fde23ad783a581&v=4 + url: https://github.com/kunansy +TimorChow: + login: TimorChow + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/18365403?u=bcbb357be0a447bc682a161932eab5032cede4af&v=4 + url: https://github.com/TimorChow +ataberkciftlikli: + login: ataberkciftlikli + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/64265169?u=ca7c1348242559f70bc1dc027a4be277c464676f&v=4 + url: https://github.com/ataberkciftlikli +leandrodesouzadev: + login: leandrodesouzadev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/85115541?u=4eb25f43f1fe23727d61e986cf83b73b86e2a95a&v=4 + url: https://github.com/leandrodesouzadev +dutkiewicz: + login: dutkiewicz + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6649846?u=e941be6e1ab2ffdf41cea227a73f0ffbef20628f&v=4 + url: https://github.com/dutkiewicz +mirusu400: + login: mirusu400 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/25397908?u=deda776115e4ee6f76fa526bb5127bd1a6c4b231&v=4 + url: https://github.com/mirusu400 +its0x08: + login: its0x08 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/15280042?u=d7c2058f29d4e8fbdae09b194e04c5e410350211&v=4 + url: https://github.com/its0x08 +linsein: + login: linsein + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/23748021?u=4db169ce262b69aa7292f82b785436544f69fb88&v=4 + url: https://github.com/linsein +0xflotus: + login: 0xflotus + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/26602940?u=3c52ce6393bb547c97e6380ccdee03e0c64152c6&v=4 + url: https://github.com/0xflotus +jonatasoli: + login: jonatasoli + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=f601c3f111f2148bd9244c2cb3ebbd57b592e674&v=4 + url: https://github.com/jonatasoli +tyzh-dev: + login: tyzh-dev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51972581?u=ba3882da7c009918a8e2d6b9ead31c89f09c922d&v=4 + url: https://github.com/tyzh-dev +yurkevich-dev: + login: yurkevich-dev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/45145188?u=db2de8c186073d95693279dcf085fcebffab57d0&v=4 + url: https://github.com/yurkevich-dev +emp7yhead: + login: emp7yhead + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/20521260?u=9494c74cb9e1601d734b1f2726e292e257777d98&v=4 + url: https://github.com/emp7yhead +BartoszCki: + login: BartoszCki + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/17833351?u=40025e1182c32a9664834baec268dadad127703d&v=4 + url: https://github.com/BartoszCki +hakancelikdev: + login: hakancelikdev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/19157033?u=095ea8e0af1de642edd92e5f806c70359e00c977&v=4 + url: https://github.com/hakancelikdev +KaterinaSolovyeva: + login: KaterinaSolovyeva + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/85114725?u=1fe81463cb6b1fd01ac047172fa4895e2a3cecaa&v=4 + url: https://github.com/KaterinaSolovyeva +zhanymkanov: + login: zhanymkanov + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/22341602?u=aa1c47285a4f5692d165ccb2a441c5553f23ef83&v=4 + url: https://github.com/zhanymkanov +felipebpl: + login: felipebpl + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62957465?u=3c05f0f358b9575503c03122daefb115b6ac1414&v=4 + url: https://github.com/felipebpl +iudeen: + login: iudeen + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=f09cdd745e5bf16138f29b42732dd57c7f02bee1&v=4 + url: https://github.com/iudeen +dwisulfahnur: + login: dwisulfahnur + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/12431528?v=4 + url: https://github.com/dwisulfahnur +ayr-ton: + login: ayr-ton + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1090517?u=5cf70a0e0f0dbf084e074e494aa94d7c91a46ba6&v=4 + url: https://github.com/ayr-ton +raphaelauv: + login: raphaelauv + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 + url: https://github.com/raphaelauv +Fahad-Md-Kamal: + login: Fahad-Md-Kamal + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/34704464?u=141086368c5557d5a1a533fe291f21f9fc584458&v=4 + url: https://github.com/Fahad-Md-Kamal +zxcq544: + login: zxcq544 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/5781268?u=25959ea03803742c3b28220b27fc07923a491dcb&v=4 + url: https://github.com/zxcq544 +AlexandrMaltsevYDX: + login: AlexandrMaltsevYDX + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/109986802?u=ed275d72bfcdb4d15abdd54e7be026adbb9ca098&v=4 + url: https://github.com/AlexandrMaltsevYDX +realFranco: + login: realFranco + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/45880759?u=22fea3007d3e2d4c8c82d6ccfbde71454c4c6dd8&v=4 + url: https://github.com/realFranco +piaria: + login: piaria + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/110835535?u=5af3d56254faa05bbca4258a46c5723489480f90&v=4 + url: https://github.com/piaria +mojtabapaso: + login: mojtabapaso + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/121169359?u=ced1d5ad673bcd9e949ebf967a4ab50185637443&v=4 + url: https://github.com/mojtabapaso +eghbalpoorMH: + login: eghbalpoorMH + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/36267498?v=4 + url: https://github.com/eghbalpoorMH +Tiazen: + login: Tiazen + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/16170159?u=0ce5e32f76e3f10733c8f25d97db9e31b753838c&v=4 + url: https://github.com/Tiazen +jfunez: + login: jfunez + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4 + url: https://github.com/jfunez +s-rigaud: + login: s-rigaud + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/46346622?u=eee0adaa9fdff9e312d52526fbd4020dd6860c27&v=4 + url: https://github.com/s-rigaud +Artem4es: + login: Artem4es + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/110793967?u=0f9d4e80e055adc1aa8b548e951f6b4989fa2e78&v=4 + url: https://github.com/Artem4es +sulemanhelp: + login: sulemanhelp + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/22400366?u=3e8e68750655c7f5b2e0ba1d54f5779ee526707d&v=4 + url: https://github.com/sulemanhelp +theRealNonso: + login: theRealNonso + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/29557286?u=6f062680edccfeb4c802daf3b1d8b2a9e21ae013&v=4 + url: https://github.com/theRealNonso +AhsanSheraz: + login: AhsanSheraz + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51913596?u=08e31cacb3048be30722c94010ddd028f3fdbec4&v=4 + url: https://github.com/AhsanSheraz +HealerNguyen: + login: HealerNguyen + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/29653304?u=6ab095689054c63b1f4ceb26dd66847450225c87&v=4 + url: https://github.com/HealerNguyen +isulim: + login: isulim + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/30448496?u=44c47838defa48a16606b895dce08890fca8482f&v=4 + url: https://github.com/isulim +siavashyj: + login: siavashyj + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/43583410?u=562005ddc7901cd27a1219a118a2363817b14977&v=4 + url: https://github.com/siavashyj +Ramin-RX7: + login: Ramin-RX7 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/52785580?u=b3678f779ad0ee9cd9dca9e50ccb804b5eb990a5&v=4 + url: https://github.com/Ramin-RX7 +DevSpace88: + login: DevSpace88 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/102557040?u=6b356e3e1b9b6bc6a208b363988d4089ef94193f&v=4 + url: https://github.com/DevSpace88 +Yum-git: + login: Yum-git + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/56100888?u=7c6ae21af081488b5fb703ab096fb1926025fd50&v=4 + url: https://github.com/Yum-git +oubush: + login: oubush + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7489099?u=c86448bc61f5e7f03a1f14a768beeb09c33899d4&v=4 + url: https://github.com/oubush +KAZAMA-DREAM: + login: KAZAMA-DREAM + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/73453137?u=5108c757a3842733a448d9a16cdc65d82899eee1&v=4 + url: https://github.com/KAZAMA-DREAM +aprilcoskun: + login: aprilcoskun + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/17393603?u=18177d5bdba3a4567b8664587c882fb734e5fa09&v=4 + url: https://github.com/aprilcoskun +zhiquanchi: + login: zhiquanchi + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/29973289?u=744c74bc2635f839235ec32a0a934c5cef9a156d&v=4 + url: https://github.com/zhiquanchi +Jamim: + login: Jamim + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/5607572?u=9ce0b6a6d1a5124e28b3c04d8d26827ca328713a&v=4 + url: https://github.com/Jamim +alvinkhalil: + login: alvinkhalil + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/84583022?u=ab0eeb9ce6ffe93fd9bb23daf782b9867b864149&v=4 + url: https://github.com/alvinkhalil +leylaeminova: + login: leylaeminova + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/100516839?u=0b0dab9e31742076b22812b14a39b4e6d8f6de4a&v=4 + url: https://github.com/leylaeminova +UN-9BOT: + login: UN-9BOT + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/111110804?u=39e158937ed795972c2d0400fc521c50e9bfb9e7&v=4 + url: https://github.com/UN-9BOT +flasonme: + login: flasonme + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/30571019?v=4 + url: https://github.com/flasonme +gustavoprezoto: + login: gustavoprezoto + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62812585?u=2e936a0c6a2f11ecf3a735ebd33386100bcfebf8&v=4 + url: https://github.com/gustavoprezoto +johnny630: + login: johnny630 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/2870590?v=4 + url: https://github.com/johnny630 +JCTrapero: + login: JCTrapero + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/109148166?u=bea607a04058176c4c2ae0d7c2e9ec647ccef002&v=4 + url: https://github.com/JCTrapero +ZhibangYue: + login: ZhibangYue + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/93324586?u=20fb23e3718e0364bb217966470d35e0637dd4fe&v=4 + url: https://github.com/ZhibangYue +saeye: + login: saeye + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62229734?u=312d619db2588b60d5d5bde65260a2f44fdc6c76&v=4 + url: https://github.com/saeye +Heumhub: + login: Heumhub + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/173761521?v=4 + url: https://github.com/Heumhub +manumolina: + login: manumolina + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/2404208?u=fdc5502910f8dec814b2477f89587b9e45fac846&v=4 + url: https://github.com/manumolina +logan2d5: + login: logan2d5 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/146642263?u=dbd6621f8b0330d6919f6a7131277b92e26fbe87&v=4 + url: https://github.com/logan2d5 +guspan-tanadi: + login: guspan-tanadi + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/36249910?v=4 + url: https://github.com/guspan-tanadi +tiaggo16: + login: tiaggo16 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62227573?u=359f4e2c51a4b13c8553ac5af405d635b07bb61f&v=4 + url: https://github.com/tiaggo16 +kiharito: + login: kiharito + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/38311245?v=4 + url: https://github.com/kiharito +t4f1d: + login: t4f1d + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4054172?u=463d5ce0ec8ad8582f6e9351bb8c9a5105b39bb7&v=4 + url: https://github.com/t4f1d +J-Fuji: + login: J-Fuji + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/101452903?v=4 + url: https://github.com/J-Fuji +MrL8199: + login: MrL8199 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/39489075?u=3fc4f89c86973e40b5970d838c801bdbc13ac828&v=4 + url: https://github.com/MrL8199 +ivintoiu: + login: ivintoiu + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1853336?u=5e3d0977f44661fb9712fa297cc8f7608ea6ce48&v=4 + url: https://github.com/ivintoiu +TechnoService2: + login: TechnoService2 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/142113388?v=4 + url: https://github.com/TechnoService2 +EgorOnishchuk: + login: EgorOnishchuk + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/120256301?v=4 + url: https://github.com/EgorOnishchuk +iamantonreznik: + login: iamantonreznik + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/112612414?u=bf6de9a1ab17326fe14de0709719fff3826526d0&v=4 + url: https://github.com/iamantonreznik +Azazul123: + login: Azazul123 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/102759111?u=b48ce6e30a81a23467cc30e0c011bcc57f0326ab&v=4 + url: https://github.com/Azazul123 +ykertytsky: + login: ykertytsky + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/83857001?u=1172902656ee604cf37f5e36abe938cd34a97a32&v=4 + url: https://github.com/ykertytsky +NavesSapnis: + login: NavesSapnis + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/79222417?u=b5b10291b8e9130ca84fd20f0a641e04ed94b6b1&v=4 + url: https://github.com/NavesSapnis +eqsdxr: + login: eqsdxr + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 + url: https://github.com/eqsdxr +syedasamina56: + login: syedasamina56 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/183273097?v=4 + url: https://github.com/syedasamina56 diff --git a/docs/en/data/translators.yml b/docs/en/data/translators.yml new file mode 100644 index 0000000000..cf61eee8ea --- /dev/null +++ b/docs/en/data/translators.yml @@ -0,0 +1,550 @@ +nilslindemann: + login: nilslindemann + count: 122 + avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 + url: https://github.com/nilslindemann +jaystone776: + login: jaystone776 + count: 46 + avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 + url: https://github.com/jaystone776 +valentinDruzhinin: + login: valentinDruzhinin + count: 29 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +ceb10n: + login: ceb10n + count: 27 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +tokusumi: + login: tokusumi + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 + url: https://github.com/tokusumi +SwftAlpc: + login: SwftAlpc + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 + url: https://github.com/SwftAlpc +hasansezertasan: + login: hasansezertasan + count: 22 + avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 + url: https://github.com/hasansezertasan +waynerv: + login: waynerv + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 + url: https://github.com/waynerv +AlertRED: + login: AlertRED + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 + url: https://github.com/AlertRED +hard-coders: + login: hard-coders + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4 + url: https://github.com/hard-coders +Joao-Pedro-P-Holanda: + login: Joao-Pedro-P-Holanda + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 + url: https://github.com/Joao-Pedro-P-Holanda +codingjenny: + login: codingjenny + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4 + url: https://github.com/codingjenny +Xewus: + login: Xewus + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 + url: https://github.com/Xewus +Zhongheng-Cheng: + login: Zhongheng-Cheng + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4 + url: https://github.com/Zhongheng-Cheng +Smlep: + login: Smlep + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/16785985?u=ffe99fa954c8e774ef1117e58d34aece92051e27&v=4 + url: https://github.com/Smlep +marcelomarkus: + login: marcelomarkus + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/20115018?u=dda090ce9160ef0cd2ff69b1e5ea741283425cba&v=4 + url: https://github.com/marcelomarkus +KaniKim: + login: KaniKim + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/19832624?u=296dbdd490e0eb96e3d45a2608c065603b17dc31&v=4 + url: https://github.com/KaniKim +Vincy1230: + login: Vincy1230 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4 + url: https://github.com/Vincy1230 +rjNemo: + login: rjNemo + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 + url: https://github.com/rjNemo +xzmeng: + login: xzmeng + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/40202897?v=4 + url: https://github.com/xzmeng +pablocm83: + login: pablocm83 + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4 + url: https://github.com/pablocm83 +ptt3199: + login: ptt3199 + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/51350651?u=2c3d947a80283e32bf616d4c3af139a6be69680f&v=4 + url: https://github.com/ptt3199 +NinaHwang: + login: NinaHwang + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=241f2cb6d38a2d379536608a8ea5a22ed4b1a3ea&v=4 + url: https://github.com/NinaHwang +batlopes: + login: batlopes + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/33462923?u=0fb3d7acb316764616f11e4947faf080e49ad8d9&v=4 + url: https://github.com/batlopes +tiangolo: + login: tiangolo + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +lucasbalieiro: + login: lucasbalieiro + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=dad91601ee4f40458d691774ec439aff308344d7&v=4 + url: https://github.com/lucasbalieiro +Alexandrhub: + login: Alexandrhub + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 + url: https://github.com/Alexandrhub +Serrones: + login: Serrones + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 + url: https://github.com/Serrones +RunningIkkyu: + login: RunningIkkyu + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4 + url: https://github.com/RunningIkkyu +Attsun1031: + login: Attsun1031 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 + url: https://github.com/Attsun1031 +rostik1410: + login: rostik1410 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/11443899?u=e26a635c2ba220467b308a326a579b8ccf4a8701&v=4 + url: https://github.com/rostik1410 +alv2017: + login: alv2017 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +komtaki: + login: komtaki + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 + url: https://github.com/komtaki +JulianMaurin: + login: JulianMaurin + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/63545168?u=b7d15ac865268cbefc2d739e2f23d9aeeac1a622&v=4 + url: https://github.com/JulianMaurin +stlucasgarcia: + login: stlucasgarcia + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=c22d8850e9dc396a8820766a59837f967e14f9a0&v=4 + url: https://github.com/stlucasgarcia +ComicShrimp: + login: ComicShrimp + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=d2fbf412e7730183ce91686ca48d4147e1b7dc74&v=4 + url: https://github.com/ComicShrimp +BilalAlpaslan: + login: BilalAlpaslan + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 + url: https://github.com/BilalAlpaslan +axel584: + login: axel584 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4 + url: https://github.com/axel584 +tamtam-fitness: + login: tamtam-fitness + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/62091034?u=8da19a6bd3d02f5d6ba30c7247d5b46c98dd1403&v=4 + url: https://github.com/tamtam-fitness +Limsunoh: + login: Limsunoh + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/90311848?u=f456e0c5709fd50c8cd2898b551558eda14e5f21&v=4 + url: https://github.com/Limsunoh +kwang1215: + login: kwang1215 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/74170199?u=2a63ff6692119dde3f5e5693365b9fcd6f977b08&v=4 + url: https://github.com/kwang1215 +k94-ishi: + login: k94-ishi + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/32672580?u=bc7c5c07af0656be9fe4f1784a444af8d81ded89&v=4 + url: https://github.com/k94-ishi +Mohammad222PR: + login: Mohammad222PR + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/116789737?u=25810a5fe049d2f1618e2e7417cea011cc353ce4&v=4 + url: https://github.com/Mohammad222PR +NavesSapnis: + login: NavesSapnis + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/79222417?u=b5b10291b8e9130ca84fd20f0a641e04ed94b6b1&v=4 + url: https://github.com/NavesSapnis +jfunez: + login: jfunez + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4 + url: https://github.com/jfunez +ycd: + login: ycd + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 + url: https://github.com/ycd +mariacamilagl: + login: mariacamilagl + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 + url: https://github.com/mariacamilagl +maoyibo: + login: maoyibo + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 + url: https://github.com/maoyibo +blt232018: + login: blt232018 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4 + url: https://github.com/blt232018 +magiskboy: + login: magiskboy + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/13352088?u=18b6d672523f9e9d98401f31dd50e28bb27d826f&v=4 + url: https://github.com/magiskboy +luccasmmg: + login: luccasmmg + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/11317382?u=65099a5a0d492b89119471f8a7014637cc2e04da&v=4 + url: https://github.com/luccasmmg +lbmendes: + login: lbmendes + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/80999926?u=646619e2f07ac5a7c3f65fe7834197461a4fff9f&v=4 + url: https://github.com/lbmendes +Zssaer: + login: Zssaer + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/45691504?u=4c0c195f25cb5ac6af32acfb0ab35427682938d2&v=4 + url: https://github.com/Zssaer +ChuyuChoyeon: + login: ChuyuChoyeon + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/129537877?u=f0c76f3327817a8b86b422d62e04a34bf2827f2b&v=4 + url: https://github.com/ChuyuChoyeon +ivan-abc: + login: ivan-abc + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4 + url: https://github.com/ivan-abc +mojtabapaso: + login: mojtabapaso + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/121169359?u=ced1d5ad673bcd9e949ebf967a4ab50185637443&v=4 + url: https://github.com/mojtabapaso +hsuanchi: + login: hsuanchi + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/24913710?u=7d25a398e478b6e63503bf6f26c54efa9e0da07b&v=4 + url: https://github.com/hsuanchi +alejsdev: + login: alejsdev + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=447d12a1b347f466b35378bee4c7104cc9b2c571&v=4 + url: https://github.com/alejsdev +riroan: + login: riroan + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/33053284?u=2d18e3771506ee874b66d6aa2b3b1107fd95c38f&v=4 + url: https://github.com/riroan +nayeonkinn: + login: nayeonkinn + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/98254573?u=64a75ac99b320d4935eff8d1fceea9680fa07473&v=4 + url: https://github.com/nayeonkinn +pe-brian: + login: pe-brian + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1783138?u=7e6242eb9e85bcf673fa88bbac9dd6dc3f03b1b5&v=4 + url: https://github.com/pe-brian +maxscheijen: + login: maxscheijen + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/47034840?v=4 + url: https://github.com/maxscheijen +ilacftemp: + login: ilacftemp + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/159066669?v=4 + url: https://github.com/ilacftemp +devluisrodrigues: + login: devluisrodrigues + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/103431660?u=d9674a3249edc4601d2c712cdebf899918503c3a&v=4 + url: https://github.com/devluisrodrigues +devfernandoa: + login: devfernandoa + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/28360583?u=c4308abd62e8847c9e572e1bb9fe6b9dc9ef8e50&v=4 + url: https://github.com/devfernandoa +kim-sangah: + login: kim-sangah + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/173775778?v=4 + url: https://github.com/kim-sangah +9zimin9: + login: 9zimin9 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/174453744?v=4 + url: https://github.com/9zimin9 +nahyunkeem: + login: nahyunkeem + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/174440096?u=e12401d492eee58570f8914d0872b52e421a776e&v=4 + url: https://github.com/nahyunkeem +timothy-jeong: + login: timothy-jeong + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=db3d0cea2f5fab64d810113c5039a369699a2774&v=4 + url: https://github.com/timothy-jeong +gerry-sabar: + login: gerry-sabar + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1120123?v=4 + url: https://github.com/gerry-sabar +Rishat-F: + login: Rishat-F + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/66554797?v=4 + url: https://github.com/Rishat-F +ruzia: + login: ruzia + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/24503?v=4 + url: https://github.com/ruzia +izaguerreiro: + login: izaguerreiro + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4 + url: https://github.com/izaguerreiro +Xaraxx: + login: Xaraxx + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/29824698?u=dde2e233e22bb5ca1f8bb0c6e353ccd0d06e6066&v=4 + url: https://github.com/Xaraxx +sh0nk: + login: sh0nk + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 + url: https://github.com/sh0nk +dukkee: + login: dukkee + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/36825394?u=ccfd86e6a4f2d093dad6f7544cc875af67fa2df8&v=4 + url: https://github.com/dukkee +oandersonmagalhaes: + login: oandersonmagalhaes + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4 + url: https://github.com/oandersonmagalhaes +leandrodesouzadev: + login: leandrodesouzadev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/85115541?u=4eb25f43f1fe23727d61e986cf83b73b86e2a95a&v=4 + url: https://github.com/leandrodesouzadev +kty4119: + login: kty4119 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4 + url: https://github.com/kty4119 +ASpathfinder: + login: ASpathfinder + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/31813636?u=2090bd1b7abb65cfeff0c618f99f11afa82c0548&v=4 + url: https://github.com/ASpathfinder +jujumilk3: + login: jujumilk3 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/41659814?u=538f7dfef03b59f25e43f10d59a31c19ef538a0c&v=4 + url: https://github.com/jujumilk3 +ayr-ton: + login: ayr-ton + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1090517?u=5cf70a0e0f0dbf084e074e494aa94d7c91a46ba6&v=4 + url: https://github.com/ayr-ton +KdHyeon0661: + login: KdHyeon0661 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/20253352?u=5ae1aae34b091a39f22cbe60a02b79dcbdbea031&v=4 + url: https://github.com/KdHyeon0661 +LorhanSohaky: + login: LorhanSohaky + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 + url: https://github.com/LorhanSohaky +cfraboulet: + login: cfraboulet + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62244267?u=ed0e286ba48fa1dafd64a08e50f3364b8e12df34&v=4 + url: https://github.com/cfraboulet +dedkot01: + login: dedkot01 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/26196675?u=e2966887124e67932853df4f10f86cb526edc7b0&v=4 + url: https://github.com/dedkot01 +AGolicyn: + login: AGolicyn + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/86262613?u=3c21606ab8d210a061a1673decff1e7d5592b380&v=4 + url: https://github.com/AGolicyn +fhabers21: + login: fhabers21 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/58401847?v=4 + url: https://github.com/fhabers21 +TabarakoAkula: + login: TabarakoAkula + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/113298631?u=add801e370dbc502cd94ce6d3484760d7fef5406&v=4 + url: https://github.com/TabarakoAkula +AhsanSheraz: + login: AhsanSheraz + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51913596?u=08e31cacb3048be30722c94010ddd028f3fdbec4&v=4 + url: https://github.com/AhsanSheraz +ArtemKhymenko: + login: ArtemKhymenko + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/14346625?u=f2fa553d9e5ec5e0f05d66bd649f7be347169631&v=4 + url: https://github.com/ArtemKhymenko +hasnatsajid: + login: hasnatsajid + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/86589885?u=6668823c3b029bfecf10a8918ed3af1aafb8b15e&v=4 + url: https://github.com/hasnatsajid +alperiox: + login: alperiox + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/34214152?u=2c5acad3461d4dbc2d48371ba86cac56ae9b25cc&v=4 + url: https://github.com/alperiox +emrhnsyts: + login: emrhnsyts + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/42899027?u=ad26798e3f8feed2041c5dd5f87e58933d6c3283&v=4 + url: https://github.com/emrhnsyts +vusallyv: + login: vusallyv + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/85983771?u=6fb8e2f876bca06e9f846606423c8f18fb46ad06&v=4 + url: https://github.com/vusallyv +jackleeio: + login: jackleeio + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/20477587?u=c5184dab6d021733d10c8f975b20e391856303d6&v=4 + url: https://github.com/jackleeio +choi-haram: + login: choi-haram + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62204475?v=4 + url: https://github.com/choi-haram +imtiaz101325: + login: imtiaz101325 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/54007087?u=194d972b501b9ea9d2ddeaed757c492936e0121a&v=4 + url: https://github.com/imtiaz101325 +fabianfalon: + login: fabianfalon + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/3700760?u=95f69e31280b17ac22299cdcd345323b142fe0af&v=4 + url: https://github.com/fabianfalon +waketzheng: + login: waketzheng + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/35413830?u=df19e4fd5bb928e7d086e053ef26a46aad23bf84&v=4 + url: https://github.com/waketzheng +billzhong: + login: billzhong + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1644011?v=4 + url: https://github.com/billzhong +chaoless: + login: chaoless + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/64477804?v=4 + url: https://github.com/chaoless +logan2d5: + login: logan2d5 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/146642263?u=dbd6621f8b0330d6919f6a7131277b92e26fbe87&v=4 + url: https://github.com/logan2d5 +andersonrocha0: + login: andersonrocha0 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/22346169?u=93a1359c8c5461d894802c0cc65bcd09217e7a02&v=4 + url: https://github.com/andersonrocha0 +saeye: + login: saeye + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62229734?u=312d619db2588b60d5d5bde65260a2f44fdc6c76&v=4 + url: https://github.com/saeye +11kkw: + login: 11kkw + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/21125286?v=4 + url: https://github.com/11kkw +yes0ng: + login: yes0ng + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/25501794?u=3aed18b0d491e0220a167a1e9e58bea3638c6707&v=4 + url: https://github.com/yes0ng +EgorOnishchuk: + login: EgorOnishchuk + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/120256301?v=4 + url: https://github.com/EgorOnishchuk +EdmilsonRodrigues: + login: EdmilsonRodrigues + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/62777025?u=217d6f3cd6cc750bb8818a3af7726c8d74eb7c2d&v=4 + url: https://github.com/EdmilsonRodrigues +YuriiMotov: + login: YuriiMotov + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + url: https://github.com/YuriiMotov diff --git a/docs/en/docs/_llm-test.md b/docs/en/docs/_llm-test.md new file mode 100644 index 0000000000..e72450b916 --- /dev/null +++ b/docs/en/docs/_llm-test.md @@ -0,0 +1,503 @@ +# LLM test file { #llm-test-file } + +This document tests if the LLM, which translates the documentation, understands the `general_prompt` in `scripts/translate.py` and the language specific prompt in `docs/{language code}/llm-prompt.md`. The language specific prompt is appended to `general_prompt`. + +Tests added here will be seen by all designers of language specific prompts. + +Use as follows: + +* Have a language specific prompt – `docs/{language code}/llm-prompt.md`. +* Do a fresh translation of this document into your desired target language (see e.g. the `translate-page` command of the `translate.py`). This will create the translation under `docs/{language code}/docs/_llm-test.md`. +* Check if things are okay in the translation. +* If necessary, improve your language specific prompt, the general prompt, or the English document. +* Then manually fix the remaining issues in the translation, so that it is a good translation. +* Retranslate, having the good translation in place. The ideal result would be that the LLM makes no changes anymore to the translation. That means that the general prompt and your language specific prompt are as good as they can be (It will sometimes make a few seemingly random changes, the reason is that LLMs are not deterministic algorithms). + +The tests: + +## Code snippets { #code-snippets} + +//// tab | Test + +This is a code snippet: `foo`. And this is another code snippet: `bar`. And another one: `baz quux`. + +//// + +//// tab | Info + +Content of code snippets should be left as is. + +See section `### Content of code snippets` in the general prompt in `scripts/translate.py`. + +//// + +## Quotes { #quotes } + +//// tab | Test + +Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'". + +/// note + +The LLM will probably translate this wrong. Interesting is only if it keeps the fixed translation when retranslating. + +/// + +//// + +//// tab | Info + +The prompt designer may choose if they want to convert neutral quotes to typographic quotes. It is okay to leave them as is. + +See for example section `### Quotes` in `docs/de/llm-prompt.md`. + +//// + +## Quotes in code snippets { #quotes-in-code-snippets} + +//// tab | Test + +`pip install "foo[bar]"` + +Examples for string literals in code snippets: `"this"`, `'that'`. + +A difficult example for string literals in code snippets: `f"I like {'oranges' if orange else "apples"}"` + +Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | Info + +... However, quotes inside code snippets must stay as is. + +//// + +## code blocks { #code-blocks } + +//// tab | Test + +A Bash code example... + +```bash +# Print a greeting to the universe +echo "Hello universe" +``` + +...and a console code example... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...and another console code example... + +```console +// Create a directory "Code" +$ mkdir code +// Switch into that directory +$ cd code +``` + +...and a Python code example... + +```Python +wont_work() # This won't work 😱 +works(foo="bar") # This works 🎉 +``` + +...and that's it. + +//// + +//// tab | Info + +Code in code blocks should not be modified, with the exception of comments. + +See section `### Content of code blocks` in the general prompt in `scripts/translate.py`. + +//// + +## Tabs and colored boxes { #tabs-and-colored-boxes } + +//// tab | Test + +/// info +Some text +/// + +/// note +Some text +/// + +/// note | Technical details +Some text +/// + +/// check +Some text +/// + +/// tip +Some text +/// + +/// warning +Some text +/// + +/// danger +Some text +/// + +//// + +//// tab | Info + +Tabs and `Info`/`Note`/`Warning`/etc. blocks should have the translation of their title added after a vertical bar (`|`). + +See sections `### Special blocks` and `### Tab blocks` in the general prompt in `scripts/translate.py`. + +//// + +## Web- and internal links { #web-and-internal-links } + +//// tab | Test + +The link text should get translated, the link address should remain unchaged: + +* [Link to heading above](#code-snippets) +* [Internal link](index.md#installation){.internal-link target=_blank} +* External link +* Link to a style +* Link to a script +* Link to an image + +The link text should get translated, the link address should point to the translation: + +* FastAPI link + +//// + +//// tab | Info + +Links should be translated, but their address shall remain unchanged. An exception are absolute links to pages of the FastAPI documentation. In that case it should link to the translation. + +See section `### Links` in the general prompt in `scripts/translate.py`. + +//// + +## HTML "abbr" elements { #html-abbr-elements } + +//// tab | Test + +Here some things wrapped in HTML "abbr" elements (Some are invented): + +### The abbr gives a full phrase { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### The abbr gives an explanation { #the-abbr-gives-an-explanation } + +* cluster +* Deep Learning + +### The abbr gives a full phrase and an explanation { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | Info + +"title" attributes of "abbr" elements are translated following some specific instructions. + +Translations can add their own "abbr" elements which the LLM should not remove. E.g. to explain English words. + +See section `### HTML abbr elements` in the general prompt in `scripts/translate.py`. + +//// + +## Headings { #headings } + +//// tab | Test + +### Develop a webapp - a tutorial { #develop-a-webapp-a-tutorial } + +Hello. + +### Type hints and -annotations { #type-hints-and-annotations } + +Hello again. + +### Super- and subclasses { #super-and-subclasses } + +Hello again. + +//// + +//// tab | Info + +The only hard rule for headings is that the LLM leaves the hash part inside curly brackets unchanged, which ensures that links do not break. + +See section `### Headings` in the general prompt in `scripts/translate.py`. + +For some language specific instructions, see e.g. section `### Headings` in `docs/de/llm-prompt.md`. + +//// + +## Terms used in the docs { #terms-used-in-the-docs } + +//// tab | Test + +* you +* your + +* e.g. +* etc. + +* `foo` as an `int` +* `bar` as a `str` +* `baz` as a `list` + +* the Tutorial - User guide +* the Advanced User Guide +* the SQLModel docs +* the API docs +* the automatic docs + +* Data Science +* Deep Learning +* Machine Learning +* Dependency Injection +* HTTP Basic authentication +* HTTP Digest +* ISO format +* the JSON Schema standard +* the JSON schema +* the schema definition +* Password Flow +* Mobile + +* deprecated +* designed +* invalid +* on the fly +* standard +* default +* case-sensitive +* case-insensitive + +* to serve the application +* to serve the page + +* the app +* the application + +* the request +* the response +* the error response + +* the path operation +* the path operation decorator +* the path operation function + +* the body +* the request body +* the response body +* the JSON body +* the form body +* the file body +* the function body + +* the parameter +* the body parameter +* the path parameter +* the query parameter +* the cookie parameter +* the header parameter +* the form parameter +* the function parameter + +* the event +* the startup event +* the startup of the server +* the shutdown event +* the lifespan event + +* the handler +* the event handler +* the exception handler +* to handle + +* the model +* the Pydantic model +* the data model +* the database model +* the form model +* the model object + +* the class +* the base class +* the parent class +* the subclass +* the child class +* the sibling class +* the class method + +* the header +* the headers +* the authorization header +* the `Authorization` header +* the forwarded header + +* the dependency injection system +* the dependency +* the dependable +* the dependant + +* I/O bound +* CPU bound +* concurrency +* parallelism +* multiprocessing + +* the env var +* the environment variable +* the `PATH` +* the `PATH` variable + +* the authentication +* the authentication provider +* the authorization +* the authorization form +* the authorization provider +* the user authenticates +* the system authenticates the user + +* the CLI +* the command line interface + +* the server +* the client + +* the cloud provider +* the cloud service + +* the development +* the development stages + +* the dict +* the dictionary +* the enumeration +* the enum +* the enum member + +* the encoder +* the decoder +* to encode +* to decode + +* the exception +* to raise + +* the expression +* the statement + +* the frontend +* the backend + +* the GitHub discussion +* the GitHub issue + +* the performance +* the performance optimization + +* the return type +* the return value + +* the security +* the security scheme + +* the task +* the background task +* the task function + +* the template +* the template engine + +* the type annotation +* the type hint + +* the server worker +* the Uvicorn worker +* the Gunicorn Worker +* the worker process +* the worker class +* the workload + +* the deployment +* to deploy + +* the SDK +* the software development kit + +* the `APIRouter` +* the `requirements.txt` +* the Bearer Token +* the breaking change +* the bug +* the button +* the callable +* the code +* the commit +* the context manager +* the coroutine +* the database session +* the disk +* the domain +* the engine +* the fake X +* the HTTP GET method +* the item +* the library +* the lifespan +* the lock +* the middleware +* the mobile application +* the module +* the mounting +* the network +* the origin +* the override +* the payload +* the processor +* the property +* the proxy +* the pull request +* the query +* the RAM +* the remote machine +* the status code +* the string +* the tag +* the web framework +* the wildcard +* to return +* to validate + +//// + +//// tab | Info + +This is a not complete and not normative list of (mostly) technical terms seen in the docs. It may be helpful for the prompt designer to figure out for which terms the LLM needs a helping hand. For example when it keeps reverting a good translation to a suboptimal translation. Or when it has problems conjugating/declinating a term in your language. + +See e.g. section `### List of English terms and their preferred German translations` in `docs/de/llm-prompt.md`. + +//// diff --git a/docs/en/docs/about/index.md b/docs/en/docs/about/index.md index 27b78696b5..d178dfec75 100644 --- a/docs/en/docs/about/index.md +++ b/docs/en/docs/about/index.md @@ -1,3 +1,3 @@ -# About +# About { #about } About FastAPI, its design, inspiration and more. 🤓 diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md index 674f0672cb..799532c5b2 100644 --- a/docs/en/docs/advanced/additional-responses.md +++ b/docs/en/docs/advanced/additional-responses.md @@ -1,4 +1,4 @@ -# Additional Responses in OpenAPI +# Additional Responses in OpenAPI { #additional-responses-in-openapi } /// warning @@ -14,11 +14,11 @@ Those additional responses will be included in the OpenAPI schema, so they will But for those additional responses you have to make sure you return a `Response` like `JSONResponse` directly, with your status code and content. -## Additional Response with `model` +## Additional Response with `model` { #additional-response-with-model } You can pass to your *path operation decorators* a parameter `responses`. -It receives a `dict`, the keys are status codes for each response, like `200`, and the values are other `dict`s with the information for each of them. +It receives a `dict`: the keys are status codes for each response (like `200`), and the values are other `dict`s with the information for each of them. Each of those response `dict`s can have a key `model`, containing a Pydantic model, just like `response_model`. @@ -26,9 +26,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 22" -{!../../../docs_src/additional_responses/tutorial001.py!} -``` +{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} /// note @@ -171,15 +169,13 @@ The schemas are referenced to another place inside the OpenAPI schema: } ``` -## Additional media types for the main response +## Additional media types for the main response { #additional-media-types-for-the-main-response } You can use this same `responses` parameter to add different media types for the same main response. For example, you can add an additional media type of `image/png`, declaring that your *path operation* can return a JSON object (with media type `application/json`) or a PNG image: -```Python hl_lines="19-24 28" -{!../../../docs_src/additional_responses/tutorial002.py!} -``` +{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} /// note @@ -195,7 +191,7 @@ But if you have specified a custom response class with `None` as its media type, /// -## Combining information +## Combining information { #combining-information } You can also combine response information from multiple places, including the `response_model`, `status_code`, and `responses` parameters. @@ -207,15 +203,13 @@ For example, you can declare a response with a status code `404` that uses a Pyd And a response with a status code `200` that uses your `response_model`, but includes a custom `example`: -```Python hl_lines="20-31" -{!../../../docs_src/additional_responses/tutorial003.py!} -``` +{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} It will all be combined and included in your OpenAPI, and shown in the API docs: -## Combine predefined responses and custom ones +## Combine predefined responses and custom ones { #combine-predefined-responses-and-custom-ones } You might want to have some predefined responses that apply to many *path operations*, but you want to combine them with custom responses needed by each *path operation*. @@ -243,11 +237,9 @@ You can use that technique to reuse some predefined responses in your *path oper For example: -```Python hl_lines="13-17 26" -{!../../../docs_src/additional_responses/tutorial004.py!} -``` +{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} -## More information about OpenAPI responses +## More information about OpenAPI responses { #more-information-about-openapi-responses } To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification: diff --git a/docs/en/docs/advanced/additional-status-codes.md b/docs/en/docs/advanced/additional-status-codes.md index 99ad72b536..23bcd13c32 100644 --- a/docs/en/docs/advanced/additional-status-codes.md +++ b/docs/en/docs/advanced/additional-status-codes.md @@ -1,10 +1,10 @@ -# Additional Status Codes +# Additional Status Codes { #additional-status-codes } By default, **FastAPI** will return the responses using a `JSONResponse`, putting the content you return from your *path operation* inside of that `JSONResponse`. It will use the default status code or the one you set in your *path operation*. -## Additional status codes +## Additional status codes { #additional-status-codes_1 } If you want to return additional status codes apart from the main one, you can do that by returning a `Response` directly, like a `JSONResponse`, and set the additional status code directly. @@ -14,57 +14,7 @@ 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: -//// tab | Python 3.10+ - -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 26" -{!> ../../../docs_src/additional_status_codes/tutorial001_an.py!} -``` - -//// - -//// tab | 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!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001.py!} -``` - -//// +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} /// warning @@ -76,7 +26,7 @@ Make sure it has the data you want it to have, and that the values are valid JSO /// -/// note | "Technical Details" +/// note | Technical Details You could also use `from starlette.responses import JSONResponse`. @@ -84,7 +34,7 @@ You could also use `from starlette.responses import JSONResponse`. /// -## OpenAPI and API docs +## OpenAPI and API docs { #openapi-and-api-docs } If you return additional status codes and responses directly, they won't be included in the OpenAPI schema (the API docs), because FastAPI doesn't have a way to know beforehand what you are going to return. diff --git a/docs/en/docs/advanced/advanced-dependencies.md b/docs/en/docs/advanced/advanced-dependencies.md index f65e1b1809..e0404b389f 100644 --- a/docs/en/docs/advanced/advanced-dependencies.md +++ b/docs/en/docs/advanced/advanced-dependencies.md @@ -1,6 +1,6 @@ -# Advanced Dependencies +# Advanced Dependencies { #advanced-dependencies } -## Parameterized dependencies +## Parameterized dependencies { #parameterized-dependencies } All the dependencies we have seen are a fixed function or class. @@ -10,7 +10,7 @@ Let's imagine that we want to have a dependency that checks if the query paramet But we want to be able to parameterize that fixed content. -## A "callable" instance +## A "callable" instance { #a-callable-instance } In Python there's a way to make an instance of a class a "callable". @@ -18,111 +18,27 @@ Not the class itself (which is already a callable), but an instance of that clas To do that, we declare a method `__call__`: -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} 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. -## Parameterize the instance +## Parameterize the instance { #parameterize-the-instance } And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency: -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code. -## Create an instance +## Create an instance { #create-an-instance } We could create an instance of this class with: -//// tab | Python 3.9+ - -```Python hl_lines="18" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="16" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`. -## Use the instance as a dependency +## Use the instance as a dependency { #use-the-instance-as-a-dependency } Then, we could use this `checker` in a `Depends(checker)`, instead of `Depends(FixedContentQueryChecker)`, because the dependency is the instance, `checker`, not the class itself. @@ -134,35 +50,7 @@ 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`: -//// tab | Python 3.9+ - -```Python hl_lines="22" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} /// tip @@ -175,3 +63,91 @@ In the chapters about security, there are utility functions that are implemented If you understood all this, you already know how those utility tools for security work underneath. /// + +## Dependencies with `yield`, `HTTPException`, `except` and Background Tasks { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning + +You most probably don't need these technical details. + +These details are useful mainly if you had a FastAPI application older than 0.118.0 and you are facing issues with dependencies with `yield`. + +/// + +Dependencies with `yield` have evolved over time to account for the different use cases and to fix some issues, here's a summary of what has changed. + +### Dependencies with `yield` and `StreamingResponse`, Technical Details { #dependencies-with-yield-and-streamingresponse-technical-details } + +Before FastAPI 0.118.0, if you used a dependency with `yield`, it would run the exit code after the *path operation function* returned but right before sending the response. + +The intention was to avoid holding resources for longer than necessary, waiting for the response to travel through the network. + +This change also meant that if you returned a `StreamingResponse`, the exit code of the dependency with `yield` would have been already run. + +For example, if you had a database session in a dependency with `yield`, the `StreamingResponse` would not be able to use that session while streaming data because the session would have already been closed in the exit code after `yield`. + +This behavior was reverted in 0.118.0, to make the exit code after `yield` be executed after the response is sent. + +/// info + +As you will see below, this is very similar to the behavior before version 0.106.0, but with several improvements and bug fixes for corner cases. + +/// + +#### Use Cases with Early Exit Code { #use-cases-with-early-exit-code } + +There are some use cases with specific conditions that could benefit from the old behavior of running the exit code of dependencies with `yield` before sending the response. + +For example, imagine you have code that uses a database session in a dependency with `yield` only to verify a user, but the database session is never used again in the *path operation function*, only in the dependency, **and** the response takes a long time to be sent, like a `StreamingResponse` that sends data slowly, but for some reason doesn't use the database. + +In this case, the database session would be held until the response is finished being sent, but if you don't use it, then it wouldn't be necessary to hold it. + +Here's how it could look like: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +The exit code, the automatic closing of the `Session` in: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +...would be run after the the response finishes sending the slow data: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +But as `generate_stream()` doesn't use the database session, it is not really necessary to keep the session open while sending the response. + +If you have this specific use case using SQLModel (or SQLAlchemy), you could explicitly close the session after you don't need it anymore: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +That way the session would release the database connection, so other requests could use it. + +If you have a different use case that needs to exit early from a dependency with `yield`, please create a GitHub Discussion Question with your specific use case and why you would benefit from having early closing for dependencies with `yield`. + +If there are compelling use cases for early closing in dependencies with `yield`, I would consider adding a new way to opt in to early closing. + +### Dependencies with `yield` and `except`, Technical Details { #dependencies-with-yield-and-except-technical-details } + +Before FastAPI 0.110.0, if you used a dependency with `yield`, and then you captured an exception with `except` in that dependency, and you didn't raise the exception again, the exception would be automatically raised/forwarded to any exception handlers or the internal server error handler. + +This was changed in version 0.110.0 to fix unhandled memory consumption from forwarded exceptions without a handler (internal server errors), and to make it consistent with the behavior of regular Python code. + +### Background Tasks and Dependencies with `yield`, Technical Details { #background-tasks-and-dependencies-with-yield-technical-details } + +Before FastAPI 0.106.0, raising exceptions after `yield` was not possible, the exit code in dependencies with `yield` was executed *after* the response was sent, so [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} would have already run. + +This was designed this way mainly to allow using the same objects "yielded" by dependencies inside of background tasks, because the exit code would be executed after the background tasks were finished. + +This was changed in FastAPI 0.106.0 with the intention to not hold resources while waiting for the response to travel through the network. + +/// tip + +Additionally, a background task is normally an independent set of logic that should be handled separately, with its own resources (e.g. its own database connection). + +So, this way you will probably have cleaner code. + +/// + +If you used to rely on this behavior, now you should create the resources for background tasks inside the background task itself, and use internally only data that doesn't depend on the resources of dependencies with `yield`. + +For example, instead of using the same database session, you would create a new database session inside of the background task, and you would obtain the objects from the database using this new session. And then instead of passing the object from the database as a parameter to the background task function, you would pass the ID of that object and then obtain the object again inside the background task function. diff --git a/docs/en/docs/advanced/async-tests.md b/docs/en/docs/advanced/async-tests.md index ac459ff0c8..e920e22c3c 100644 --- a/docs/en/docs/advanced/async-tests.md +++ b/docs/en/docs/advanced/async-tests.md @@ -1,4 +1,4 @@ -# Async Tests +# Async Tests { #async-tests } 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. @@ -6,11 +6,11 @@ Being able to use asynchronous functions in your tests could be useful, for exam Let's look at how we can make that work. -## pytest.mark.anyio +## pytest.mark.anyio { #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. -## HTTPX +## HTTPX { #httpx } Even if your **FastAPI** application uses normal `def` functions instead of `async def`, it is still an `async` application underneath. @@ -18,7 +18,7 @@ The `TestClient` does some magic inside to call the asynchronous FastAPI applica The `TestClient` is based on HTTPX, and luckily, we can use it directly to test the API. -## Example +## Example { #example } 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}: @@ -32,17 +32,13 @@ For a simple example, let's consider a file structure similar to the one describ The file `main.py` would have: -```Python -{!../../../docs_src/async_tests/main.py!} -``` +{* ../../docs_src/async_tests/main.py *} 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!} -``` +{* ../../docs_src/async_tests/test_main.py *} -## Run it +## Run it { #run-it } You can run your tests as usual via: @@ -56,13 +52,11 @@ $ pytest
-## In Detail +## In Detail { #in-detail } The marker `@pytest.mark.anyio` tells pytest that this test function should be called asynchronously: -```Python hl_lines="7" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[7] *} /// tip @@ -72,9 +66,7 @@ Note that the test function is now `async def` instead of just `def` as before w Then we can create an `AsyncClient` with the app, and send async requests to it, using `await`. -```Python hl_lines="9-10" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} This is the equivalent to: @@ -96,12 +88,12 @@ If your application relies on lifespan events, the `AsyncClient` won't trigger t /// -## Other Asynchronous Function Calls +## Other Asynchronous Function Calls { #other-asynchronous-function-calls } As the testing function is now asynchronous, you can now also call (and `await`) other `async` functions apart from sending requests to your FastAPI application in your tests, exactly as you would call them anywhere else in your code. /// tip -If you encounter a `RuntimeError: Task attached to a different loop` when integrating asynchronous function calls in your tests (e.g. when using MongoDB's MotorClient) Remember to instantiate objects that need an event loop only within async functions, e.g. an `'@app.on_event("startup")` callback. +If you encounter a `RuntimeError: Task attached to a different loop` when integrating asynchronous function calls in your tests (e.g. when using MongoDB's MotorClient), remember to instantiate objects that need an event loop only within async functions, e.g. an `@app.on_event("startup")` callback. /// diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index 5ff64016c2..4d19d29e02 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -1,6 +1,105 @@ -# Behind a Proxy +# Behind a Proxy { #behind-a-proxy } -In some situations, you might need to use a **proxy** server like Traefik or Nginx with a configuration that adds an extra path prefix that is not seen by your application. +In many situations, you would use a **proxy** like Traefik or Nginx in front of your FastAPI app. + +These proxies could handle HTTPS certificates and other things. + +## Proxy Forwarded Headers { #proxy-forwarded-headers } + +A **proxy** in front of your application would normally set some headers on the fly before sending the requests to your **server** to let the server know that the request was **forwarded** by the proxy, letting it know the original (public) URL, including the domain, that it is using HTTPS, etc. + +The **server** program (for example **Uvicorn** via **FastAPI CLI**) is capable of interpreting these headers, and then passing that information to your application. + +But for security, as the server doesn't know it is behind a trusted proxy, it won't interpret those headers. + +/// note | Technical Details + +The proxy headers are: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### Enable Proxy Forwarded Headers { #enable-proxy-forwarded-headers } + +You can start FastAPI CLI with the *CLI Option* `--forwarded-allow-ips` and pass the IP addresses that should be trusted to read those forwarded headers. + +If you set it to `--forwarded-allow-ips="*"` it would trust all the incoming IPs. + +If your **server** is behind a trusted **proxy** and only the proxy talks to it, this would make it accept whatever is the IP of that **proxy**. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Redirects with HTTPS { #redirects-with-https } + +For example, let's say you define a *path operation* `/items/`: + +{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} + +If the client tries to go to `/items`, by default, it would be redirected to `/items/`. + +But before setting the *CLI Option* `--forwarded-allow-ips` it could redirect to `http://localhost:8000/items/`. + +But maybe your application is hosted at `https://mysuperapp.com`, and the redirection should be to `https://mysuperapp.com/items/`. + +By setting `--proxy-headers` now FastAPI would be able to redirect to the right location. 😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip + +If you want to learn more about HTTPS, check the guide [About HTTPS](../deployment/https.md){.internal-link target=_blank}. + +/// + +### How Proxy Forwarded Headers Work + +Here's a visual representation of how the **proxy** adds forwarded headers between the client and the **application server**: + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Load Balancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +The **proxy** intercepts the original client request and adds the special *forwarded* headers (`X-Forwarded-*`) before passing the request to the **application server**. + +These headers preserve information about the original request that would otherwise be lost: + +* **X-Forwarded-For**: The original client's IP address +* **X-Forwarded-Proto**: The original protocol (`https`) +* **X-Forwarded-Host**: The original host (`mysuperapp.com`) + +When **FastAPI CLI** is configured with `--forwarded-allow-ips`, it trusts these headers and uses them, for example to generate the correct URLs in redirects. + +## Proxy with a stripped path prefix { #proxy-with-a-stripped-path-prefix } + +You could have a proxy that adds a path prefix to your application. In these cases you can use `root_path` to configure your application. @@ -10,17 +109,13 @@ The `root_path` is used to handle these specific cases. And it's also used internally when mounting sub-applications. -## Proxy with a stripped path prefix - Having a proxy with a stripped path prefix, in this case, means that you could declare a path at `/app` in your code, but then, you add a layer on top (the proxy) that would put your **FastAPI** application under a path like `/api/v1`. In this case, the original path `/app` would actually be served at `/api/v1/app`. Even though all your code is written assuming there's just `/app`. -```Python hl_lines="6" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} And the proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. @@ -68,14 +163,14 @@ The docs UI would also need the OpenAPI schema to declare that this API `server` In this example, the "Proxy" could be something like **Traefik**. And the server would be something like FastAPI CLI with **Uvicorn**, running your FastAPI application. -### Providing the `root_path` +### Providing the `root_path` { #providing-the-root-path } To achieve this, you can use the command line option `--root-path` like:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -84,7 +179,7 @@ $ fastapi run main.py --root-path /api/v1 If you use Hypercorn, it also has the option `--root-path`. -/// note | "Technical Details" +/// note | Technical Details The ASGI specification defines a `root_path` for this use case. @@ -92,22 +187,20 @@ And the `--root-path` command line option provides that `root_path`. /// -### Checking the current `root_path` +### Checking the current `root_path` { #checking-the-current-root-path } You can get the current `root_path` used by your application for each request, it is part of the `scope` dictionary (that's part of the ASGI spec). Here we are including it in the message just for demonstration purposes. -```Python hl_lines="8" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} Then, if you start Uvicorn with:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -123,17 +216,15 @@ The response would be something like: } ``` -### Setting the `root_path` in the FastAPI app +### Setting the `root_path` in the FastAPI app { #setting-the-root-path-in-the-fastapi-app } Alternatively, if you don't have a way to provide a command line option like `--root-path` or equivalent, you can set the `root_path` parameter when creating your FastAPI app: -```Python hl_lines="3" -{!../../../docs_src/behind_a_proxy/tutorial002.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} Passing the `root_path` to `FastAPI` would be the equivalent of passing the `--root-path` command line option to Uvicorn or Hypercorn. -### About `root_path` +### About `root_path` { #about-root-path } Keep in mind that the server (Uvicorn) won't use that `root_path` for anything else than passing it to the app. @@ -150,7 +241,7 @@ So, it won't expect to be accessed at `http://127.0.0.1:8000/api/v1/app`. Uvicorn will expect the proxy to access Uvicorn at `http://127.0.0.1:8000/app`, and then it would be the proxy's responsibility to add the extra `/api/v1` prefix on top. -## About proxies with a stripped path prefix +## About proxies with a stripped path prefix { #about-proxies-with-a-stripped-path-prefix } Keep in mind that a proxy with stripped path prefix is only one of the ways to configure it. @@ -158,7 +249,7 @@ Probably in many cases the default will be that the proxy doesn't have a strippe In a case like that (without a stripped path prefix), the proxy would listen on something like `https://myawesomeapp.com`, and then if the browser goes to `https://myawesomeapp.com/api/v1/app` and your server (e.g. Uvicorn) listens on `http://127.0.0.1:8000` the proxy (without a stripped path prefix) would access Uvicorn at the same path: `http://127.0.0.1:8000/api/v1/app`. -## Testing locally with Traefik +## Testing locally with Traefik { #testing-locally-with-traefik } You can easily run the experiment locally with a stripped path prefix using Traefik. @@ -230,14 +321,14 @@ And now start your app, using the `--root-path` option:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-### Check the responses +### Check the responses { #check-the-responses } Now, if you go to the URL with the port for Uvicorn: http://127.0.0.1:8000/app, you will see the normal response: @@ -273,7 +364,7 @@ And the version without the path prefix (`http://127.0.0.1:8000/app`), provided That demonstrates how the Proxy (Traefik) uses the path prefix and how the server (Uvicorn) uses the `root_path` from the option `--root-path`. -### Check the docs UI +### Check the docs UI { #check-the-docs-ui } But here's the fun part. ✨ @@ -293,7 +384,7 @@ Right as we wanted it. ✔️ This is because FastAPI uses this `root_path` to create the default `server` in OpenAPI with the URL provided by `root_path`. -## Additional servers +## Additional servers { #additional-servers } /// warning @@ -303,15 +394,13 @@ This is a more advanced use case. Feel free to skip it. By default, **FastAPI** will create a `server` in the OpenAPI schema with the URL for the `root_path`. -But you can also provide other alternative `servers`, for example if you want *the same* docs UI to interact with a staging and production environments. +But you can also provide other alternative `servers`, for example if you want *the same* docs UI to interact with both a staging and a production environment. If you pass a custom list of `servers` and there's a `root_path` (because your API lives behind a proxy), **FastAPI** will insert a "server" with this `root_path` at the beginning of the list. For example: -```Python hl_lines="4-7" -{!../../../docs_src/behind_a_proxy/tutorial003.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} Will generate an OpenAPI schema like: @@ -354,17 +443,15 @@ The docs UI will interact with the server that you select. /// -### Disable automatic server from `root_path` +### Disable automatic server from `root_path` { #disable-automatic-server-from-root-path } If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`: -```Python hl_lines="9" -{!../../../docs_src/behind_a_proxy/tutorial004.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} and then it won't include it in the OpenAPI schema. -## Mounting a sub-application +## Mounting a sub-application { #mounting-a-sub-application } If you need to mount a sub-application (as described in [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}) while also using a proxy with `root_path`, you can do it normally, as you would expect. diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index 8a6555dba1..0f3d8b7017 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -1,12 +1,12 @@ -# Custom Response - HTML, Stream, File, others +# Custom Response - HTML, Stream, File, others { #custom-response-html-stream-file-others } By default, **FastAPI** will return the responses using `JSONResponse`. You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}. -But if you return a `Response` directly, the data won't be automatically converted, and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type` as part of the generated OpenAPI). +But if you return a `Response` directly (or any subclass, like `JSONResponse`), the data won't be automatically converted (even if you declare a `response_model`), and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type` as part of the generated OpenAPI). -But you can also declare the `Response` that you want to be used, in the *path operation decorator*. +But you can also declare the `Response` that you want to be used (e.g. any `Response` subclass), in the *path operation decorator* using the `response_class` parameter. The contents that you return from your *path operation function* will be put inside of that `Response`. @@ -18,7 +18,7 @@ If you use a response class with no media type, FastAPI will expect your respons /// -## Use `ORJSONResponse` +## Use `ORJSONResponse` { #use-orjsonresponse } For example, if you are squeezing performance, you can install and use `orjson` and set the response to be `ORJSONResponse`. @@ -30,9 +30,7 @@ This is because by default, FastAPI will inspect every item inside and make sure But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class. -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001b.py!} -``` +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} /// info @@ -50,16 +48,14 @@ The `ORJSONResponse` is only available in FastAPI, not in Starlette. /// -## HTML Response +## HTML Response { #html-response } To return a response with HTML directly from **FastAPI**, use `HTMLResponse`. * Import `HTMLResponse`. * Pass `HTMLResponse` as the parameter `response_class` of your *path operation decorator*. -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial002.py!} -``` +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} /// info @@ -71,15 +67,13 @@ And it will be documented as such in OpenAPI. /// -### Return a `Response` +### Return a `Response` { #return-a-response } As seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}, you can also override the response directly in your *path operation*, by returning it. The same example from above, returning an `HTMLResponse`, could look like: -```Python hl_lines="2 7 19" -{!../../../docs_src/custom_response/tutorial003.py!} -``` +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} /// warning @@ -93,19 +87,17 @@ Of course, the actual `Content-Type` header, status code, etc, will come from th /// -### Document in OpenAPI and override `Response` +### Document in OpenAPI and override `Response` { #document-in-openapi-and-override-response } If you want to override the response from inside of the function but at the same time document the "media type" in OpenAPI, you can use the `response_class` parameter AND return a `Response` object. The `response_class` will then be used only to document the OpenAPI *path operation*, but your `Response` will be used as is. -#### Return an `HTMLResponse` directly +#### Return an `HTMLResponse` directly { #return-an-htmlresponse-directly } For example, it could be something like: -```Python hl_lines="7 21 23" -{!../../../docs_src/custom_response/tutorial004.py!} -``` +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} In this example, the function `generate_html_response()` already generates and returns a `Response` instead of returning the HTML in a `str`. @@ -115,13 +107,13 @@ But as you passed the `HTMLResponse` in the `response_class` too, **FastAPI** wi -## Available responses +## Available responses { #available-responses } Here are some of the available responses. Keep in mind that you can use `Response` to return anything else, or even create a custom sub-class. -/// note | "Technical Details" +/// note | Technical Details You could also use `from starlette.responses import HTMLResponse`. @@ -129,7 +121,7 @@ You could also use `from starlette.responses import HTMLResponse`. /// -### `Response` +### `Response` { #response } The main `Response` class, all the other responses inherit from it. @@ -142,31 +134,27 @@ It accepts the following parameters: * `headers` - A `dict` of strings. * `media_type` - A `str` giving the media type. E.g. `"text/html"`. -FastAPI (actually Starlette) will automatically include a Content-Length header. It will also include a Content-Type header, based on the media_type and appending a charset for text types. +FastAPI (actually Starlette) will automatically include a Content-Length header. It will also include a Content-Type header, based on the `media_type` and appending a charset for text types. -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} -### `HTMLResponse` +### `HTMLResponse` { #htmlresponse } Takes some text or bytes and returns an HTML response, as you read above. -### `PlainTextResponse` +### `PlainTextResponse` { #plaintextresponse } -Takes some text or bytes and returns an plain text response. +Takes some text or bytes and returns a plain text response. -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial005.py!} -``` +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} -### `JSONResponse` +### `JSONResponse` { #jsonresponse } Takes some data and returns an `application/json` encoded response. This is the default response used in **FastAPI**, as you read above. -### `ORJSONResponse` +### `ORJSONResponse` { #orjsonresponse } A fast alternative JSON response using `orjson`, as you read above. @@ -176,7 +164,7 @@ This requires installing `orjson` for example with `pip install orjson`. /// -### `UJSONResponse` +### `UJSONResponse` { #ujsonresponse } An alternative JSON response using `ujson`. @@ -192,9 +180,7 @@ This requires installing `ujson` for example with `pip install ujson`. /// -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001.py!} -``` +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} /// tip @@ -202,24 +188,20 @@ It's possible that `ORJSONResponse` might be a faster alternative. /// -### `RedirectResponse` +### `RedirectResponse` { #redirectresponse } Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default. You can return a `RedirectResponse` directly: -```Python hl_lines="2 9" -{!../../../docs_src/custom_response/tutorial006.py!} -``` +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} --- Or you can use it in the `response_class` parameter: -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial006b.py!} -``` +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} If you do that, then you can return the URL directly from your *path operation* function. @@ -229,29 +211,23 @@ In this case, the `status_code` used will be the default one for the `RedirectRe You can also use the `status_code` parameter combined with the `response_class` parameter: -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial006c.py!} -``` +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} -### `StreamingResponse` +### `StreamingResponse` { #streamingresponse } Takes an async generator or a normal generator/iterator and streams the response body. -```Python hl_lines="2 14" -{!../../../docs_src/custom_response/tutorial007.py!} -``` +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} -#### Using `StreamingResponse` with file-like objects +#### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects } -If you have a file-like object (e.g. the object returned by `open()`), you can create a generator function to iterate over that file-like object. +If you have a file-like object (e.g. the object returned by `open()`), you can create a generator function to iterate over that file-like object. That way, you don't have to read it all first in memory, and you can pass that generator function to the `StreamingResponse`, and return it. This includes many libraries to interact with cloud storage, video processing, and others. -```{ .python .annotate hl_lines="2 10-12 14" } -{!../../../docs_src/custom_response/tutorial008.py!} -``` +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} 1. This is the generator function. It's a "generator function" because it contains `yield` statements inside. 2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response. @@ -267,32 +243,28 @@ Notice that here as we are using standard `open()` that doesn't support `async` /// -### `FileResponse` +### `FileResponse` { #fileresponse } Asynchronously streams a file as the response. Takes a different set of arguments to instantiate than the other response types: -* `path` - The filepath to the file to stream. +* `path` - The file path to the file to stream. * `headers` - Any custom headers to include, as a dictionary. * `media_type` - A string giving the media type. If unset, the filename or path will be used to infer a media type. * `filename` - If set, this will be included in the response `Content-Disposition`. File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers. -```Python hl_lines="2 10" -{!../../../docs_src/custom_response/tutorial009.py!} -``` +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} You can also use the `response_class` parameter: -```Python hl_lines="2 8 10" -{!../../../docs_src/custom_response/tutorial009b.py!} -``` +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} In this case, you can return the file path directly from your *path operation* function. -## Custom response class +## Custom response class { #custom-response-class } You can create your own custom response class, inheriting from `Response` and using it. @@ -302,9 +274,7 @@ Let's say you want it to return indented and formatted JSON, so you want to use 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!} -``` +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} Now instead of returning: @@ -322,7 +292,7 @@ Now instead of returning: Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉 -## Default response class +## Default response class { #default-response-class } When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default. @@ -330,9 +300,7 @@ The parameter that defines this is `default_response_class`. In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`. -```Python hl_lines="2 4" -{!../../../docs_src/custom_response/tutorial010.py!} -``` +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} /// tip @@ -340,6 +308,6 @@ You can still override `response_class` in *path operations* as before. /// -## Additional documentation +## Additional documentation { #additional-documentation } You can also declare the media type and many other details in OpenAPI using `responses`: [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md index 252ab6fa5e..b7b9b65c52 100644 --- a/docs/en/docs/advanced/dataclasses.md +++ b/docs/en/docs/advanced/dataclasses.md @@ -1,12 +1,10 @@ -# Using Dataclasses +# Using Dataclasses { #using-dataclasses } FastAPI is built on top of **Pydantic**, and I have been showing you how to use Pydantic models to declare requests and responses. But FastAPI also supports using `dataclasses` the same way: -```Python hl_lines="1 7-12 19-20" -{!../../../docs_src/dataclasses/tutorial001.py!} -``` +{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} This is still supported thanks to **Pydantic**, as it has internal support for `dataclasses`. @@ -30,13 +28,11 @@ But if you have a bunch of dataclasses laying around, this is a nice trick to us /// -## Dataclasses in `response_model` +## Dataclasses in `response_model` { #dataclasses-in-response-model } You can also use `dataclasses` in the `response_model` parameter: -```Python hl_lines="1 7-13 19" -{!../../../docs_src/dataclasses/tutorial002.py!} -``` +{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} The dataclass will be automatically converted to a Pydantic dataclass. @@ -44,7 +40,7 @@ This way, its schema will show up in the API docs user interface: -## Dataclasses in Nested Data Structures +## Dataclasses in Nested Data Structures { #dataclasses-in-nested-data-structures } You can also combine `dataclasses` with other type annotations to make nested data structures. @@ -52,9 +48,7 @@ In some cases, you might still have to use Pydantic's version of `dataclasses`. In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement: -```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } -{!../../../docs_src/dataclasses/tutorial003.py!} -``` +{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} 1. We still import `field` from standard `dataclasses`. @@ -90,12 +84,12 @@ You can combine `dataclasses` with other type annotations in many different comb Check the in-code annotation tips above to see more specific details. -## Learn More +## Learn More { #learn-more } You can also combine `dataclasses` with other Pydantic models, inherit from them, include them in your own models, etc. To learn more, check the Pydantic docs about dataclasses. -## Version +## Version { #version } This is available since FastAPI version `0.67.0`. 🔖 diff --git a/docs/en/docs/advanced/events.md b/docs/en/docs/advanced/events.md index 7fd9343446..d9e3cb52e7 100644 --- a/docs/en/docs/advanced/events.md +++ b/docs/en/docs/advanced/events.md @@ -1,4 +1,4 @@ -# Lifespan Events +# Lifespan Events { #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**. @@ -8,7 +8,7 @@ Because this code is executed before the application **starts** taking requests, 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 +## Use Case { #use-case } Let's start with an example **use case** and then see how to solve it with this. @@ -22,7 +22,7 @@ You could load it at the top level of the module/file, but that would also mean 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 +## Lifespan { #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). @@ -30,9 +30,7 @@ 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!} -``` +{* ../../docs_src/events/tutorial003.py hl[16,19] *} 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*. @@ -46,27 +44,23 @@ Maybe you need to start a new version, or you just got tired of running it. 🤷 /// -### Lifespan function +### Lifespan function { #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!} -``` +{* ../../docs_src/events/tutorial003.py hl[14:19] *} 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 +### Async Context Manager { #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!} -``` +{* ../../docs_src/events/tutorial003.py hl[1,13] *} 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: @@ -88,11 +82,9 @@ In our code example above, we don't use it directly, but we pass it to FastAPI f 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!} -``` +{* ../../docs_src/events/tutorial003.py hl[22] *} -## Alternative Events (deprecated) +## Alternative Events (deprecated) { #alternative-events-deprecated } /// warning @@ -108,13 +100,11 @@ You can define event handlers (functions) that need to be executed before the ap These functions can be declared with `async def` or normal `def`. -### `startup` event +### `startup` event { #startup-event } To add a function that should be run before the application starts, declare it with the event `"startup"`: -```Python hl_lines="8" -{!../../../docs_src/events/tutorial001.py!} -``` +{* ../../docs_src/events/tutorial001.py hl[8] *} In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values. @@ -122,13 +112,11 @@ 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 { #shutdown-event } To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`: -```Python hl_lines="6" -{!../../../docs_src/events/tutorial002.py!} -``` +{* ../../docs_src/events/tutorial002.py hl[6] *} Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`. @@ -150,7 +138,7 @@ So, we declare the event handler function with standard `def` instead of `async /// -### `startup` and `shutdown` together +### `startup` and `shutdown` together { #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. @@ -158,7 +146,7 @@ Doing that in separated functions that don't share logic or variables together i Because of that, it's now recommended to instead use the `lifespan` as explained above. -## Technical Details +## Technical Details { #technical-details } Just a technical detail for the curious nerds. 🤓 @@ -166,12 +154,12 @@ Underneath, in the ASGI technical specification, this is part of the Starlette's Lifespan' 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 +## Sub Applications { #sub-applications } 🚨 Keep 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 faa7c323f5..897c308086 100644 --- a/docs/en/docs/advanced/generate-clients.md +++ b/docs/en/docs/advanced/generate-clients.md @@ -1,129 +1,76 @@ -# Generate Clients +# Generating SDKs { #generating-sdks } -As **FastAPI** is based on the OpenAPI specification, you get automatic compatibility with many tools, including the automatic API docs (provided by Swagger UI). +Because **FastAPI** is based on the **OpenAPI** specification, its APIs can be described in a standard format that many tools understand. -One particular advantage that is not necessarily obvious is that you can **generate clients** (sometimes called **SDKs** ) for your API, for many different **programming languages**. +This makes it easy to generate up-to-date **documentation**, client libraries (**SDKs**) in multiple languages, and **testing** or **automation workflows** that stay in sync with your code. -## OpenAPI Client Generators +In this guide, you'll learn how to generate a **TypeScript SDK** for your FastAPI backend. -There are many tools to generate clients from **OpenAPI**. +## Open Source SDK Generators { #open-source-sdk-generators } -A common tool is OpenAPI Generator. +A versatile option is the OpenAPI Generator, which supports **many programming languages** and can generate SDKs from your OpenAPI specification. -If you are building a **frontend**, a very interesting alternative is openapi-ts. +For **TypeScript clients**, Hey API is a purpose-built solution, providing an optimized experience for the TypeScript ecosystem. -## Client and SDK Generators - Sponsor +You can discover more SDK generators on OpenAPI.Tools. -There are also some **company-backed** Client and SDK generators based on OpenAPI (FastAPI), in some cases they can offer you **additional features** on top of high-quality generated SDKs/clients. +/// tip -Some of them also ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**. +FastAPI automatically generates **OpenAPI 3.1** specifications, so any tool you use must support this version. -And it shows their true commitment to FastAPI and its **community** (you), as they not only want to provide you a **good service** but also want to make sure you have a **good and healthy framework**, FastAPI. 🙇 +/// + +## SDK Generators from FastAPI Sponsors { #sdk-generators-from-fastapi-sponsors } + +This section highlights **venture-backed** and **company-supported** solutions from companies that sponsor FastAPI. These products provide **additional features** and **integrations** on top of high-quality generated SDKs. + +By ✨ [**sponsoring FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, these companies help ensure the framework and its **ecosystem** remain healthy and **sustainable**. + +Their sponsorship also demonstrates a strong commitment to the FastAPI **community** (you), showing that they care not only about offering a **great service** but also about supporting a **robust and thriving framework**, FastAPI. 🙇 For example, you might want to try: -* Speakeasy -* Stainless -* liblab +* Speakeasy +* Stainless +* liblab -There are also several other companies offering similar services that you can search and find online. 🤓 +Some of these solutions may also be open source or offer free tiers, so you can try them without a financial commitment. Other commercial SDK generators are available and can be found online. 🤓 -## Generate a TypeScript Frontend Client +## Create a TypeScript SDK { #create-a-typescript-sdk } Let's start with a simple FastAPI application: -//// tab | Python 3.9+ - -```Python hl_lines="7-9 12-13 16-17 21" -{!> ../../../docs_src/generate_clients/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-11 14-15 18 19 23" -{!> ../../../docs_src/generate_clients/tutorial001.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} Notice that the *path operations* define the models they use for request payload and response payload, using the models `Item` and `ResponseMessage`. -### API Docs +### API Docs { #api-docs } -If you go to the API docs, you will see that it has the **schemas** for the data to be sent in requests and received in responses: +If you go to `/docs`, you will see that it has the **schemas** for the data to be sent in requests and received in responses: You can see those schemas because they were declared with the models in the app. -That information is available in the app's **OpenAPI schema**, and then shown in the API docs (by Swagger UI). +That information is available in the app's **OpenAPI schema**, and then shown in the API docs. -And that same information from the models that is included in OpenAPI is what can be used to **generate the client code**. +That same information from the models that is included in OpenAPI is what can be used to **generate the client code**. -### Generate a TypeScript Client +### Hey API { #hey-api } -Now that we have the app with the models, we can generate the client code for the frontend. +Once we have a FastAPI app with the models, we can use Hey API to generate a TypeScript client. The fastest way to do that is via npx. -#### Install `openapi-ts` - -You can install `openapi-ts` in your frontend code with: - -
- -```console -$ npm install @hey-api/openapi-ts --save-dev - ----> 100% +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client ``` -
+This will generate a TypeScript SDK in `./src/client`. -#### Generate Client Code +You can learn how to install `@hey-api/openapi-ts` and read about the generated output on their website. -To generate the client code you can use the command line application `openapi-ts` that would now be installed. +### Using the SDK { #using-the-sdk } -Because it is installed in the local project, you probably wouldn't be able to call that command directly, but you would put it on your `package.json` file. - -It could look like this: - -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} -``` - -After having that NPM `generate-client` script there, you can run it with: - -
- -```console -$ npm run generate-client - -frontend-app@1.0.0 generate-client /home/user/code/frontend-app -> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios -``` - -
- -That command will generate code in `./src/client` and will use `axios` (the frontend HTTP library) internally. - -### Try Out the Client Code - -Now you can import and use the client code, it could look like this, notice that you get autocompletion for the methods: +Now you can import and use the client code. It could look like this, notice that you get autocompletion for the methods: @@ -145,44 +92,30 @@ The response object will also have autocompletion: -## FastAPI App with Tags +## FastAPI App with Tags { #fastapi-app-with-tags } -In many cases your FastAPI app will be bigger, and you will probably use tags to separate different groups of *path operations*. +In many cases, your FastAPI app will be bigger, and you will probably use tags to separate different groups of *path operations*. For example, you could have a section for **items** and another section for **users**, and they could be separated by tags: -//// tab | Python 3.9+ +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} -```Python hl_lines="21 26 34" -{!> ../../../docs_src/generate_clients/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23 28 36" -{!> ../../../docs_src/generate_clients/tutorial002.py!} -``` - -//// - -### Generate a TypeScript Client with Tags +### Generate a TypeScript Client with Tags { #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. -This way you will be able to have things ordered and grouped correctly for the client code: +This way, you will be able to have things ordered and grouped correctly for the client code: -In this case you have: +In this case, you have: * `ItemsService` * `UsersService` -### Client Method Names +### Client Method Names { #client-method-names } -Right now the generated method names like `createItemItemsPost` don't look very clean: +Right now, the generated method names like `createItemItemsPost` don't look very clean: ```TypeScript ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) @@ -194,17 +127,17 @@ OpenAPI requires that each operation ID is unique across all the *path operation But I'll show you how to improve that next. 🤓 -## Custom Operation IDs and Better Method Names +## Custom Operation IDs and Better Method Names { #custom-operation-ids-and-better-method-names } You can **modify** the way these operation IDs are **generated** to make them simpler and have **simpler method names** in the clients. -In this case you will have to ensure that each operation ID is **unique** in some other way. +In this case, you will have to ensure that each operation ID is **unique** in some other way. For example, you could make sure that each *path operation* has a tag, and then generate the operation ID based on the **tag** and the *path operation* **name** (the function name). -### Custom Generate Unique ID Function +### Custom Generate Unique ID Function { #custom-generate-unique-id-function } -FastAPI uses a **unique ID** for each *path operation*, it is used for the **operation ID** and also for the names of any needed custom models, for requests or responses. +FastAPI uses a **unique ID** for each *path operation*, which is used for the **operation ID** and also for the names of any needed custom models, for requests or responses. You can customize that function. It takes an `APIRoute` and outputs a string. @@ -212,31 +145,17 @@ 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: -//// tab | Python 3.9+ +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} -```Python hl_lines="6-7 10" -{!> ../../../docs_src/generate_clients/tutorial003_py39.py!} -``` +### Generate a TypeScript Client with Custom Operation IDs { #generate-a-typescript-client-with-custom-operation-ids } -//// - -//// tab | Python 3.8+ - -```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: +Now, if you generate the client again, you will see that it has the improved method names: As you see, the method names now have the tag and then the function name, now they don't include information from the URL path and the HTTP operation. -### Preprocess the OpenAPI Specification for the Client Generator +### Preprocess the OpenAPI Specification for the Client Generator { #preprocess-the-openapi-specification-for-the-client-generator } The generated code still has some **duplicated information**. @@ -244,57 +163,37 @@ We already know that this method is related to the **items** because that word i We will probably still want to keep it for OpenAPI in general, as that will ensure that the operation IDs are **unique**. -But for the generated client we could **modify** the OpenAPI operation IDs right before generating the clients, just to make those method names nicer and **cleaner**. +But for the generated client, we could **modify** the OpenAPI operation IDs right before generating the clients, just to make those method names nicer and **cleaner**. We could download the OpenAPI JSON to a file `openapi.json` and then we could **remove that prefixed tag** with a script like this: -//// tab | Python - -```Python -{!> ../../../docs_src/generate_clients/tutorial004.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial004.py *} //// tab | Node.js ```Javascript -{!> ../../../docs_src/generate_clients/tutorial004.js!} +{!> ../../docs_src/generate_clients/tutorial004.js!} ``` //// With that, the operation IDs would be renamed from things like `items-get_items` to just `get_items`, that way the client generator can generate simpler method names. -### Generate a TypeScript Client with the Preprocessed OpenAPI +### Generate a TypeScript Client with the Preprocessed OpenAPI { #generate-a-typescript-client-with-the-preprocessed-openapi } -Now as the end result is in a file `openapi.json`, you would modify the `package.json` to use that local file, for example: +Since the end result is now in an `openapi.json` file, you need to update your input location: -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client ``` After generating the new client, you would now have **clean method names**, with all the **autocompletion**, **inline errors**, etc: -## Benefits +## Benefits { #benefits } -When using the automatically generated clients you would get **autocompletion** for: +When using the automatically generated clients, you would get **autocompletion** for: * Methods. * Request payloads in the body, query parameters, etc. @@ -304,6 +203,6 @@ You would also have **inline errors** for everything. And whenever you update the backend code, and **regenerate** the frontend, it would have any new *path operations* available as methods, the old ones removed, and any other change would be reflected on the generated code. 🤓 -This also means that if something changed it will be **reflected** on the client code automatically. And if you **build** the client it will error out if you have any **mismatch** in the data used. +This also means that if something changed, it will be **reflected** on the client code automatically. And if you **build** the client, it will error out if you have any **mismatch** in the data used. So, you would **detect many errors** very early in the development cycle instead of having to wait for the errors to show up to your final users in production and then trying to debug where the problem is. ✨ diff --git a/docs/en/docs/advanced/index.md b/docs/en/docs/advanced/index.md index 36f0720c0e..9355516fb4 100644 --- a/docs/en/docs/advanced/index.md +++ b/docs/en/docs/advanced/index.md @@ -1,6 +1,6 @@ -# Advanced User Guide +# Advanced User Guide { #advanced-user-guide } -## Additional Features +## Additional Features { #additional-features } The main [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} should be enough to give you a tour through all the main features of **FastAPI**. @@ -14,23 +14,8 @@ And it's possible that for your use case, the solution is in one of them. /// -## Read the Tutorial first +## Read the Tutorial first { #read-the-tutorial-first } You could still use most of the features in **FastAPI** with the knowledge from the main [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank}. And the next sections assume you already read it, and assume that you know those main ideas. - -## External Courses - -Although the [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} and this **Advanced User Guide** are written as a guided tutorial (like a book) and should be enough for you to **learn FastAPI**, you might want to complement it with additional courses. - -Or it might be the case that you just prefer to take other courses because they adapt better to your learning style. - -Some course providers ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**. - -And it shows their true commitment to FastAPI and its **community** (you), as they not only want to provide you a **good learning experience** but also want to make sure you have a **good and healthy framework**, FastAPI. 🙇 - -You might want to try their courses: - -* Talk Python Training -* Test-Driven Development diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md index 70415adca3..8deb0d917d 100644 --- a/docs/en/docs/advanced/middleware.md +++ b/docs/en/docs/advanced/middleware.md @@ -1,4 +1,4 @@ -# Advanced Middleware +# Advanced Middleware { #advanced-middleware } In the main tutorial you read how to add [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank} to your application. @@ -6,7 +6,7 @@ And then you also read how to handle [CORS with the `CORSMiddleware`](../tutoria In this section we'll see how to use other middlewares. -## Adding ASGI middlewares +## Adding ASGI middlewares { #adding-asgi-middlewares } As **FastAPI** is based on Starlette and implements the ASGI specification, you can use any ASGI middleware. @@ -24,7 +24,7 @@ app = SomeASGIApp() new_app = UnicornMiddleware(app, some_config="rainbow") ``` -But FastAPI (actually Starlette) provides a simpler way to do it that makes sure that the internal middlewares to handle server errors and custom exception handlers work properly. +But FastAPI (actually Starlette) provides a simpler way to do it that makes sure that the internal middlewares handle server errors and custom exception handlers work properly. For that, you use `app.add_middleware()` (as in the example for CORS). @@ -39,11 +39,11 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") `app.add_middleware()` receives a middleware class as the first argument and any additional arguments to be passed to the middleware. -## Integrated middlewares +## Integrated middlewares { #integrated-middlewares } **FastAPI** includes several middlewares for common use cases, we'll see next how to use them. -/// note | "Technical Details" +/// note | Technical Details For the next examples, you could also use `from starlette.middleware.something import SomethingMiddleware`. @@ -51,46 +51,41 @@ For the next examples, you could also use `from starlette.middleware.something i /// -## `HTTPSRedirectMiddleware` +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } Enforces that all incoming requests must either be `https` or `wss`. -Any incoming requests to `http` or `ws` will be redirected to the secure scheme instead. +Any incoming request to `http` or `ws` will be redirected to the secure scheme instead. -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial001.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} -## `TrustedHostMiddleware` +## `TrustedHostMiddleware` { #trustedhostmiddleware } Enforces that all incoming requests have a correctly set `Host` header, in order to guard against HTTP Host Header attacks. -```Python hl_lines="2 6-8" -{!../../../docs_src/advanced_middleware/tutorial002.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} 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. +* `www_redirect` - If set to True, requests to non-www versions of the allowed hosts will be redirected to their www counterparts. Defaults to `True`. If an incoming request does not validate correctly then a `400` response will be sent. -## `GZipMiddleware` +## `GZipMiddleware` { #gzipmiddleware } Handles GZip responses for any request that includes `"gzip"` in the `Accept-Encoding` header. The middleware will handle both standard and streaming responses. -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial003.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} The following arguments are supported: * `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`. * `compresslevel` - Used during GZip compression. It is an integer ranging from 1 to 9. Defaults to `9`. Lower value results in faster compression but larger file sizes, while higher value results in slower compression but smaller file sizes. -## Other middlewares +## Other middlewares { #other-middlewares } There are many other ASGI middlewares. @@ -99,4 +94,4 @@ For example: * Uvicorn's `ProxyHeadersMiddleware` * MessagePack -To see other available middlewares check Starlette's Middleware docs and the ASGI Awesome List. +To see other available middlewares check Starlette's Middleware docs and the ASGI Awesome List. diff --git a/docs/en/docs/advanced/openapi-callbacks.md b/docs/en/docs/advanced/openapi-callbacks.md index 7fead2ed9f..059d893c26 100644 --- a/docs/en/docs/advanced/openapi-callbacks.md +++ b/docs/en/docs/advanced/openapi-callbacks.md @@ -1,4 +1,4 @@ -# OpenAPI Callbacks +# OpenAPI Callbacks { #openapi-callbacks } You could create an API with a *path operation* that could trigger a request to an *external API* created by someone else (probably the same developer that would be *using* your API). @@ -6,7 +6,7 @@ The process that happens when your API app calls the *external API* is named a " In this case, you could want to document how that external API *should* look like. What *path operation* it should have, what body it should expect, what response it should return, etc. -## An app with callbacks +## An app with callbacks { #an-app-with-callbacks } Let's see all this with an example. @@ -23,7 +23,7 @@ Then your API will (let's imagine): * Send a notification back to the API user (the external developer). * This will be done by sending a POST request (from *your API*) to some *external API* provided by that external developer (this is the "callback"). -## The normal **FastAPI** app +## The normal **FastAPI** app { #the-normal-fastapi-app } Let's first see how the normal API app would look like before adding the callback. @@ -31,9 +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="9-13 36-53" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} /// tip @@ -43,7 +41,7 @@ The `callback_url` query parameter uses a Pydantic OpenAPI 3 expression (see more below) where it can use variables with parameters and parts of the original request sent to *your API*. -### The callback path expression +### The callback path expression { #the-callback-path-expression } The callback *path* can have an OpenAPI 3 expression that can contain parts of the original request sent to *your API*. @@ -169,15 +163,13 @@ Notice how the callback URL used contains the URL received as a query parameter /// -### Add the callback router +### Add the callback router { #add-the-callback-router } At this point you have the *callback path operation(s)* needed (the one(s) that the *external developer* should implement in the *external API*) in the callback router you created above. 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="35" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} /// tip @@ -185,7 +177,7 @@ Notice that you are not passing the router itself (`invoices_callback_router`) t /// -### Check the docs +### Check the docs { #check-the-docs } Now you can start your app and go to http://127.0.0.1:8000/docs. diff --git a/docs/en/docs/advanced/openapi-webhooks.md b/docs/en/docs/advanced/openapi-webhooks.md index 5ee321e2ae..416cf4b75b 100644 --- a/docs/en/docs/advanced/openapi-webhooks.md +++ b/docs/en/docs/advanced/openapi-webhooks.md @@ -1,4 +1,4 @@ -# OpenAPI Webhooks +# OpenAPI Webhooks { #openapi-webhooks } There are cases where you want to tell your API **users** that your app could call *their* app (sending a request) with some data, normally to **notify** of some type of **event**. @@ -6,7 +6,7 @@ This means that instead of the normal process of your users sending requests to This is normally called a **webhook**. -## Webhooks steps +## Webhooks steps { #webhooks-steps } The process normally is that **you define** in your code what is the message that you will send, the **body of the request**. @@ -16,7 +16,7 @@ And **your users** define in some way (for example in a web dashboard somewhere) All the **logic** about how to register the URLs for webhooks and the code to actually send those requests is up to you. You write it however you want to in **your own code**. -## Documenting webhooks with **FastAPI** and OpenAPI +## Documenting webhooks with **FastAPI** and OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } With **FastAPI**, using OpenAPI, you can define the names of these webhooks, the types of HTTP operations that your app can send (e.g. `POST`, `PUT`, etc.) and the request **bodies** that your app would send. @@ -28,13 +28,11 @@ Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` /// -## An app with webhooks +## An app with webhooks { #an-app-with-webhooks } When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`. -```Python hl_lines="9-13 36-53" -{!../../../docs_src/openapi_webhooks/tutorial001.py!} -``` +{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**. @@ -48,7 +46,7 @@ Notice that with webhooks you are actually not declaring a *path* (like `/items/ This is because it is expected that **your users** would define the actual **URL path** where they want to receive the webhook request in some other way (e.g. a web dashboard). -### Check the docs +### Check the docs { #check-the-docs } Now you can start your app and go to http://127.0.0.1:8000/docs. diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index c8874bad90..b9961f9f38 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -1,6 +1,6 @@ -# Path Operation Advanced Configuration +# Path Operation Advanced Configuration { #path-operation-advanced-configuration } -## OpenAPI operationId +## OpenAPI operationId { #openapi-operationid } /// warning @@ -12,19 +12,15 @@ You can set the OpenAPI `operationId` to be used in your *path operation* with t You would have to make sure that it is unique for each operation. -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} -### Using the *path operation function* name as the operationId +### Using the *path operation function* name as the operationId { #using-the-path-operation-function-name-as-the-operationid } If you want to use your APIs' function names as `operationId`s, you can iterate over all of them and override each *path operation's* `operation_id` using their `APIRoute.name`. You should do it after adding all your *path operations*. -```Python hl_lines="2 12-21 24" -{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} /// tip @@ -40,15 +36,13 @@ Even if they are in different modules (Python files). /// -## Exclude from OpenAPI +## Exclude from OpenAPI { #exclude-from-openapi } To exclude a *path operation* from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`: -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} -## Advanced description from docstring +## Advanced description from docstring { #advanced-description-from-docstring } You can limit the lines used from the docstring of a *path operation function* for OpenAPI. @@ -56,11 +50,9 @@ Adding an `\f` (an escaped "form feed" character) causes **FastAPI** to truncate It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest. -```Python hl_lines="19-29" -{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} -## Additional Responses +## Additional Responses { #additional-responses } You probably have seen how to declare the `response_model` and `status_code` for a *path operation*. @@ -70,11 +62,11 @@ You can also declare additional responses with their models, status codes, etc. There's a whole chapter here in the documentation about it, you can read it at [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank}. -## OpenAPI Extra +## OpenAPI Extra { #openapi-extra } When you declare a *path operation* in your application, **FastAPI** automatically generates the relevant metadata about that *path operation* to be included in the OpenAPI schema. -/// note | "Technical details" +/// note | Technical details In the OpenAPI specification it is called the Operation Object. @@ -96,13 +88,11 @@ If you only need to declare additional responses, a more convenient way to do it You can extend the OpenAPI schema for a *path operation* using the parameter `openapi_extra`. -### OpenAPI Extensions +### OpenAPI Extensions { #openapi-extensions } This `openapi_extra` can be helpful, for example, to declare [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!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*. @@ -139,7 +129,7 @@ And if you see the resulting OpenAPI (at `/openapi.json` in your API), you will } ``` -### Custom OpenAPI *path operation* schema +### Custom OpenAPI *path operation* schema { #custom-openapi-path-operation-schema } The dictionary in `openapi_extra` will be deeply merged with the automatically generated OpenAPI schema for the *path operation*. @@ -149,15 +139,13 @@ For example, you could decide to read and validate the request with your own cod You could do that with `openapi_extra`: -```Python hl_lines="20-37 39-40" -{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} In this example, we didn't declare any Pydantic model. In fact, the request body is not even parsed as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. Nevertheless, we can declare the expected schema for the request body. -### Custom OpenAPI content type +### Custom OpenAPI content type { #custom-openapi-content-type } Using this same trick, you could use a Pydantic model to define the JSON Schema that is then included in the custom OpenAPI schema section for the *path operation*. @@ -167,17 +155,13 @@ For example, in this application we don't use FastAPI's integrated functionality //// tab | Pydantic v2 -```Python hl_lines="17-22 24" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} //// //// tab | Pydantic v1 -```Python hl_lines="17-22 24" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} //// @@ -195,17 +179,13 @@ And then in our code, we parse that YAML content directly, and then we are again //// tab | Pydantic v2 -```Python hl_lines="26-33" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} //// //// tab | Pydantic v1 -```Python hl_lines="26-33" -{!> ../../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} //// diff --git a/docs/en/docs/advanced/response-change-status-code.md b/docs/en/docs/advanced/response-change-status-code.md index b88d74a8af..912ed0f1a1 100644 --- a/docs/en/docs/advanced/response-change-status-code.md +++ b/docs/en/docs/advanced/response-change-status-code.md @@ -1,10 +1,10 @@ -# Response - Change Status Code +# Response - Change Status Code { #response-change-status-code } You probably read before that you can set a default [Response Status Code](../tutorial/response-status-code.md){.internal-link target=_blank}. But in some cases you need to return a different status code than the default. -## Use case +## Use case { #use-case } For example, imagine that you want to return an HTTP status code of "OK" `200` by default. @@ -14,15 +14,13 @@ But you still want to be able to filter and convert the data you return with a ` For those cases, you can use a `Response` parameter. -## Use a `Response` parameter +## Use a `Response` parameter { #use-a-response-parameter } You can declare a parameter of type `Response` in your *path operation function* (as you can do for cookies and headers). And then you can set the `status_code` in that *temporal* response object. -```Python hl_lines="1 9 12" -{!../../../docs_src/response_change_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} And then you can return any object you need, as you normally would (a `dict`, a database model, etc). diff --git a/docs/en/docs/advanced/response-cookies.md b/docs/en/docs/advanced/response-cookies.md index 85e423f42a..1f41d84b7c 100644 --- a/docs/en/docs/advanced/response-cookies.md +++ b/docs/en/docs/advanced/response-cookies.md @@ -1,14 +1,12 @@ -# Response Cookies +# Response Cookies { #response-cookies } -## Use a `Response` parameter +## Use a `Response` parameter { #use-a-response-parameter } You can declare a parameter of type `Response` in your *path operation function*. And then you can set cookies in that *temporal* response object. -```Python hl_lines="1 8-9" -{!../../../docs_src/response_cookies/tutorial002.py!} -``` +{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} And then you can return any object you need, as you normally would (a `dict`, a database model, etc). @@ -18,7 +16,7 @@ And if you declared a `response_model`, it will still be used to filter and conv You can also declare the `Response` parameter in dependencies, and set cookies (and headers) in them. -## Return a `Response` directly +## Return a `Response` directly { #return-a-response-directly } You can also create cookies when returning a `Response` directly in your code. @@ -26,9 +24,7 @@ To do that, you can create a response as described in [Return a Response Directl Then set Cookies in it, and then return it: -```Python hl_lines="10-12" -{!../../../docs_src/response_cookies/tutorial001.py!} -``` +{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} /// tip @@ -40,9 +36,9 @@ And also that you are not sending any data that should have been filtered by a ` /// -### More info +### More info { #more-info } -/// note | "Technical Details" +/// note | Technical Details You could also use `from starlette.responses import Response` or `from starlette.responses import JSONResponse`. @@ -52,4 +48,4 @@ And as the `Response` can be used frequently to set headers and cookies, **FastA /// -To see all the available parameters and options, check the documentation in Starlette. +To see all the available parameters and options, check the documentation in Starlette. diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md index 2251659c55..3197e1bd46 100644 --- a/docs/en/docs/advanced/response-directly.md +++ b/docs/en/docs/advanced/response-directly.md @@ -1,4 +1,4 @@ -# Return a Response Directly +# Return a Response Directly { #return-a-response-directly } When you create a **FastAPI** *path operation* you can normally return any data from it: a `dict`, a `list`, a Pydantic model, a database model, etc. @@ -10,7 +10,7 @@ But you can return a `JSONResponse` directly from your *path operations*. It might be useful, for example, to return custom headers or cookies. -## Return a `Response` +## Return a `Response` { #return-a-response } In fact, you can return any `Response` or any sub-class of it. @@ -26,19 +26,17 @@ It won't do any data conversion with Pydantic models, it won't convert the conte This gives you a lot of flexibility. You can return any data type, override any data declaration or validation, etc. -## Using the `jsonable_encoder` in a `Response` +## Using the `jsonable_encoder` in a `Response` { #using-the-jsonable-encoder-in-a-response } -Because **FastAPI** doesn't do any change to a `Response` you return, you have to make sure its contents are ready for it. +Because **FastAPI** doesn't make any changes to a `Response` you return, you have to make sure its contents are ready for it. For example, you cannot put a Pydantic model in a `JSONResponse` without first converting it to a `dict` with all the data types (like `datetime`, `UUID`, etc) converted to JSON-compatible types. For those cases, you can use the `jsonable_encoder` to convert your data before passing it to a response: -```Python hl_lines="6-7 21-22" -{!../../../docs_src/response_directly/tutorial001.py!} -``` +{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} -/// note | "Technical Details" +/// note | Technical Details You could also use `from starlette.responses import JSONResponse`. @@ -46,7 +44,7 @@ You could also use `from starlette.responses import JSONResponse`. /// -## Returning a custom `Response` +## Returning a custom `Response` { #returning-a-custom-response } The example above shows all the parts you need, but it's not very useful yet, as you could have just returned the `item` directly, and **FastAPI** would put it in a `JSONResponse` for you, converting it to a `dict`, etc. All that by default. @@ -56,13 +54,11 @@ Let's say that you want to return an using the 'X-' prefix. +Keep in mind that custom proprietary headers can be added using the `X-` prefix. -But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS configurations (read more in [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), using the parameter `expose_headers` documented in Starlette's CORS docs. +But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS configurations (read more in [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), using the parameter `expose_headers` documented in Starlette's CORS docs. diff --git a/docs/en/docs/advanced/security/http-basic-auth.md b/docs/en/docs/advanced/security/http-basic-auth.md index c302bf8dcb..01b98eeff3 100644 --- a/docs/en/docs/advanced/security/http-basic-auth.md +++ b/docs/en/docs/advanced/security/http-basic-auth.md @@ -1,4 +1,4 @@ -# HTTP Basic Auth +# HTTP Basic Auth { #http-basic-auth } For the simplest cases, you can use HTTP Basic Auth. @@ -12,7 +12,7 @@ That tells the browser to show the integrated prompt for a username and password Then, when you type that username and password, the browser sends them in the header automatically. -## Simple HTTP Basic Auth +## Simple HTTP Basic Auth { #simple-http-basic-auth } * Import `HTTPBasic` and `HTTPBasicCredentials`. * Create a "`security` scheme" using `HTTPBasic`. @@ -20,41 +20,13 @@ 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. -//// tab | Python 3.9+ - -```Python hl_lines="4 8 12" -{!> ../../../docs_src/security/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="2 7 11" -{!> ../../../docs_src/security/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="2 6 10" -{!> ../../../docs_src/security/tutorial006.py!} -``` - -//// +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} 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: -## Check the username +## Check the username { #check-the-username } Here's a more complete example. @@ -68,35 +40,7 @@ To handle that, we first convert the `username` and `password` to `bytes` encodi Then we can use `secrets.compare_digest()` to ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. -//// tab | Python 3.9+ - -```Python hl_lines="1 12-24" -{!> ../../../docs_src/security/tutorial007_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 12-24" -{!> ../../../docs_src/security/tutorial007_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="1 11-21" -{!> ../../../docs_src/security/tutorial007.py!} -``` - -//// +{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} This would be similar to: @@ -108,7 +52,7 @@ if not (credentials.username == "stanleyjobson") or not (credentials.password == But by using the `secrets.compare_digest()` it will be secure against a type of attacks called "timing attacks". -### Timing Attacks +### Timing Attacks { #timing-attacks } But what's a "timing attack"? @@ -136,19 +80,19 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": Python will have to compare the whole `stanleyjobso` in both `stanleyjobsox` and `stanleyjobson` before realizing that both strings are not the same. So it will take some extra microseconds to reply back "Incorrect username or password". -#### The time to answer helps the attackers +#### The time to answer helps the attackers { #the-time-to-answer-helps-the-attackers } At that point, by noticing that the server took some microseconds longer to send the "Incorrect username or password" response, the attackers will know that they got _something_ right, some of the initial letters were right. And then they can try again knowing that it's probably something more similar to `stanleyjobsox` than to `johndoe`. -#### A "professional" attack +#### A "professional" attack { #a-professional-attack } -Of course, the attackers would not try all this by hand, they would write a program to do it, possibly with thousands or millions of tests per second. And would get just one extra correct letter at a time. +Of course, the attackers would not try all this by hand, they would write a program to do it, possibly with thousands or millions of tests per second. And they would get just one extra correct letter at a time. But doing that, in some minutes or hours the attackers would have guessed the correct username and password, with the "help" of our application, just using the time taken to answer. -#### Fix it with `secrets.compare_digest()` +#### Fix it with `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest } But in our code we are actually using `secrets.compare_digest()`. @@ -156,36 +100,8 @@ In short, it will take the same time to compare `stanleyjobsox` to `stanleyjobso That way, using `secrets.compare_digest()` in your application code, it will be safe against this whole range of security attacks. -### Return the error +### Return the error { #return-the-error } 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: -//// tab | Python 3.9+ - -```Python hl_lines="26-30" -{!> ../../../docs_src/security/tutorial007_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="26-30" -{!> ../../../docs_src/security/tutorial007_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="23-27" -{!> ../../../docs_src/security/tutorial007.py!} -``` - -//// +{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/en/docs/advanced/security/index.md b/docs/en/docs/advanced/security/index.md index edb42132e6..996d716b4c 100644 --- a/docs/en/docs/advanced/security/index.md +++ b/docs/en/docs/advanced/security/index.md @@ -1,6 +1,6 @@ -# Advanced Security +# Advanced Security { #advanced-security } -## Additional Features +## Additional Features { #additional-features } There are some extra features to handle security apart from the ones covered in the [Tutorial - User Guide: Security](../../tutorial/security/index.md){.internal-link target=_blank}. @@ -12,7 +12,7 @@ And it's possible that for your use case, the solution is in one of them. /// -## Read the Tutorial first +## Read the Tutorial first { #read-the-tutorial-first } The next sections assume you already read the main [Tutorial - User Guide: Security](../../tutorial/security/index.md){.internal-link target=_blank}. diff --git a/docs/en/docs/advanced/security/oauth2-scopes.md b/docs/en/docs/advanced/security/oauth2-scopes.md index ff52d7bb80..67c927cd08 100644 --- a/docs/en/docs/advanced/security/oauth2-scopes.md +++ b/docs/en/docs/advanced/security/oauth2-scopes.md @@ -1,12 +1,12 @@ -# OAuth2 scopes +# OAuth2 scopes { #oauth2-scopes } You can use OAuth2 scopes directly with **FastAPI**, they are integrated to work seamlessly. This would allow you to have a more fine-grained permission system, following the OAuth2 standard, integrated into your OpenAPI application (and the API docs). -OAuth2 with scopes is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, Twitter, etc. They use it to provide specific permissions to users and applications. +OAuth2 with scopes is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, X (Twitter), etc. They use it to provide specific permissions to users and applications. -Every time you "log in with" Facebook, Google, GitHub, Microsoft, Twitter, that application is using OAuth2 with scopes. +Every time you "log in with" Facebook, Google, GitHub, Microsoft, X (Twitter), that application is using OAuth2 with scopes. In this section you will see how to manage authentication and authorization with the same OAuth2 with scopes in your **FastAPI** application. @@ -26,7 +26,7 @@ But if you know you need it, or you are curious, keep reading. /// -## OAuth2 scopes and OpenAPI +## OAuth2 scopes and OpenAPI { #oauth2-scopes-and-openapi } The OAuth2 specification defines "scopes" as a list of strings separated by spaces. @@ -58,149 +58,21 @@ For OAuth2 they are just strings. /// -## Global view +## Global view { #global-view } 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: -//// tab | Python 3.10+ - -```Python hl_lines="5 9 13 47 65 106 108-116 122-125 129-135 140 156" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="2 5 9 13 47 65 106 108-116 122-125 129-135 140 156" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="2 5 9 13 48 66 107 109-117 123-126 130-136 141 157" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="4 8 12 46 64 105 107-115 121-124 128-134 139 155" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="2 5 9 13 47 65 106 108-116 122-125 129-135 140 156" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="2 5 9 13 47 65 106 108-116 122-125 129-135 140 156" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} Now let's review those changes step by step. -## OAuth2 Security scheme +## OAuth2 Security scheme { #oauth2-security-scheme } The first change is that now we are declaring the OAuth2 security scheme with two available scopes, `me` and `items`. The `scopes` parameter receives a `dict` with each scope as a key and the description as the value: -//// tab | Python 3.10+ - -```Python hl_lines="63-66" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="63-66" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="64-67" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="62-65" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="63-66" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="63-66" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} Because we are now declaring those scopes, they will show up in the API docs when you log-in/authorize. @@ -210,7 +82,7 @@ This is the same mechanism used when you give permissions while logging in with -## JWT token with scopes +## JWT token with scopes { #jwt-token-with-scopes } Now, modify the token *path operation* to return the scopes requested. @@ -226,73 +98,9 @@ But in your application, for security, you should make sure you only add the sco /// -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} -```Python hl_lines="156" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="156" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="157" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="155" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="156" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="156" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Declare scopes in *path operations* and dependencies +## Declare scopes in *path operations* and dependencies { #declare-scopes-in-path-operations-and-dependencies } Now we declare that the *path operation* for `/users/me/items/` requires the scope `items`. @@ -316,73 +124,9 @@ We are doing it here to demonstrate how **FastAPI** handles scopes declared at d /// -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} -```Python hl_lines="5 140 171" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="5 140 171" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="5 141 172" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="4 139 168" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="5 140 169" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="5 140 169" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -/// info | "Technical Details" +/// info | Technical Details `Security` is actually a subclass of `Depends`, and it has just one extra parameter that we'll see later. @@ -392,7 +136,7 @@ But when you import `Query`, `Path`, `Depends`, `Security` and others from `fast /// -## Use `SecurityScopes` +## Use `SecurityScopes` { #use-securityscopes } Now update the dependency `get_current_user`. @@ -406,73 +150,9 @@ 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). -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} -```Python hl_lines="9 106" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9 106" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 107" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="8 105" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="9 106" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="9 106" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Use the `scopes` +## Use the `scopes` { #use-the-scopes } The parameter `security_scopes` will be of type `SecurityScopes`. @@ -484,73 +164,9 @@ We create an `HTTPException` that we can reuse (`raise`) later at several points 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). -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} -```Python hl_lines="106 108-116" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="106 108-116" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="107 109-117" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="105 107-115" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="106 108-116" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="106 108-116" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Verify the `username` and data shape +## Verify the `username` and data shape { #verify-the-username-and-data-shape } We verify that we get a `username`, and extract the scopes. @@ -564,145 +180,17 @@ 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. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} -```Python hl_lines="47 117-128" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="47 117-128" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="48 118-129" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="46 116-127" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="47 117-128" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="47 117-128" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Verify the `scopes` +## Verify the `scopes` { #verify-the-scopes } We now verify that all the scopes required, by this dependency and all the dependants (including *path operations*), are included in the scopes provided in the token received, otherwise raise an `HTTPException`. For this, we use `security_scopes.scopes`, that contains a `list` with all these scopes as `str`. -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} -```Python hl_lines="129-135" -{!> ../../../docs_src/security/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="129-135" -{!> ../../../docs_src/security/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="130-136" -{!> ../../../docs_src/security/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="128-134" -{!> ../../../docs_src/security/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="129-135" -{!> ../../../docs_src/security/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="129-135" -{!> ../../../docs_src/security/tutorial005.py!} -``` - -//// - -## Dependency tree and scopes +## Dependency tree and scopes { #dependency-tree-and-scopes } Let's review again this dependency tree and the scopes. @@ -735,7 +223,7 @@ All depending on the `scopes` declared in each *path operation* and each depende /// -## More details about `SecurityScopes` +## More details about `SecurityScopes` { #more-details-about-securityscopes } You can use `SecurityScopes` at any point, and in multiple places, it doesn't have to be at the "root" dependency. @@ -745,7 +233,7 @@ Because the `SecurityScopes` will have all the scopes declared by dependants, yo They will be checked independently for each *path operation*. -## Check it +## Check it { #check-it } If you open the API docs, you can authenticate and specify which scopes you want to authorize. @@ -757,7 +245,7 @@ And if you select the scope `me` but not the scope `items`, you will be able to That's what would happen to a third party application that tried to access one of these *path operations* with a token provided by a user, depending on how many permissions the user gave the application. -## About third party integrations +## About third party integrations { #about-third-party-integrations } In this example we are using the OAuth2 "password" flow. @@ -769,7 +257,7 @@ But if you are building an OAuth2 application that others would connect to (i.e. The most common is the implicit flow. -The most secure is the code flow, but is more complex to implement as it requires more steps. As it is more complex, many providers end up suggesting the implicit flow. +The most secure is the code flow, but it's more complex to implement as it requires more steps. As it is more complex, many providers end up suggesting the implicit flow. /// note @@ -781,6 +269,6 @@ But in the end, they are implementing the same OAuth2 standard. **FastAPI** includes utilities for all these OAuth2 authentication flows in `fastapi.security.oauth2`. -## `Security` in decorator `dependencies` +## `Security` in decorator `dependencies` { #security-in-decorator-dependencies } The same way you can define a `list` of `Depends` in the decorator's `dependencies` parameter (as explained in [Dependencies in path operation decorators](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), you could also use `Security` with `scopes` there. diff --git a/docs/en/docs/advanced/settings.md b/docs/en/docs/advanced/settings.md index 22bf7de200..a218c3d016 100644 --- a/docs/en/docs/advanced/settings.md +++ b/docs/en/docs/advanced/settings.md @@ -1,4 +1,4 @@ -# Settings and Environment Variables +# Settings and Environment Variables { #settings-and-environment-variables } In many cases your application could need some external settings or configurations, for example secret keys, database credentials, credentials for email services, etc. @@ -12,17 +12,17 @@ To understand environment variables you can read [Environment Variables](../envi /// -## Types and validation +## Types and validation { #types-and-validation } These environment variables can only handle text strings, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS). That means that any value read in Python from an environment variable will be a `str`, and any conversion to a different type or any validation has to be done in code. -## Pydantic `Settings` +## Pydantic `Settings` { #pydantic-settings } Fortunately, Pydantic provides a great utility to handle these settings coming from environment variables with Pydantic: Settings management. -### Install `pydantic-settings` +### Install `pydantic-settings` { #install-pydantic-settings } First, make sure you create your [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install the `pydantic-settings` package: @@ -52,7 +52,7 @@ In Pydantic v1 it came included with the main package. Now it is distributed as /// -### Create the `Settings` object +### Create the `Settings` object { #create-the-settings-object } Import `BaseSettings` from Pydantic and create a sub-class, very much like with a Pydantic model. @@ -62,9 +62,7 @@ You can use all the same validation features and tools you use for Pydantic mode //// tab | Pydantic v2 -```Python hl_lines="2 5-8 11" -{!> ../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} //// @@ -76,9 +74,7 @@ In Pydantic v1 you would import `BaseSettings` directly from `pydantic` instead /// -```Python hl_lines="2 5-8 11" -{!> ../../../docs_src/settings/tutorial001_pv1.py!} -``` +{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} //// @@ -92,15 +88,13 @@ Then, when you create an instance of that `Settings` class (in this case, in the Next it will convert and validate the data. So, when you use that `settings` object, you will have data of the types you declared (e.g. `items_per_user` will be an `int`). -### Use the `settings` +### Use the `settings` { #use-the-settings } Then you can use the new `settings` object in your application: -```Python hl_lines="18-20" -{!../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[18:20] *} -### Run the server +### Run the server { #run-the-server } Next, you would run the server passing the configurations as environment variables, for example you could set an `ADMIN_EMAIL` and `APP_NAME` with: @@ -126,21 +120,17 @@ The `app_name` would be `"ChimichangApp"`. And the `items_per_user` would keep its default value of `50`. -## Settings in another module +## Settings in another module { #settings-in-another-module } You could put those settings in another module file as you saw in [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}. For example, you could have a file `config.py` with: -```Python -{!../../../docs_src/settings/app01/config.py!} -``` +{* ../../docs_src/settings/app01/config.py *} And then use it in a file `main.py`: -```Python hl_lines="3 11-13" -{!../../../docs_src/settings/app01/main.py!} -``` +{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} /// tip @@ -148,55 +138,25 @@ You would also need a file `__init__.py` as you saw in [Bigger Applications - Mu /// -## Settings in a dependency +## Settings in a dependency { #settings-in-a-dependency } In some occasions it might be useful to provide the settings from a dependency, instead of having a global object with `settings` that is used everywhere. This could be especially useful during testing, as it's very easy to override a dependency with your own custom settings. -### The config file +### The config file { #the-config-file } Coming from the previous example, your `config.py` file could look like: -```Python hl_lines="10" -{!../../../docs_src/settings/app02/config.py!} -``` +{* ../../docs_src/settings/app02/config.py hl[10] *} Notice that now we don't create a default instance `settings = Settings()`. -### The main app file +### The main app file { #the-main-app-file } Now we create a dependency that returns a new `config.Settings()`. -//// tab | Python 3.9+ - -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="5 11-12" -{!> ../../../docs_src/settings/app02/main.py!} -``` - -//// +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} /// tip @@ -208,49 +168,19 @@ For now you can assume `get_settings()` is a normal function. And then we can require it from the *path operation function* as a dependency and use it anywhere we need it. -//// tab | Python 3.9+ +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ 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 +### Settings and testing { #settings-and-testing } Then it would be very easy to provide a different settings object during testing by creating a dependency override for `get_settings`: -```Python hl_lines="9-10 13 21" -{!../../../docs_src/settings/app02/test_main.py!} -``` +{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} In the dependency override we set a new value for the `admin_email` when creating the new `Settings` object, and then we return that new object. Then we can test that it is used. -## Reading a `.env` file +## Reading a `.env` file { #reading-a-env-file } If you have many settings that possibly change a lot, maybe in different environments, it might be useful to put them on a file and then read them from it as if they were environment variables. @@ -272,7 +202,7 @@ For this to work, you need to `pip install python-dotenv`. /// -### The `.env` file +### The `.env` file { #the-env-file } You could have a `.env` file with: @@ -281,15 +211,13 @@ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" ``` -### Read settings from `.env` +### Read settings from `.env` { #read-settings-from-env } And then update your `config.py` with: //// tab | Pydantic v2 -```Python hl_lines="9" -{!> ../../../docs_src/settings/app03_an/config.py!} -``` +{* ../../docs_src/settings/app03_an/config.py hl[9] *} /// tip @@ -301,9 +229,7 @@ The `model_config` attribute is used just for Pydantic configuration. You can re //// tab | Pydantic v1 -```Python hl_lines="9-10" -{!> ../../../docs_src/settings/app03_an/config_pv1.py!} -``` +{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} /// tip @@ -321,7 +247,7 @@ In Pydantic version 1 the configuration was done in an internal class `Config`, Here we define the config `env_file` inside of your Pydantic `Settings` class, and set the value to the filename with the dotenv file we want to use. -### Creating the `Settings` only once with `lru_cache` +### Creating the `Settings` only once with `lru_cache` { #creating-the-settings-only-once-with-lru-cache } Reading a file from disk is normally a costly (slow) operation, so you probably want to do it only once and then reuse the same settings object, instead of reading it for each request. @@ -344,39 +270,11 @@ 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. ✔️ -//// tab | Python 3.9+ +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an_py39/main.py!} -``` +Then for any subsequent call 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. -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ 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. - -#### `lru_cache` Technical Details +#### `lru_cache` Technical Details { #lru-cache-technical-details } `@lru_cache` modifies the function it decorates to return the same value that was returned the first time, instead of computing it again, executing the code of the function every time. @@ -439,7 +337,7 @@ That way, it behaves almost as if it was just a global variable. But as it uses `@lru_cache` is part of `functools` which is part of Python's standard library, you can read more about it in the Python docs for `@lru_cache`. -## Recap +## Recap { #recap } You can use Pydantic Settings to handle the settings or configurations for your application, with all the power of Pydantic models. diff --git a/docs/en/docs/advanced/sub-applications.md b/docs/en/docs/advanced/sub-applications.md index 8c52e091f0..fbd0e1af39 100644 --- a/docs/en/docs/advanced/sub-applications.md +++ b/docs/en/docs/advanced/sub-applications.md @@ -1,47 +1,41 @@ -# Sub Applications - Mounts +# Sub Applications - Mounts { #sub-applications-mounts } If you need to have two independent FastAPI applications, with their own independent OpenAPI and their own docs UIs, you can have a main app and "mount" one (or more) sub-application(s). -## Mounting a **FastAPI** application +## Mounting a **FastAPI** application { #mounting-a-fastapi-application } "Mounting" means adding a completely "independent" application in a specific path, that then takes care of handling everything under that path, with the _path operations_ declared in that sub-application. -### Top-level application +### Top-level application { #top-level-application } First, create the main, top-level, **FastAPI** application, and its *path operations*: -```Python hl_lines="3 6-8" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} -### Sub-application +### Sub-application { #sub-application } Then, create your sub-application, and its *path operations*. This sub-application is just another standard FastAPI application, but this is the one that will be "mounted": -```Python hl_lines="11 14-16" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} -### Mount the sub-application +### Mount the sub-application { #mount-the-sub-application } In your top-level application, `app`, mount the sub-application, `subapi`. In this case, it will be mounted at the path `/subapi`: -```Python hl_lines="11 19" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} -### Check the automatic API docs +### Check the automatic API docs { #check-the-automatic-api-docs } -Now, run `uvicorn` with the main app, if your file is `main.py`, it would be: +Now, run the `fastapi` command with your file:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -62,7 +56,7 @@ You will see the automatic API docs for the sub-application, including only its If you try interacting with any of the two user interfaces, they will work correctly, because the browser will be able to talk to each specific app or sub-app. -### Technical Details: `root_path` +### Technical Details: `root_path` { #technical-details-root-path } When you mount a sub-application as described above, FastAPI will take care of communicating the mount path for the sub-application using a mechanism from the ASGI specification called a `root_path`. diff --git a/docs/en/docs/advanced/templates.md b/docs/en/docs/advanced/templates.md index 416540ba4f..356f4d9caf 100644 --- a/docs/en/docs/advanced/templates.md +++ b/docs/en/docs/advanced/templates.md @@ -1,4 +1,4 @@ -# Templates +# Templates { #templates } You can use any template engine you want with **FastAPI**. @@ -6,7 +6,7 @@ A common choice is Jinja2, the same one used by Flask and other tools. There are utilities to configure it easily that you can use directly in your **FastAPI** application (provided by Starlette). -## Install dependencies +## Install dependencies { #install-dependencies } Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and install `jinja2`: @@ -20,16 +20,14 @@ $ pip install jinja2
-## Using `Jinja2Templates` +## Using `Jinja2Templates` { #using-jinja2templates } * Import `Jinja2Templates`. * Create a `templates` object that you can reuse later. * Declare a `Request` parameter in the *path operation* that will return a template. * Use the `templates` you created to render and return a `TemplateResponse`, pass the name of the template, the request object, and a "context" dictionary with key-value pairs to be used inside of the Jinja2 template. -```Python hl_lines="4 11 15-18" -{!../../../docs_src/templates/tutorial001.py!} -``` +{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} /// note @@ -45,7 +43,7 @@ By declaring `response_class=HTMLResponse` the docs UI will be able to know that /// -/// note | "Technical Details" +/// note | Technical Details You could also use `from starlette.templating import Jinja2Templates`. @@ -53,15 +51,15 @@ You could also use `from starlette.templating import Jinja2Templates`. /// -## Writing templates +## Writing templates { #writing-templates } Then you can write a template at `templates/item.html` with, for example: ```jinja hl_lines="7" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` -### Template Context Values +### Template Context Values { #template-context-values } In the HTML that contains: @@ -85,7 +83,7 @@ For example, with an ID of `42`, this would render: Item ID: 42 ``` -### Template `url_for` Arguments +### Template `url_for` Arguments { #template-url-for-arguments } You can also use `url_for()` inside of the template, it takes as arguments the same arguments that would be used by your *path operation function*. @@ -107,22 +105,22 @@ For example, with an ID of `42`, this would render: ``` -## Templates and static files +## Templates and static files { #templates-and-static-files } You can also use `url_for()` inside of the template, and use it, for example, with the `StaticFiles` you mounted with the `name="static"`. ```jinja hl_lines="4" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` In this example, it would link to a CSS file at `static/styles.css` with: ```CSS hl_lines="4" -{!../../../docs_src/templates/static/styles.css!} +{!../../docs_src/templates/static/styles.css!} ``` And because you are using `StaticFiles`, that CSS file would be served automatically by your **FastAPI** application at the URL `/static/styles.css`. -## More details +## More details { #more-details } -For more details, including how to test templates, check Starlette's docs on templates. +For more details, including how to test templates, check Starlette's docs on templates. diff --git a/docs/en/docs/advanced/testing-database.md b/docs/en/docs/advanced/testing-database.md deleted file mode 100644 index 974cf4caa7..0000000000 --- a/docs/en/docs/advanced/testing-database.md +++ /dev/null @@ -1,111 +0,0 @@ -# Testing a Database - -/// info - -These docs are about to be updated. 🎉 - -The current version assumes Pydantic v1, and SQLAlchemy versions less than 2.0. - -The new docs will include Pydantic v2 and will use SQLModel (which is also based on SQLAlchemy) once it is updated to use Pydantic v2 as well. - -/// - -You can use the same dependency overrides from [Testing Dependencies with Overrides](testing-dependencies.md){.internal-link target=_blank} to alter a database for testing. - -You could want to set up a different database for testing, rollback the data after the tests, pre-fill it with some testing data, etc. - -The main idea is exactly the same you saw in that previous chapter. - -## Add tests for the SQL app - -Let's update the example from [SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank} to use a testing database. - -All the app code is the same, you can go back to that chapter check how it was. - -The only changes here are in the new testing file. - -Your normal dependency `get_db()` would return a database session. - -In the test, you could use a dependency override to return your *custom* database session instead of the one that would be used normally. - -In this example we'll create a temporary database only for the tests. - -## File structure - -We create a new file at `sql_app/tests/test_sql_app.py`. - -So the new file structure looks like: - -``` 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 -``` - -## Create the new database session - -First, we create a new database session with the new database. - -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. - -```Python hl_lines="8-13" -{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip - -You could reduce duplication in that code by putting it in a function and using it from both `database.py` and `tests/test_sql_app.py`. - -For simplicity and to focus on the specific testing code, we are just copying it. - -/// - -## Create the database - -Because now we are going to use a new database in a new file, we need to make sure we create the database with: - -```Python -Base.metadata.create_all(bind=engine) -``` - -That is normally called in `main.py`, but the line in `main.py` uses the database file `sql_app.db`, and we need to make sure we create `test.db` for the tests. - -So we add that line here, with the new file. - -```Python hl_lines="16" -{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -## Dependency override - -Now we create the dependency override and add it to the overrides for our app. - -```Python hl_lines="19-24 27" -{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip - -The code for `override_get_db()` is almost exactly the same as for `get_db()`, but in `override_get_db()` we use the `TestingSessionLocal` for the testing database instead. - -/// - -## Test the app - -Then we can just test the app as normally. - -```Python hl_lines="32-47" -{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -And all the modifications we made in the database during the tests will be in the `test.db` database instead of the main `sql_app.db`. diff --git a/docs/en/docs/advanced/testing-dependencies.md b/docs/en/docs/advanced/testing-dependencies.md index 92e25f88d9..b52b47c969 100644 --- a/docs/en/docs/advanced/testing-dependencies.md +++ b/docs/en/docs/advanced/testing-dependencies.md @@ -1,6 +1,6 @@ -# Testing Dependencies with Overrides +# Testing Dependencies with Overrides { #testing-dependencies-with-overrides } -## Overriding dependencies during testing +## Overriding dependencies during testing { #overriding-dependencies-during-testing } There are some scenarios where you might want to override a dependency during testing. @@ -8,7 +8,7 @@ You don't want the original dependency to run (nor any of the sub-dependencies i Instead, you want to provide a different dependency that will be used only during tests (possibly only some specific tests), and will provide a value that can be used where the value of the original dependency was used. -### Use cases: external service +### Use cases: external service { #use-cases-external-service } An example could be that you have an external authentication provider that you need to call. @@ -20,7 +20,7 @@ You probably want to test the external provider once, but not necessarily call i In this case, you can override the dependency that calls that provider, and use a custom dependency that returns a mock user, only for your tests. -### Use the `app.dependency_overrides` attribute +### Use the `app.dependency_overrides` attribute { #use-the-app-dependency-overrides-attribute } For these cases, your **FastAPI** application has an attribute `app.dependency_overrides`, it is a simple `dict`. @@ -28,57 +28,7 @@ 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. -//// tab | Python 3.10+ - -```Python hl_lines="26-27 30" -{!> ../../../docs_src/dependency_testing/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="28-29 32" -{!> ../../../docs_src/dependency_testing/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="29-30 33" -{!> ../../../docs_src/dependency_testing/tutorial001_an.py!} -``` - -//// - -//// tab | 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!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="28-29 32" -{!> ../../../docs_src/dependency_testing/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} /// tip diff --git a/docs/en/docs/advanced/testing-events.md b/docs/en/docs/advanced/testing-events.md index b24a2ccfe8..dd93374c4c 100644 --- a/docs/en/docs/advanced/testing-events.md +++ b/docs/en/docs/advanced/testing-events.md @@ -1,7 +1,12 @@ -# Testing Events: startup - shutdown +# Testing Events: lifespan and startup - shutdown { #testing-events-lifespan-and-startup-shutdown } -When you need your event handlers (`startup` and `shutdown`) to run in your tests, you can use the `TestClient` with a `with` statement: +When you need `lifespan` to run in your tests, you can use the `TestClient` with a `with` statement: -```Python hl_lines="9-12 20-24" -{!../../../docs_src/app_testing/tutorial003.py!} -``` +{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *} + + +You can read more details about the ["Running lifespan in tests in the official Starlette documentation site."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) + +For the deprecated `startup` and `shutdown` events, you can use the `TestClient` as follows: + +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/en/docs/advanced/testing-websockets.md b/docs/en/docs/advanced/testing-websockets.md index 6c071bc198..27eb2de2fc 100644 --- a/docs/en/docs/advanced/testing-websockets.md +++ b/docs/en/docs/advanced/testing-websockets.md @@ -1,15 +1,13 @@ -# Testing WebSockets +# Testing WebSockets { #testing-websockets } You can use the same `TestClient` to test WebSockets. For this, you use the `TestClient` in a `with` statement, connecting to the WebSocket: -```Python hl_lines="27-31" -{!../../../docs_src/app_testing/tutorial002.py!} -``` +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} /// note -For more details, check Starlette's documentation for testing WebSockets. +For more details, check Starlette's documentation for testing WebSockets. /// diff --git a/docs/en/docs/advanced/using-request-directly.md b/docs/en/docs/advanced/using-request-directly.md index 5473db5cba..c71d3b05d1 100644 --- a/docs/en/docs/advanced/using-request-directly.md +++ b/docs/en/docs/advanced/using-request-directly.md @@ -1,4 +1,4 @@ -# Using the Request Directly +# Using the Request Directly { #using-the-request-directly } Up to now, you have been declaring the parts of the request that you need with their types. @@ -13,9 +13,9 @@ And by doing so, **FastAPI** is validating that data, converting it and generati But there are situations where you might need to access the `Request` object directly. -## Details about the `Request` object +## Details about the `Request` object { #details-about-the-request-object } -As **FastAPI** is actually **Starlette** underneath, with a layer of several tools on top, you can use Starlette's `Request` object directly when you need to. +As **FastAPI** is actually **Starlette** underneath, with a layer of several tools on top, you can use Starlette's `Request` object directly when you need to. It would also mean that if you get data from the `Request` object directly (for example, read the body) it won't be validated, converted or documented (with OpenAPI, for the automatic API user interface) by FastAPI. @@ -23,15 +23,13 @@ Although any other parameter declared normally (for example, the body with a Pyd But there are specific cases where it's useful to get the `Request` object. -## Use the `Request` object directly +## Use the `Request` object directly { #use-the-request-object-directly } Let's imagine you want to get the client's IP address/host inside of your *path operation function*. For that you need to access the request directly. -```Python hl_lines="1 7-8" -{!../../../docs_src/using_request_directly/tutorial001.py!} -``` +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} By declaring a *path operation function* parameter with the type being the `Request` **FastAPI** will know to pass the `Request` in that parameter. @@ -45,11 +43,11 @@ The same way, you can declare any other parameter as normally, and additionally, /// -## `Request` documentation +## `Request` documentation { #request-documentation } -You can read more details about the `Request` object in the official Starlette documentation site. +You can read more details about the `Request` object in the official Starlette documentation site. -/// note | "Technical Details" +/// note | Technical Details You could also use `from starlette.requests import Request`. diff --git a/docs/en/docs/advanced/websockets.md b/docs/en/docs/advanced/websockets.md index 44c6c74284..ce11485a89 100644 --- a/docs/en/docs/advanced/websockets.md +++ b/docs/en/docs/advanced/websockets.md @@ -1,10 +1,10 @@ -# WebSockets +# WebSockets { #websockets } You can use WebSockets with **FastAPI**. -## Install `WebSockets` +## Install `websockets` { #install-websockets } -Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and install `websockets`: +Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and install `websockets` (a Python library that makes it easy to use the "WebSocket" protocol):
@@ -16,9 +16,9 @@ $ pip install websockets
-## WebSockets client +## WebSockets client { #websockets-client } -### In production +### In production { #in-production } In your production system, you probably have a frontend created with a modern framework like React, Vue.js or Angular. @@ -38,19 +38,15 @@ In production you would have one of the options above. But it's the simplest way to focus on the server-side of WebSockets and have a working example: -```Python hl_lines="2 6-38 41-43" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} -## Create a `websocket` +## Create a `websocket` { #create-a-websocket } In your **FastAPI** application, create a `websocket`: -```Python hl_lines="1 46-47" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} -/// note | "Technical Details" +/// note | Technical Details You could also use `from starlette.websockets import WebSocket`. @@ -58,17 +54,15 @@ You could also use `from starlette.websockets import WebSocket`. /// -## Await for messages and send messages +## Await for messages and send messages { #await-for-messages-and-send-messages } In your WebSocket route you can `await` for messages and send messages. -```Python hl_lines="48-52" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} You can receive and send binary, text, and JSON data. -## Try it +## Try it { #try-it } If your file is named `main.py`, run your application with: @@ -102,7 +96,7 @@ You can send (and receive) many messages: And all of them will use the same WebSocket connection. -## Using `Depends` and others +## Using `Depends` and others { #using-depends-and-others } In WebSocket endpoints you can import from `fastapi` and use: @@ -115,57 +109,7 @@ In WebSocket endpoints you can import from `fastapi` and use: They work the same way as for other FastAPI endpoints/*path operations*: -//// tab | Python 3.10+ - -```Python hl_lines="68-69 82" -{!> ../../../docs_src/websockets/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="68-69 82" -{!> ../../../docs_src/websockets/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="69-70 83" -{!> ../../../docs_src/websockets/tutorial002_an.py!} -``` - -//// - -//// tab | 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!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="68-69 81" -{!> ../../../docs_src/websockets/tutorial002.py!} -``` - -//// +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -175,7 +119,7 @@ You can use a closing code from the -## Handling disconnections and multiple clients +## Handling disconnections and multiple clients { #handling-disconnections-and-multiple-clients } 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. -//// tab | Python 3.9+ - -```Python hl_lines="79-81" -{!> ../../../docs_src/websockets/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="81-83" -{!> ../../../docs_src/websockets/tutorial003.py!} -``` - -//// +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} To try it out: @@ -248,9 +178,9 @@ If you need something easy to integrate with FastAPI but that is more robust, su /// -## More info +## More info { #more-info } To learn more about the options, check Starlette's documentation for: -* The `WebSocket` class. -* Class-based WebSocket handling. +* The `WebSocket` class. +* Class-based WebSocket handling. diff --git a/docs/en/docs/advanced/wsgi.md b/docs/en/docs/advanced/wsgi.md index f07609ed6f..29fd2d359c 100644 --- a/docs/en/docs/advanced/wsgi.md +++ b/docs/en/docs/advanced/wsgi.md @@ -1,10 +1,10 @@ -# Including WSGI - Flask, Django, others +# Including WSGI - Flask, Django, others { #including-wsgi-flask-django-others } You can mount WSGI applications as you saw with [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank}. For that, you can use the `WSGIMiddleware` and use it to wrap your WSGI application, for example, Flask, Django, etc. -## Using `WSGIMiddleware` +## Using `WSGIMiddleware` { #using-wsgimiddleware } You need to import `WSGIMiddleware`. @@ -12,11 +12,9 @@ Then wrap the WSGI (e.g. Flask) app with the middleware. And then mount that under a path. -```Python hl_lines="2-3 23" -{!../../../docs_src/wsgi/tutorial001.py!} -``` +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} -## Check it +## Check it { #check-it } Now, every request under the path `/v1/` will be handled by the Flask application. diff --git a/docs/en/docs/alternatives.md b/docs/en/docs/alternatives.md index 2ad584c95b..e656815430 100644 --- a/docs/en/docs/alternatives.md +++ b/docs/en/docs/alternatives.md @@ -1,8 +1,8 @@ -# Alternatives, Inspiration and Comparisons +# Alternatives, Inspiration and Comparisons { #alternatives-inspiration-and-comparisons } What inspired **FastAPI**, how it compares to alternatives and what it learned from them. -## Intro +## Intro { #intro } **FastAPI** wouldn't exist if not for the previous work of others. @@ -12,9 +12,9 @@ I have been avoiding the creation of a new framework for several years. First I But at some point, there was no other option than creating something that provided all these features, taking the best ideas from previous tools, and combining them in the best way possible, using language features that weren't even available before (Python 3.6+ type hints). -## Previous tools +## Previous tools { #previous-tools } -### Django +### Django { #django } It's the most popular Python framework and is widely trusted. It is used to build systems like Instagram. @@ -22,7 +22,7 @@ It's relatively tightly coupled with relational databases (like MySQL or Postgre It was created to generate the HTML in the backend, not to create APIs used by a modern frontend (like React, Vue.js and Angular) or by other systems (like IoT devices) communicating with it. -### Django REST Framework +### Django REST Framework { #django-rest-framework } Django REST framework was created to be a flexible toolkit for building Web APIs using Django underneath, to improve its API capabilities. @@ -36,13 +36,13 @@ Django REST Framework was created by Tom Christie. The same creator of Starlette /// -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Have an automatic API documentation web user interface. /// -### Flask +### Flask { #flask } Flask is a "microframework", it doesn't include database integrations nor many of the things that come by default in Django. @@ -56,7 +56,7 @@ This decoupling of parts, and being a "microframework" that could be extended to Given the simplicity of Flask, it seemed like a good match for building APIs. The next thing to find was a "Django REST Framework" for Flask. -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Be a micro-framework. Making it easy to mix and match the tools and parts needed. @@ -64,7 +64,7 @@ Have a simple and easy to use routing system. /// -### Requests +### Requests { #requests } **FastAPI** is not actually an alternative to **Requests**. Their scope is very different. @@ -98,7 +98,7 @@ def read_url(): See the similarities in `requests.get(...)` and `@app.get(...)`. -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to * Have a simple and intuitive API. * Use HTTP method names (operations) directly, in a straightforward and intuitive way. @@ -106,7 +106,7 @@ See the similarities in `requests.get(...)` and `@app.get(...)`. /// -### Swagger / OpenAPI +### Swagger / OpenAPI { #swagger-openapi } The main feature I wanted from Django REST Framework was the automatic API documentation. @@ -118,7 +118,7 @@ At some point, Swagger was given to the Linux Foundation, to be renamed OpenAPI. That's why when talking about version 2.0 it's common to say "Swagger", and for version 3+ "OpenAPI". -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Adopt and use an open standard for API specifications, instead of a custom schema. @@ -131,11 +131,11 @@ These two were chosen for being fairly popular and stable, but doing a quick sea /// -### Flask REST frameworks +### Flask REST frameworks { #flask-rest-frameworks } There are several Flask REST frameworks, but after investing the time and work into investigating them, I found that many are discontinued or abandoned, with several standing issues that made them unfit. -### Marshmallow +### 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. @@ -147,13 +147,13 @@ These features are what Marshmallow was built to provide. It is a great library, But it was created before there existed Python type hints. So, to define every schema you need to use specific utils and classes provided by Marshmallow. -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Use code to define "schemas" that provide data types and validation, automatically. /// -### Webargs +### Webargs { #webargs } Another big feature required by APIs is parsing data from incoming requests. @@ -169,13 +169,13 @@ Webargs was created by the same Marshmallow developers. /// -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Have automatic validation of incoming request data. /// -### APISpec +### APISpec { #apispec } Marshmallow and Webargs provide validation, parsing and serialization as plug-ins. @@ -199,13 +199,13 @@ APISpec was created by the same Marshmallow developers. /// -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Support the open standard for APIs, OpenAPI. /// -### Flask-apispec +### Flask-apispec { #flask-apispec } It's a Flask plug-in, that ties together Webargs, Marshmallow and APISpec. @@ -231,13 +231,13 @@ Flask-apispec was created by the same Marshmallow developers. /// -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Generate the OpenAPI schema automatically, from the same code that defines serialization and validation. /// -### NestJS (and Angular) +### NestJS (and Angular) { #nestjs-and-angular } This isn't even Python, NestJS is a JavaScript (TypeScript) NodeJS framework inspired by Angular. @@ -251,7 +251,7 @@ But as TypeScript data is not preserved after compilation to JavaScript, it cann It can't handle nested models very well. So, if the JSON body in the request is a JSON object that has inner fields that in turn are nested JSON objects, it cannot be properly documented and validated. -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Use Python types to have great editor support. @@ -259,11 +259,11 @@ Have a powerful dependency injection system. Find a way to minimize code repetit /// -### Sanic +### Sanic { #sanic } It was one of the first extremely fast Python frameworks based on `asyncio`. It was made to be very similar to Flask. -/// note | "Technical Details" +/// note | Technical Details It used `uvloop` instead of the default Python `asyncio` loop. That's what made it so fast. @@ -271,7 +271,7 @@ It clearly inspired Uvicorn and Starlette, that are currently faster than Sanic /// -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Find a way to have a crazy performance. @@ -279,7 +279,7 @@ That's why **FastAPI** is based on Starlette, as it is the fastest framework ava /// -### Falcon +### Falcon { #falcon } Falcon is another high performance Python framework, it is designed to be minimal, and work as the foundation of other frameworks like Hug. @@ -287,7 +287,7 @@ It is designed to have functions that receive two parameters, one "request" and So, data validation, serialization, and documentation, have to be done in code, not automatically. Or they have to be implemented as a framework on top of Falcon, like Hug. This same distinction happens in other frameworks that are inspired by Falcon's design, of having one request object and one response object as parameters. -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Find ways to get great performance. @@ -297,7 +297,7 @@ Although in FastAPI it's optional, and is used mainly to set headers, cookies, a /// -### Molten +### Molten { #molten } I discovered Molten in the first stages of building **FastAPI**. And it has quite similar ideas: @@ -313,7 +313,7 @@ The dependency injection system requires pre-registration of the dependencies an Routes are declared in a single place, using functions declared in other places (instead of using decorators that can be placed right on top of the function that handles the endpoint). This is closer to how Django does it than to how Flask (and Starlette) does it. It separates in the code things that are relatively tightly coupled. -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Define extra validations for data types using the "default" value of model attributes. This improves editor support, and it was not available in Pydantic before. @@ -321,7 +321,7 @@ This actually inspired updating parts of Pydantic, to support the same validatio /// -### Hug +### Hug { #hug } Hug was one of the first frameworks to implement the declaration of API parameter types using Python type hints. This was a great idea that inspired other tools to do the same. @@ -341,7 +341,7 @@ Hug was created by Timothy Crosley, the same creator of APIStar (<= 0.5) +### APIStar (<= 0.5) { #apistar-0-5 } Right before deciding to build **FastAPI** I found **APIStar** server. It had almost everything I was looking for and had a great design. @@ -385,7 +385,7 @@ APIStar was created by Tom Christie. The same guy that created: /// -/// check | "Inspired **FastAPI** to" +/// check | Inspired **FastAPI** to Exist. @@ -399,9 +399,9 @@ I consider **FastAPI** a "spiritual successor" to APIStar, while improving and i /// -## Used by **FastAPI** +## Used by **FastAPI** { #used-by-fastapi } -### Pydantic +### Pydantic { #pydantic } Pydantic is a library to define data validation, serialization and documentation (using JSON Schema) based on Python type hints. @@ -409,7 +409,7 @@ That makes it extremely intuitive. It is comparable to Marshmallow. Although it's faster than Marshmallow in benchmarks. And as it is based on the same Python type hints, the editor support is great. -/// check | "**FastAPI** uses it to" +/// check | **FastAPI** uses it to Handle all the data validation, data serialization and automatic model documentation (based on JSON Schema). @@ -417,7 +417,7 @@ Handle all the data validation, data serialization and automatic model documenta /// -### Starlette +### Starlette { #starlette } Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high-performance asyncio services. @@ -444,7 +444,7 @@ But it doesn't provide automatic data validation, serialization or documentation That's one of the main things that **FastAPI** adds on top, all based on Python type hints (using Pydantic). That, plus the dependency injection system, security utilities, OpenAPI schema generation, etc. -/// note | "Technical Details" +/// note | Technical Details ASGI is a new "standard" being developed by Django core team members. It is still not a "Python standard" (a PEP), although they are in the process of doing that. @@ -452,7 +452,7 @@ Nevertheless, it is already being used as a "standard" by several tools. This gr /// -/// check | "**FastAPI** uses it to" +/// check | **FastAPI** uses it to Handle all the core web parts. Adding features on top. @@ -462,7 +462,7 @@ So, anything that you can do with Starlette, you can do it directly with **FastA /// -### Uvicorn +### Uvicorn { #uvicorn } Uvicorn is a lightning-fast ASGI server, built on uvloop and httptools. @@ -470,16 +470,16 @@ It is not a web framework, but a server. For example, it doesn't provide tools f It is the recommended server for Starlette and **FastAPI**. -/// check | "**FastAPI** recommends it as" +/// check | **FastAPI** recommends it as The main web server to run **FastAPI** applications. -You can combine it with Gunicorn, to have an asynchronous multi-process server. +You can also use the `--workers` command line option to have an asynchronous multi-process server. Check more details in the [Deployment](deployment/index.md){.internal-link target=_blank} section. /// -## Benchmarks and speed +## Benchmarks and speed { #benchmarks-and-speed } To understand, compare, and see the difference between Uvicorn, Starlette and FastAPI, check the section about [Benchmarks](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/en/docs/async.md b/docs/en/docs/async.md index 752a5c247d..eac473bde4 100644 --- a/docs/en/docs/async.md +++ b/docs/en/docs/async.md @@ -1,8 +1,8 @@ -# Concurrency and async / await +# Concurrency and async / await { #concurrency-and-async-await } Details about the `async def` syntax for *path operation functions* and some background about asynchronous code, concurrency, and parallelism. -## In a hurry? +## In a hurry? { #in-a-hurry } TL;DR: @@ -40,7 +40,7 @@ def results(): --- -If your application (somehow) doesn't have to communicate with anything else and wait for it to respond, use `async def`. +If your application (somehow) doesn't have to communicate with anything else and wait for it to respond, use `async def`, even if you don't need to use `await` inside. --- @@ -54,7 +54,7 @@ Anyway, in any of the cases above, FastAPI will still work asynchronously and be But by following the steps above, it will be able to do some performance optimizations. -## Technical Details +## Technical Details { #technical-details } Modern versions of Python have support for **"asynchronous code"** using something called **"coroutines"**, with **`async` and `await`** syntax. @@ -64,7 +64,7 @@ Let's see that phrase by parts in the sections below: * **`async` and `await`** * **Coroutines** -## Asynchronous Code +## Asynchronous Code { #asynchronous-code } Asynchronous code just means that the language 💬 has a way to tell the computer / program 🤖 that at some point in the code, it 🤖 will have to wait for *something else* to finish somewhere else. Let's say that *something else* is called "slow-file" 📝. @@ -93,7 +93,7 @@ Instead of that, by being an "asynchronous" system, once finished, the task can For "synchronous" (contrary to "asynchronous") they commonly also use the term "sequential", because the computer / program follows all the steps in sequence before switching to a different task, even if those steps involve waiting. -### Concurrency and Burgers +### Concurrency and Burgers { #concurrency-and-burgers } This idea of **asynchronous** code described above is also sometimes called **"concurrency"**. It is different from **"parallelism"**. @@ -103,7 +103,7 @@ But the details between *concurrency* and *parallelism* are quite different. To see the difference, imagine the following story about burgers: -### Concurrent Burgers +### Concurrent 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. 😍 @@ -163,7 +163,7 @@ So you wait for your crush to finish the story (finish the current work ⏯ / ta 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 +### Parallel Burgers { #parallel-burgers } Now let's imagine these aren't "Concurrent Burgers", but "Parallel Burgers". @@ -233,7 +233,7 @@ And you have to wait 🕙 in the line for a long time or you lose your turn. You probably wouldn't want to take your crush 😍 with you to run errands at the bank 🏦. -### Burger Conclusion +### Burger Conclusion { #burger-conclusion } In this scenario of "fast food burgers with your crush", as there is a lot of waiting 🕙, it makes a lot more sense to have a concurrent system ⏸🔀⏯. @@ -253,7 +253,7 @@ And that's the same level of performance you get with **FastAPI**. And as you can have parallelism and asynchronicity at the same time, you get higher performance than most of the tested NodeJS frameworks and on par with Go, which is a compiled language closer to C (all thanks to Starlette). -### Is concurrency better than parallelism? +### Is concurrency better than parallelism? { #is-concurrency-better-than-parallelism } Nope! That's not the moral of the story. @@ -290,7 +290,7 @@ For example: * **Machine Learning**: it normally requires lots of "matrix" and "vector" multiplications. Think of a huge spreadsheet with numbers and multiplying all of them together at the same time. * **Deep Learning**: this is a sub-field of Machine Learning, so, the same applies. It's just that there is not a single spreadsheet of numbers to multiply, but a huge set of them, and in many cases, you use a special processor to build and / or use those models. -### Concurrency + Parallelism: Web + Machine Learning +### Concurrency + Parallelism: Web + Machine Learning { #concurrency-parallelism-web-machine-learning } With **FastAPI** you can take advantage of concurrency that is very common for web development (the same main attraction of NodeJS). @@ -300,7 +300,7 @@ That, plus the simple fact that Python is the main language for **Data Science** To see how to achieve this parallelism in production see the section about [Deployment](deployment/index.md){.internal-link target=_blank}. -## `async` and `await` +## `async` and `await` { #async-and-await } Modern versions of Python have a very intuitive way to define asynchronous code. This makes it look just like normal "sequential" code and do the "awaiting" for you at the right moments. @@ -349,7 +349,7 @@ async def read_burgers(): return burgers ``` -### More technical details +### More technical details { #more-technical-details } You might have noticed that `await` can only be used inside of functions defined with `async def`. @@ -361,7 +361,7 @@ If you are working with **FastAPI** you don't have to worry about that, because But if you want to use `async` / `await` without FastAPI, you can do it as well. -### Write your own async code +### Write your own async code { #write-your-own-async-code } Starlette (and **FastAPI**) are based on AnyIO, which makes it compatible with both Python's standard library asyncio and Trio. @@ -371,7 +371,7 @@ And even if you were not using FastAPI, you could also write your own async appl I created another library on top of AnyIO, as a thin layer on top, to improve a bit the type annotations and get better **autocompletion**, **inline errors**, etc. It also has a friendly introduction and tutorial to help you **understand** and write **your own async code**: Asyncer. It would be particularly useful if you need to **combine async code with regular** (blocking/synchronous) code. -### Other forms of asynchronous code +### Other forms of asynchronous code { #other-forms-of-asynchronous-code } This style of using `async` and `await` is relatively new in the language. @@ -383,15 +383,15 @@ But before that, handling asynchronous code was quite more complex and difficult In previous versions of Python, you could have used threads or Gevent. But the code is way more complex to understand, debug, and think about. -In previous versions of NodeJS / Browser JavaScript, you would have used "callbacks". Which leads to callback hell. +In previous versions of NodeJS / Browser JavaScript, you would have used "callbacks". Which leads to "callback hell". -## Coroutines +## Coroutines { #coroutines } -**Coroutine** is just the very fancy term for the thing returned by an `async def` function. Python knows that it is something like a function that it can start and that it will end at some point, but that it might be paused ⏸ internally too, whenever there is an `await` inside of it. +**Coroutine** is just the very fancy term for the thing returned by an `async def` function. Python knows that it is something like a function, that it can start and that it will end at some point, but that it might be paused ⏸ internally too, whenever there is an `await` inside of it. But all this functionality of using asynchronous code with `async` and `await` is many times summarized as using "coroutines". It is comparable to the main key feature of Go, the "Goroutines". -## Conclusion +## Conclusion { #conclusion } Let's see the same phrase from above: @@ -401,7 +401,7 @@ That should make more sense now. ✨ All that is what powers FastAPI (through Starlette) and what makes it have such an impressive performance. -## Very Technical Details +## Very Technical Details { #very-technical-details } /// warning @@ -413,7 +413,7 @@ If you have quite some technical knowledge (coroutines, threads, blocking, etc.) /// -### Path operation functions +### Path operation functions { #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). @@ -421,15 +421,15 @@ If you are coming from another async framework that does not work in the way des Still, in both situations, chances are that **FastAPI** will [still be faster](index.md#performance){.internal-link target=_blank} than (or at least comparable to) your previous framework. -### Dependencies +### Dependencies { #dependencies } 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 +### Sub-dependencies { #sub-dependencies } 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 +### Other utility functions { #other-utility-functions } Any other utility function that you call directly can be created with normal `def` or `async def` and FastAPI won't affect the way you call it. diff --git a/docs/en/docs/benchmarks.md b/docs/en/docs/benchmarks.md index 62266c449b..551f6316d3 100644 --- a/docs/en/docs/benchmarks.md +++ b/docs/en/docs/benchmarks.md @@ -1,10 +1,10 @@ -# Benchmarks +# Benchmarks { #benchmarks } 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). But when checking benchmarks and comparisons you should keep the following in mind. -## Benchmarks and speed +## Benchmarks and speed { #benchmarks-and-speed } When you check the benchmarks, it is common to see several tools of different types compared as equivalent. diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index 91d5724a8f..ae99059f41 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -10,10 +10,12 @@ If you already cloned the
`uv`: + +
+ +```console +$ uv pip install -r requirements.txt + +---> 100% +``` + +
+ +//// + It will install all the dependencies and your local FastAPI in your local environment. ### Using your local FastAPI @@ -34,7 +54,7 @@ And if you update that local FastAPI source code when you run that Python file a That way, you don't have to "install" your local version to be able to test every change. -/// note | "Technical Details" +/// note | Technical Details This only happens when you install using this included `requirements.txt` instead of running `pip install fastapi` directly. @@ -107,7 +127,7 @@ $ cd docs/en/ Then run `mkdocs` in that directory: ```console -$ mkdocs serve --dev-addr 8008 +$ mkdocs serve --dev-addr 127.0.0.1:8008 ``` /// @@ -170,7 +190,7 @@ If you run the examples with, e.g.:
```console -$ uvicorn tutorial001:app --reload +$ fastapi dev tutorial001.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -181,6 +201,28 @@ as Uvicorn by default will use the port `8000`, the documentation on port `8008` ### Translations +/// warning | Attention + +**Update on Translations** + +We're updating the way we handle documentation translations. + +Until now, we invited community members to translate pages via pull requests, which were then reviewed by at least two native speakers. While this has helped bring FastAPI to many more users, we’ve also run into several challenges - some languages have only a few translated pages, others are outdated and hard to maintain over time. +To improve this, we’re working on automation tools 🤖 to manage translations more efficiently. Once ready, documentation will be machine-translated and still reviewed by at least two native speakers ✅ before publishing. This will allow us to keep translations up-to-date while reducing the review burden on maintainers. + +What’s changing now: + +* 🚫 We’re no longer accepting new community-submitted translation PRs. + +* ⏳ Existing open PRs will be reviewed and can still be merged if completed within the next 3 weeks (since July 11 2025). + +* 🌐 In the future, we will only support languages where at least three active native speakers are available to review and maintain translations. + +This transition will help us keep translations more consistent and timely while better supporting our contributors 🙌. Thank you to everyone who has contributed so far — your help has been invaluable! 💖 + +/// + + Help with translations is VERY MUCH appreciated! And it can't be done without the help from the community. 🌎 🚀 Here are the steps to help with translations. @@ -245,7 +287,7 @@ $ cd docs/es/ Then run `mkdocs` in that directory: ```console -$ mkdocs serve --dev-addr 8008 +$ mkdocs serve --dev-addr 127.0.0.1:8008 ``` /// @@ -289,33 +331,51 @@ Now you can translate it all and see how it looks as you save the file. * `newsletter.md` * `management-tasks.md` * `management.md` +* `contributing.md` Some of these files are updated very frequently and a translation would always be behind, or they include the main content from English source files, etc. +#### Request a New Language + +Let's say that you want to request translations for a language that is not yet translated, not even some pages. For example, Latin. + +If there is no discussion for that language, you can start by requesting the new language. For that, you can follow these steps: + +* Create a new discussion following the template. +* Get a few native speakers to comment on the discussion and commit to help review translations for that language. + +Once there are several people in the discussion, the FastAPI team can evaluate it and can make it an official translation. + +Then the docs will be automatically translated using AI, and the team of native speakers can review the translation, and help tweak the AI prompts. + +Once there's a new translation, for example if docs are updated or there's a new section, there will be a comment in the same discussion with the link to the new translation to review. + #### New Language -Let's say that you want to add translations for a language that is not yet translated, not even some pages. +/// note -Let's say you want to add translations for Creole, and it's not yet there in the docs. +These steps will be performed by the FastAPI team. -Checking the link from above, the code for "Creole" is `ht`. +/// -The next step is to run the script to generate a new translation directory: +Checking the link from above (List of ISO 639-1 codes), you can see that the 2-letter code for Latin is `la`. + +Now you can create a new directory for the new language, running the following script:
```console // Use the command new-lang, pass the language code as a CLI argument -$ python ./scripts/docs.py new-lang ht +$ python ./scripts/docs.py new-lang la -Successfully initialized: docs/ht +Successfully initialized: docs/la ```
-Now you can check in your code editor the newly created directory `docs/ht/`. +Now you can check in your code editor the newly created directory `docs/la/`. -That command created a file `docs/ht/mkdocs.yml` with a simple config that inherits everything from the `en` version: +That command created a file `docs/la/mkdocs.yml` with a simple config that inherits everything from the `en` version: ```yaml INHERIT: ../en/mkdocs.yml @@ -327,11 +387,11 @@ You could also simply create that file with those contents manually. /// -That command also created a dummy file `docs/ht/index.md` for the main page, you can start by translating that one. +That command also created a dummy file `docs/la/index.md` for the main page, you can start by translating that one. You can continue with the previous instructions for an "Existing Language" for that process. -You can make the first pull request with those two files, `docs/ht/mkdocs.yml` and `docs/ht/index.md`. 🎉 +You can make the first pull request with those two files, `docs/la/mkdocs.yml` and `docs/la/index.md`. 🎉 #### Preview the result @@ -380,7 +440,7 @@ Serving at: http://127.0.0.1:8008 * Do not change anything enclosed in "``" (inline code). -* In lines starting with `///` translate only the ` "... Text ..."` part. Leave the rest unchanged. +* In lines starting with `///` translate only the text part after `|`. Leave the rest unchanged. * You can translate info boxes like `/// warning` with for example `/// warning | Achtung`. But do not change the word immediately after the `///`, it determines the color of the info box. diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css index b192f6123a..a38df772f9 100644 --- a/docs/en/docs/css/custom.css +++ b/docs/en/docs/css/custom.css @@ -102,7 +102,15 @@ a.announce-link:hover { align-items: center; } -.announce-wrapper div.item { +.announce-wrapper #announce-left div.item { + display: none; +} + +.announce-wrapper #announce-right { + display: none; +} + +.announce-wrapper #announce-right div.item { display: none; } @@ -112,7 +120,7 @@ a.announce-link:hover { top: -10px; right: 0; font-size: 0.5rem; - color: #999; + color: #e6e6e6; background-color: #666; border-radius: 10px; padding: 0 10px; diff --git a/docs/en/docs/deployment/cloud.md b/docs/en/docs/deployment/cloud.md index d34fbe2f71..c88c4b51a8 100644 --- a/docs/en/docs/deployment/cloud.md +++ b/docs/en/docs/deployment/cloud.md @@ -1,10 +1,10 @@ -# Deploy FastAPI on Cloud Providers +# Deploy FastAPI on Cloud Providers { #deploy-fastapi-on-cloud-providers } You can use virtually **any cloud provider** to deploy your FastAPI application. In most of the cases, the main cloud providers have guides to deploy FastAPI with them. -## Cloud Providers - Sponsors +## Cloud Providers - Sponsors { #cloud-providers-sponsors } Some cloud providers ✨ [**sponsor FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, this ensures the continued and healthy **development** of FastAPI and its **ecosystem**. @@ -12,6 +12,5 @@ And it shows their true commitment to FastAPI and its **community** (you), as th You might want to try their services and follow their guides: -* Platform.sh -* Porter -* Coherence +* Render +* Railway diff --git a/docs/en/docs/deployment/concepts.md b/docs/en/docs/deployment/concepts.md index f917d18b30..2174443f0f 100644 --- a/docs/en/docs/deployment/concepts.md +++ b/docs/en/docs/deployment/concepts.md @@ -1,4 +1,4 @@ -# Deployments Concepts +# Deployments Concepts { #deployments-concepts } When deploying a **FastAPI** application, or actually, any type of web API, there are several concepts that you probably care about, and using them you can find the **most appropriate** way to **deploy your application**. @@ -23,7 +23,7 @@ In the next chapters, I'll give you more **concrete recipes** to deploy FastAPI But for now, let's check these important **conceptual ideas**. These concepts also apply to any other type of web API. 💡 -## Security - HTTPS +## Security - HTTPS { #security-https } In the [previous chapter about HTTPS](https.md){.internal-link target=_blank} we learned about how HTTPS provides encryption for your API. @@ -31,7 +31,7 @@ We also saw that HTTPS is normally provided by a component **external** to your And there has to be something in charge of **renewing the HTTPS certificates**, it could be the same component or it could be something different. -### Example Tools for HTTPS +### Example Tools for HTTPS { #example-tools-for-https } Some of the tools you could use as a TLS Termination Proxy are: @@ -55,19 +55,19 @@ I'll show you some concrete examples in the next chapters. Then the next concepts to consider are all about the program running your actual API (e.g. Uvicorn). -## Program and Process +## Program and Process { #program-and-process } We will talk a lot about the running "**process**", so it's useful to have clarity about what it means, and what's the difference with the word "**program**". -### What is a Program +### What is a Program { #what-is-a-program } The word **program** is commonly used to describe many things: * The **code** that you write, the **Python files**. * The **file** that can be **executed** by the operating system, for example: `python`, `python.exe` or `uvicorn`. -* A particular program while it is **running** on the operating system, using the CPU, and storing things on memory. This is also called a **process**. +* A particular program while it is **running** on the operating system, using the CPU, and storing things in memory. This is also called a **process**. -### What is a Process +### What is a Process { #what-is-a-process } The word **process** is normally used in a more specific way, only referring to the thing that is running in the operating system (like in the last point above): @@ -88,13 +88,13 @@ And, for example, you will probably see that there are multiple processes runnin Now that we know the difference between the terms **process** and **program**, let's continue talking about deployments. -## Running on Startup +## Running on Startup { #running-on-startup } In most cases, when you create a web API, you want it to be **always running**, uninterrupted, so that your clients can always access it. This is of course, unless you have a specific reason why you want it to run only in certain situations, but most of the time you want it constantly running and **available**. -### In a Remote Server +### In a Remote Server { #in-a-remote-server } -When you set up a remote server (a cloud server, a virtual machine, etc.) the simplest thing you can do is to use `fastapi run`, Uvicorn (or similar) manually, the same way you do when developing locally. +When you set up a remote server (a cloud server, a virtual machine, etc.) the simplest thing you can do is use `fastapi run` (which uses Uvicorn) or something similar, manually, the same way you do when developing locally. And it will work and will be useful **during development**. @@ -102,15 +102,15 @@ But if your connection to the server is lost, the **running process** will proba And if the server is restarted (for example after updates, or migrations from the cloud provider) you probably **won't notice it**. And because of that, you won't even know that you have to restart the process manually. So, your API will just stay dead. 😱 -### Run Automatically on Startup +### Run Automatically on Startup { #run-automatically-on-startup } In general, you will probably want the server program (e.g. Uvicorn) to be started automatically on server startup, and without needing any **human intervention**, to have a process always running with your API (e.g. Uvicorn running your FastAPI app). -### Separate Program +### Separate Program { #separate-program } To achieve this, you will normally have a **separate program** that would make sure your application is run on startup. And in many cases, it would also make sure other components or applications are also run, for example, a database. -### Example Tools to Run at Startup +### Example Tools to Run at Startup { #example-tools-to-run-at-startup } Some examples of the tools that can do this job are: @@ -125,29 +125,29 @@ Some examples of the tools that can do this job are: I'll give you more concrete examples in the next chapters. -## Restarts +## Restarts { #restarts } Similar to making sure your application is run on startup, you probably also want to make sure it is **restarted** after failures. -### We Make Mistakes +### We Make Mistakes { #we-make-mistakes } We, as humans, make **mistakes**, all the time. Software almost *always* has **bugs** hidden in different places. 🐛 And we as developers keep improving the code as we find those bugs and as we implement new features (possibly adding new bugs too 😅). -### Small Errors Automatically Handled +### Small Errors Automatically Handled { #small-errors-automatically-handled } When building web APIs with FastAPI, if there's an error in our code, FastAPI will normally contain it to the single request that triggered the error. 🛡 The client will get a **500 Internal Server Error** for that request, but the application will continue working for the next requests instead of just crashing completely. -### Bigger Errors - Crashes +### Bigger Errors - Crashes { #bigger-errors-crashes } Nevertheless, there might be cases where we write some code that **crashes the entire application** making Uvicorn and Python crash. 💥 And still, you would probably not want the application to stay dead because there was an error in one place, you probably want it to **continue running** at least for the *path operations* that are not broken. -### Restart After Crash +### Restart After Crash { #restart-after-crash } But in those cases with really bad errors that crash the running **process**, you would want an external component that is in charge of **restarting** the process, at least a couple of times... @@ -161,7 +161,7 @@ So let's focus on the main cases, where it could crash entirely in some particul You would probably want to have the thing in charge of restarting your application as an **external component**, because by that point, the same application with Uvicorn and Python already crashed, so there's nothing in the same code of the same app that could do anything about it. -### Example Tools to Restart Automatically +### Example Tools to Restart Automatically { #example-tools-to-restart-automatically } In most cases, the same tool that is used to **run the program on startup** is also used to handle automatic **restarts**. @@ -176,19 +176,19 @@ For example, this could be handled by: * Handled internally by a cloud provider as part of their services * Others... -## Replication - Processes and Memory +## Replication - Processes and Memory { #replication-processes-and-memory } -With a FastAPI application, using a server program like Uvicorn, running it once in **one process** can serve multiple clients concurrently. +With a FastAPI application, using a server program like the `fastapi` command that runs Uvicorn, running it once in **one process** can serve multiple clients concurrently. But in many cases, you will want to run several worker processes at the same time. -### Multiple Processes - Workers +### Multiple Processes - Workers { #multiple-processes-workers } If you have more clients than what a single process can handle (for example if the virtual machine is not too big) and you have **multiple cores** in the server's CPU, then you could have **multiple processes** running with the same application at the same time, and distribute all the requests among them. When you run **multiple processes** of the same API program, they are commonly called **workers**. -### Worker Processes and Ports +### Worker Processes and Ports { #worker-processes-and-ports } Remember from the docs [About HTTPS](https.md){.internal-link target=_blank} that only one process can be listening on one combination of port and IP address in a server? @@ -196,19 +196,19 @@ This is still true. So, to be able to have **multiple processes** at the same time, there has to be a **single process listening on a port** that then transmits the communication to each worker process in some way. -### Memory per Process +### Memory per Process { #memory-per-process } Now, when the program loads things in memory, for example, a machine learning model in a variable, or the contents of a large file in a variable, all that **consumes a bit of the memory (RAM)** of the server. And multiple processes normally **don't share any memory**. This means that each running process has its own things, variables, and memory. And if you are consuming a large amount of memory in your code, **each process** will consume an equivalent amount of memory. -### Server Memory +### Server Memory { #server-memory } For example, if your code loads a Machine Learning model with **1 GB in size**, when you run one process with your API, it will consume at least 1 GB of RAM. And if you start **4 processes** (4 workers), each will consume 1 GB of RAM. So in total, your API will consume **4 GB of RAM**. And if your remote server or virtual machine only has 3 GB of RAM, trying to load more than 4 GB of RAM will cause problems. 🚨 -### Multiple Processes - An Example +### Multiple Processes - An Example { #multiple-processes-an-example } In this example, there's a **Manager Process** that starts and controls two **Worker Processes**. @@ -216,7 +216,7 @@ This Manager Process would probably be the one listening on the **port** in the Those worker processes would be the ones running your application, they would perform the main computations to receive a **request** and return a **response**, and they would load anything you put in variables in RAM. - + And of course, the same machine would probably have **other processes** running as well, apart from your application. @@ -224,7 +224,7 @@ An interesting detail is that the percentage of the **CPU used** by each process If you have an API that does a comparable amount of computations every time and you have a lot of clients, then the **CPU utilization** will probably *also be stable* (instead of constantly going up and down quickly). -### Examples of Replication Tools and Strategies +### Examples of Replication Tools and Strategies { #examples-of-replication-tools-and-strategies } There can be several approaches to achieve this, and I'll tell you more about specific strategies in the next chapters, for example when talking about Docker and containers. @@ -232,9 +232,7 @@ The main constraint to consider is that there has to be a **single** component h Here are some possible combinations and strategies: -* **Gunicorn** managing **Uvicorn workers** - * Gunicorn would be the **process manager** listening on the **IP** and **port**, the replication would be by having **multiple Uvicorn worker processes**. -* **Uvicorn** managing **Uvicorn workers** +* **Uvicorn** with `--workers` * 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. @@ -249,7 +247,7 @@ I'll tell you more about container images, Docker, Kubernetes, etc. in a future /// -## Previous Steps Before Starting +## Previous Steps Before Starting { #previous-steps-before-starting } There are many cases where you want to perform some steps **before starting** your application. @@ -259,7 +257,7 @@ But in most cases, you will want to perform these steps only **once**. So, you will want to have a **single process** to perform those **previous steps**, before starting the application. -And you will have to make sure that it's a single process running those previous steps *even* if afterwards, you start **multiple processes** (multiple workers) for the application itself. If those steps were run by **multiple processes**, they would **duplicate** the work by running it on **parallel**, and if the steps were something delicate like a database migration, they could cause conflicts with each other. +And you will have to make sure that it's a single process running those previous steps *even* if afterwards, you start **multiple processes** (multiple workers) for the application itself. If those steps were run by **multiple processes**, they would **duplicate** the work by running it in **parallel**, and if the steps were something delicate like a database migration, they could cause conflicts with each other. Of course, there are some cases where there's no problem in running the previous steps multiple times, in that case, it's a lot easier to handle. @@ -271,7 +269,7 @@ In that case, you wouldn't have to worry about any of this. 🤷 /// -### Examples of Previous Steps Strategies +### Examples of Previous Steps Strategies { #examples-of-previous-steps-strategies } This will **depend heavily** on the way you **deploy your system**, and it would probably be connected to the way you start programs, handling restarts, etc. @@ -287,7 +285,7 @@ I'll give you more concrete examples for doing this with containers in a future /// -## Resource Utilization +## Resource Utilization { #resource-utilization } Your server(s) is (are) a **resource**, you can consume or **utilize**, with your programs, the computation time on the CPUs, and the RAM memory available. @@ -307,7 +305,7 @@ You could put an **arbitrary number** to target, for example, something **betwee You can use simple tools like `htop` to see the CPU and RAM used in your server or the amount used by each process. Or you can use more complex monitoring tools, which may be distributed across servers, etc. -## Recap +## Recap { #recap } You have been reading here some of the main concepts that you would probably need to keep in mind when deciding how to deploy your application: diff --git a/docs/en/docs/deployment/docker.md b/docs/en/docs/deployment/docker.md index ab1c2201fc..6b71f7360d 100644 --- a/docs/en/docs/deployment/docker.md +++ b/docs/en/docs/deployment/docker.md @@ -1,4 +1,4 @@ -# FastAPI in Containers - Docker +# FastAPI in Containers - Docker { #fastapi-in-containers-docker } When deploying FastAPI applications a common approach is to build a **Linux container image**. It's normally done using **Docker**. You can then deploy that container image in one of a few possible ways. @@ -32,7 +32,7 @@ CMD ["fastapi", "run", "app/main.py", "--port", "80"] -## What is a Container +## What is a Container { #what-is-a-container } Containers (mainly Linux containers) are a very **lightweight** way to package applications including all their dependencies and necessary files while keeping them isolated from other containers (other applications or components) in the same system. @@ -42,7 +42,7 @@ This way, containers consume **little resources**, an amount comparable to runni Containers also have their own **isolated** running processes (commonly just one process), file system, and network, simplifying deployment, security, development, etc. -## What is a Container Image +## What is a Container Image { #what-is-a-container-image } A **container** is run from a **container image**. @@ -56,7 +56,7 @@ A container image is comparable to the **program** file and contents, e.g. `pyth And the **container** itself (in contrast to the **container image**) is the actual running instance of the image, comparable to a **process**. In fact, a container is running only when it has a **process running** (and normally it's only a single process). The container stops when there's no process running in it. -## Container Images +## Container Images { #container-images } Docker has been one of the main tools to create and manage **container images** and **containers**. @@ -79,7 +79,7 @@ So, you would run **multiple containers** with different things, like a database All the container management systems (like Docker or Kubernetes) have these networking features integrated into them. -## Containers and Processes +## Containers and Processes { #containers-and-processes } A **container image** normally includes in its metadata the default program or command that should be run when the **container** is started and the parameters to be passed to that program. Very similar to what would be if it was in the command line. @@ -91,7 +91,7 @@ A container normally has a **single process**, but it's also possible to start s But it's not possible to have a running container without **at least one running process**. If the main process stops, the container stops. -## Build a Docker Image for FastAPI +## Build a Docker Image for FastAPI { #build-a-docker-image-for-fastapi } Okay, let's build something now! 🚀 @@ -103,7 +103,7 @@ This is what you would want to do in **most cases**, for example: * When running on a **Raspberry Pi** * Using a cloud service that would run a container image for you, etc. -### Package Requirements +### Package Requirements { #package-requirements } You would normally have the **package requirements** for your application in some file. @@ -138,7 +138,7 @@ There are other formats and tools to define and install package dependencies. /// -### Create the **FastAPI** Code +### Create the **FastAPI** Code { #create-the-fastapi-code } * Create an `app` directory and enter it. * Create an empty file `__init__.py`. @@ -162,27 +162,27 @@ def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -### Dockerfile +### Dockerfile { #dockerfile } Now in the same project directory create a file `Dockerfile` with: ```{ .dockerfile .annotate } -# (1) +# (1)! FROM python:3.9 -# (2) +# (2)! WORKDIR /code -# (3) +# (3)! COPY ./requirements.txt /code/requirements.txt -# (4) +# (4)! RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -# (5) +# (5)! COPY ./app /code/app -# (6) +# (6)! CMD ["fastapi", "run", "app/main.py", "--port", "80"] ``` @@ -238,7 +238,7 @@ Make sure to **always** use the **exec form** of the `CMD` instruction, as expla /// -#### Use `CMD` - Exec Form +#### Use `CMD` - Exec Form { #use-cmd-exec-form } The `CMD` Docker instruction can be written using two forms: @@ -262,7 +262,7 @@ You can read more about it in the Why do my services take 10 seconds to recreate or stop?. -#### Directory Structure +#### Directory Structure { #directory-structure } You should now have a directory structure like: @@ -275,7 +275,7 @@ You should now have a directory structure like: └── requirements.txt ``` -#### Behind a TLS Termination Proxy +#### Behind a TLS Termination Proxy { #behind-a-tls-termination-proxy } If you are running your container behind a TLS Termination Proxy (load balancer) like Nginx or Traefik, add the option `--proxy-headers`, this will tell Uvicorn (through the FastAPI CLI) to trust the headers sent by that proxy telling it that the application is running behind HTTPS, etc. @@ -283,7 +283,7 @@ If you are running your container behind a TLS Termination Proxy (load balancer) CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] ``` -#### Docker Cache +#### Docker Cache { #docker-cache } There's an important trick in this `Dockerfile`, we first copy the **file with the dependencies alone**, not the rest of the code. Let me tell you why is that. @@ -315,7 +315,7 @@ Then, near the end of the `Dockerfile`, we copy all the code. As this is what ** COPY ./app /code/app ``` -### Build the Docker Image +### Build the Docker Image { #build-the-docker-image } Now that all the files are in place, let's build the container image. @@ -340,7 +340,7 @@ In this case, it's the same current directory (`.`). /// -### Start the Docker Container +### Start the Docker Container { #start-the-docker-container } * Run a container based on your image: @@ -352,7 +352,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage
-## Check it +## Check it { #check-it } You should be able to check it in your Docker container's URL, for example: http://192.168.99.100/items/5?q=somequery or http://127.0.0.1/items/5?q=somequery (or equivalent, using your Docker host). @@ -362,7 +362,7 @@ You will see something like: {"item_id": 5, "q": "somequery"} ``` -## Interactive API docs +## Interactive API docs { #interactive-api-docs } Now you can go to http://192.168.99.100/docs or http://127.0.0.1/docs (or equivalent, using your Docker host). @@ -370,7 +370,7 @@ You will see the automatic interactive API documentation (provided by http://192.168.99.100/redoc or http://127.0.0.1/redoc (or equivalent, using your Docker host). @@ -378,7 +378,7 @@ You will see the alternative automatic documentation (provided by cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Gunicorn with workers) in each container. +If you have a cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Uvicorn with workers) in each container. One of those distributed container management systems like Kubernetes normally has some integrated way of handling **replication of containers** while still supporting **load balancing** for the incoming requests. All at the **cluster level**. -In those cases, you would probably want to build a **Docker image from scratch** as [explained above](#dockerfile), installing your dependencies, and running **a single Uvicorn process** instead of running something like Gunicorn with Uvicorn workers. +In those cases, you would probably want to build a **Docker image from scratch** as [explained above](#dockerfile), installing your dependencies, and running **a single Uvicorn process** instead of using multiple Uvicorn workers. -### Load Balancer +### Load Balancer { #load-balancer } When using containers, you would normally have some component **listening on the main port**. It could possibly be another container that is also a **TLS Termination Proxy** to handle **HTTPS** or some similar tool. @@ -476,7 +476,7 @@ The same **TLS Termination Proxy** component used for HTTPS would probably also And when working with containers, the same system you use to start and manage them would already have internal tools to transmit the **network communication** (e.g. HTTP requests) from that **load balancer** (that could also be a **TLS Termination Proxy**) to the container(s) with your app. -### One Load Balancer - Multiple Worker Containers +### One Load Balancer - Multiple Worker Containers { #one-load-balancer-multiple-worker-containers } When working with **Kubernetes** or similar distributed container management systems, using their internal networking mechanisms would allow the single **load balancer** that is listening on the main **port** to transmit communication (requests) to possibly **multiple containers** running your app. @@ -486,42 +486,49 @@ And the distributed container system with the **load balancer** would **distribu And normally this **load balancer** would be able to handle requests that go to *other* apps in your cluster (e.g. to a different domain, or under a different URL path prefix), and would transmit that communication to the right containers for *that other* application running in your cluster. -### One Process per Container +### One Process per Container { #one-process-per-container } In this type of scenario, you probably would want to have **a single (Uvicorn) process per container**, as you would already be handling replication at the cluster level. -So, in this case, you **would not** want to have a process manager like Gunicorn with Uvicorn workers, or Uvicorn using its own Uvicorn workers. You would want to have just a **single Uvicorn process** per container (but probably multiple containers). +So, in this case, you **would not** want to have a multiple workers in the container, for example with the `--workers` command line option. You would want to have just a **single Uvicorn process** per container (but probably multiple containers). -Having another process manager inside the container (as would be with Gunicorn or Uvicorn managing Uvicorn workers) would only add **unnecessary complexity** that you are most probably already taking care of with your cluster system. +Having another process manager inside the container (as would be with multiple workers) would only add **unnecessary complexity** that you are most probably already taking care of with your cluster system. -### Containers with Multiple Processes and Special Cases +### Containers with Multiple Processes and Special Cases { #containers-with-multiple-processes-and-special-cases } -Of course, there are **special cases** where you could want to have **a container** with a **Gunicorn process manager** starting several **Uvicorn worker processes** inside. +Of course, there are **special cases** where you could want to have **a container** with several **Uvicorn worker processes** inside. -In those cases, you can use the **official Docker image** that includes **Gunicorn** as a process manager running multiple **Uvicorn worker processes**, and some default settings to adjust the number of workers based on the current CPU cores automatically. I'll tell you more about it below in [Official Docker Image with Gunicorn - Uvicorn](#official-docker-image-with-gunicorn-uvicorn). +In those cases, you can use the `--workers` command line option to set the number of workers that you want to run: + +```{ .dockerfile .annotate } +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 + +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` + +1. Here we use the `--workers` command line option to set the number of workers to 4. Here are some examples of when that could make sense: -#### A Simple App +#### A Simple App { #a-simple-app } -You could want a process manager in the container if your application is **simple enough** that you don't need (at least not yet) to fine-tune the number of processes too much, and you can just use an automated default (with the official Docker image), and you are running it on a **single server**, not a cluster. +You could want a process manager in the container if your application is **simple enough** that can run it on a **single server**, not a cluster. -#### Docker Compose +#### Docker Compose { #docker-compose } You could be deploying to a **single server** (not a cluster) with **Docker Compose**, so you wouldn't have an easy way to manage replication of containers (with Docker Compose) while preserving the shared network and **load balancing**. Then you could want to have **a single container** with a **process manager** starting **several worker processes** inside. -#### Prometheus and Other Reasons - -You could also have **other reasons** that would make it easier to have a **single container** with **multiple processes** instead of having **multiple containers** with **a single process** in each of them. - -For example (depending on your setup) you could have some tool like a Prometheus exporter in the same container that should have access to **each of the requests** that come. - -In this case, if you had **multiple containers**, by default, when Prometheus came to **read the metrics**, it would get the ones for **a single container each time** (for the container that handled that particular request), instead of getting the **accumulated metrics** for all the replicated containers. - -Then, in that case, it could be simpler to have **one container** with **multiple processes**, and a local tool (e.g. a Prometheus exporter) on the same container collecting Prometheus metrics for all the internal processes and exposing those metrics on that single container. - --- The main point is, **none** of these are **rules written in stone** that you have to blindly follow. You can use these ideas to **evaluate your own use case** and decide what is the best approach for your system, checking out how to manage the concepts of: @@ -533,7 +540,7 @@ The main point is, **none** of these are **rules written in stone** that you hav * Memory * Previous steps before starting -## Memory +## Memory { #memory } If you run **a single process per container** you will have a more or less well-defined, stable, and limited amount of memory consumed by each of those containers (more than one if they are replicated). @@ -541,13 +548,13 @@ And then you can set those same memory limits and requirements in your configura If your application is **simple**, this will probably **not be a problem**, and you might not need to specify hard memory limits. But if you are **using a lot of memory** (for example with **machine learning** models), you should check how much memory you are consuming and adjust the **number of containers** that runs in **each machine** (and maybe add more machines to your cluster). -If you run **multiple processes per container** (for example with the official Docker image) you will have to make sure that the number of processes started doesn't **consume more memory** than what is available. +If you run **multiple processes per container** you will have to make sure that the number of processes started doesn't **consume more memory** than what is available. -## Previous Steps Before Starting and Containers +## Previous Steps Before Starting and Containers { #previous-steps-before-starting-and-containers } If you are using containers (e.g. Docker, Kubernetes), then there are two main approaches you can use. -### Multiple Containers +### Multiple Containers { #multiple-containers } If you have **multiple containers**, probably each one running a **single process** (for example, in a **Kubernetes** cluster), then you would probably want to have a **separate container** doing the work of the **previous steps** in a single container, running a single process, **before** running the replicated worker containers. @@ -559,83 +566,29 @@ If you are using Kubernetes, this would probably be an tiangolo/uvicorn-gunicorn-fastapi. But it is now deprecated. ⛔️ -This image would be useful mainly in the situations described above in: [Containers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases). +You should probably **not** use this base Docker image (or any other similar one). -* tiangolo/uvicorn-gunicorn-fastapi. +If you are using **Kubernetes** (or others) and you are already setting **replication** at the cluster level, with multiple **containers**. In those cases, you are better off **building an image from scratch** as described above: [Build a Docker Image for FastAPI](#build-a-docker-image-for-fastapi). -/// warning +And if you need to have multiple workers, you can simply use the `--workers` command line option. -There's a high chance that you **don't** need this base image or any other similar one, and would be better off by building the image from scratch as [described above in: Build a Docker Image for FastAPI](#build-a-docker-image-for-fastapi). +/// note | Technical Details + +The Docker image was created when Uvicorn didn't support managing and restarting dead workers, so it was needed to use Gunicorn with Uvicorn, which added quite some complexity, just to have Gunicorn manage and restart the Uvicorn worker processes. + +But now that Uvicorn (and the `fastapi` command) support using `--workers`, there's no reason to use a base Docker image instead of building your own (it's pretty much the same amount of code 😅). /// -This image has an **auto-tuning** mechanism included to set the **number of worker processes** based on the CPU cores available. - -It has **sensible defaults**, but you can still change and update all the configurations with **environment variables** or configuration files. - -It also supports running **previous steps before starting** with a script. - -/// tip - -To see all the configurations and options, go to the Docker image page: tiangolo/uvicorn-gunicorn-fastapi. - -/// - -### Number of Processes on the Official Docker Image - -The **number of processes** on this image is **computed automatically** from the CPU **cores** available. - -This means that it will try to **squeeze** as much **performance** from the CPU as possible. - -You can also adjust it with the configurations using **environment variables**, etc. - -But it also means that as the number of processes depends on the CPU the container is running, the **amount of memory consumed** will also depend on that. - -So, if your application consumes a lot of memory (for example with machine learning models), and your server has a lot of CPU cores **but little memory**, then your container could end up trying to use more memory than what is available, and degrading performance a lot (or even crashing). 🚨 - -### Create a `Dockerfile` - -Here's how you would create a `Dockerfile` based on this image: - -```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 -``` - -### Bigger Applications - -If you followed the section about creating [Bigger Applications with Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}, your `Dockerfile` might instead look like: - -```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 -``` - -### When to Use - -You should probably **not** use this official base image (or any other similar one) if you are using **Kubernetes** (or others) and you are already setting **replication** at the cluster level, with multiple **containers**. In those cases, you are better off **building an image from scratch** as described above: [Build a Docker Image for FastAPI](#build-a-docker-image-for-fastapi). - -This image would be useful mainly in the special cases described above in [Containers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases). For example, if your application is **simple enough** that setting a default number of processes based on the CPU works well, you don't want to bother with manually configuring the replication at the cluster level, and you are not running more than one container with your app. Or if you are deploying with **Docker Compose**, running on a single server, etc. - -## Deploy the Container Image +## Deploy the Container Image { #deploy-the-container-image } After having a Container (Docker) Image there are several ways to deploy it. @@ -647,100 +600,11 @@ For example: * With another tool like Nomad * With a cloud service that takes your container image and deploys it -## Docker Image with Poetry +## Docker Image with `uv` { #docker-image-with-uv } -If you use Poetry to manage your project's dependencies, you could use Docker multi-stage building: +If you are using uv to install and manage your project, you can follow their uv Docker guide. -```{ .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 ["fastapi", "run", "app/main.py", "--port", "80"] -``` - -1. This is the first stage, it is named `requirements-stage`. - -2. Set `/tmp` as the current working directory. - - Here's where we will generate the file `requirements.txt` - -3. Install Poetry in this Docker stage. - -4. Copy the `pyproject.toml` and `poetry.lock` files to the `/tmp` directory. - - Because it uses `./poetry.lock*` (ending with a `*`), it won't crash if that file is not available yet. - -5. Generate the `requirements.txt` file. - -6. This is the final stage, anything here will be preserved in the final container image. - -7. Set the current working directory to `/code`. - -8. Copy the `requirements.txt` file to the `/code` directory. - - This file only lives in the previous Docker stage, that's why we use `--from-requirements-stage` to copy it. - -9. Install the package dependencies in the generated `requirements.txt` file. - -10. Copy the `app` directory to the `/code` directory. - -11. Use the `fastapi run` command to run your app. - -/// tip - -Click the bubble numbers to see what each line does. - -/// - -A **Docker stage** is a part of a `Dockerfile` that works as a **temporary container image** that is only used to generate some files to be used later. - -The first stage will only be used to **install Poetry** and to **generate the `requirements.txt`** with your project dependencies from Poetry's `pyproject.toml` file. - -This `requirements.txt` file will be used with `pip` later in the **next stage**. - -In the final container image **only the final stage** is preserved. The previous stage(s) will be discarded. - -When using Poetry, it would make sense to use **Docker multi-stage builds** because you don't really need to have Poetry and its dependencies installed in the final container image, you **only need** to have the generated `requirements.txt` file to install your project dependencies. - -Then in the next (and final) stage you would build the image more or less in the same way as described before. - -### Behind a TLS Termination Proxy - Poetry - -Again, if you are running your container behind a TLS Termination Proxy (load balancer) like Nginx or Traefik, add the option `--proxy-headers` to the command: - -```Dockerfile -CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] -``` - -## Recap +## Recap { #recap } Using container systems (e.g. with **Docker** and **Kubernetes**) it becomes fairly straightforward to handle all the **deployment concepts**: @@ -751,8 +615,6 @@ Using container systems (e.g. with **Docker** and **Kubernetes**) it becomes fai * Memory * Previous steps before starting -In most cases, you probably won't want to use any base image, and instead **build a container image from scratch** one based on the official Python Docker image. +In most cases, you probably won't want to use any base image, and instead **build a container image from scratch** based on the official Python Docker image. Taking care of the **order** of instructions in the `Dockerfile` and the **Docker cache** you can **minimize build times**, to maximize your productivity (and avoid boredom). 😎 - -In certain special cases, you might want to use the official Docker image for FastAPI. 🤓 diff --git a/docs/en/docs/deployment/https.md b/docs/en/docs/deployment/https.md index 46eda791e7..a249a36721 100644 --- a/docs/en/docs/deployment/https.md +++ b/docs/en/docs/deployment/https.md @@ -1,4 +1,4 @@ -# About HTTPS +# About HTTPS { #about-https } It is easy to assume that HTTPS is something that is just "enabled" or not. @@ -43,7 +43,7 @@ Some of the options you could use as a TLS Termination Proxy are: * Nginx * HAProxy -## Let's Encrypt +## Let's Encrypt { #lets-encrypt } Before Let's Encrypt, these **HTTPS certificates** were sold by trusted third parties. @@ -57,11 +57,11 @@ The domains are securely verified and the certificates are generated automatical The idea is to automate the acquisition and renewal of these certificates so that you can have **secure HTTPS, for free, forever**. -## HTTPS for Developers +## HTTPS for Developers { #https-for-developers } Here's an example of how an HTTPS API could look like, step by step, paying attention mainly to the ideas important for developers. -### Domain Name +### Domain Name { #domain-name } It would probably all start by you **acquiring** some **domain name**. Then, you would configure it in a DNS server (possibly your same cloud provider). @@ -77,7 +77,7 @@ This Domain Name part is way before HTTPS, but as everything depends on the doma /// -### DNS +### DNS { #dns } Now let's focus on all the actual HTTPS parts. @@ -85,19 +85,19 @@ First, the browser would check with the **DNS servers** what is the **IP for the The DNS servers would tell the browser to use some specific **IP address**. That would be the public IP address used by your server, that you configured in the DNS servers. - + -### TLS Handshake Start +### TLS Handshake Start { #tls-handshake-start } The browser would then communicate with that IP address on **port 443** (the HTTPS port). The first part of the communication is just to establish the connection between the client and the server and to decide the cryptographic keys they will use, etc. - + This interaction between the client and the server to establish the TLS connection is called the **TLS handshake**. -### TLS with SNI Extension +### TLS with SNI Extension { #tls-with-sni-extension } **Only one process** in the server can be listening on a specific **port** in a specific **IP address**. There could be other processes listening on other ports in the same IP address, but only one for each combination of IP address and port. @@ -111,7 +111,7 @@ Using the **SNI extension** discussed above, the TLS Termination Proxy would che In this case, it would use the certificate for `someapp.example.com`. - + The client already **trusts** the entity that generated that TLS certificate (in this case Let's Encrypt, but we'll see about that later), so it can **verify** that the certificate is valid. @@ -127,53 +127,53 @@ Notice that the encryption of the communication happens at the **TCP level**, no /// -### HTTPS Request +### HTTPS Request { #https-request } Now that the client and server (specifically the browser and the TLS Termination Proxy) have an **encrypted TCP connection**, they can start the **HTTP communication**. So, the client sends an **HTTPS request**. This is just an HTTP request through an encrypted TLS connection. - + -### Decrypt the Request +### Decrypt the Request { #decrypt-the-request } The TLS Termination Proxy would use the encryption agreed to **decrypt the request**, and would transmit the **plain (decrypted) HTTP request** to the process running the application (for example a process with Uvicorn running the FastAPI application). - + -### HTTP Response +### HTTP Response { #http-response } The application would process the request and send a **plain (unencrypted) HTTP response** to the TLS Termination Proxy. - + -### HTTPS Response +### HTTPS Response { #https-response } The TLS Termination Proxy would then **encrypt the response** using the cryptography agreed before (that started with the certificate for `someapp.example.com`), and send it back to the browser. Next, the browser would verify that the response is valid and encrypted with the right cryptographic key, etc. It would then **decrypt the response** and process it. - + The client (browser) will know that the response comes from the correct server because it is using the cryptography they agreed using the **HTTPS certificate** before. -### Multiple Applications +### Multiple Applications { #multiple-applications } In the same server (or servers), there could be **multiple applications**, for example, other API programs or a database. Only one process can be handling the specific IP and port (the TLS Termination Proxy in our example) but the other applications/processes can be running on the server(s) too, as long as they don't try to use the same **combination of public IP and port**. - + That way, the TLS Termination Proxy could handle HTTPS and certificates for **multiple domains**, for multiple applications, and then transmit the requests to the right application in each case. -### Certificate Renewal +### Certificate Renewal { #certificate-renewal } At some point in the future, each certificate would **expire** (about 3 months after acquiring it). And then, there would be another program (in some cases it's another program, in some cases it could be the same TLS Termination Proxy) that would talk to Let's Encrypt, and renew the certificate(s). - + The **TLS certificates** are **associated with a domain name**, not with an IP address. @@ -190,7 +190,39 @@ To do that, and to accommodate different application needs, there are several wa All this renewal process, while still serving the app, is one of the main reasons why you would want to have a **separate system to handle HTTPS** with a TLS Termination Proxy instead of just using the TLS certificates with the application server directly (e.g. Uvicorn). -## Recap +## Proxy Forwarded Headers { #proxy-forwarded-headers } + +When using a proxy to handle HTTPS, your **application server** (for example Uvicorn via FastAPI CLI) doesn't known anything about the HTTPS process, it communicates with plain HTTP with the **TLS Termination Proxy**. + +This **proxy** would normally set some HTTP headers on the fly before transmitting the request to the **application server**, to let the application server know that the request is being **forwarded** by the proxy. + +/// note | Technical Details + +The proxy headers are: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +Nevertheless, as the **application server** doesn't know it is behind a trusted **proxy**, by default, it wouldn't trust those headers. + +But you can configure the **application server** to trust the *forwarded* headers sent by the **proxy**. If you are using FastAPI CLI, you can use the *CLI Option* `--forwarded-allow-ips` to tell it from which IPs it should trust those *forwarded* headers. + +For example, if the **application server** is only receiving communication from the trusted **proxy**, you can set it to `--forwarded-allow-ips="*"` to make it trust all incoming IPs, as it will only receive requests from whatever is the IP used by the **proxy**. + +This way the application would be able to know what is its own public URL, if it is using HTTPS, the domain, etc. + +This would be useful for example to properly handle redirects. + +/// tip + +You can learn more about this in the documentation for [Behind a Proxy - Enable Proxy Forwarded Headers](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} + +/// + +## Recap { #recap } Having **HTTPS** is very important, and quite **critical** in most cases. Most of the effort you as a developer have to put around HTTPS is just about **understanding these concepts** and how they work. diff --git a/docs/en/docs/deployment/index.md b/docs/en/docs/deployment/index.md index b43bd050a3..2364791a7e 100644 --- a/docs/en/docs/deployment/index.md +++ b/docs/en/docs/deployment/index.md @@ -1,8 +1,8 @@ -# Deployment +# Deployment { #deployment } Deploying a **FastAPI** application is relatively easy. -## What Does Deployment Mean +## What Does Deployment Mean { #what-does-deployment-mean } To **deploy** an application means to perform the necessary steps to make it **available to the users**. @@ -10,7 +10,7 @@ For a **web API**, it normally involves putting it in a **remote machine**, with This is in contrast to the **development** stages, where you are constantly changing the code, breaking it and fixing it, stopping and restarting the development server, etc. -## Deployment Strategies +## Deployment Strategies { #deployment-strategies } There are several ways to do it depending on your specific use case and the tools that you use. diff --git a/docs/en/docs/deployment/manually.md b/docs/en/docs/deployment/manually.md index 3324a75038..311efb99fa 100644 --- a/docs/en/docs/deployment/manually.md +++ b/docs/en/docs/deployment/manually.md @@ -1,51 +1,39 @@ -# Run a Server Manually +# Run a Server Manually { #run-a-server-manually } -## Use the `fastapi run` Command +## Use the `fastapi run` Command { #use-the-fastapi-run-command } In short, use `fastapi run` to serve your FastAPI application:
```console -$ fastapi run main.py -INFO Using path main.py -INFO Resolved absolute path /home/user/code/awesomeapp/main.py -INFO Searching for package file structure from directories with __init__.py files -INFO Importing from /home/user/code/awesomeapp +$ fastapi run main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting production server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with + the following code: - ╭─────────── FastAPI CLI - Production mode ───────────╮ - │ │ - │ Serving at: http://0.0.0.0:8000 │ - │ │ - │ API docs: http://0.0.0.0:8000/docs │ - │ │ - │ Running in production mode, for development use: │ - │ │ - fastapi dev - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -INFO: Started server process [2306215] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) ```
@@ -54,7 +42,7 @@ That would work for most of the cases. 😎 You could use that command for example to start your **FastAPI** app in a container, in a server, etc. -## ASGI Servers +## ASGI Servers { #asgi-servers } Let's go a little deeper into the details. @@ -64,11 +52,13 @@ The main thing you need to run a **FastAPI** application (or any other ASGI appl There are several alternatives, including: -* Uvicorn: a high performance ASGI server. +* Uvicorn: a high performance ASGI server. * Hypercorn: an ASGI server compatible with HTTP/2 and Trio among other features. * Daphne: the ASGI server built for Django Channels. +* Granian: A Rust HTTP server for Python applications. +* NGINX Unit: NGINX Unit is a lightweight and versatile web application runtime. -## Server Machine and Server Program +## Server Machine and Server Program { #server-machine-and-server-program } There's a small detail about names to keep in mind. 💡 @@ -78,17 +68,15 @@ Just keep in mind that when you read "server" in general, it could refer to one When referring to the remote machine, it's common to call it **server**, but also **machine**, **VM** (virtual machine), **node**. Those all refer to some type of remote machine, normally running Linux, where you run programs. -## Install the Server Program +## Install the Server Program { #install-the-server-program } When you install FastAPI, it comes with a production server, Uvicorn, and you can start it with the `fastapi run` command. But you can also install an ASGI server manually. -Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then you can install the server: +Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then you can install the server application. -//// tab | Uvicorn - -* Uvicorn, a lightning-fast ASGI server, built on uvloop and httptools. +For example, to install Uvicorn:
@@ -100,6 +88,8 @@ $ pip install "uvicorn[standard]"
+A similar process would apply to any other ASGI server program. + /// tip By adding the `standard`, Uvicorn will install and use some recommended extra dependencies. @@ -110,32 +100,10 @@ When you install FastAPI with something like `pip install "fastapi[standard]"` y /// -//// - -//// tab | Hypercorn - -* Hypercorn, an ASGI server also compatible with HTTP/2. - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...or any other ASGI server. - -//// - -## Run the Server Program +## Run the Server Program { #run-the-server-program } If you installed an ASGI server manually, you would normally need to pass an import string in a special format for it to import your FastAPI application: -//// tab | Uvicorn -
```console @@ -146,22 +114,6 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
-//// - -//// tab | Hypercorn - -
- -```console -$ hypercorn main:app --bind 0.0.0.0:80 - -Running on 0.0.0.0:8080 over http (CTRL + C to quit) -``` - -
- -//// - /// note The command `uvicorn main:app` refers to: @@ -177,9 +129,11 @@ from main import app /// +Each alternative ASGI server program would have a similar command, you can read more in their respective documentation. + /// warning -Uvicorn and others support a `--reload` option that is useful during development. +Uvicorn and other servers support a `--reload` option that is useful during development. The `--reload` option consumes much more resources, is more unstable, etc. @@ -187,44 +141,7 @@ It helps a lot during **development**, but you **shouldn't** use it in **product /// -## Hypercorn with Trio - -Starlette and **FastAPI** are based on AnyIO, which makes them compatible with both Python's standard library asyncio and Trio. - -Nevertheless, Uvicorn is currently only compatible with asyncio, and it normally uses `uvloop`, the high-performance drop-in replacement for `asyncio`. - -But if you want to directly use **Trio**, then you can use **Hypercorn** as it supports it. ✨ - -### Install Hypercorn with Trio - -First you need to install Hypercorn with Trio support: - -
- -```console -$ pip install "hypercorn[trio]" ----> 100% -``` - -
- -### Run with Trio - -Then you can pass the command line option `--worker-class` with the value `trio`: - -
- -```console -$ hypercorn main:app --worker-class trio -``` - -
- -And that will start Hypercorn with your app using Trio as the backend. - -Now you can use Trio internally in your app. Or even better, you can use AnyIO, to keep your code compatible with both Trio and asyncio. 🎉 - -## Deployment Concepts +## Deployment Concepts { #deployment-concepts } These examples run the server program (e.g Uvicorn), starting **a single process**, listening on all the IPs (`0.0.0.0`) on a predefined port (e.g. `80`). diff --git a/docs/en/docs/deployment/server-workers.md b/docs/en/docs/deployment/server-workers.md index efde5f3a1c..0351e8b5e5 100644 --- a/docs/en/docs/deployment/server-workers.md +++ b/docs/en/docs/deployment/server-workers.md @@ -1,4 +1,4 @@ -# Server Workers - Gunicorn with Uvicorn +# Server Workers - Uvicorn with Workers { #server-workers-uvicorn-with-workers } Let's check back those deployment concepts from before: @@ -9,125 +9,79 @@ Let's check back those deployment concepts from before: * Memory * Previous steps before starting -Up to this point, with all the tutorials in the docs, you have probably been running a **server program** like Uvicorn, running a **single process**. +Up to this point, with all the tutorials in the docs, you have probably been running a **server program**, for example, using the `fastapi` command, that runs Uvicorn, running a **single process**. When deploying applications you will probably want to have some **replication of processes** to take advantage of **multiple cores** and to be able to handle more requests. As you saw in the previous chapter about [Deployment Concepts](concepts.md){.internal-link target=_blank}, there are multiple strategies you can use. -Here I'll show you how to use **Gunicorn** with **Uvicorn worker processes**. +Here I'll show you how to use **Uvicorn** with **worker processes** using the `fastapi` command or the `uvicorn` command directly. /// info If you are using containers, for example with Docker or Kubernetes, I'll tell you more about that in the next chapter: [FastAPI in Containers - Docker](docker.md){.internal-link target=_blank}. -In particular, when running on **Kubernetes** you will probably **not** want to use Gunicorn and instead run **a single Uvicorn process per container**, but I'll tell you about it later in that chapter. +In particular, when running on **Kubernetes** you will probably **not** want to use workers and instead run **a single Uvicorn process per container**, but I'll tell you about it later in that chapter. /// -## Gunicorn with Uvicorn Workers +## Multiple Workers { #multiple-workers } -**Gunicorn** is mainly an application server using the **WSGI standard**. That means that Gunicorn can serve applications like Flask and Django. Gunicorn by itself is not compatible with **FastAPI**, as FastAPI uses the newest **ASGI standard**. +You can start multiple workers with the `--workers` command line option: -But Gunicorn supports working as a **process manager** and allowing users to tell it which specific **worker process class** to use. Then Gunicorn would start one or more **worker processes** using that class. +//// tab | `fastapi` -And **Uvicorn** has a **Gunicorn-compatible worker class**. - -Using that combination, Gunicorn would act as a **process manager**, listening on the **port** and the **IP**. And it would **transmit** the communication to the worker processes running the **Uvicorn class**. - -And then the Gunicorn-compatible **Uvicorn worker** class would be in charge of converting the data sent by Gunicorn to the ASGI standard for FastAPI to use it. - -## Install Gunicorn and Uvicorn - -Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install `gunicorn`: +If you use the `fastapi` command:
```console -$ pip install "uvicorn[standard]" gunicorn +$ fastapi run --workers 4 main.py ----> 100% + FastAPI Starting production server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. ```
-That will install both Uvicorn with the `standard` extra packages (to get high performance) and Gunicorn. +//// -## Run Gunicorn with Uvicorn Workers +//// tab | `uvicorn` -Then you can run Gunicorn with: - -
- -```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. -``` - -
- -Let's see what each of those options mean: - -* `main:app`: This is the same syntax used by Uvicorn, `main` means the Python module named "`main`", so, a file `main.py`. And `app` is the name of the variable that is the **FastAPI** application. - * You can imagine that `main:app` is equivalent to a Python `import` statement like: - - ```Python - from main import app - ``` - - * So, the colon in `main:app` would be equivalent to the Python `import` part in `from main import app`. - -* `--workers`: The number of worker processes to use, each will run a Uvicorn worker, in this case, 4 workers. - -* `--worker-class`: The Gunicorn-compatible worker class to use in the worker processes. - * Here we pass the class that Gunicorn can import and use with: - - ```Python - import uvicorn.workers.UvicornWorker - ``` - -* `--bind`: This tells Gunicorn the IP and the port to listen to, using a colon (`:`) to separate the IP and the port. - * If you were running Uvicorn directly, instead of `--bind 0.0.0.0:80` (the Gunicorn option) you would use `--host 0.0.0.0` and `--port 80`. - -In the output, you can see that it shows the **PID** (process ID) of each process (it's just a number). - -You can see that: - -* The Gunicorn **process manager** starts with PID `19499` (in your case it will be a different number). -* Then it starts `Listening at: http://0.0.0.0:80`. -* Then it detects that it has to use the worker class at `uvicorn.workers.UvicornWorker`. -* And then it starts **4 workers**, each with its own PID: `19511`, `19513`, `19514`, and `19515`. - -Gunicorn would also take care of managing **dead processes** and **restarting** new ones if needed to keep the number of workers. So that helps in part with the **restart** concept from the list above. - -Nevertheless, you would probably also want to have something outside making sure to **restart Gunicorn** if necessary, and also to **run it on startup**, etc. - -## Uvicorn with Workers - -Uvicorn also has an option to start and run several **worker processes**. - -Nevertheless, as of now, Uvicorn's capabilities for handling worker processes are more limited than Gunicorn's. So, if you want to have a process manager at this level (at the Python level), then it might be better to try with Gunicorn as the process manager. - -In any case, you would run it like this: +If you prefer to use the `uvicorn` command directly:
@@ -151,13 +105,15 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
+//// + The only new option here is `--workers` telling Uvicorn to start 4 worker processes. You can also see that it shows the **PID** of each process, `27365` for the parent process (this is the **process manager**) and one for each worker process: `27368`, `27369`, `27370`, and `27367`. -## Deployment Concepts +## Deployment Concepts { #deployment-concepts } -Here you saw how to use **Gunicorn** (or Uvicorn) managing **Uvicorn worker processes** to **parallelize** the execution of the application, take advantage of **multiple cores** in the CPU, and be able to serve **more requests**. +Here you saw how to use multiple **workers** to **parallelize** the execution of the application, take advantage of **multiple cores** in the CPU, and be able to serve **more requests**. From the list of deployment concepts from above, using workers would mainly help with the **replication** part, and a little bit with the **restarts**, but you still need to take care of the others: @@ -168,17 +124,15 @@ From the list of deployment concepts from above, using workers would mainly help * **Memory** * **Previous steps before starting** -## Containers and Docker +## Containers and Docker { #containers-and-docker } -In the next chapter about [FastAPI in Containers - Docker](docker.md){.internal-link target=_blank} I'll tell some strategies you could use to handle the other **deployment concepts**. +In the next chapter about [FastAPI in Containers - Docker](docker.md){.internal-link target=_blank} I'll explain some strategies you could use to handle the other **deployment concepts**. -I'll also show you the **official Docker image** that includes **Gunicorn with Uvicorn workers** and some default configurations that can be useful for simple cases. +I'll show you how to **build your own image from scratch** to run a single Uvicorn process. It is a simple process and is probably what you would want to do when using a distributed container management system like **Kubernetes**. -There I'll also show you how to **build your own image from scratch** to run a single Uvicorn process (without Gunicorn). It is a simple process and is probably what you would want to do when using a distributed container management system like **Kubernetes**. +## Recap { #recap } -## Recap - -You can use **Gunicorn** (or also Uvicorn) as a process manager with Uvicorn workers to take advantage of **multi-core CPUs**, to run **multiple processes in parallel**. +You can use multiple worker processes with the `--workers` CLI option with the `fastapi` or `uvicorn` commands to take advantage of **multi-core CPUs**, to run **multiple processes in parallel**. You could use these tools and ideas if you are setting up **your own deployment system** while taking care of the other deployment concepts yourself. diff --git a/docs/en/docs/deployment/versions.md b/docs/en/docs/deployment/versions.md index e387d5712f..15b4491844 100644 --- a/docs/en/docs/deployment/versions.md +++ b/docs/en/docs/deployment/versions.md @@ -1,4 +1,4 @@ -# About FastAPI versions +# About FastAPI versions { #about-fastapi-versions } **FastAPI** is already being used in production in many applications and systems. And the test coverage is kept at 100%. But its development is still moving quickly. @@ -8,7 +8,7 @@ That's why the current versions are still `0.x.x`, this reflects that each versi You can create production applications with **FastAPI** right now (and you have probably been doing it for some time), you just have to make sure that you use a version that works correctly with the rest of your code. -## Pin your `fastapi` version +## Pin your `fastapi` version { #pin-your-fastapi-version } The first thing you should do is to "pin" the version of **FastAPI** you are using to the specific latest version that you know works correctly for your application. @@ -30,13 +30,13 @@ fastapi[standard]>=0.112.0,<0.113.0 that would mean that you would use the versions `0.112.0` or above, but less than `0.113.0`, for example, a version `0.112.2` would still be accepted. -If you use any other tool to manage your installations, like Poetry, Pipenv, or others, they all have a way that you can use to define specific versions for your packages. +If you use any other tool to manage your installations, like `uv`, Poetry, Pipenv, or others, they all have a way that you can use to define specific versions for your packages. -## Available versions +## Available versions { #available-versions } You can see the available versions (e.g. to check what is the current latest) in the [Release Notes](../release-notes.md){.internal-link target=_blank}. -## About versions +## About versions { #about-versions } Following the Semantic Versioning conventions, any version below `1.0.0` could potentially add breaking changes. @@ -62,7 +62,7 @@ The "MINOR" is the number in the middle, for example, in `0.2.3`, the MINOR vers /// -## Upgrading the FastAPI versions +## Upgrading the FastAPI versions { #upgrading-the-fastapi-versions } You should add tests for your app. @@ -72,7 +72,7 @@ After you have tests, then you can upgrade the **FastAPI** version to a more rec If everything is working, or after you make the necessary changes, and all your tests are passing, then you can pin your `fastapi` to that new recent version. -## About Starlette +## About Starlette { #about-starlette } You shouldn't pin the version of `starlette`. @@ -80,7 +80,7 @@ Different versions of **FastAPI** will use a specific newer version of Starlette So, you can just let **FastAPI** use the correct Starlette version. -## About Pydantic +## About Pydantic { #about-pydantic } Pydantic includes the tests for **FastAPI** with its own tests, so new versions of Pydantic (above `1.0.0`) are always compatible with FastAPI. diff --git a/docs/en/docs/environment-variables.md b/docs/en/docs/environment-variables.md index 78e82d5af6..1dbd93570e 100644 --- a/docs/en/docs/environment-variables.md +++ b/docs/en/docs/environment-variables.md @@ -1,4 +1,4 @@ -# Environment Variables +# Environment Variables { #environment-variables } /// tip @@ -10,7 +10,7 @@ An environment variable (also known as "**env var**") is a variable that lives * Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc. -## Create and Use Env Vars +## Create and Use Env Vars { #create-and-use-env-vars } You can **create** and use environment variables in the **shell (terminal)**, without needing Python: @@ -50,7 +50,7 @@ Hello Wade Wilson //// -## Read env vars in Python +## Read env vars in Python { #read-env-vars-in-python } You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**. @@ -157,7 +157,7 @@ You can read more about it at @@ -289,7 +287,7 @@ $ C:\opt\custompython\bin\python This information will be useful when learning about [Virtual Environments](virtual-environments.md){.internal-link target=_blank}. -## Conclusion +## Conclusion { #conclusion } With this you should have a basic understanding of what **environment variables** are and how to use them in Python. diff --git a/docs/en/docs/external-links.md b/docs/en/docs/external-links.md index 5a3b8ee33d..3ed04e5c55 100644 --- a/docs/en/docs/external-links.md +++ b/docs/en/docs/external-links.md @@ -28,9 +28,12 @@ If you have an article, project, tool, or anything related to **FastAPI** that i {% endfor %} {% endfor %} -## Projects +## GitHub Repositories -Latest GitHub projects with the topic `fastapi`: +Most starred GitHub repositories with the topic `fastapi`: -
-
+{% for repo in topic_repos %} + +
★ {{repo.stars}} - {{repo.name}} by @{{repo.owner_login}}. + +{% endfor %} diff --git a/docs/en/docs/fastapi-cli.md b/docs/en/docs/fastapi-cli.md index e27bebcb4c..3e5f4e3503 100644 --- a/docs/en/docs/fastapi-cli.md +++ b/docs/en/docs/fastapi-cli.md @@ -1,4 +1,4 @@ -# FastAPI CLI +# FastAPI CLI { #fastapi-cli } **FastAPI CLI** is a command line program that you can use to serve your FastAPI app, manage your FastAPI project, and more. @@ -9,47 +9,39 @@ To run your FastAPI app for development, you can use the `fastapi dev` command:
```console -$ fastapi dev main.py -INFO Using path main.py -INFO Resolved absolute path /home/user/code/awesomeapp/main.py -INFO Searching for package file structure from directories with __init__.py files -INFO Importing from /home/user/code/awesomeapp +$ fastapi dev main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting development server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with the + following code: - ╭────────── FastAPI CLI - Development mode ───────────╮ - │ │ - │ Serving at: http://127.0.0.1:8000 │ - │ │ - │ API docs: http://127.0.0.1:8000/docs │ - │ │ - │ Running in development mode, for production use: │ - │ │ - fastapi run - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [2265862] using WatchFiles -INFO: Started server process [2265873] -INFO: Waiting for application startup. -INFO: Application startup complete. + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to + quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. ```
@@ -60,15 +52,15 @@ FastAPI CLI takes the path to your Python program (e.g. `main.py`) and automatic For production you would use `fastapi run` instead. 🚀 -Internally, **FastAPI CLI** uses Uvicorn, a high-performance, production-ready, ASGI server. 😎 +Internally, **FastAPI CLI** uses Uvicorn, a high-performance, production-ready, ASGI server. 😎 -## `fastapi dev` +## `fastapi dev` { #fastapi-dev } Running `fastapi dev` initiates development mode. By default, **auto-reload** is enabled, automatically reloading the server when you make changes to your code. This is resource-intensive and could be less stable than when it's disabled. You should only use it for development. It also listens on the IP address `127.0.0.1`, which is the IP for your machine to communicate with itself alone (`localhost`). -## `fastapi run` +## `fastapi run` { #fastapi-run } Executing `fastapi run` starts FastAPI in production mode by default. diff --git a/docs/en/docs/fastapi-people.md b/docs/en/docs/fastapi-people.md index bf7954449b..f2ca26013c 100644 --- a/docs/en/docs/fastapi-people.md +++ b/docs/en/docs/fastapi-people.md @@ -13,15 +13,13 @@ Hey! 👋 This is me: -{% if people %}
{% for user in people.maintainers %} -
@{{ user.login }}
Answers: {{ user.answers }}
Pull Requests: {{ user.prs }}
+
@{{ contributors.tiangolo.login }}
Answers: {{ user.answers }}
Pull Requests: {{ contributors.tiangolo.count }}
{% endfor %}
-{% endif %} I'm the creator of **FastAPI**. You can read more about that in [Help FastAPI - Get Help - Connect with the author](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}. @@ -49,9 +47,11 @@ This is the current list of team members. 😎 They have different levels of involvement and permissions, they can perform [repository management tasks](./management-tasks.md){.internal-link target=_blank} and together we [manage the FastAPI repository](./management.md){.internal-link target=_blank}.
+ {% for user in members["members"] %} + {% endfor %}
@@ -84,57 +84,73 @@ You can see the **FastAPI Experts** for: These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last month. 🤓 -{% if people %}
+ {% for user in people.last_month_experts[:10] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
-{% endif %} ### FastAPI Experts - 3 Months These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 3 months. 😎 -{% if people %}
+ {% for user in people.three_months_experts[:10] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
-{% endif %} ### FastAPI Experts - 6 Months These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 6 months. 🧐 -{% if people %}
+ {% for user in people.six_months_experts[:10] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
-{% endif %} ### FastAPI Experts - 1 Year These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last year. 🧑‍🔬 -{% if people %}
+ {% for user in people.one_year_experts[:20] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
-{% endif %} ### FastAPI Experts - All Time @@ -142,15 +158,19 @@ Here are the all time **FastAPI Experts**. 🤓🤯 These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} through *all time*. 🧙 -{% if people %}
+ {% for user in people.experts[:50] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
-{% endif %} ## Top Contributors @@ -158,19 +178,43 @@ Here are the **Top Contributors**. 👷 These users have [created the most Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} that have been *merged*. -They have contributed source code, documentation, translations, etc. 📦 +They have contributed source code, documentation, etc. 📦 -{% if people %}
-{% for user in people.top_contributors[:50] %} + +{% for user in (contributors.values() | list)[:50] %} + +{% if user.login not in skip_users %}
@{{ user.login }}
Pull Requests: {{ user.count }}
+ +{% endif %} + {% endfor %}
+ +There are hundreds of other contributors, you can see them all in the FastAPI GitHub Contributors page. 👷 + +## Top Translators + +These are the **Top Translators**. 🌐 + +These users have created the most Pull Requests with [translations to other languages](contributing.md#translations){.internal-link target=_blank} that have been *merged*. + +
+ +{% for user in (translators.values() | list)[:50] %} + +{% if user.login not in skip_users %} + +
@{{ user.login }}
Translations: {{ user.count }}
+ {% endif %} -There are many other contributors (more than a hundred), you can see them all in the FastAPI GitHub Contributors page. 👷 +{% endfor %} + +
## Top Translation Reviewers @@ -178,15 +222,18 @@ These users are the **Top Translation Reviewers**. 🕵️ I only speak a few languages (and not very well 😅). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages. -{% if people %}
-{% for user in people.top_translations_reviewers[:50] %} +{% for user in (translation_reviewers.values() | list)[:50] %} + +{% if user.login not in skip_users %}
@{{ user.login }}
Reviews: {{ user.count }}
+ +{% endif %} + {% endfor %}
-{% endif %} ## Sponsors @@ -251,7 +298,7 @@ The main intention of this page is to highlight the effort of the community to h Especially including efforts that are normally less visible, and in many cases more arduous, like helping others with questions and reviewing Pull Requests with translations. -The data is calculated each month, you can read the source code here. +The data is calculated each month, you can read the source code here. Here I'm also highlighting contributions from sponsors. diff --git a/docs/en/docs/features.md b/docs/en/docs/features.md index 9c38a4bd2f..a345e4a0e3 100644 --- a/docs/en/docs/features.md +++ b/docs/en/docs/features.md @@ -1,17 +1,17 @@ -# Features +# Features { #features } -## FastAPI features +## FastAPI features { #fastapi-features } **FastAPI** gives you the following: -### Based on open standards +### Based on open standards { #based-on-open-standards } * OpenAPI for API creation, including declarations of path operations, parameters, request bodies, security, etc. * Automatic data model documentation with JSON Schema (as OpenAPI itself is based on JSON Schema). * Designed around these standards, after a meticulous study. Instead of an afterthought layer on top. * This also allows using automatic **client code generation** in many languages. -### Automatic docs +### Automatic docs { #automatic-docs } Interactive API documentation and exploration web user interfaces. As the framework is based on OpenAPI, there are multiple options, 2 included by default. @@ -23,7 +23,7 @@ Interactive API documentation and exploration web user interfaces. As the framew ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Just Modern Python +### Just Modern Python { #just-modern-python } It's all based on standard **Python type** declarations (thanks to Pydantic). No new syntax to learn. Just standard modern Python. @@ -71,7 +71,7 @@ Pass the keys and values of the `second_user_data` dict directly as key-value ar /// -### Editor support +### Editor support { #editor-support } All the framework was designed to be easy and intuitive to use, all the decisions were tested on multiple editors even before starting development, to ensure the best development experience. @@ -95,13 +95,13 @@ You will get completion in code you might even consider impossible before. As fo No more typing the wrong key names, coming back and forth between docs, or scrolling up and down to find if you finally used `username` or `user_name`. -### Short +### Short { #short } It has sensible **defaults** for everything, with optional configurations everywhere. All the parameters can be fine-tuned to do what you need and to define the API you need. But by default, it all **"just works"**. -### Validation +### Validation { #validation } * Validation for most (or all?) Python **data types**, including: * JSON objects (`dict`). @@ -117,7 +117,7 @@ But by default, it all **"just works"**. All the validation is handled by the well-established and robust **Pydantic**. -### Security and authentication +### Security and authentication { #security-and-authentication } Security and authentication integrated. Without any compromise with databases or data models. @@ -134,7 +134,7 @@ Plus all the security features from Starlette (including **session cookies**). All built as reusable tools and components that are easy to integrate with your systems, data stores, relational and NoSQL databases, etc. -### Dependency Injection +### Dependency Injection { #dependency-injection } FastAPI includes an extremely easy to use, but extremely powerful Dependency Injection system. @@ -145,21 +145,21 @@ FastAPI includes an extremely easy to use, but extremely powerful Pydantic. So, any additional Pydantic code you have, will also work. @@ -190,7 +190,7 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on * **No brainfuck**: * No new schema definition micro-language to learn. * If you know Python types you know how to use Pydantic. -* Plays nicely with your **IDE/linter/brain**: +* 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. * Validate **complex structures**: * Use of hierarchical Pydantic models, Python `typing`’s `List` and `Dict`, etc. diff --git a/docs/en/docs/help-fastapi.md b/docs/en/docs/help-fastapi.md index 81151032fd..c7acd69a62 100644 --- a/docs/en/docs/help-fastapi.md +++ b/docs/en/docs/help-fastapi.md @@ -1,4 +1,4 @@ -# Help FastAPI - Get Help +# Help FastAPI - Get Help { #help-fastapi-get-help } Do you like **FastAPI**? @@ -10,7 +10,7 @@ There are very simple ways to help (several involve just one or two clicks). And there are several ways to get help too. -## Subscribe to the newsletter +## Subscribe to the newsletter { #subscribe-to-the-newsletter } You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](newsletter.md){.internal-link target=_blank} to stay updated about: @@ -20,17 +20,17 @@ You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](newsl * Breaking changes 🚨 * Tips and tricks ✅ -## Follow FastAPI on Twitter +## Follow FastAPI on X (Twitter) { #follow-fastapi-on-x-twitter } -Follow @fastapi on **Twitter** to get the latest news about **FastAPI**. 🐦 +Follow @fastapi on **X (Twitter)** to get the latest news about **FastAPI**. 🐦 -## Star **FastAPI** in GitHub +## Star **FastAPI** in GitHub { #star-fastapi-in-github } You can "star" FastAPI in GitHub (clicking the star button at the top right): https://github.com/fastapi/fastapi. ⭐️ By adding a star, other users will be able to find it more easily and see that it has been already useful for others. -## Watch the GitHub repository for releases +## Watch the GitHub repository for releases { #watch-the-github-repository-for-releases } You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right): https://github.com/fastapi/fastapi. 👀 @@ -38,7 +38,7 @@ There you can select "Releases only". By doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **FastAPI** with bug fixes and new features. -## Connect with the author +## Connect with the author { #connect-with-the-author } You can connect with me (Sebastián Ramírez / `tiangolo`), the author. @@ -47,29 +47,29 @@ 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** or Mastodon. +* Follow me on **X (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). + * You can also follow @fastapi on X (Twitter) (a separate account). * Follow me on **LinkedIn**. - * Hear when I make announcements or release new tools (although I use Twitter more often 🤷‍♂). + * Hear when I make announcements or release new tools (although I use X (Twitter) more often 🤷‍♂). * Read what I write (or follow me) on **Dev.to** or **Medium**. * Read other ideas, articles, and read about tools I have created. * Follow me to read when I publish something new. -## Tweet about **FastAPI** +## Tweet about **FastAPI** { #tweet-about-fastapi } -Tweet about **FastAPI** and let me and others know why you like it. 🎉 +Tweet about **FastAPI** and let me and others know why you like it. 🎉 I love to hear about how **FastAPI** is being used, what you have liked in it, in which project/company are you using it, etc. -## Vote for FastAPI +## Vote for FastAPI { #vote-for-fastapi } * Vote for **FastAPI** in Slant. * Vote for **FastAPI** in AlternativeTo. * Say you use **FastAPI** on StackShare. -## Help others with questions in GitHub +## Help others with questions in GitHub { #help-others-with-questions-in-github } You can try and help others with their questions in: @@ -88,7 +88,7 @@ The idea is for the **FastAPI** community to be kind and welcoming. At the same Here's how to help others with questions (in discussions or issues): -### Understand the question +### Understand the question { #understand-the-question } * Check if you can understand what is the **purpose** and use case of the person asking. @@ -98,7 +98,7 @@ Here's how to help others with questions (in discussions or issues): * If you can't understand the question, ask for more **details**. -### Reproduce the problem +### Reproduce the problem { #reproduce-the-problem } For most of the cases and most of the questions there's something related to the person's **original code**. @@ -108,13 +108,13 @@ In many cases they will only copy a fragment of the code, but that's not enough * 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 keep 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 +### Suggest solutions { #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 +### Ask to close { #ask-to-close } If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸 @@ -123,7 +123,7 @@ If they reply, there's a high chance you would have solved their problem, congra * In GitHub Discussions: mark the comment as the **answer**. * In GitHub Issues: **close** the issue. -## Watch the GitHub repository +## Watch the GitHub repository { #watch-the-github-repository } You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right): https://github.com/fastapi/fastapi. 👀 @@ -131,7 +131,7 @@ If you select "Watching" instead of "Releases only" you will receive notificatio Then you can try and help them solve those questions. -## Ask Questions +## Ask Questions { #ask-questions } You can create a new question in the GitHub repository, for example to: @@ -140,7 +140,7 @@ You can Discord chat server 👥 and hang out with others in the FastAPI community. @@ -237,7 +237,7 @@ Use the chat only for other general conversations. /// -### Don't use the chat for questions +### Don't use the chat for questions { #dont-use-the-chat-for-questions } Keep in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers. @@ -247,22 +247,9 @@ Conversations in the chat systems are also not as easily searchable as in GitHub On the other side, there are thousands of users in the chat systems, so there's a high chance you'll find someone to talk to there, almost all the time. 😄 -## Sponsor the author +## Sponsor the author { #sponsor-the-author } -You can also financially support the author (me) through GitHub sponsors. - -There you could buy me a coffee ☕️ to say thanks. 😄 - -And you can also become a Silver or Gold sponsor for FastAPI. 🏅🎉 - -## Sponsor the tools that power FastAPI - -As you have seen in the documentation, FastAPI stands on the shoulders of giants, Starlette and Pydantic. - -You can also sponsor: - -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) +If your **product/company** depends on or is related to **FastAPI** and you want to reach its users, you can sponsor the author (me) through GitHub sponsors. Depending on the tier, you could get some extra benefits, like a badge in the docs. 🎁 --- diff --git a/docs/en/docs/history-design-future.md b/docs/en/docs/history-design-future.md index b4a744d64b..6175dcbbe3 100644 --- a/docs/en/docs/history-design-future.md +++ b/docs/en/docs/history-design-future.md @@ -1,4 +1,4 @@ -# History, Design and Future +# History, Design and Future { #history-design-and-future } Some time ago, a **FastAPI** user asked: @@ -6,7 +6,7 @@ Some time ago, **Pydantic** for its advantages. Then I contributed to it, to make it fully compliant with JSON Schema, to support different ways to define constraint declarations, and to improve editor support (type checks, autocompletion) based on the tests in several editors. -During the development, I also contributed to **Starlette**, the other key requirement. +During the development, I also contributed to **Starlette**, the other key requirement. -## Development +## Development { #development } By the time I started creating **FastAPI** itself, most of the pieces were already in place, the design was defined, the requirements and tools were ready, and the knowledge about the standards and specifications was clear and fresh. -## Future +## Future { #future } By this point, it's already clear that **FastAPI** with its ideas is being useful for many people. diff --git a/docs/en/docs/how-to/async-sql-encode-databases.md b/docs/en/docs/how-to/async-sql-encode-databases.md deleted file mode 100644 index a75f8ef582..0000000000 --- a/docs/en/docs/how-to/async-sql-encode-databases.md +++ /dev/null @@ -1,201 +0,0 @@ -# ~~Async SQL (Relational) Databases with Encode/Databases~~ (deprecated) - -/// info - -These docs are about to be updated. 🎉 - -The current version assumes Pydantic v1. - -The new docs will include Pydantic v2 and will use SQLModel once it is updated to use Pydantic v2 as well. - -/// - -/// warning | "Deprecated" - -This tutorial is deprecated and will be removed in a future version. - -/// - -You can also use `encode/databases` with **FastAPI** to connect to databases using `async` and `await`. - -It is compatible with: - -* PostgreSQL -* MySQL -* SQLite - -In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is. - -Later, for your production application, you might want to use a database server like **PostgreSQL**. - -/// tip - -You could adopt ideas from the section about SQLAlchemy ORM ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}), like using utility functions to perform operations in the database, independent of your **FastAPI** code. - -This section doesn't apply those ideas, to be equivalent to the counterpart in Starlette. - -/// - -## Import and set up `SQLAlchemy` - -* Import `SQLAlchemy`. -* Create a `metadata` object. -* Create a table `notes` using the `metadata` object. - -```Python hl_lines="4 14 16-22" -{!../../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// tip - -Notice that all this code is pure SQLAlchemy Core. - -`databases` is not doing anything here yet. - -/// - -## Import and set up `databases` - -* Import `databases`. -* Create a `DATABASE_URL`. -* Create a `database` object. - -```Python hl_lines="3 9 12" -{!../../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// tip - -If you were connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`. - -/// - -## Create the tables - -In this case, we are creating the tables in the same Python file, but in production, you would probably want to create them with Alembic, integrated with migrations, etc. - -Here, this section would run directly, right before starting your **FastAPI** application. - -* Create an `engine`. -* Create all the tables from the `metadata` object. - -```Python hl_lines="25-28" -{!../../../docs_src/async_sql_databases/tutorial001.py!} -``` - -## Create models - -Create Pydantic models for: - -* Notes to be created (`NoteIn`). -* Notes to be returned (`Note`). - -```Python hl_lines="31-33 36-39" -{!../../../docs_src/async_sql_databases/tutorial001.py!} -``` - -By creating these Pydantic models, the input data will be validated, serialized (converted), and annotated (documented). - -So, you will be able to see it all in the interactive API docs. - -## Connect and disconnect - -* Create your `FastAPI` application. -* Create event handlers to connect and disconnect from the database. - -```Python hl_lines="42 45-47 50-52" -{!../../../docs_src/async_sql_databases/tutorial001.py!} -``` - -## Read notes - -Create the *path operation function* to read notes: - -```Python hl_lines="55-58" -{!../../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// note - -Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. - -/// - -### Notice the `response_model=List[Note]` - -It uses `typing.List`. - -That documents (and validates, serializes, filters) the output data, as a `list` of `Note`s. - -## Create notes - -Create the *path operation function* to create notes: - -```Python hl_lines="61-65" -{!../../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// info - -In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`. - -The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2. - -/// - -/// note - -Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. - -/// - -### About `{**note.dict(), "id": last_record_id}` - -`note` is a Pydantic `Note` object. - -`note.dict()` returns a `dict` with its data, something like: - -```Python -{ - "text": "Some note", - "completed": False, -} -``` - -but it doesn't have the `id` field. - -So we create a new `dict`, that contains the key-value pairs from `note.dict()` with: - -```Python -{**note.dict()} -``` - -`**note.dict()` "unpacks" the key value pairs directly, so, `{**note.dict()}` would be, more or less, a copy of `note.dict()`. - -And then, we extend that copy `dict`, adding another key-value pair: `"id": last_record_id`: - -```Python -{**note.dict(), "id": last_record_id} -``` - -So, the final result returned would be something like: - -```Python -{ - "id": 1, - "text": "Some note", - "completed": False, -} -``` - -## Check it - -You can copy this code as is, and see the docs at http://127.0.0.1:8000/docs. - -There you can see all your API documented and interact with it: - - - -## More info - -You can read more about `encode/databases` at its GitHub page. diff --git a/docs/en/docs/how-to/conditional-openapi.md b/docs/en/docs/how-to/conditional-openapi.md index add16fbec5..e5893e584b 100644 --- a/docs/en/docs/how-to/conditional-openapi.md +++ b/docs/en/docs/how-to/conditional-openapi.md @@ -1,8 +1,8 @@ -# Conditional OpenAPI +# Conditional OpenAPI { #conditional-openapi } If you needed to, you could use settings and environment variables to configure OpenAPI conditionally depending on the environment, and even disable it entirely. -## About security, APIs, and docs +## About security, APIs, and docs { #about-security-apis-and-docs } Hiding your documentation user interfaces in production *shouldn't* be the way to protect your API. @@ -17,21 +17,19 @@ If you want to secure your API, there are several better things you can do, for * Make sure you have well defined Pydantic models for your request bodies and responses. * Configure any required permissions and roles using dependencies. * Never store plaintext passwords, only password hashes. -* Implement and use well-known cryptographic tools, like Passlib and JWT tokens, etc. +* Implement and use well-known cryptographic tools, like pwdlib and JWT tokens, etc. * Add more granular permission controls with OAuth2 scopes where needed. * ...etc. Nevertheless, you might have a very specific use case where you really need to disable the API docs for some environment (e.g. for production) or depending on configurations from environment variables. -## Conditional OpenAPI from settings and env vars +## Conditional OpenAPI from settings and env vars { #conditional-openapi-from-settings-and-env-vars } You can easily use the same Pydantic settings to configure your generated OpenAPI and the docs UIs. For example: -```Python hl_lines="6 11" -{!../../../docs_src/conditional_openapi/tutorial001.py!} -``` +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} Here we declare the setting `openapi_url` with the same default of `"/openapi.json"`. diff --git a/docs/en/docs/how-to/configure-swagger-ui.md b/docs/en/docs/how-to/configure-swagger-ui.md index 108afb929b..2d7b99f8fa 100644 --- a/docs/en/docs/how-to/configure-swagger-ui.md +++ b/docs/en/docs/how-to/configure-swagger-ui.md @@ -1,6 +1,6 @@ -# Configure Swagger UI +# Configure Swagger UI { #configure-swagger-ui } -You can configure some extra Swagger UI parameters. +You can configure some extra Swagger UI parameters. To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function. @@ -8,7 +8,7 @@ To configure them, pass the `swagger_ui_parameters` argument when creating the ` FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs. -## Disable Syntax Highlighting +## Disable Syntax Highlighting { #disable-syntax-highlighting } For example, you could disable syntax highlighting in Swagger UI. @@ -18,49 +18,41 @@ Without changing the settings, syntax highlighting is enabled by default: But you can disable it by setting `syntaxHighlight` to `False`: -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial001.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} ...and then Swagger UI won't show the syntax highlighting anymore: -## Change the Theme +## Change the Theme { #change-the-theme } The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle): -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial002.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} That configuration would change the syntax highlighting color theme: -## Change Default Swagger UI Parameters +## Change Default Swagger UI Parameters { #change-default-swagger-ui-parameters } FastAPI includes some default configuration parameters appropriate for most of the use cases. It includes these default configurations: -```Python -{!../../../fastapi/openapi/docs.py[ln:7-23]!} -``` +{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} You can override any of them by setting a different value in the argument `swagger_ui_parameters`. For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`: -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial003.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} -## Other Swagger UI Parameters +## Other Swagger UI Parameters { #other-swagger-ui-parameters } -To see all the other possible configurations you can use, read the official docs for Swagger UI parameters. +To see all the other possible configurations you can use, read the official docs for Swagger UI parameters. -## JavaScript-only settings +## JavaScript-only settings { #javascript-only-settings } Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions). diff --git a/docs/en/docs/how-to/custom-docs-ui-assets.md b/docs/en/docs/how-to/custom-docs-ui-assets.md index 0c766d3e43..91228c8c93 100644 --- a/docs/en/docs/how-to/custom-docs-ui-assets.md +++ b/docs/en/docs/how-to/custom-docs-ui-assets.md @@ -1,4 +1,4 @@ -# Custom Docs UI Static Assets (Self-Hosting) +# Custom Docs UI Static Assets (Self-Hosting) { #custom-docs-ui-static-assets-self-hosting } The API docs use **Swagger UI** and **ReDoc**, and each of those need some JavaScript and CSS files. @@ -6,23 +6,21 @@ By default, those files are served from a CDN, for example you want to use `https://unpkg.com/`. This could be useful if for example you live in a country that restricts some URLs. -### Disable the automatic docs +### Disable the automatic docs { #disable-the-automatic-docs } The first step is to disable the automatic docs, as by default, those use the default CDN. To disable them, set their URLs to `None` when creating your `FastAPI` app: -```Python hl_lines="8" -{!../../../docs_src/custom_docs_ui/tutorial001.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} -### Include the custom docs +### Include the custom docs { #include-the-custom-docs } Now you can create the *path operations* for the custom docs. @@ -36,9 +34,7 @@ You can reuse FastAPI's internal functions to create the HTML pages for the docs And similarly for ReDoc... -```Python hl_lines="2-6 11-19 22-24 27-33" -{!../../../docs_src/custom_docs_ui/tutorial001.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} /// tip @@ -50,25 +46,23 @@ Swagger UI will handle it behind the scenes for you, but it needs this "redirect /// -### Create a *path operation* to test it +### Create a *path operation* to test it { #create-a-path-operation-to-test-it } Now, to be able to test that everything works, create a *path operation*: -```Python hl_lines="36-38" -{!../../../docs_src/custom_docs_ui/tutorial001.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} -### Test it +### Test it { #test-it } Now, you should be able to go to your docs at http://127.0.0.1:8000/docs, and reload the page, it will load those assets from the new CDN. -## Self-hosting JavaScript and CSS for docs +## Self-hosting JavaScript and CSS for docs { #self-hosting-javascript-and-css-for-docs } Self-hosting the JavaScript and CSS could be useful if, for example, you need your app to keep working even while offline, without open Internet access, or in a local network. Here you'll see how to serve those files yourself, in the same FastAPI app, and configure the docs to use them. -### Project file structure +### Project file structure { #project-file-structure } Let's say your project file structure looks like this: @@ -91,11 +85,11 @@ Your new file structure could look like this: └── static/ ``` -### Download the files +### Download the files { #download-the-files } Download the static files needed for the docs and put them on that `static/` directory. -You can probably right-click each link and select an option similar to `Save link as...`. +You can probably right-click each link and select an option similar to "Save link as...". **Swagger UI** uses the files: @@ -104,7 +98,7 @@ You can probably right-click each link and select an option similar to `Save lin And **ReDoc** uses the file: -* `redoc.standalone.js` +* `redoc.standalone.js` After that, your file structure could look like: @@ -119,16 +113,14 @@ After that, your file structure could look like: └── swagger-ui.css ``` -### Serve the static files +### Serve the static files { #serve-the-static-files } * Import `StaticFiles`. * "Mount" a `StaticFiles()` instance in a specific path. -```Python hl_lines="7 11" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} -### Test the static files +### Test the static files { #test-the-static-files } Start your application and go to http://127.0.0.1:8000/static/redoc.standalone.js. @@ -137,14 +129,8 @@ You should see a very long JavaScript file for **ReDoc**. It could start with something like: ```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 - +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): ... ``` @@ -152,17 +138,15 @@ That confirms that you are being able to serve static files from your app, and t Now we can configure the app to use those static files for the docs. -### Disable the automatic docs for static files +### Disable the automatic docs for static files { #disable-the-automatic-docs-for-static-files } The same as when using a custom CDN, the first step is to disable the automatic docs, as those use the CDN by default. To disable them, set their URLs to `None` when creating your `FastAPI` app: -```Python hl_lines="9" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} -### Include the custom docs for static files +### Include the custom docs for static files { #include-the-custom-docs-for-static-files } And the same way as with a custom CDN, now you can create the *path operations* for the custom docs. @@ -176,9 +160,7 @@ Again, you can reuse FastAPI's internal functions to create the HTML pages for t And similarly for ReDoc... -```Python hl_lines="2-6 14-22 25-27 30-36" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} /// tip @@ -190,15 +172,13 @@ Swagger UI will handle it behind the scenes for you, but it needs this "redirect /// -### Create a *path operation* to test static files +### Create a *path operation* to test static files { #create-a-path-operation-to-test-static-files } Now, to be able to test that everything works, create a *path operation*: -```Python hl_lines="39-41" -{!../../../docs_src/custom_docs_ui/tutorial002.py!} -``` +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} -### Test Static Files UI +### Test Static Files UI { #test-static-files-ui } Now, you should be able to disconnect your WiFi, go to your docs at http://127.0.0.1:8000/docs, and reload the page. diff --git a/docs/en/docs/how-to/custom-request-and-route.md b/docs/en/docs/how-to/custom-request-and-route.md index 20e1904f1e..884c8ed04f 100644 --- a/docs/en/docs/how-to/custom-request-and-route.md +++ b/docs/en/docs/how-to/custom-request-and-route.md @@ -1,4 +1,4 @@ -# Custom Request and APIRoute class +# Custom Request and APIRoute class { #custom-request-and-apiroute-class } In some cases, you may want to override the logic used by the `Request` and `APIRoute` classes. @@ -14,7 +14,7 @@ If you are just starting with **FastAPI** you might want to skip this section. /// -## Use cases +## Use cases { #use-cases } Some use cases include: @@ -22,13 +22,13 @@ Some use cases include: * Decompressing gzip-compressed request bodies. * Automatically logging all request bodies. -## Handling custom request body encodings +## Handling custom request body encodings { #handling-custom-request-body-encodings } Let's see how to make use of a custom `Request` subclass to decompress gzip requests. And an `APIRoute` subclass to use that custom request class. -### Create a custom `GzipRequest` class +### Create a custom `GzipRequest` class { #create-a-custom-gziprequest-class } /// tip @@ -42,11 +42,9 @@ If there's no `gzip` in the header, it will not try to decompress the body. That way, the same route class can handle gzip compressed or uncompressed requests. -```Python hl_lines="8-15" -{!../../../docs_src/custom_request_and_route/tutorial001.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} -### Create a custom `GzipRoute` class +### Create a custom `GzipRoute` class { #create-a-custom-gziproute-class } Next, we create a custom subclass of `fastapi.routing.APIRoute` that will make use of the `GzipRequest`. @@ -56,11 +54,9 @@ This method returns a function. And that function is what will receive a request Here we use it to create a `GzipRequest` from the original request. -```Python hl_lines="18-26" -{!../../../docs_src/custom_request_and_route/tutorial001.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} -/// note | "Technical Details" +/// note | Technical Details A `Request` has a `request.scope` attribute, that's just a Python `dict` containing the metadata related to the request. @@ -70,7 +66,7 @@ The `scope` `dict` and `receive` function are both part of the ASGI specificatio And those two things, `scope` and `receive`, are what is needed to create a new `Request` instance. -To learn more about the `Request` check Starlette's docs about Requests. +To learn more about the `Request` check Starlette's docs about Requests. /// @@ -82,7 +78,7 @@ After that, all of the processing logic is the same. But because of our changes in `GzipRequest.body`, the request body will be automatically decompressed when it is loaded by **FastAPI** when needed. -## Accessing the request body in an exception handler +## Accessing the request body in an exception handler { #accessing-the-request-body-in-an-exception-handler } /// tip @@ -96,26 +92,18 @@ We can also use this same approach to access the request body in an exception ha All we need to do is handle the request inside a `try`/`except` block: -```Python hl_lines="13 15" -{!../../../docs_src/custom_request_and_route/tutorial002.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} If an exception occurs, the`Request` instance will still be in scope, so we can read and make use of the request body when handling the error: -```Python hl_lines="16-18" -{!../../../docs_src/custom_request_and_route/tutorial002.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} -## Custom `APIRoute` class in a router +## Custom `APIRoute` class in a router { #custom-apiroute-class-in-a-router } You can also set the `route_class` parameter of an `APIRouter`: -```Python hl_lines="26" -{!../../../docs_src/custom_request_and_route/tutorial003.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} In this example, the *path operations* under the `router` will use the custom `TimedRoute` class, and will have an extra `X-Response-Time` header in the response with the time it took to generate the response: -```Python hl_lines="13-20" -{!../../../docs_src/custom_request_and_route/tutorial003.py!} -``` +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} diff --git a/docs/en/docs/how-to/extending-openapi.md b/docs/en/docs/how-to/extending-openapi.md index 9909f778c3..5e672665ef 100644 --- a/docs/en/docs/how-to/extending-openapi.md +++ b/docs/en/docs/how-to/extending-openapi.md @@ -1,10 +1,10 @@ -# Extending OpenAPI +# Extending OpenAPI { #extending-openapi } There are some cases where you might need to modify the generated OpenAPI schema. In this section you will see how. -## The normal process +## The normal process { #the-normal-process } The normal (default) process, is as follows. @@ -33,37 +33,31 @@ The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by Fa /// -## Overriding the defaults +## Overriding the defaults { #overriding-the-defaults } Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need. For example, let's add ReDoc's OpenAPI extension to include a custom logo. -### Normal **FastAPI** +### Normal **FastAPI** { #normal-fastapi } First, write all your **FastAPI** application as normally: -```Python hl_lines="1 4 7-9" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} -### Generate the OpenAPI schema +### Generate the OpenAPI schema { #generate-the-openapi-schema } Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function: -```Python hl_lines="2 15-21" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} -### Modify the OpenAPI schema +### Modify the OpenAPI schema { #modify-the-openapi-schema } Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema: -```Python hl_lines="22-24" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} -### Cache the OpenAPI schema +### Cache the OpenAPI schema { #cache-the-openapi-schema } You can use the property `.openapi_schema` as a "cache", to store your generated schema. @@ -71,19 +65,15 @@ That way, your application won't have to generate the schema every time a user o It will be generated only once, and then the same cached schema will be used for the next requests. -```Python hl_lines="13-14 25-26" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} -### Override the method +### Override the method { #override-the-method } Now you can replace the `.openapi()` method with your new function. -```Python hl_lines="29" -{!../../../docs_src/extending_openapi/tutorial001.py!} -``` +{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} -### Check it +### Check it { #check-it } Once you go to http://127.0.0.1:8000/redoc you will see that you are using your custom logo (in this example, **FastAPI**'s logo): diff --git a/docs/en/docs/how-to/general.md b/docs/en/docs/how-to/general.md index 04367c6b76..9347192607 100644 --- a/docs/en/docs/how-to/general.md +++ b/docs/en/docs/how-to/general.md @@ -1,39 +1,39 @@ -# General - How To - Recipes +# General - How To - Recipes { #general-how-to-recipes } Here are several pointers to other places in the docs, for general or frequent questions. -## Filter Data - Security +## Filter Data - Security { #filter-data-security } To ensure that you don't return more data than you should, read the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. -## Documentation Tags - OpenAPI +## Documentation Tags - OpenAPI { #documentation-tags-openapi } To add tags to your *path operations*, and group them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. -## Documentation Summary and Description - OpenAPI +## Documentation Summary and Description - OpenAPI { #documentation-summary-and-description-openapi } To add a summary and description to your *path operations*, and show them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Summary and Description](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. -## Documentation Response description - OpenAPI +## Documentation Response description - OpenAPI { #documentation-response-description-openapi } To define the description of the response, shown in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Response description](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. -## Documentation Deprecate a *Path Operation* - OpenAPI +## Documentation Deprecate a *Path Operation* - OpenAPI { #documentation-deprecate-a-path-operation-openapi } To deprecate a *path operation*, and show it in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Deprecation](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. -## Convert any Data to JSON-compatible +## Convert any Data to JSON-compatible { #convert-any-data-to-json-compatible } To convert any data to JSON-compatible, read the docs for [Tutorial - JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. -## OpenAPI Metadata - Docs +## OpenAPI Metadata - Docs { #openapi-metadata-docs } To add metadata to your OpenAPI schema, including a license, version, contact, etc, read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md){.internal-link target=_blank}. -## OpenAPI Custom URL +## OpenAPI Custom URL { #openapi-custom-url } To customize the OpenAPI URL (or remove it), read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. -## OpenAPI Docs URLs +## OpenAPI Docs URLs { #openapi-docs-urls } To update the URLs used for the automatically generated docs user interfaces, read the docs for [Tutorial - Metadata and Docs URLs](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/en/docs/how-to/graphql.md b/docs/en/docs/how-to/graphql.md index d4b7cfdaa5..99b024d39b 100644 --- a/docs/en/docs/how-to/graphql.md +++ b/docs/en/docs/how-to/graphql.md @@ -1,4 +1,4 @@ -# GraphQL +# GraphQL { #graphql } As **FastAPI** is based on the **ASGI** standard, it's very easy to integrate any **GraphQL** library also compatible with ASGI. @@ -14,7 +14,7 @@ Make sure you evaluate if the **benefits** for your use case compensate the **dr /// -## GraphQL Libraries +## GraphQL Libraries { #graphql-libraries } Here are some of the **GraphQL** libraries that have **ASGI** support. You could use them with **FastAPI**: @@ -27,7 +27,7 @@ Here are some of the **GraphQL** libraries that have **ASGI** support. You could * Graphene * With starlette-graphene3 -## GraphQL with Strawberry +## GraphQL with Strawberry { #graphql-with-strawberry } If you need or want to work with **GraphQL**, **Strawberry** is the **recommended** library as it has the design closest to **FastAPI's** design, it's all based on **type annotations**. @@ -35,15 +35,13 @@ Depending on your use case, you might prefer to use a different library, but if Here's a small preview of how you could integrate Strawberry with FastAPI: -```Python hl_lines="3 22 25-26" -{!../../../docs_src/graphql/tutorial001.py!} -``` +{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} You can learn more about Strawberry in the Strawberry documentation. And also the docs about Strawberry with FastAPI. -## Older `GraphQLApp` from Starlette +## Older `GraphQLApp` from Starlette { #older-graphqlapp-from-starlette } Previous versions of Starlette included a `GraphQLApp` class to integrate with Graphene. @@ -55,7 +53,7 @@ If you need GraphQL, I still would recommend you check out official GraphQL documentation. diff --git a/docs/en/docs/how-to/index.md b/docs/en/docs/how-to/index.md index 730dce5d55..5a8ce08de7 100644 --- a/docs/en/docs/how-to/index.md +++ b/docs/en/docs/how-to/index.md @@ -1,4 +1,4 @@ -# How To - Recipes +# How To - Recipes { #how-to-recipes } Here you will see different recipes or "how to" guides for **several topics**. diff --git a/docs/en/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/en/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 0000000000..e85d122be8 --- /dev/null +++ b/docs/en/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,133 @@ +# Migrate from Pydantic v1 to Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 } + +If you have an old FastAPI app, you might be using Pydantic version 1. + +FastAPI has had support for either Pydantic v1 or v2 since version 0.100.0. + +If you had installed Pydantic v2, it would use it. If instead you had Pydantic v1, it would use that. + +Pydantic v1 is now deprecated and support for it will be removed in the next versions of FastAPI, you should **migrate to Pydantic v2**. This way you will get the latest features, improvements, and fixes. + +/// warning + +Also, the Pydantic team stopped support for Pydantic v1 for the latest versions of Python, starting with **Python 3.14**. + +If you want to use the latest features of Python, you will need to make sure you use Pydantic v2. + +/// + +If you have an old FastAPI app with Pydantic v1, here I'll show you how to migrate it to Pydantic v2, and the **new features in FastAPI 0.119.0** to help you with a gradual migration. + +## Official Guide { #official-guide } + +Pydantic has an official Migration Guide from v1 to v2. + +It also includes what has changed, how validations are now more correct and strict, possible caveats, etc. + +You can read it to understand better what has changed. + +## Tests { #tests } + +Make sure you have [tests](../tutorial/testing.md){.internal-link target=_blank} for your app and you run them on continuous integration (CI). + +This way, you can do the upgrade and make sure everything is still working as expected. + +## `bump-pydantic` { #bump-pydantic } + +In many cases, when you use regular Pydantic models without customizations, you will be able to automate most of the process of migrating from Pydantic v1 to Pydantic v2. + +You can use `bump-pydantic` from the same Pydantic team. + +This tool will help you to automatically change most of the code that needs to be changed. + +After this, you can run the tests and check if everything works. If it does, you are done. 😎 + +## Pydantic v1 in v2 { #pydantic-v1-in-v2 } + +Pydantic v2 includes everything from Pydantic v1 as a submodule `pydantic.v1`. + +This means that you can install the latest version of Pydantic v2 and import and use the old Pydantic v1 components from this submodule, as if you had the old Pydantic v1 installed. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### FastAPI support for Pydantic v1 in v2 { #fastapi-support-for-pydantic-v1-in-v2 } + +Since FastAPI 0.119.0, there's also partial support for Pydantic v1 from inside of Pydantic v2, to facilitate the migration to v2. + +So, you could upgrade Pydantic to the latest version 2, and change the imports to use the `pydantic.v1` submodule, and in many cases it would just work. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning + +Have in mind that as the Pydantic team no longer supports Pydantic v1 in recent versions of Python, starting from Python 3.14, using `pydantic.v1` is also not supported in Python 3.14 and above. + +/// + +### Pydantic v1 and v2 on the same app { #pydantic-v1-and-v2-on-the-same-app } + +It's **not supported** by Pydantic to have a model of Pydantic v2 with its own fields defined as Pydantic v1 models or vice versa. + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +...but, you can have separated models using Pydantic v1 and v2 in the same app. + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +In some cases, it's even possible to have both Pydantic v1 and v2 models in the same **path operation** in your FastAPI app: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +In this example above, the input model is a Pydantic v1 model, and the output model (defined in `response_model=ItemV2`) is a Pydantic v2 model. + +### Pydantic v1 parameters { #pydantic-v1-parameters } + +If you need to use some of the FastAPI-specific tools for parameters like `Body`, `Query`, `Form`, etc. with Pydantic v1 models, you can import them from `fastapi.temp_pydantic_v1_params` while you finish the migration to Pydantic v2: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### Migrate in steps { #migrate-in-steps } + +/// tip + +First try with `bump-pydantic`, if your tests pass and that works, then you're done in one command. ✨ + +/// + +If `bump-pydantic` doesn't work for your use case, you can use the support for both Pydantic v1 and v2 models in the same app to do the migration to Pydantic v2 gradually. + +You could fist upgrade Pydantic to use the latest version 2, and change the imports to use `pydantic.v1` for all your models. + +Then, you can start migrating your models from Pydantic v1 to v2 in groups, in gradual steps. 🚶 diff --git a/docs/en/docs/how-to/nosql-databases-couchbase.md b/docs/en/docs/how-to/nosql-databases-couchbase.md deleted file mode 100644 index a0abfe21d2..0000000000 --- a/docs/en/docs/how-to/nosql-databases-couchbase.md +++ /dev/null @@ -1,178 +0,0 @@ -# ~~NoSQL (Distributed / Big Data) Databases with Couchbase~~ (deprecated) - -/// info - -These docs are about to be updated. 🎉 - -The current version assumes Pydantic v1. - -The new docs will hopefully use Pydantic v2 and will use ODMantic with MongoDB. - -/// - -/// warning | "Deprecated" - -This tutorial is deprecated and will be removed in a future version. - -/// - -**FastAPI** can also be integrated with any NoSQL. - -Here we'll see an example using **Couchbase**, a document based NoSQL database. - -You can adapt it to any other NoSQL database like: - -* **MongoDB** -* **Cassandra** -* **CouchDB** -* **ArangoDB** -* **ElasticSearch**, etc. - -/// tip - -There is an official project generator with **FastAPI** and **Couchbase**, all based on **Docker**, including a frontend and more tools: https://github.com/tiangolo/full-stack-fastapi-couchbase - -/// - -## Import Couchbase components - -For now, don't pay attention to the rest, only the imports: - -```Python hl_lines="3-5" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Define a constant to use as a "document type" - -We will use it later as a fixed field `type` in our documents. - -This is not required by Couchbase, but is a good practice that will help you afterwards. - -```Python hl_lines="9" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Add a function to get a `Bucket` - -In **Couchbase**, a bucket is a set of documents, that can be of different types. - -They are generally all related to the same application. - -The analogy in the relational database world would be a "database" (a specific database, not the database server). - -The analogy in **MongoDB** would be a "collection". - -In the code, a `Bucket` represents the main entrypoint of communication with the database. - -This utility function will: - -* Connect to a **Couchbase** cluster (that might be a single machine). - * Set defaults for timeouts. -* Authenticate in the cluster. -* Get a `Bucket` instance. - * Set defaults for timeouts. -* Return it. - -```Python hl_lines="12-21" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Create Pydantic models - -As **Couchbase** "documents" are actually just "JSON objects", we can model them with Pydantic. - -### `User` model - -First, let's create a `User` model: - -```Python hl_lines="24-28" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -We will use this model in our *path operation function*, so, we don't include in it the `hashed_password`. - -### `UserInDB` model - -Now, let's create a `UserInDB` model. - -This will have the data that is actually stored in the database. - -We don't create it as a subclass of Pydantic's `BaseModel` but as a subclass of our own `User`, because it will have all the attributes in `User` plus a couple more: - -```Python hl_lines="31-33" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -/// note - -Notice that we have a `hashed_password` and a `type` field that will be stored in the database. - -But it is not part of the general `User` model (the one we will return in the *path operation*). - -/// - -## Get the user - -Now create a function that will: - -* Take a username. -* Generate a document ID from it. -* Get the document with that ID. -* Put the contents of the document in a `UserInDB` model. - -By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your *path operation function*, you can more easily reuse it in multiple parts and also add unit tests for it: - -```Python hl_lines="36-42" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -### f-strings - -If you are not familiar with the `f"userprofile::{username}"`, it is a Python "f-string". - -Any variable that is put inside of `{}` in an f-string will be expanded / injected in the string. - -### `dict` unpacking - -If you are not familiar with the `UserInDB(**result.value)`, it is using `dict` "unpacking". - -It will take the `dict` at `result.value`, and take each of its keys and values and pass them as key-values to `UserInDB` as keyword arguments. - -So, if the `dict` contains: - -```Python -{ - "username": "johndoe", - "hashed_password": "some_hash", -} -``` - -It will be passed to `UserInDB` as: - -```Python -UserInDB(username="johndoe", hashed_password="some_hash") -``` - -## Create your **FastAPI** code - -### Create the `FastAPI` app - -```Python hl_lines="46" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -### Create the *path operation function* - -As our code is calling Couchbase and we are not using the experimental Python await support, we should declare our function with normal `def` instead of `async def`. - -Also, Couchbase recommends not using a single `Bucket` object in multiple "threads", so, we can just get the bucket directly and pass it to our utility functions: - -```Python hl_lines="49-53" -{!../../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Recap - -You can integrate any third party NoSQL database, just using their standard packages. - -The same applies to any other external tool, system or API. diff --git a/docs/en/docs/how-to/separate-openapi-schemas.md b/docs/en/docs/how-to/separate-openapi-schemas.md index 0ab5b13371..3c78a56d36 100644 --- a/docs/en/docs/how-to/separate-openapi-schemas.md +++ b/docs/en/docs/how-to/separate-openapi-schemas.md @@ -1,4 +1,4 @@ -# Separate OpenAPI Schemas for Input and Output or Not +# Separate OpenAPI Schemas for Input and Output or Not { #separate-openapi-schemas-for-input-and-output-or-not } When using **Pydantic v2**, the generated OpenAPI is a bit more exact and **correct** than before. 😎 @@ -6,131 +6,21 @@ In fact, in some cases, it will even have **two JSON Schemas** in OpenAPI for th Let's see how that works and how to change it if you need to do that. -## Pydantic Models for Input and Output +## Pydantic Models for Input and Output { #pydantic-models-for-input-and-output } Let's say you have a Pydantic model with default values, like this one: -//// tab | Python 3.10+ +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} -```Python hl_lines="7" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-7]!} - -# Code below omitted 👇 -``` - -
-👀 Full file preview - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} -``` - -
- -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-9]!} - -# Code below omitted 👇 -``` - -
-👀 Full file preview - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} -``` - -
- -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-9]!} - -# Code below omitted 👇 -``` - -
-👀 Full file preview - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} -``` - -
- -//// - -### Model for Input +### Model for Input { #model-for-input } If you use this model as an input like here: -//// tab | Python 3.10+ - -```Python hl_lines="14" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-15]!} - -# Code below omitted 👇 -``` - -
-👀 Full file preview - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} -``` - -
- -//// - -//// tab | Python 3.9+ - -```Python hl_lines="16" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-17]!} - -# Code below omitted 👇 -``` - -
-👀 Full file preview - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} -``` - -
- -//// - -//// tab | Python 3.8+ - -```Python hl_lines="16" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-17]!} - -# Code below omitted 👇 -``` - -
-👀 Full file preview - -```Python -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} -``` - -
- -//// +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} ...then the `description` field will **not be required**. Because it has a default value of `None`. -### Input Model in Docs +### Input Model in Docs { #input-model-in-docs } You can confirm that in the docs, the `description` field doesn't have a **red asterisk**, it's not marked as required: @@ -138,37 +28,15 @@ You can confirm that in the docs, the `description` field doesn't have a **red a
-### Model for Output +### Model for Output { #model-for-output } But if you use the same model as an output, like here: -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="21" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/separate_openapi_schemas/tutorial001.py!} -``` - -//// +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} ...then because `description` has a default value, if you **don't return anything** for that field, it will still have that **default value**. -### Model for Output Response Data +### Model for Output Response Data { #model-for-output-response-data } If you interact with the docs and check the response, even though the code didn't add anything in one of the `description` fields, the JSON response contains the default value (`null`): @@ -187,7 +55,7 @@ Because of that, the JSON Schema for a model can be different depending on if it * for **input** the `description` will **not be required** * for **output** it will be **required** (and possibly `None`, or in JSON terms, `null`) -### Model for Output in Docs +### Model for Output in Docs { #model-for-output-in-docs } You can check the output model in the docs too, **both** `name` and `description` are marked as **required** with a **red asterisk**: @@ -195,7 +63,7 @@ You can check the output model in the docs too, **both** `name` and `description
-### Model for Input and Output in Docs +### Model for Input and Output in Docs { #model-for-input-and-output-in-docs } And if you check all the available Schemas (JSON Schemas) in OpenAPI, you will see that there are two, one `Item-Input` and one `Item-Output`. @@ -209,7 +77,7 @@ But for `Item-Output`, `description` is **required**, it has a red asterisk. With this feature from **Pydantic v2**, your API documentation is more **precise**, and if you have autogenerated clients and SDKs, they will be more precise too, with a better **developer experience** and consistency. 🎉 -## Do not Separate Schemas +## Do not Separate Schemas { #do-not-separate-schemas } Now, there are some cases where you might want to have the **same schema for input and output**. @@ -223,31 +91,9 @@ Support for `separate_input_output_schemas` was added in FastAPI `0.102.0`. 🤓 /// -//// tab | Python 3.10+ +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} -```Python hl_lines="10" -{!> ../../../docs_src/separate_openapi_schemas/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/separate_openapi_schemas/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12" -{!> ../../../docs_src/separate_openapi_schemas/tutorial002.py!} -``` - -//// - -### Same Schema for Input and Output Models in Docs +### Same Schema for Input and Output Models in Docs { #same-schema-for-input-and-output-models-in-docs } And now there will be one single schema for input and output for the model, only `Item`, and it will have `description` as **not required**: diff --git a/docs/en/docs/how-to/sql-databases-peewee.md b/docs/en/docs/how-to/sql-databases-peewee.md deleted file mode 100644 index e411c200c6..0000000000 --- a/docs/en/docs/how-to/sql-databases-peewee.md +++ /dev/null @@ -1,594 +0,0 @@ -# ~~SQL (Relational) Databases with Peewee~~ (deprecated) - -/// warning | "Deprecated" - -This tutorial is deprecated and will be removed in a future version. - -/// - -/// warning - -If you are just starting, the tutorial [SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank} that uses SQLAlchemy should be enough. - -Feel free to skip this. - -Peewee is not recommended with FastAPI as it doesn't play well with anything async Python. There are several better alternatives. - -/// - -/// info - -These docs assume Pydantic v1. - -Because Pewee doesn't play well with anything async and there are better alternatives, I won't update these docs for Pydantic v2, they are kept for now only for historical purposes. - -The examples here are no longer tested in CI (as they were before). - -/// - -If you are starting a project from scratch, you are probably better off with SQLAlchemy ORM ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}), or any other async ORM. - -If you already have a code base that uses Peewee ORM, you can check here how to use it with **FastAPI**. - -/// warning | "Python 3.7+ required" - -You will need Python 3.7 or above to safely use Peewee with FastAPI. - -/// - -## Peewee for async - -Peewee was not designed for async frameworks, or with them in mind. - -Peewee has some heavy assumptions about its defaults and about how it should be used. - -If you are developing an application with an older non-async framework, and can work with all its defaults, **it can be a great tool**. - -But if you need to change some of the defaults, support more than one predefined database, work with an async framework (like FastAPI), etc, you will need to add quite some complex extra code to override those defaults. - -Nevertheless, it's possible to do it, and here you'll see exactly what code you have to add to be able to use Peewee with FastAPI. - -/// note | "Technical Details" - -You can read more about Peewee's stand about async in Python in the docs, an issue, a PR. - -/// - -## The same app - -We are going to create the same application as in the SQLAlchemy tutorial ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}). - -Most of the code is actually the same. - -So, we are going to focus only on the differences. - -## File structure - -Let's say you have a directory named `my_super_project` that contains a sub-directory called `sql_app` with a structure like this: - -``` -. -└── sql_app - ├── __init__.py - ├── crud.py - ├── database.py - ├── main.py - └── schemas.py -``` - -This is almost the same structure as we had for the SQLAlchemy tutorial. - -Now let's see what each file/module does. - -## Create the Peewee parts - -Let's refer to the file `sql_app/database.py`. - -### The standard Peewee code - -Let's first check all the normal Peewee code, create a Peewee database: - -```Python hl_lines="3 5 22" -{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -/// tip - -Keep in mind that if you wanted to use a different database, like PostgreSQL, you couldn't just change the string. You would need to use a different Peewee database class. - -/// - -#### Note - -The argument: - -```Python -check_same_thread=False -``` - -is equivalent to the one in the SQLAlchemy tutorial: - -```Python -connect_args={"check_same_thread": False} -``` - -...it is needed only for `SQLite`. - -/// info | "Technical Details" - -Exactly the same technical details as in [SQL (Relational) Databases](../tutorial/sql-databases.md#note){.internal-link target=_blank} apply. - -/// - -### Make Peewee async-compatible `PeeweeConnectionState` - -The main issue with Peewee and FastAPI is that Peewee relies heavily on Python's `threading.local`, and it doesn't have a direct way to override it or let you handle connections/sessions directly (as is done in the SQLAlchemy tutorial). - -And `threading.local` is not compatible with the new async features of modern Python. - -/// note | "Technical Details" - -`threading.local` is used to have a "magic" variable that has a different value for each thread. - -This was useful in older frameworks designed to have one single thread per request, no more, no less. - -Using this, each request would have its own database connection/session, which is the actual final goal. - -But FastAPI, using the new async features, could handle more than one request on the same thread. And at the same time, for a single request, it could run multiple things in different threads (in a threadpool), depending on if you use `async def` or normal `def`. This is what gives all the performance improvements to FastAPI. - -/// - -But Python 3.7 and above provide a more advanced alternative to `threading.local`, that can also be used in the places where `threading.local` would be used, but is compatible with the new async features. - -We are going to use that. It's called `contextvars`. - -We are going to override the internal parts of Peewee that use `threading.local` and replace them with `contextvars`, with the corresponding updates. - -This might seem a bit complex (and it actually is), you don't really need to completely understand how it works to use it. - -We will create a `PeeweeConnectionState`: - -```Python hl_lines="10-19" -{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -This class inherits from a special internal class used by Peewee. - -It has all the logic to make Peewee use `contextvars` instead of `threading.local`. - -`contextvars` works a bit differently than `threading.local`. But the rest of Peewee's internal code assumes that this class works with `threading.local`. - -So, we need to do some extra tricks to make it work as if it was just using `threading.local`. The `__init__`, `__setattr__`, and `__getattr__` implement all the required tricks for this to be used by Peewee without knowing that it is now compatible with FastAPI. - -/// tip - -This will just make Peewee behave correctly when used with FastAPI. Not randomly opening or closing connections that are being used, creating errors, etc. - -But it doesn't give Peewee async super-powers. You should still use normal `def` functions and not `async def`. - -/// - -### Use the custom `PeeweeConnectionState` class - -Now, overwrite the `._state` internal attribute in the Peewee database `db` object using the new `PeeweeConnectionState`: - -```Python hl_lines="24" -{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -/// tip - -Make sure you overwrite `db._state` *after* creating `db`. - -/// - -/// tip - -You would do the same for any other Peewee database, including `PostgresqlDatabase`, `MySQLDatabase`, etc. - -/// - -## Create the database models - -Let's now see the file `sql_app/models.py`. - -### Create Peewee models for our data - -Now create the Peewee models (classes) for `User` and `Item`. - -This is the same you would do if you followed the Peewee tutorial and updated the models to have the same data as in the SQLAlchemy tutorial. - -/// tip - -Peewee also uses the term "**model**" to refer to these classes and instances that interact with the database. - -But Pydantic also uses the term "**model**" to refer to something different, the data validation, conversion, and documentation classes and instances. - -/// - -Import `db` from `database` (the file `database.py` from above) and use it here. - -```Python hl_lines="3 6-12 15-21" -{!../../../docs_src/sql_databases_peewee/sql_app/models.py!} -``` - -/// tip - -Peewee creates several magic attributes. - -It will automatically add an `id` attribute as an integer to be the primary key. - -It will chose the name of the tables based on the class names. - -For the `Item`, it will create an attribute `owner_id` with the integer ID of the `User`. But we don't declare it anywhere. - -/// - -## Create the Pydantic models - -Now let's check the file `sql_app/schemas.py`. - -/// tip - -To avoid confusion between the Peewee *models* and the Pydantic *models*, we will have the file `models.py` with the Peewee models, and the file `schemas.py` with the Pydantic models. - -These Pydantic models define more or less a "schema" (a valid data shape). - -So this will help us avoiding confusion while using both. - -/// - -### Create the Pydantic *models* / schemas - -Create all the same Pydantic models as in the SQLAlchemy tutorial: - -```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 - -Here we are creating the models with an `id`. - -We didn't explicitly specify an `id` attribute in the Peewee models, but Peewee adds one automatically. - -We are also adding the magic `owner_id` attribute to `Item`. - -/// - -### Create a `PeeweeGetterDict` for the Pydantic *models* / schemas - -When you access a relationship in a Peewee object, like in `some_user.items`, Peewee doesn't provide a `list` of `Item`. - -It provides a special custom object of class `ModelSelect`. - -It's possible to create a `list` of its items with `list(some_user.items)`. - -But the object itself is not a `list`. And it's also not an actual Python generator. Because of this, Pydantic doesn't know by default how to convert it to a `list` of Pydantic *models* / schemas. - -But recent versions of Pydantic allow providing a custom class that inherits from `pydantic.utils.GetterDict`, to provide the functionality used when using the `orm_mode = True` to retrieve the values for ORM model attributes. - -We are going to create a custom `PeeweeGetterDict` class and use it in all the same Pydantic *models* / schemas that use `orm_mode`: - -```Python hl_lines="3 8-13 31 49" -{!../../../docs_src/sql_databases_peewee/sql_app/schemas.py!} -``` - -Here we are checking if the attribute that is being accessed (e.g. `.items` in `some_user.items`) is an instance of `peewee.ModelSelect`. - -And if that's the case, just return a `list` with it. - -And then we use it in the Pydantic *models* / schemas that use `orm_mode = True`, with the configuration variable `getter_dict = PeeweeGetterDict`. - -/// tip - -We only need to create one `PeeweeGetterDict` class, and we can use it in all the Pydantic *models* / schemas. - -/// - -## CRUD utils - -Now let's see the file `sql_app/crud.py`. - -### Create all the CRUD utils - -Create all the same CRUD utils as in the SQLAlchemy tutorial, all the code is very similar: - -```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!} -``` - -There are some differences with the code for the SQLAlchemy tutorial. - -We don't pass a `db` attribute around. Instead we use the models directly. This is because the `db` object is a global object, that includes all the connection logic. That's why we had to do all the `contextvars` updates above. - -Aso, when returning several objects, like in `get_users`, we directly call `list`, like in: - -```Python -list(models.User.select()) -``` - -This is for the same reason that we had to create a custom `PeeweeGetterDict`. But by returning something that is already a `list` instead of the `peewee.ModelSelect` the `response_model` in the *path operation* with `List[models.User]` (that we'll see later) will work correctly. - -## Main **FastAPI** app - -And now in the file `sql_app/main.py` let's integrate and use all the other parts we created before. - -### Create the database tables - -In a very simplistic way create the database tables: - -```Python hl_lines="9-11" -{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### Create a dependency - -Create a dependency that will connect the database right at the beginning of a request and disconnect it at the end: - -```Python hl_lines="23-29" -{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -Here we have an empty `yield` because we are actually not using the database object directly. - -It is connecting to the database and storing the connection data in an internal variable that is independent for each request (using the `contextvars` tricks from above). - -Because the database connection is potentially I/O blocking, this dependency is created with a normal `def` function. - -And then, in each *path operation function* that needs to access the database we add it as a dependency. - -But we are not using the value given by this dependency (it actually doesn't give any value, as it has an empty `yield`). So, we don't add it to the *path operation function* but to the *path operation decorator* in the `dependencies` parameter: - -```Python hl_lines="32 40 47 59 65 72" -{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### Context variable sub-dependency - -For all the `contextvars` parts to work, we need to make sure we have an independent value in the `ContextVar` for each request that uses the database, and that value will be used as the database state (connection, transactions, etc) for the whole request. - -For that, we need to create another `async` dependency `reset_db_state()` that is used as a sub-dependency in `get_db()`. It will set the value for the context variable (with just a default `dict`) that will be used as the database state for the whole request. And then the dependency `get_db()` will store in it the database state (connection, transactions, etc). - -```Python hl_lines="18-20" -{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -For the **next request**, as we will reset that context variable again in the `async` dependency `reset_db_state()` and then create a new connection in the `get_db()` dependency, that new request will have its own database state (connection, transactions, etc). - -/// tip - -As FastAPI is an async framework, one request could start being processed, and before finishing, another request could be received and start processing as well, and it all could be processed in the same thread. - -But context variables are aware of these async features, so, a Peewee database state set in the `async` dependency `reset_db_state()` will keep its own data throughout the entire request. - -And at the same time, the other concurrent request will have its own database state that will be independent for the whole request. - -/// - -#### Peewee Proxy - -If you are using a Peewee Proxy, the actual database is at `db.obj`. - -So, you would reset it with: - -```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() -``` - -### Create your **FastAPI** *path operations* - -Now, finally, here's the standard **FastAPI** *path operations* code. - -```Python hl_lines="32-37 40-43 46-53 56-62 65-68 71-79" -{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### About `def` vs `async def` - -The same as with SQLAlchemy, we are not doing something like: - -```Python -user = await models.User.select().first() -``` - -...but instead we are using: - -```Python -user = models.User.select().first() -``` - -So, again, we should declare the *path operation functions* and the dependency without `async def`, just with a normal `def`, as: - -```Python hl_lines="2" -# Something goes here -def read_users(skip: int = 0, limit: int = 100): - # Something goes here -``` - -## Testing Peewee with async - -This example includes an extra *path operation* that simulates a long processing request with `time.sleep(sleep_time)`. - -It will have the database connection open at the beginning and will just wait some seconds before replying back. And each new request will wait one second less. - -This will easily let you test that your app with Peewee and FastAPI is behaving correctly with all the stuff about threads. - -If you want to check how Peewee would break your app if used without modification, go the `sql_app/database.py` file and comment the line: - -```Python -# db._state = PeeweeConnectionState() -``` - -And in the file `sql_app/main.py` file, comment the body of the `async` dependency `reset_db_state()` and replace it with a `pass`: - -```Python -async def reset_db_state(): -# database.db._state._state.set(db_state_default.copy()) -# database.db._state.reset() - pass -``` - -Then run your app with Uvicorn: - -
- -```console -$ uvicorn sql_app.main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -Open your browser at http://127.0.0.1:8000/docs and create a couple of users. - -Then open 10 tabs at http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get at the same time. - -Go to the *path operation* "Get `/slowusers/`" in all of the tabs. Use the "Try it out" button and execute the request in each tab, one right after the other. - -The tabs will wait for a bit and then some of them will show `Internal Server Error`. - -### What happens - -The first tab will make your app create a connection to the database and wait for some seconds before replying back and closing the database connection. - -Then, for the request in the next tab, your app will wait for one second less, and so on. - -This means that it will end up finishing some of the last tabs' requests earlier than some of the previous ones. - -Then one the last requests that wait less seconds will try to open a database connection, but as one of those previous requests for the other tabs will probably be handled in the same thread as the first one, it will have the same database connection that is already open, and Peewee will throw an error and you will see it in the terminal, and the response will have an `Internal Server Error`. - -This will probably happen for more than one of those tabs. - -If you had multiple clients talking to your app exactly at the same time, this is what could happen. - -And as your app starts to handle more and more clients at the same time, the waiting time in a single request needs to be shorter and shorter to trigger the error. - -### Fix Peewee with FastAPI - -Now go back to the file `sql_app/database.py`, and uncomment the line: - -```Python -db._state = PeeweeConnectionState() -``` - -And in the file `sql_app/main.py` file, uncomment the body of the `async` dependency `reset_db_state()`: - -```Python -async def reset_db_state(): - database.db._state._state.set(db_state_default.copy()) - database.db._state.reset() -``` - -Terminate your running app and start it again. - -Repeat the same process with the 10 tabs. This time all of them will wait and you will get all the results without errors. - -...You fixed it! - -## Review all the files - - Remember you should have a directory named `my_super_project` (or however you want) that contains a sub-directory called `sql_app`. - -`sql_app` should have the following files: - -* `sql_app/__init__.py`: is an empty file. - -* `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!} -``` - -## Technical Details - -/// warning - -These are very technical details that you probably don't need. - -/// - -### The problem - -Peewee uses `threading.local` by default to store it's database "state" data (connection, transactions, etc). - -`threading.local` creates a value exclusive to the current thread, but an async framework would run all the code (e.g. for each request) in the same thread, and possibly not in order. - -On top of that, an async framework could run some sync code in a threadpool (using `asyncio.run_in_executor`), but belonging to the same request. - -This means that, with Peewee's current implementation, multiple tasks could be using the same `threading.local` variable and end up sharing the same connection and data (that they shouldn't), and at the same time, if they execute sync I/O-blocking code in a threadpool (as with normal `def` functions in FastAPI, in *path operations* and dependencies), that code won't have access to the database state variables, even while it's part of the same request and it should be able to get access to the same database state. - -### Context variables - -Python 3.7 has `contextvars` that can create a local variable very similar to `threading.local`, but also supporting these async features. - -There are several things to keep in mind. - -The `ContextVar` has to be created at the top of the module, like: - -```Python -some_var = ContextVar("some_var", default="default value") -``` - -To set a value used in the current "context" (e.g. for the current request) use: - -```Python -some_var.set("new value") -``` - -To get a value anywhere inside of the context (e.g. in any part handling the current request) use: - -```Python -some_var.get() -``` - -### Set context variables in the `async` dependency `reset_db_state()` - -If some part of the async code sets the value with `some_var.set("updated in function")` (e.g. like the `async` dependency), the rest of the code in it and the code that goes after (including code inside of `async` functions called with `await`) will see that new value. - -So, in our case, if we set the Peewee state variable (with a default `dict`) in the `async` dependency, all the rest of the internal code in our app will see this value and will be able to reuse it for the whole request. - -And the context variable would be set again for the next request, even if they are concurrent. - -### Set database state in the dependency `get_db()` - -As `get_db()` is a normal `def` function, **FastAPI** will make it run in a threadpool, with a *copy* of the "context", holding the same value for the context variable (the `dict` with the reset database state). Then it can add database state to that `dict`, like the connection, etc. - -But if the value of the context variable (the default `dict`) was set in that normal `def` function, it would create a new value that would stay only in that thread of the threadpool, and the rest of the code (like the *path operation functions*) wouldn't have access to it. In `get_db()` we can only set values in the `dict`, but not the entire `dict` itself. - -So, we need to have the `async` dependency `reset_db_state()` to set the `dict` in the context variable. That way, all the code has access to the same `dict` for the database state for a single request. - -### Connect and disconnect in the dependency `get_db()` - -Then the next question would be, why not just connect and disconnect the database in the `async` dependency itself, instead of in `get_db()`? - -The `async` dependency has to be `async` for the context variable to be preserved for the rest of the request, but creating and closing the database connection is potentially blocking, so it could degrade performance if it was there. - -So we also need the normal `def` dependency `get_db()`. diff --git a/docs/en/docs/how-to/testing-database.md b/docs/en/docs/how-to/testing-database.md new file mode 100644 index 0000000000..400fdcfc64 --- /dev/null +++ b/docs/en/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Testing a Database { #testing-a-database } + +You can study about databases, SQL, and SQLModel in the SQLModel docs. 🤓 + +There's a mini tutorial on using SQLModel with FastAPI. ✨ + +That tutorial includes a section about testing SQL databases. 😎 diff --git a/docs/en/docs/img/deployment/concepts/process-ram.drawio b/docs/en/docs/img/deployment/concepts/process-ram.drawio deleted file mode 100644 index b29c8a3424..0000000000 --- a/docs/en/docs/img/deployment/concepts/process-ram.drawio +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/concepts/process-ram.drawio.svg b/docs/en/docs/img/deployment/concepts/process-ram.drawio.svg new file mode 100644 index 0000000000..a6a5c81d03 --- /dev/null +++ b/docs/en/docs/img/deployment/concepts/process-ram.drawio.svg @@ -0,0 +1,297 @@ + + + + + + + + + + + + + +
+
+
+ + + Server + + +
+
+
+
+ + Server + +
+
+
+ + + + + + + + + + +
+
+
+ + + RAM + +
+
+
+
+
+
+ + RAM + +
+
+
+ + + + + + + + + + +
+
+
+ + + CPU + +
+
+
+
+
+
+ + CPU + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + Process + + + + Manager + + +
+
+
+
+ + Process Manager + +
+
+
+ + + + + + + + + + +
+
+
+ + + Worker Process + + +
+
+
+
+ + Worker Process + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + Worker Process + + +
+
+
+
+ + Worker Process + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + Another Process + + +
+
+
+
+ + Another Process + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + 1 GB + +
+
+
+
+ + 1 GB + +
+
+
+ + + + + + + +
+
+
+ + 1 GB + +
+
+
+
+ + 1 GB + +
+
+
+ + + + + + +
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/concepts/process-ram.svg b/docs/en/docs/img/deployment/concepts/process-ram.svg deleted file mode 100644 index c1bf0d5890..0000000000 --- a/docs/en/docs/img/deployment/concepts/process-ram.svg +++ /dev/null @@ -1,59 +0,0 @@ -
Server
Server
RAM
RAM -
CPU
CPU -
Process Manager
Process Manager
Worker Process
Worker Process
Worker Process
Worker Process
Another Process
Another Process
1 GB
1 GB
1 GB
1 GB
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https.drawio b/docs/en/docs/img/deployment/https/https.drawio deleted file mode 100644 index c4c8a36281..0000000000 --- a/docs/en/docs/img/deployment/https/https.drawio +++ /dev/null @@ -1,277 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https.drawio.svg b/docs/en/docs/img/deployment/https/https.drawio.svg new file mode 100644 index 0000000000..c2a65b69ff --- /dev/null +++ b/docs/en/docs/img/deployment/https/https.drawio.svg @@ -0,0 +1,907 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Termination Proxy + +
+
+
+
+
+
+ + TLS Termination Proxy + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + Cert Renovation Program + +
+
+
+
+ + Cert Renovation Program + +
+
+
+ + + + + + + + + + + +
+
+
+ + Let's Encrypt + +
+
+
+
+ + Let's Encrypt + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + FastAPI + + + app for: someapp.example.com + + +
+
+
+
+ + FastAPI app for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Another app + + + : another.example.com + + +
+
+
+
+ + Another app: another.example.com + +
+
+
+ + + + + + + +
+
+
+ + + One more app + + + : onemore.example.com + + +
+
+
+
+ + One more app: onemore.example.com + +
+
+
+ + + + + + + +
+
+
+ + + A Database + + +
+
+
+
+ + A Database + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + Plain response from: someapp.example.com + + +
+
+
+
+ + Plain response from: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Encrypted request for: someapp.example.com + + +
+
+
+
+ + Encrypted request for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + + Renew HTTPS cert for: someapp.example.com + + +
+
+
+
+ + Renew HTTPS cert for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + New HTTPS cert for: someapp.example.com + + +
+
+
+
+ + New HTTPS cert for: someapp.example.com + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + +
+
+
+ + + Encrypted response from: someapp.example.com + + +
+
+
+
+ + Encrypted response from: someapp.example.com + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + HTTPS certificates + +
+
+
+
+
+
+ + HTTPS certificates + +
+
+
+ + + + + + + +
+
+
+ + + + someapp.example.com + + +
+
+
+
+
+
+ + someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + + another.example.net + + +
+
+
+
+
+
+ + another.example.net + +
+
+
+ + + + + + + +
+
+
+ + + + onemore.example.org + + +
+
+
+
+
+
+ + onemore.example.org + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + + Decrypted request for: someapp.example.com + + +
+
+
+
+ + Decrypted request for: someapp.example.com + +
+
+
+ + + + + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https.svg b/docs/en/docs/img/deployment/https/https.svg deleted file mode 100644 index 69497518a5..0000000000 --- a/docs/en/docs/img/deployment/https/https.svg +++ /dev/null @@ -1,62 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy -
Cert Renovation Program
Cert Renovation Program
Let's Encrypt
Let's Encrypt
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Another app: another.example.com
Another app: another.example.com
One more app: onemore.example.com
One more app: onemore.example.com
A Database
A Database
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
Renew HTTPS cert for: someapp.example.com
Renew HTTPS cert for: someapp.example.com
New HTTPS cert for: someapp.example.com
New HTTPS cert for: someapp.example.com
TLS Handshake
TLS Handshake
Encrypted response from: someapp.example.com
Encrypted response from: someapp.example.com
HTTPS certificates
HTTPS certificates -
someapp.example.com
someapp.example.com -
another.example.net
another.example.net -
onemore.example.org
onemore.example.org -
IP:
123.124.125.126
IP:...
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https01.drawio b/docs/en/docs/img/deployment/https/https01.drawio deleted file mode 100644 index 181582f9bd..0000000000 --- a/docs/en/docs/img/deployment/https/https01.drawio +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https01.drawio.svg b/docs/en/docs/img/deployment/https/https01.drawio.svg new file mode 100644 index 0000000000..ea128daf82 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https01.drawio.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https01.svg b/docs/en/docs/img/deployment/https/https01.svg deleted file mode 100644 index 2edbd06239..0000000000 --- a/docs/en/docs/img/deployment/https/https01.svg +++ /dev/null @@ -1,57 +0,0 @@ -
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https02.drawio b/docs/en/docs/img/deployment/https/https02.drawio deleted file mode 100644 index 650c06d1e1..0000000000 --- a/docs/en/docs/img/deployment/https/https02.drawio +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https02.drawio.svg b/docs/en/docs/img/deployment/https/https02.drawio.svg new file mode 100644 index 0000000000..c29d593568 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https02.drawio.svg @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https02.svg b/docs/en/docs/img/deployment/https/https02.svg deleted file mode 100644 index e16b7e94a1..0000000000 --- a/docs/en/docs/img/deployment/https/https02.svg +++ /dev/null @@ -1,57 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
Port 443 (HTTPS)
Port 443 (HTTPS)
IP:
123.124.125.126
IP:...
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https03.drawio b/docs/en/docs/img/deployment/https/https03.drawio deleted file mode 100644 index c178fd3631..0000000000 --- a/docs/en/docs/img/deployment/https/https03.drawio +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https03.drawio.svg b/docs/en/docs/img/deployment/https/https03.drawio.svg new file mode 100644 index 0000000000..6971e4c9c2 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https03.drawio.svg @@ -0,0 +1,715 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Termination Proxy + +
+
+
+
+
+
+ + TLS Termination Proxy + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + HTTPS certificates + +
+
+
+
+
+
+ + HTTPS certificates + +
+
+
+ + + + + + + +
+
+
+ + + + someapp.example.com + + +
+
+
+
+
+
+ + someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + + another.example.net + + +
+
+
+
+
+
+ + another.example.net + +
+
+
+ + + + + + + +
+
+
+ + + + onemore.example.org + + +
+
+
+
+
+
+ + onemore.example.org + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https03.svg b/docs/en/docs/img/deployment/https/https03.svg deleted file mode 100644 index 2badd1c7d2..0000000000 --- a/docs/en/docs/img/deployment/https/https03.svg +++ /dev/null @@ -1,62 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy -
Port 443 (HTTPS)
Port 443 (HTTPS)
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates -
someapp.example.com
someapp.example.com -
another.example.net
another.example.net -
onemore.example.org
onemore.example.org -
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https04.drawio b/docs/en/docs/img/deployment/https/https04.drawio deleted file mode 100644 index 78a6e919a0..0000000000 --- a/docs/en/docs/img/deployment/https/https04.drawio +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https04.drawio.svg b/docs/en/docs/img/deployment/https/https04.drawio.svg new file mode 100644 index 0000000000..7e32bcdfe3 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https04.drawio.svg @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Termination Proxy + +
+
+
+
+
+
+ + TLS Termination Proxy + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Encrypted request for: someapp.example.com + + +
+
+
+
+ + Encrypted request for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + HTTPS certificates + +
+
+
+
+
+
+ + HTTPS certificates + +
+
+
+ + + + + + + +
+
+
+ + + + someapp.example.com + + +
+
+
+
+
+
+ + someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + + another.example.net + + +
+
+
+
+
+
+ + another.example.net + +
+
+
+ + + + + + + +
+
+
+ + + + onemore.example.org + + +
+
+
+
+
+
+ + onemore.example.org + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https04.svg b/docs/en/docs/img/deployment/https/https04.svg deleted file mode 100644 index 4513ac76b5..0000000000 --- a/docs/en/docs/img/deployment/https/https04.svg +++ /dev/null @@ -1,62 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy -
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates -
someapp.example.com
someapp.example.com -
another.example.net
another.example.net -
onemore.example.org
onemore.example.org -
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https05.drawio b/docs/en/docs/img/deployment/https/https05.drawio deleted file mode 100644 index 236ecd841f..0000000000 --- a/docs/en/docs/img/deployment/https/https05.drawio +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https05.drawio.svg b/docs/en/docs/img/deployment/https/https05.drawio.svg new file mode 100644 index 0000000000..fed2fad169 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https05.drawio.svg @@ -0,0 +1,641 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Termination Proxy + +
+
+
+
+
+
+ + TLS Termination Proxy + +
+
+
+ + + + + + + +
+
+
+ + + FastAPI + + + app for: someapp.example.com + + +
+
+
+
+ + FastAPI app for: someapp.example.com + +
+
+
+ + + + + + + + + + + +
+
+
+ + + Decrypted request for: someapp.example.com + + +
+
+
+
+ + Decrypted request for: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Encrypted request for: someapp.example.com + + +
+
+
+
+ + Encrypted request for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + HTTPS certificates + +
+
+
+
+
+
+ + HTTPS certificates + +
+
+
+ + + + + + + +
+
+
+ + + + someapp.example.com + + +
+
+
+
+
+
+ + someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + + another.example.net + + +
+
+
+
+
+
+ + another.example.net + +
+
+
+ + + + + + + +
+
+
+ + + + onemore.example.org + + +
+
+
+
+
+
+ + onemore.example.org + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https05.svg b/docs/en/docs/img/deployment/https/https05.svg deleted file mode 100644 index ddcd2760a8..0000000000 --- a/docs/en/docs/img/deployment/https/https05.svg +++ /dev/null @@ -1,62 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy -
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates -
someapp.example.com
someapp.example.com -
another.example.net
another.example.net -
onemore.example.org
onemore.example.org -
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https06.drawio b/docs/en/docs/img/deployment/https/https06.drawio deleted file mode 100644 index 9dec131846..0000000000 --- a/docs/en/docs/img/deployment/https/https06.drawio +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https06.drawio.svg b/docs/en/docs/img/deployment/https/https06.drawio.svg new file mode 100644 index 0000000000..e0bd9bc6e3 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https06.drawio.svg @@ -0,0 +1,673 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Termination Proxy + +
+
+
+
+
+
+ + TLS Termination Proxy + +
+
+
+ + + + + + + + + + + +
+
+
+ + + FastAPI + + + app for: someapp.example.com + + +
+
+
+
+ + FastAPI app for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Plain response from: someapp.example.com + + +
+
+
+
+ + Plain response from: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Decrypted request for: someapp.example.com + + +
+
+
+
+ + Decrypted request for: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Encrypted request for: someapp.example.com + + +
+
+
+
+ + Encrypted request for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + HTTPS certificates + +
+
+
+
+
+
+ + HTTPS certificates + +
+
+
+ + + + + + + +
+
+
+ + + + someapp.example.com + + +
+
+
+
+
+
+ + someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + + another.example.net + + +
+
+
+
+
+
+ + another.example.net + +
+
+
+ + + + + + + +
+
+
+ + + + onemore.example.org + + +
+
+
+
+
+
+ + onemore.example.org + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https06.svg b/docs/en/docs/img/deployment/https/https06.svg deleted file mode 100644 index 3695de40c7..0000000000 --- a/docs/en/docs/img/deployment/https/https06.svg +++ /dev/null @@ -1,62 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy -
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates -
someapp.example.com
someapp.example.com -
another.example.net
another.example.net -
onemore.example.org
onemore.example.org -
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https07.drawio b/docs/en/docs/img/deployment/https/https07.drawio deleted file mode 100644 index aa8f4d6bed..0000000000 --- a/docs/en/docs/img/deployment/https/https07.drawio +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https07.drawio.svg b/docs/en/docs/img/deployment/https/https07.drawio.svg new file mode 100644 index 0000000000..b74b338078 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https07.drawio.svg @@ -0,0 +1,540 @@ + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Termination Proxy + +
+
+
+
+
+
+ + TLS Termination Proxy + +
+
+
+ + + + + + + + + + + +
+
+
+ + + FastAPI + + + app for: someapp.example.com + + +
+
+
+
+ + FastAPI app for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Plain response from: someapp.example.com + + +
+
+
+
+ + Plain response from: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Decrypted request for: someapp.example.com + + +
+
+
+
+ + Decrypted request for: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Encrypted request for: someapp.example.com + + +
+
+
+
+ + Encrypted request for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + +
+
+
+ + + Encrypted response from: someapp.example.com + + +
+
+
+
+ + Encrypted response from: someapp.example.com + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + HTTPS certificates + +
+
+
+
+
+
+ + HTTPS certificates + +
+
+
+ + + + + + + +
+
+
+ + + + someapp.example.com + + +
+
+
+
+
+
+ + someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + + another.example.net + + +
+
+
+
+
+
+ + another.example.net + +
+
+
+ + + + + + + +
+
+
+ + + + onemore.example.org + + +
+
+
+
+
+
+ + onemore.example.org + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https07.svg b/docs/en/docs/img/deployment/https/https07.svg deleted file mode 100644 index 551354cef8..0000000000 --- a/docs/en/docs/img/deployment/https/https07.svg +++ /dev/null @@ -1,62 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy -
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
Encrypted response from: someapp.example.com
Encrypted response from: someapp.example.com
HTTPS certificates
HTTPS certificates -
someapp.example.com
someapp.example.com -
another.example.net
another.example.net -
onemore.example.org
onemore.example.org -
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/deployment/https/https08.drawio b/docs/en/docs/img/deployment/https/https08.drawio deleted file mode 100644 index 794b192dfe..0000000000 --- a/docs/en/docs/img/deployment/https/https08.drawio +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/deployment/https/https08.drawio.svg b/docs/en/docs/img/deployment/https/https08.drawio.svg new file mode 100644 index 0000000000..8fc0b31ecb --- /dev/null +++ b/docs/en/docs/img/deployment/https/https08.drawio.svg @@ -0,0 +1,625 @@ + + + + + + + + + + + + + + + + + +
+
+
+ + + Server(s) + + +
+
+
+
+ + Server(s) + +
+
+
+ + + + + + + + + + + +
+
+
+ + DNS Servers + +
+
+
+
+ + DNS Servers + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Termination Proxy + +
+
+
+
+
+
+ + TLS Termination Proxy + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + FastAPI + + + app for: someapp.example.com + + +
+
+
+
+ + FastAPI app for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Another app + + + : another.example.com + + +
+
+
+
+ + Another app: another.example.com + +
+
+
+ + + + + + + +
+
+
+ + + One more app + + + : onemore.example.com + + +
+
+
+
+ + One more app: onemore.example.com + +
+
+
+ + + + + + + +
+
+
+ + + A Database + + +
+
+
+
+ + A Database + +
+
+
+ + + + + + + +
+
+
+ + + Plain response from: someapp.example.com + + +
+
+
+
+ + Plain response from: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Decrypted request for: someapp.example.com + + +
+
+
+
+ + Decrypted request for: someapp.example.com + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Port 443 (HTTPS) + +
+
+
+
+ + Port 443 (HTTPS) + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Encrypted request for: someapp.example.com + + +
+
+
+
+ + Encrypted request for: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + Who is: someapp.example.com + + +
+
+
+
+ + Who is: someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + + + + +
+
+
+ + + TLS Handshake + + +
+
+
+
+ + TLS Handshake + +
+
+
+ + + + + + + + + + +
+
+
+ + + Encrypted response from: someapp.example.com + + +
+
+
+
+ + Encrypted response from: someapp.example.com + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + HTTPS certificates + +
+
+
+
+
+
+ + HTTPS certificates + +
+
+
+ + + + + + + +
+
+
+ + + + someapp.example.com + + +
+
+
+
+
+
+ + someapp.example.com + +
+
+
+ + + + + + + +
+
+
+ + + + another.example.net + + +
+
+
+
+
+
+ + another.example.net + +
+
+
+ + + + + + + +
+
+
+ + + + onemore.example.org + + +
+
+
+
+
+
+ + onemore.example.org + +
+
+
+ + + + + + + +
+
+
+ + + + IP: + +
+ + 123.124.125.126 + +
+
+
+
+
+
+
+ + IP:... + +
+
+
+ + + + + + + +
+
+
+ + https://someapp.example.com + +
+
+
+
+ + https://someapp.example.com + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/deployment/https/https08.svg b/docs/en/docs/img/deployment/https/https08.svg deleted file mode 100644 index 2d4680dcc2..0000000000 --- a/docs/en/docs/img/deployment/https/https08.svg +++ /dev/null @@ -1,62 +0,0 @@ -
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy -
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Another app: another.example.com
Another app: another.example.com
One more app: onemore.example.com
One more app: onemore.example.com
A Database
A Database
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
Encrypted response from: someapp.example.com
Encrypted response from: someapp.example.com
HTTPS certificates
HTTPS certificates -
someapp.example.com
someapp.example.com -
another.example.net
another.example.net -
onemore.example.org
onemore.example.org -
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/sponsors/blockbee-banner.png b/docs/en/docs/img/sponsors/blockbee-banner.png new file mode 100644 index 0000000000..074b360312 Binary files /dev/null and b/docs/en/docs/img/sponsors/blockbee-banner.png differ diff --git a/docs/en/docs/img/sponsors/blockbee.png b/docs/en/docs/img/sponsors/blockbee.png new file mode 100644 index 0000000000..6d2fcf7019 Binary files /dev/null and b/docs/en/docs/img/sponsors/blockbee.png differ diff --git a/docs/en/docs/img/sponsors/coderabbit-banner.png b/docs/en/docs/img/sponsors/coderabbit-banner.png new file mode 100644 index 0000000000..da3bb34820 Binary files /dev/null and b/docs/en/docs/img/sponsors/coderabbit-banner.png differ diff --git a/docs/en/docs/img/sponsors/coderabbit.png b/docs/en/docs/img/sponsors/coderabbit.png new file mode 100644 index 0000000000..1fb74569be Binary files /dev/null and b/docs/en/docs/img/sponsors/coderabbit.png differ diff --git a/docs/en/docs/img/sponsors/dribia.png b/docs/en/docs/img/sponsors/dribia.png new file mode 100644 index 0000000000..f40e140863 Binary files /dev/null and b/docs/en/docs/img/sponsors/dribia.png differ diff --git a/docs/en/docs/img/sponsors/interviewpal.png b/docs/en/docs/img/sponsors/interviewpal.png new file mode 100644 index 0000000000..e40ed01fd4 Binary files /dev/null and b/docs/en/docs/img/sponsors/interviewpal.png differ diff --git a/docs/en/docs/img/sponsors/lambdatest.png b/docs/en/docs/img/sponsors/lambdatest.png new file mode 100644 index 0000000000..674cbcb893 Binary files /dev/null and b/docs/en/docs/img/sponsors/lambdatest.png differ diff --git a/docs/en/docs/img/sponsors/mobbai-banner.png b/docs/en/docs/img/sponsors/mobbai-banner.png new file mode 100644 index 0000000000..1f59294abe Binary files /dev/null and b/docs/en/docs/img/sponsors/mobbai-banner.png differ diff --git a/docs/en/docs/img/sponsors/mobbai.png b/docs/en/docs/img/sponsors/mobbai.png new file mode 100644 index 0000000000..b519fd8857 Binary files /dev/null and b/docs/en/docs/img/sponsors/mobbai.png differ diff --git a/docs/en/docs/img/sponsors/permit.png b/docs/en/docs/img/sponsors/permit.png new file mode 100644 index 0000000000..4f07f22e27 Binary files /dev/null and b/docs/en/docs/img/sponsors/permit.png differ diff --git a/docs/en/docs/img/sponsors/railway-banner.png b/docs/en/docs/img/sponsors/railway-banner.png new file mode 100644 index 0000000000..f6146a7c1f Binary files /dev/null and b/docs/en/docs/img/sponsors/railway-banner.png differ diff --git a/docs/en/docs/img/sponsors/railway.png b/docs/en/docs/img/sponsors/railway.png new file mode 100644 index 0000000000..dc6ccacc4a Binary files /dev/null and b/docs/en/docs/img/sponsors/railway.png differ diff --git a/docs/en/docs/img/sponsors/render-banner.svg b/docs/en/docs/img/sponsors/render-banner.svg new file mode 100644 index 0000000000..b8b1ed2e9b --- /dev/null +++ b/docs/en/docs/img/sponsors/render-banner.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/render.svg b/docs/en/docs/img/sponsors/render.svg new file mode 100644 index 0000000000..4a830482da --- /dev/null +++ b/docs/en/docs/img/sponsors/render.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/requestly.png b/docs/en/docs/img/sponsors/requestly.png new file mode 100644 index 0000000000..a167aa0174 Binary files /dev/null and b/docs/en/docs/img/sponsors/requestly.png differ diff --git a/docs/en/docs/img/sponsors/speakeasy.png b/docs/en/docs/img/sponsors/speakeasy.png index 001b4b4caf..7bb9c3a18e 100644 Binary files a/docs/en/docs/img/sponsors/speakeasy.png and b/docs/en/docs/img/sponsors/speakeasy.png differ diff --git a/docs/en/docs/img/sponsors/subtotal-banner.svg b/docs/en/docs/img/sponsors/subtotal-banner.svg new file mode 100644 index 0000000000..3d6c98dfc6 --- /dev/null +++ b/docs/en/docs/img/sponsors/subtotal-banner.svg @@ -0,0 +1,133 @@ + + + + + sponsorship-banner + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/subtotal.svg b/docs/en/docs/img/sponsors/subtotal.svg new file mode 100644 index 0000000000..b944c1b2c3 --- /dev/null +++ b/docs/en/docs/img/sponsors/subtotal.svg @@ -0,0 +1,31 @@ + + + sponsorship-badge + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/zuplo.png b/docs/en/docs/img/sponsors/zuplo.png index 7a7c168629..6a4ed233e9 100644 Binary files a/docs/en/docs/img/sponsors/zuplo.png and b/docs/en/docs/img/sponsors/zuplo.png differ diff --git a/docs/en/docs/img/tutorial/bigger-applications/package.drawio b/docs/en/docs/img/tutorial/bigger-applications/package.drawio deleted file mode 100644 index cab3de2ca8..0000000000 --- a/docs/en/docs/img/tutorial/bigger-applications/package.drawio +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/en/docs/img/tutorial/bigger-applications/package.drawio.svg b/docs/en/docs/img/tutorial/bigger-applications/package.drawio.svg new file mode 100644 index 0000000000..7e28f4a684 --- /dev/null +++ b/docs/en/docs/img/tutorial/bigger-applications/package.drawio.svg @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + +
+
+
+ + Package app +
+ app/__init__.py +
+
+
+
+
+ + Package app... + +
+
+
+ + + + + + + +
+
+
+ + + Module app.main + +
+ + app/main.py + +
+
+
+
+
+ + Module app.main... + +
+
+
+ + + + + + + +
+
+
+ + + Module app.dependencies + +
+ + app/dependencies.py + +
+
+
+
+
+ + Module app.dependencies... + +
+
+
+ + + + + + + + + + +
+
+
+ + + Subpackage app.internal +
+
+ + app/internal/__init__.py + +
+ +
+
+
+
+
+
+ + Subpackage app.internal... + +
+
+
+ + + + + + + +
+
+
+ + + Module app.internal.admin + +
+ + app/internal/admin.py + +
+
+
+
+
+ + Module app.internal.admin... + +
+
+
+ + + + + + + + + + +
+
+
+ + + Subpackage app.routers +
+ app/routers/__init__.py +
+
+
+
+
+
+
+ + Subpackage app.routers... + +
+
+
+ + + + + + + +
+
+
+ + + Module app.routers.items + +
+ + app/routers/items.py + +
+
+
+
+
+ + Module app.routers.items... + +
+
+
+ + + + + + + +
+
+
+ + + Module app.routers.users + +
+ + app/routers/users.py + +
+
+
+
+
+ + Module app.routers.users... + +
+
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/en/docs/img/tutorial/bigger-applications/package.svg b/docs/en/docs/img/tutorial/bigger-applications/package.svg deleted file mode 100644 index 44da1dc30d..0000000000 --- a/docs/en/docs/img/tutorial/bigger-applications/package.svg +++ /dev/null @@ -1 +0,0 @@ -
Package app
app/__init__.py
Package app...
Module app.main
app/main.py
Module app.main...
Module app.dependencies
app/dependencies.py
Module app.dependencies...
Subpackage app.internal
app/internal/__init__.py
Subpackage app.internal...
Module app.internal.admin
app/internal/admin.py
Module app.internal.admin...
Subpackage app.routers
app/routers/__init__.py
Subpackage app.routers...
Module app.routers.items
app/routers/items.py
Module app.routers.items...
Module app.routers.users
app/routers/users.py
Module app.routers.users...
Viewer does not support full SVG 1.1
diff --git a/docs/en/docs/img/tutorial/body-nested-models/image01.png b/docs/en/docs/img/tutorial/body-nested-models/image01.png index f3644ce792..1f7e07cfef 100644 Binary files a/docs/en/docs/img/tutorial/body-nested-models/image01.png and b/docs/en/docs/img/tutorial/body-nested-models/image01.png differ diff --git a/docs/en/docs/img/tutorial/cookie-param-models/image01.png b/docs/en/docs/img/tutorial/cookie-param-models/image01.png new file mode 100644 index 0000000000..85c370f80a Binary files /dev/null and b/docs/en/docs/img/tutorial/cookie-param-models/image01.png differ diff --git a/docs/en/docs/img/tutorial/header-param-models/image01.png b/docs/en/docs/img/tutorial/header-param-models/image01.png new file mode 100644 index 0000000000..849dea3d8b Binary files /dev/null and b/docs/en/docs/img/tutorial/header-param-models/image01.png differ diff --git a/docs/en/docs/img/tutorial/query-param-models/image01.png b/docs/en/docs/img/tutorial/query-param-models/image01.png new file mode 100644 index 0000000000..e7a61b61fb Binary files /dev/null and b/docs/en/docs/img/tutorial/query-param-models/image01.png differ diff --git a/docs/en/docs/img/tutorial/request-form-models/image01.png b/docs/en/docs/img/tutorial/request-form-models/image01.png new file mode 100644 index 0000000000..3fe32c03d5 Binary files /dev/null and b/docs/en/docs/img/tutorial/request-form-models/image01.png differ diff --git a/docs/en/docs/img/tutorial/sql-databases/image01.png b/docs/en/docs/img/tutorial/sql-databases/image01.png index 8e575abd65..bfcdb57a07 100644 Binary files a/docs/en/docs/img/tutorial/sql-databases/image01.png and b/docs/en/docs/img/tutorial/sql-databases/image01.png differ diff --git a/docs/en/docs/img/tutorial/sql-databases/image02.png b/docs/en/docs/img/tutorial/sql-databases/image02.png index ee59fc9398..7bcad83783 100644 Binary files a/docs/en/docs/img/tutorial/sql-databases/image02.png and b/docs/en/docs/img/tutorial/sql-databases/image02.png differ diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index 3ed3d7bf6c..35c46d15f7 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -1,4 +1,4 @@ -# FastAPI +# FastAPI { #fastapi } - -

- FastAPI -

-

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

-

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

- ---- - -**תיעוד**: https://fastapi.tiangolo.com - -**קוד**: https://github.com/fastapi/fastapi - ---- - -FastAPI היא תשתית רשת מודרנית ומהירה (ביצועים גבוהים) לבניית ממשקי תכנות יישומים (API) עם פייתון 3.6+ בהתבסס על רמזי טיפוסים סטנדרטיים. - -תכונות המפתח הן: - -- **מהירה**: ביצועים גבוהים מאוד, בקנה אחד עם NodeJS ו - Go (תודות ל - Starlette ו - Pydantic). [אחת מתשתיות הפייתון המהירות ביותר](#_14). - -- **מהירה לתכנות**: הגבירו את מהירות פיתוח התכונות החדשות בכ - %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). - -בשימוש FastAPI / Starlette: - -- uvicorn - לשרת שטוען ומגיש את האפליקציה שלכם. -- orjson - דרוש אם ברצונכם להשתמש ב - `ORJSONResponse`. -- ujson - דרוש אם ברצונכם להשתמש ב - `UJSONResponse`. - -תוכלו להתקין את כל אלו באמצעות pip install "fastapi[all]". - -## רשיון - -הפרויקט הזה הוא תחת התנאים של רשיון MIT. diff --git a/docs/he/mkdocs.yml b/docs/he/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/he/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/hu/docs/index.md b/docs/hu/docs/index.md deleted file mode 100644 index 8e326a78b3..0000000000 --- a/docs/hu/docs/index.md +++ /dev/null @@ -1,467 +0,0 @@ -

- FastAPI -

-

- FastAPI keretrendszer, nagy teljesítmény, könnyen tanulható, gyorsan kódolható, productionre kész -

-

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

- ---- - -**Dokumentáció**: https://fastapi.tiangolo.com - -**Forrás kód**: https://github.com/fastapi/fastapi - ---- -A FastAPI egy modern, gyors (nagy teljesítményű), webes keretrendszer API-ok építéséhez Python -al, a Python szabványos típusjelöléseire építve. - - -Kulcs funkciók: - -* **Gyors**: Nagyon nagy teljesítmény, a **NodeJS**-el és a **Go**-val egyenrangú (a Starlettenek és a Pydantic-nek köszönhetően). [Az egyik leggyorsabb Python keretrendszer](#performance). -* **Gyorsan kódolható**: A funkciók fejlesztési sebességét 200-300 százalékkal megnöveli. * -* **Kevesebb hiba**: Körülbelül 40%-al csökkenti az emberi (fejlesztői) hibák számát. * -* **Intuitív**: Kiváló szerkesztő támogatás. Kiegészítés mindenhol. Kevesebb hibakereséssel töltött idő. -* **Egyszerű**: Egyszerű tanulásra és használatra tervezve. Kevesebb dokumentáció olvasással töltött idő. -* **Rövid**: Kód duplikáció minimalizálása. Több funkció minden paraméter deklarálásával. Kevesebb hiba. -* **Robosztus**: Production ready kód. Automatikus interaktív dokumentáció val. -* **Szabvány alapú**: Az API-ok nyílt szabványaira alapuló (és azokkal teljesen kompatibilis): OpenAPI (korábban Swagger néven ismert) és a JSON Schema. - -* Egy production alkalmazásokat építő belső fejlesztői csapat tesztjein alapuló becslés. - -## Szponzorok - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -További szponzorok - -## Vélemények - -"_[...] 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)
- ---- - -"_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**, a CLI-ok FastAPI-ja - - - -Ha egy olyan CLI alkalmazást fejlesztesz amit a parancssorban kell használni webes API helyett, tekintsd meg: **Typer**. - -**Typer** a FastAPI kistestvére. A **CLI-k FastAPI-ja**. ⌨️ 🚀 - -## Követelmények - -A FastAPI óriások vállán áll: - -* Starlette a webes részekhez. -* Pydantic az adat részekhez. - -## Telepítés - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -A production-höz egy ASGI szerverre is szükség lesz, mint például az Uvicorn vagy a Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Példa - -### Hozd létre - -* Hozz létre a `main.py` fájlt a következő tartalommal: - -```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} -``` - -
-Vagy használd az async def-et... - -Ha a kódod `async` / `await`-et, használ `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} -``` - -**Megjegyzés**: - -Ha nem tudod, tekintsd meg a _"Sietsz?"_ szekciót `async` és `await`-ről dokumentációba. - -
- -### Futtasd le - -Indítsd el a szervert a következő paranccsal: - -
- -```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. -``` - -
- -
-A parancsról uvicorn main:app --reload... - -A `uvicorn main:app` parancs a következőre utal: - -* `main`: fájl `main.py` (a Python "modul"). -* `app`: a `main.py`-ban a `app = FastAPI()` sorral létrehozott objektum. -* `--reload`: kód változtatás esetén újra indítja a szervert. Csak fejlesztés közben használandó. - -
- -### Ellenőrizd - -Nyisd meg a böngésződ a következő címen: http://127.0.0.1:8000/items/5?q=somequery. - -A következő JSON választ fogod látni: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -Máris létrehoztál egy API-t ami: - -* HTTP kéréseket fogad a `/` és `/items/{item_id}` _útvonalakon_. -* Mindkét _útvonal_ a `GET` műveletet használja (másik elnevezés: HTTP _metódus_). -* A `/items/{item_id}` _útvonalnak_ van egy _path paramétere_, az `item_id`, aminek `int` típusúnak kell lennie. -* A `/items/{item_id}` _útvonalnak_ még van egy opcionális, `str` típusú _query paramétere_ is, a `q`. - -### Interaktív API dokumentáció - -Most nyisd meg a http://127.0.0.1:8000/docs címet. - -Az automatikus interaktív API dokumentációt fogod látni (amit a Swagger UI-al hozunk létre): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternatív API dokumentáció - -És most menj el a http://127.0.0.1:8000/redoc címre. - -Az alternatív automatikus dokumentációt fogod látni. (lásd ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Példa frissítése - -Módosítsuk a `main.py` fájlt, hogy `PUT` kérések esetén tudjon body-t fogadni. - -Deklaráld a body-t standard Python típusokkal, a Pydantic-nak köszönhetően. - -```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} -``` - -A szerver automatikusan újraindul (mert hozzáadtuk a --reload paramétert a fenti `uvicorn` parancshoz). - -### Interaktív API dokumentáció frissítése - -Most menj el a http://127.0.0.1:8000/docs címre. - -* Az interaktív API dokumentáció automatikusan frissült így már benne van az új body. - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Kattints rá a "Try it out" gombra, ennek segítségével kitöltheted a paramétereket és közvetlen használhatod az API-t: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Ezután kattints az "Execute" gompra, a felhasználói felület kommunikálni fog az API-oddal. Elküldi a paramétereket és a visszakapott választ megmutatja a képernyődön. - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternatív API dokumentáció frissítés - -Most menj el a http://127.0.0.1:8000/redoc címre. - -* Az alternatív dokumentáció szintúgy tükrözni fogja az új kérési paraméter és body-t. - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Összefoglalás - -Összegzésül, deklarálod **egyszer** a paraméterek, body, stb típusát funkciós paraméterekként. - -Ezt standard modern Python típusokkal csinálod. - -Nem kell új szintaxist, vagy specifikus könyvtár mert metódósait, stb. megtanulnod. - -Csak standard **Python**. - -Például egy `int`-nek: - -```Python -item_id: int -``` - -Egy komplexebb `Item` modellnek: - -```Python -item: Item -``` - -... És csupán egy deklarációval megkapod a: - -* Szerkesztő támogatást, beleértve: - * Szövegkiegészítés. - * Típus ellenőrzés. -* Adatok validációja: - * Automatikus és érthető hibák amikor az adatok hibásak. - * Validáció mélyen ágyazott objektumok esetén is. -* Bemeneti adatok átváltása : a hálózatról érkező Python adatokká és típusokká. Adatok olvasása következő forrásokból: - * JSON. - * Cím paraméterek. - * Query paraméterek. - * Cookie-k. - * Header-ök. - * Formok. - * Fájlok. -* Kimeneti adatok átváltása: Python adatok is típusokról hálózati adatokká: - * válts át Python típusokat (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` csak objektumokat. - * `UUID` objektumokat. - * Adatbázis modelleket. - * ...És sok mást. -* Automatikus interaktív dokumentáció, beleértve két alternatív dokumentációt is: - * Swagger UI. - * ReDoc. - ---- - -Visszatérve az előző kód példához. A **FastAPI**: - -* Validálja hogy van egy `item_id` mező a `GET` és `PUT` kérésekben. -* Validálja hogy az `item_id` `int` típusú a `GET` és `PUT` kérésekben. - * Ha nem akkor látni fogunk egy tiszta hibát ezzel kapcsolatban. -* ellenőrzi hogyha van egy opcionális query paraméter `q` névvel (azaz `http://127.0.0.1:8000/items/foo?q=somequery`) `GET` kérések esetén. - * Mivel a `q` paraméter `= None`-al van deklarálva, ezért opcionális. - * `None` nélkül ez a mező kötelező lenne (mint például a body `PUT` kérések esetén). -* a `/items/{item_id}` címre érkező `PUT` kérések esetén, a JSON-t a következőképpen olvassa be: - * Ellenőrzi hogy létezik a kötelező `name` nevű attribútum és `string`. - * Ellenőrzi hogy létezik a kötelező `price` nevű attribútum és `float`. - * Ellenőrzi hogy létezik a `is_offer` nevű opcionális paraméter, ami ha létezik akkor `bool` - * Ez ágyazott JSON objektumokkal is működik -* JSONről való automatikus konvertálás. -* dokumentáljuk mindent OpenAPI-al amit használható: - * Interaktív dokumentációs rendszerekkel. - * Automatikus kliens kód generáló a rendszerekkel, több nyelven. -* Hozzá tartozik kettő interaktív dokumentációs web felület. - ---- - -Eddig csak a felszínt kapargattuk, de a lényeg hogy most már könnyebben érthető hogyan működik. - -Próbáld kicserélni a következő sorban: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...ezt: - -```Python - ... "item_name": item.name ... -``` - -...erre: - -```Python - ... "item_price": item.price ... -``` - -... És figyeld meg hogy a szerkesztő automatikusan tudni fogja a típusokat és kiegészíti azokat: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -Teljesebb példákért és funkciókért tekintsd meg a Tutorial - User Guide -t. - -**Spoiler veszély**: a Tutorial - User Guidehoz tartozik: - -* **Paraméterek** deklarációja különböző helyekről: **header-ök**, **cookie-k**, **form mezők** és **fájlok**. -* Hogyan állíts be **validációs feltételeket** mint a `maximum_length` vagy a `regex`. -* Nagyon hatékony és erős **Függőség Injekció** rendszerek. -* Biztonság és autentikáció beleértve, **OAuth2**, **JWT tokens** és **HTTP Basic** támogatást. -* Több haladó (de ugyanannyira könnyű) technika **mélyen ágyazott JSON modellek deklarációjára** (Pydantic-nek köszönhetően). -* **GraphQL** integráció Strawberry-vel és más könyvtárakkal. -* több extra funkció (Starlette-nek köszönhetően) pl.: - * **WebSockets** - * rendkívül könnyű tesztek HTTPX és `pytest` alapokra építve - * **CORS** - * **Cookie Sessions** - * ...és több. - -## Teljesítmény - -A független TechEmpower benchmarkok szerint az Uvicorn alatt futó **FastAPI** alkalmazások az egyik leggyorsabb Python keretrendszerek közé tartoznak, éppen lemaradva a Starlette és az Uvicorn (melyeket a FastAPI belsőleg használ) mögött.(*) - -Ezeknek a további megértéséhez: Benchmarks. - -## Opcionális követelmények - -Pydantic által használt: - -* email-validator - e-mail validációkra. -* pydantic-settings - Beállítások követésére. -* pydantic-extra-types - Extra típusok Pydantic-hoz. - -Starlette által használt: - -* httpx - Követelmény ha a `TestClient`-et akarod használni. -* jinja2 - Követelmény ha az alap template konfigurációt akarod használni. -* python-multipart - Követelmény ha "parsing"-ot akarsz támogatni, `request.form()`-al. -* itsdangerous - Követelmény `SessionMiddleware` támogatáshoz. -* pyyaml - Követelmény a Starlette `SchemaGenerator`-ának támogatásához (valószínűleg erre nincs szükség FastAPI használása esetén). - -FastAPI / Starlette által használt - -* uvicorn - Szerverekhez amíg betöltik és szolgáltatják az applikációdat. -* orjson - Követelmény ha `ORJSONResponse`-t akarsz használni. -* ujson - Követelmény ha `UJSONResponse`-t akarsz használni. - -Ezeket mind telepítheted a `pip install "fastapi[all]"` paranccsal. - -## Licensz -Ez a projekt az MIT license, licensz alatt fut diff --git a/docs/hu/mkdocs.yml b/docs/hu/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/hu/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/id/docs/tutorial/index.md b/docs/id/docs/tutorial/index.md deleted file mode 100644 index f0dee3d730..0000000000 --- a/docs/id/docs/tutorial/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# 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. - -/// note | "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 deleted file mode 100644 index de18856f44..0000000000 --- a/docs/id/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md deleted file mode 100644 index 57940f0ed8..0000000000 --- a/docs/it/docs/index.md +++ /dev/null @@ -1,462 +0,0 @@ -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, alte prestazioni, facile da imparare, rapido da implementare, pronto per il rilascio in produzione -

-

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

- ---- - -**Documentazione**: https://fastapi.tiangolo.com - -**Codice Sorgente**: https://github.com/fastapi/fastapi - ---- - -FastAPI è un web framework moderno e veloce (a prestazioni elevate) che serve a creare API con Python 3.6+ basato sulle annotazioni di tipo di Python. - -Le sue caratteristiche principali sono: - -* **Velocità**: Prestazioni molto elevate, alla pari di **NodeJS** e **Go** (grazie a Starlette e Pydantic). [Uno dei framework Python più veloci in circolazione](#performance). -* **Veloce da programmare**: Velocizza il lavoro consentendo il rilascio di nuove funzionalità tra il 200% e il 300% più rapidamente. * -* **Meno bug**: Riduce di circa il 40% gli errori che commettono gli sviluppatori durante la scrittura del codice. * -* **Intuitivo**: Grande supporto per gli editor di testo con autocompletamento in ogni dove. In questo modo si può dedicare meno tempo al debugging. -* **Facile**: Progettato per essere facile da usare e imparare. Si riduce il tempo da dedicare alla lettura della documentazione. -* **Sintentico**: Minimizza la duplicazione di codice. Molteplici funzionalità, ognuna con la propria dichiarazione dei parametri. Meno errori. -* **Robusto**: Crea codice pronto per la produzione con documentazione automatica interattiva. -* **Basato sugli standard**: Basato su (e completamente compatibile con) gli open standard per le API: OpenAPI (precedentemente Swagger) e JSON Schema. - -* Stima basata sull'esito di test eseguiti su codice sorgente di applicazioni rilasciate in produzione da un team interno di sviluppatori. - -## Sponsor - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Altri sponsor - -## Recensioni - -"_[...] 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, e 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**, la FastAPI delle CLI - - - -Se stai sviluppando un'app CLI da usare nel terminale invece che una web API, ti consigliamo **Typer**. - -**Typer** è il fratello minore di FastAPI. Ed è stato ideato per essere la **FastAPI delle CLI**. ⌨️ 🚀 - -## Requisiti - -Python 3.6+ - -FastAPI è basata su importanti librerie: - -* Starlette per le parti web. -* Pydantic per le parti dei dati. - -## Installazione - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -Per il rilascio in produzione, sarà necessario un server ASGI come Uvicorn oppure Hypercorn. - -
- -```console -$ pip install uvicorn[standard] - ----> 100% -``` - -
- -## Esempio - -### Crea un file - -* Crea un file `main.py` con: - -```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} -``` - -
-Oppure usa async def... - -Se il tuo codice usa `async` / `await`, allora usa `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} -``` - -**Nota**: - -e vuoi approfondire, consulta la sezione _"In a hurry?"_ su `async` e `await` nella documentazione. - -
- -### Esegui il server - -Puoi far partire il server così: - -
- -```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. -``` - -
- -
-Informazioni sul comando uvicorn main:app --reload... - -Vediamo il comando `uvicorn main:app` in dettaglio: - -* `main`: il file `main.py` (il "modulo" Python). -* `app`: l'oggetto creato dentro `main.py` con la riga di codice `app = FastAPI()`. -* `--reload`: ricarica il server se vengono rilevati cambiamenti del codice. Usalo solo durante la fase di sviluppo. - -
- -### Testa l'API - -Apri il browser all'indirizzo http://127.0.0.1:8000/items/5?q=somequery. - -Vedrai la seguente risposta JSON: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -Hai appena creato un'API che: - -* Riceve richieste HTTP sui _paths_ `/` and `/items/{item_id}`. -* Entrambi i _paths_ accettano`GET` operations (conosciuti anche come HTTP _methods_). -* Il _path_ `/items/{item_id}` ha un _path parameter_ `item_id` che deve essere un `int`. -* Il _path_ `/items/{item_id}` ha una `str` _query parameter_ `q`. - -### Documentazione interattiva dell'API - -Adesso vai all'indirizzo http://127.0.0.1:8000/docs. - -Vedrai la documentazione interattiva dell'API (offerta da Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Documentazione interattiva alternativa - -Adesso accedi all'url http://127.0.0.1:8000/redoc. - -Vedrai la documentazione interattiva dell'API (offerta da ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Esempio più avanzato - -Adesso modifica il file `main.py` per ricevere un _body_ da una richiesta `PUT`. - -Dichiara il _body_ usando le annotazioni di tipo standard di Python, grazie a 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} -``` - -Il server dovrebbe ricaricarsi in automatico (perché hai specificato `--reload` al comando `uvicorn` lanciato precedentemente). - -### Aggiornamento della documentazione interattiva - -Adesso vai su http://127.0.0.1:8000/docs. - -* La documentazione interattiva dell'API verrà automaticamente aggiornata, includendo il nuovo _body_: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Fai click sul pulsante "Try it out", che ti permette di inserire i parametri per interagire direttamente con l'API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Successivamente, premi sul pulsante "Execute". L'interfaccia utente comunicherà con la tua API, invierà i parametri, riceverà i risultati della richiesta, e li mostrerà sullo schermo: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Aggiornamento della documentazione alternativa - -Ora vai su http://127.0.0.1:8000/redoc. - -* Anche la documentazione alternativa dell'API mostrerà il nuovo parametro della query e il _body_: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Riepilogo - -Ricapitolando, è sufficiente dichiarare **una sola volta** i tipi dei parametri, del body, ecc. come parametri di funzioni. - -Questo con le annotazioni per i tipi standard di Python. - -Non c'è bisogno di imparare una nuova sintassi, metodi o classi specifici a una libreria, ecc. - -È normalissimo **Python 3.6+**. - -Per esempio, per un `int`: - -```Python -item_id: int -``` - -o per un modello `Item` più complesso: - -```Python -item: Item -``` - -...e con quella singola dichiarazione hai in cambio: - -* Supporto per gli editor di testo, incluso: - * Autocompletamento. - * Controllo sulle annotazioni di tipo. -* Validazione dei dati: - * Errori chiari e automatici quando i dati sono invalidi. - * Validazione anche per gli oggetti JSON più complessi. -* Conversione dei dati di input: da risorse esterne a dati e tipi di Python. È possibile leggere da: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Form. - * File. -* Conversione dei dati di output: converte dati e tipi di Python a dati per la rete (come JSON): - * Converte i tipi di Python (`str`, `int`, `float`, `bool`, `list`, ecc). - * Oggetti `datetime`. - * Oggetti `UUID`. - * Modelli del database. - * ...e molto di più. -* Generazione di una documentazione dell'API interattiva, con scelta dell'interfaccia grafica: - * Swagger UI. - * ReDoc. - ---- - -Tornando al precedente esempio, **FastAPI**: - -* Validerà che esiste un `item_id` nel percorso delle richieste `GET` e `PUT`. -* Validerà che `item_id` sia di tipo `int` per le richieste `GET` e `PUT`. - * Se non lo è, il client vedrà un errore chiaro e utile. -* Controllerà se ci sia un parametro opzionale chiamato `q` (per esempio `http://127.0.0.1:8000/items/foo?q=somequery`) per le richieste `GET`. - * Siccome il parametro `q` è dichiarato con `= None`, è opzionale. - * Senza il `None` sarebbe stato obbligatorio (come per il body della richiesta `PUT`). -* Per le richieste `PUT` su `/items/{item_id}`, leggerà il body come JSON, questo comprende: - * verifica che la richiesta abbia un attributo obbligatorio `name` e che sia di tipo `str`. - * verifica che la richiesta abbia un attributo obbligatorio `price` e che sia di tipo `float`. - * verifica che la richiesta abbia un attributo opzionale `is_offer` e che sia di tipo `bool`, se presente. - * Tutto questo funzionerebbe anche con oggetti JSON più complessi. -* Convertirà *da* e *a* JSON automaticamente. -* Documenterà tutto con OpenAPI, che può essere usato per: - * Sistemi di documentazione interattivi. - * Sistemi di generazione di codice dal lato client, per molti linguaggi. -* Fornirà 2 interfacce di documentazione dell'API interattive. - ---- - -Questa è solo la punta dell'iceberg, ma dovresti avere già un'idea di come il tutto funzioni. - -Prova a cambiare questa riga di codice: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...da: - -```Python - ... "item_name": item.name ... -``` - -...a: - -```Python - ... "item_price": item.price ... -``` - -...e osserva come il tuo editor di testo autocompleterà gli attributi e sarà in grado di riconoscere i loro tipi: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -Per un esempio più completo che mostra più funzionalità del framework, consulta Tutorial - Guida Utente. - -**Spoiler alert**: il tutorial - Guida Utente include: - -* Dichiarazione di **parameters** da altri posti diversi come: **headers**, **cookies**, **form fields** e **files**. -* Come stabilire **vincoli di validazione** come `maximum_length` o `regex`. -* Un sistema di **Dependency Injection** facile da usare e molto potente. -e potente. -* Sicurezza e autenticazione, incluso il supporto per **OAuth2** con **token JWT** e autenticazione **HTTP Basic**. -* Tecniche più avanzate (ma ugualmente semplici) per dichiarare **modelli JSON altamente nidificati** (grazie a Pydantic). -* E altre funzionalità (grazie a Starlette) come: - * **WebSockets** - * **GraphQL** - * test molto facili basati su `requests` e `pytest` - * **CORS** - * **Cookie Sessions** - * ...e altro ancora. - -## Prestazioni - -Benchmark indipendenti di TechEmpower mostrano che **FastAPI** basato su Uvicorn è uno dei framework Python più veloci in circolazione, solamente dietro a Starlette e Uvicorn (usate internamente da FastAPI). (*) - -Per approfondire, consulta la sezione Benchmarks. - -## Dipendenze opzionali - -Usate da Pydantic: - -* email-validator - per la validazione di email. - -Usate da Starlette: - -* requests - Richiesto se vuoi usare il `TestClient`. -* aiofiles - Richiesto se vuoi usare `FileResponse` o `StaticFiles`. -* jinja2 - Richiesto se vuoi usare la configurazione template di default. -* python-multipart - Richiesto se vuoi supportare il "parsing" con `request.form()`. -* itsdangerous - Richiesto per usare `SessionMiddleware`. -* pyyaml - Richiesto per il supporto dello `SchemaGenerator` di Starlette (probabilmente non ti serve con FastAPI). -* graphene - Richiesto per il supporto di `GraphQLApp`. - -Usate da FastAPI / Starlette: - -* uvicorn - per il server che carica e serve la tua applicazione. -* orjson - ichiesto se vuoi usare `ORJSONResponse`. -* ujson - Richiesto se vuoi usare `UJSONResponse`. - -Puoi installarle tutte con `pip install fastapi[all]`. - -## Licenza - -Questo progetto è concesso in licenza in base ai termini della licenza MIT. diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/it/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/ja/docs/advanced/additional-status-codes.md b/docs/ja/docs/advanced/additional-status-codes.md index 622affa6eb..33457f591c 100644 --- a/docs/ja/docs/advanced/additional-status-codes.md +++ b/docs/ja/docs/advanced/additional-status-codes.md @@ -14,11 +14,9 @@ これを達成するには、 `JSONResponse` をインポートし、 `status_code` を設定して直接内容を返します。 -```Python hl_lines="4 25" -{!../../../docs_src/additional_status_codes/tutorial001.py!} -``` +{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *} -/// warning | "注意" +/// warning | 注意 上記の例のように `Response` を明示的に返す場合、それは直接返されます。 @@ -28,7 +26,7 @@ /// -/// note | "技術詳細" +/// note | 技術詳細 `from starlette.responses import JSONResponse` を利用することもできます。 diff --git a/docs/ja/docs/advanced/custom-response.md b/docs/ja/docs/advanced/custom-response.md index a7ce6b54d9..1b2cd914d4 100644 --- a/docs/ja/docs/advanced/custom-response.md +++ b/docs/ja/docs/advanced/custom-response.md @@ -12,7 +12,7 @@ そしてもし、`Response` が、`JSONResponse` や `UJSONResponse` の場合のようにJSONメディアタイプ (`application/json`) ならば、データは *path operationデコレータ* に宣言したPydantic `response_model` により自動的に変換 (もしくはフィルタ) されます。 -/// note | "備考" +/// note | 備考 メディアタイプを指定せずにレスポンスクラスを利用すると、FastAPIは何もコンテンツがないことを期待します。そのため、生成されるOpenAPIドキュメントにレスポンスフォーマットが記載されません。 @@ -24,11 +24,9 @@ 使いたい `Response` クラス (サブクラス) をインポートし、 *path operationデコレータ* に宣言します。 -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001b.py!} -``` +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} -/// info | "情報" +/// info | 情報 パラメータ `response_class` は、レスポンスの「メディアタイプ」を定義するために利用することもできます。 @@ -38,7 +36,7 @@ /// -/// tip | "豆知識" +/// tip | 豆知識 `ORJSONResponse` は、現在はFastAPIのみで利用可能で、Starletteでは利用できません。 @@ -51,11 +49,9 @@ * `HTMLResponse` をインポートする。 * *path operation* のパラメータ `content_type` に `HTMLResponse` を渡す。 -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial002.py!} -``` +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} -/// info | "情報" +/// info | 情報 パラメータ `response_class` は、レスポンスの「メディアタイプ」を定義するために利用されます。 @@ -71,17 +67,15 @@ 上記と同じ例において、 `HTMLResponse` を返すと、このようになります: -```Python hl_lines="2 7 19" -{!../../../docs_src/custom_response/tutorial003.py!} -``` +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} -/// warning | "注意" +/// warning | 注意 *path operation関数* から直接返される `Response` は、OpenAPIにドキュメントされず (例えば、 `Content-Type` がドキュメントされない) 、自動的な対話的ドキュメントからも閲覧できません。 /// -/// info | "情報" +/// info | 情報 もちろん、実際の `Content-Type` ヘッダーやステータスコードなどは、返された `Response` オブジェクトに由来しています。 @@ -97,9 +91,7 @@ 例えば、このようになります: -```Python hl_lines="7 21 23" -{!../../../docs_src/custom_response/tutorial004.py!} -``` +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} この例では、関数 `generate_html_response()` は、`str` のHTMLを返すのではなく `Response` を生成して返しています。 @@ -115,7 +107,7 @@ `Response` を使って他の何かを返せますし、カスタムのサブクラスも作れることを覚えておいてください。 -/// note | "技術詳細" +/// note | 技術詳細 `from starlette.responses import HTMLResponse` も利用できます。 @@ -138,9 +130,7 @@ FastAPI (実際にはStarlette) は自動的にContent-Lengthヘッダーを含みます。また、media_typeに基づいたContent-Typeヘッダーを含み、テキストタイプのためにcharsetを追加します。 -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} ### `HTMLResponse` @@ -150,9 +140,7 @@ FastAPI (実際にはStarlette) は自動的にContent-Lengthヘッダーを含 テキストやバイトを受け取り、プレーンテキストのレスポンスを返します。 -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial005.py!} -``` +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} ### `JSONResponse` @@ -168,17 +156,15 @@ FastAPI (実際にはStarlette) は自動的にContent-Lengthヘッダーを含 `ujson`を使った、代替のJSONレスポンスです。 -/// warning | "注意" +/// warning | 注意 `ujson` は、いくつかのエッジケースの取り扱いについて、Pythonにビルトインされた実装よりも作りこまれていません。 /// -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001.py!} -``` +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} -/// tip | "豆知識" +/// tip | 豆知識 `ORJSONResponse` のほうが高速な代替かもしれません。 @@ -188,17 +174,13 @@ FastAPI (実際にはStarlette) は自動的にContent-Lengthヘッダーを含 HTTPリダイレクトを返します。デフォルトでは307ステータスコード (Temporary Redirect) となります。 -```Python hl_lines="2 9" -{!../../../docs_src/custom_response/tutorial006.py!} -``` +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} ### `StreamingResponse` 非同期なジェネレータか通常のジェネレータ・イテレータを受け取り、レスポンスボディをストリームします。 -```Python hl_lines="2 14" -{!../../../docs_src/custom_response/tutorial007.py!} -``` +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} #### `StreamingResponse` をファイルライクなオブジェクトとともに使う @@ -206,11 +188,9 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス これにはクラウドストレージとの連携や映像処理など、多くのライブラリが含まれています。 -```Python hl_lines="2 10-12 14" -{!../../../docs_src/custom_response/tutorial008.py!} -``` +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} -/// tip | "豆知識" +/// tip | 豆知識 ここでは `async` や `await` をサポートしていない標準の `open()` を使っているので、通常の `def` でpath operationを宣言していることに注意してください。 @@ -229,9 +209,7 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス ファイルレスポンスには、適切な `Content-Length` 、 `Last-Modified` 、 `ETag` ヘッダーが含まれます。 -```Python hl_lines="2 10" -{!../../../docs_src/custom_response/tutorial009.py!} -``` +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} ## デフォルトレスポンスクラス @@ -241,11 +219,9 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス 以下の例では、 **FastAPI** は、全ての *path operation* で `JSONResponse` の代わりに `ORJSONResponse` をデフォルトとして利用します。 -```Python hl_lines="2 4" -{!../../../docs_src/custom_response/tutorial010.py!} -``` +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} -/// tip | "豆知識" +/// tip | 豆知識 前に見たように、 *path operation* の中で `response_class` をオーバーライドできます。 diff --git a/docs/ja/docs/advanced/index.md b/docs/ja/docs/advanced/index.md index da3c2a2bf6..22eaf6eb80 100644 --- a/docs/ja/docs/advanced/index.md +++ b/docs/ja/docs/advanced/index.md @@ -6,7 +6,7 @@ 以降のセクションでは、チュートリアルでは説明しきれなかったオプションや設定、および機能について説明します。 -/// tip | "豆知識" +/// tip | 豆知識 以降のセクションは、 **必ずしも"応用編"ではありません**。 diff --git a/docs/ja/docs/advanced/path-operation-advanced-configuration.md b/docs/ja/docs/advanced/path-operation-advanced-configuration.md index ae60126cbc..05188d5b25 100644 --- a/docs/ja/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/ja/docs/advanced/path-operation-advanced-configuration.md @@ -2,7 +2,7 @@ ## OpenAPI operationId -/// warning | "注意" +/// warning | 注意 あなたがOpenAPIの「エキスパート」でなければ、これは必要ないかもしれません。 @@ -12,9 +12,7 @@ `operation_id` は各オペレーションで一意にする必要があります。 -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} ### *path operation関数* の名前をoperationIdとして使用する @@ -22,17 +20,15 @@ APIの関数名を `operationId` として利用したい場合、すべてのAP そうする場合は、すべての *path operation* を追加した後に行う必要があります。 -```Python hl_lines="2 12-21 24" -{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *} -/// tip | "豆知識" +/// tip | 豆知識 `app.openapi()` を手動でコールする場合、その前に`operationId`を更新する必要があります。 /// -/// warning | "注意" +/// warning | 注意 この方法をとる場合、各 *path operation関数* が一意な名前である必要があります。 @@ -44,9 +40,7 @@ APIの関数名を `operationId` として利用したい場合、すべてのAP 生成されるOpenAPIスキーマ (つまり、自動ドキュメント生成の仕組み) から *path operation* を除外するには、 `include_in_schema` パラメータを `False` にします。 -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} ## docstringによる説明の高度な設定 @@ -56,6 +50,4 @@ APIの関数名を `operationId` として利用したい場合、すべてのAP ドキュメントには表示されませんが、他のツール (例えばSphinx) では残りの部分を利用できるでしょう。 -```Python hl_lines="19-29" -{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} diff --git a/docs/ja/docs/advanced/response-directly.md b/docs/ja/docs/advanced/response-directly.md index 5c25471b1b..42412d5070 100644 --- a/docs/ja/docs/advanced/response-directly.md +++ b/docs/ja/docs/advanced/response-directly.md @@ -14,7 +14,7 @@ 実際は、`Response` やそのサブクラスを返すことができます。 -/// tip | "豆知識" +/// tip | 豆知識 `JSONResponse` それ自体は、 `Response` のサブクラスです。 @@ -34,11 +34,9 @@ このようなケースでは、レスポンスにデータを含める前に `jsonable_encoder` を使ってデータを変換できます。 -```Python hl_lines="6-7 21-22" -{!../../../docs_src/response_directly/tutorial001.py!} -``` +{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} -/// note | "技術詳細" +/// note | 技術詳細 また、`from starlette.responses import JSONResponse` も利用できます。 @@ -56,9 +54,7 @@ XMLを文字列にし、`Response` に含め、それを返します。 -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} ## 備考 diff --git a/docs/ja/docs/advanced/websockets.md b/docs/ja/docs/advanced/websockets.md index 615f9d17c8..2517530abe 100644 --- a/docs/ja/docs/advanced/websockets.md +++ b/docs/ja/docs/advanced/websockets.md @@ -38,19 +38,15 @@ $ pip install websockets しかし、これはWebSocketのサーバーサイドに焦点を当て、実用的な例を示す最も簡単な方法です。 -```Python hl_lines="2 6-38 41-43" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} ## `websocket` を作成する **FastAPI** アプリケーションで、`websocket` を作成します。 -```Python hl_lines="1 46-47" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} -/// note | "技術詳細" +/// note | 技術詳細 `from starlette.websockets import WebSocket` を使用しても構いません. @@ -62,9 +58,7 @@ $ pip install websockets WebSocketルートでは、 `await` を使ってメッセージの送受信ができます。 -```Python hl_lines="48-52" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} バイナリやテキストデータ、JSONデータを送受信できます。 @@ -115,11 +109,9 @@ WebSocketエンドポイントでは、`fastapi` から以下をインポート これらは、他のFastAPI エンドポイント/*path operation* の場合と同じように機能します。 -```Python hl_lines="58-65 68-83" -{!../../../docs_src/websockets/tutorial002.py!} -``` +{* ../../docs_src/websockets/tutorial002.py hl[58:65,68:83] *} -/// info | "情報" +/// info | 情報 WebSocket で `HTTPException` を発生させることはあまり意味がありません。したがって、WebSocketの接続を直接閉じる方がよいでしょう。 @@ -150,7 +142,7 @@ $ uvicorn main:app --reload * パスで使用される「Item ID」 * クエリパラメータとして使用される「Token」 -/// tip | "豆知識" +/// tip | 豆知識 クエリ `token` は依存パッケージによって処理されることに注意してください。 @@ -164,9 +156,7 @@ $ uvicorn main:app --reload WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。 -```Python hl_lines="81-83" -{!../../../docs_src/websockets/tutorial003.py!} -``` +{* ../../docs_src/websockets/tutorial003.py hl[81:83] *} 試してみるには、 @@ -180,7 +170,7 @@ WebSocket接続が閉じられると、 `await websocket.receive_text()` は例 Client #1596980209979 left the chat ``` -/// tip | "豆知識" +/// tip | 豆知識 上記のアプリは、複数の WebSocket 接続に対してメッセージを処理し、ブロードキャストする方法を示すための最小限のシンプルな例です。 @@ -194,5 +184,5 @@ Client #1596980209979 left the chat オプションの詳細については、Starletteのドキュメントを確認してください。 -* `WebSocket` クラス -* クラスベースのWebSocket処理 +* `WebSocket` クラス +* クラスベースのWebSocket処理 diff --git a/docs/ja/docs/alternatives.md b/docs/ja/docs/alternatives.md index 343ae4ed87..9f5152c08a 100644 --- a/docs/ja/docs/alternatives.md +++ b/docs/ja/docs/alternatives.md @@ -30,13 +30,13 @@ Mozilla、Red Hat、Eventbrite など多くの企業で利用されています これは**自動的なAPIドキュメント生成**の最初の例であり、これは**FastAPI**に向けた「調査」を触発した最初のアイデアの一つでした。 -/// note | "備考" +/// note | 備考 Django REST Framework は Tom Christie によって作成されました。StarletteとUvicornの生みの親であり、**FastAPI**のベースとなっています。 /// -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション 自動でAPIドキュメントを生成するWebユーザーインターフェースを持っている点。 @@ -56,7 +56,7 @@ Flask は「マイクロフレームワーク」であり、データベース Flaskのシンプルさを考えると、APIを構築するのに適しているように思えました。次に見つけるべきは、Flask 用の「Django REST Framework」でした。 -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション マイクロフレームワークであること。ツールやパーツを目的に合うように簡単に組み合わせられる点。 @@ -98,7 +98,7 @@ def read_url(): `requests.get(...)` と`@app.get(...)` には類似点が見受けられます。 -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション * シンプルで直感的なAPIを持っている点。 * HTTPメソッド名を直接利用し、単純で直感的である。 @@ -118,7 +118,7 @@ def read_url(): そのため、バージョン2.0では「Swagger」、バージョン3以上では「OpenAPI」と表記するのが一般的です。 -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション 独自のスキーマの代わりに、API仕様のオープンな標準を採用しました。 @@ -147,7 +147,7 @@ APIが必要とするもう一つの大きな機能はデータのバリデー しかし、それはPythonの型ヒントが存在する前に作られたものです。そのため、すべてのスキーマを定義するためには、Marshmallowが提供する特定のユーティリティやクラスを使用する必要があります。 -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション コードで「スキーマ」を定義し、データの型やバリデーションを自動で提供する点。 @@ -163,13 +163,13 @@ WebargsはFlaskをはじめとするいくつかのフレームワークの上 素晴らしいツールで、私も**FastAPI**を持つ前はよく使っていました。 -/// info | "情報" +/// info | 情報 Webargsは、Marshmallowと同じ開発者により作られました。 /// -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション 受信したデータに対する自動的なバリデーションを持っている点。 @@ -193,13 +193,13 @@ Flask, Starlette, Responderなどにおいてはそのように動作します エディタでは、この問題を解決することはできません。また、パラメータやMarshmallowスキーマを変更したときに、YAMLのdocstringを変更するのを忘れてしまうと、生成されたスキーマが古くなってしまいます。 -/// info | "情報" +/// info | 情報 APISpecは、Marshmallowと同じ開発者により作成されました。 /// -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション OpenAPIという、APIについてのオープンな標準をサポートしている点。 @@ -225,13 +225,13 @@ Flask、Flask-apispec、Marshmallow、Webargsの組み合わせは、**FastAPI** そして、これらのフルスタックジェネレーターは、[**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}の元となっていました。 -/// info | "情報" +/// info | 情報 Flask-apispecはMarshmallowと同じ開発者により作成されました。 /// -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション シリアライゼーションとバリデーションを定義したコードから、OpenAPIスキーマを自動的に生成する点。 @@ -251,7 +251,7 @@ Angular 2にインスピレーションを受けた、統合された依存性 入れ子になったモデルをうまく扱えません。そのため、リクエストのJSONボディが内部フィールドを持つJSONオブジェクトで、それが順番にネストされたJSONオブジェクトになっている場合、適切にドキュメント化やバリデーションをすることができません。 -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション 素晴らしいエディターの補助を得るために、Pythonの型ヒントを利用している点。 @@ -263,7 +263,7 @@ Angular 2にインスピレーションを受けた、統合された依存性 `asyncio`に基づいた、Pythonのフレームワークの中でも非常に高速なものの一つです。Flaskと非常に似た作りになっています。 -/// note | "技術詳細" +/// note | 技術詳細 Pythonの`asyncio`ループの代わりに、`uvloop`が利用されています。それにより、非常に高速です。 @@ -271,7 +271,7 @@ Pythonの`asyncio`ループの代わりに、`uvloop`が利用されています /// -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション 物凄い性能を出す方法を見つけた点。 @@ -289,7 +289,7 @@ Pythonのウェブフレームワーク標準規格 (WSGI) を使用していま そのため、データのバリデーション、シリアライゼーション、ドキュメント化は、自動的にできずコードの中で行わなければなりません。あるいは、HugのようにFalconの上にフレームワークとして実装されなければなりません。このような分断は、パラメータとして1つのリクエストオブジェクトと1つのレスポンスオブジェクトを持つというFalconのデザインにインスピレーションを受けた他のフレームワークでも起こります。 -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション 素晴らしい性能を得るための方法を見つけた点。 @@ -315,7 +315,7 @@ Pydanticのようなデータのバリデーション、シリアライゼーシ ルーティングは一つの場所で宣言され、他の場所で宣言された関数を使用します (エンドポイントを扱う関数のすぐ上に配置できるデコレータを使用するのではなく) 。これはFlask (やStarlette) よりも、Djangoに近いです。これは、比較的緊密に結合されているものをコードの中で分離しています。 -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション モデルの属性の「デフォルト」値を使用したデータ型の追加バリデーションを定義します。これはエディタの補助を改善するもので、以前はPydanticでは利用できませんでした。 @@ -337,13 +337,13 @@ OpenAPIやJSON Schemaのような標準に基づいたものではありませ 以前のPythonの同期型Webフレームワーク標準 (WSGI) をベースにしているため、Websocketなどは扱えませんが、それでも高性能です。 -/// info | "情報" +/// info | 情報 HugはTimothy Crosleyにより作成されました。彼は`isort`など、Pythonのファイル内のインポートの並び替えを自動的におこうなう素晴らしいツールの開発者です。 /// -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション HugはAPIStarに部分的なインスピレーションを与えており、私が発見した中ではAPIStarと同様に最も期待の持てるツールの一つでした。 @@ -377,7 +377,7 @@ Hugは、**FastAPI**がヘッダーやクッキーを設定するために関数 今ではAPIStarはOpenAPI仕様を検証するためのツールセットであり、ウェブフレームワークではありません。 -/// info | "情報" +/// info | 情報 APIStarはTom Christieにより開発されました。以下の開発者でもあります: @@ -387,7 +387,7 @@ APIStarはTom Christieにより開発されました。以下の開発者でも /// -/// check | "**FastAPI**へ与えたインスピレーション" +/// check | **FastAPI**へ与えたインスピレーション 存在そのもの。 @@ -411,7 +411,7 @@ Pydanticは、Pythonの型ヒントを元にデータのバリデーション、 Marshmallowに匹敵しますが、ベンチマークではMarshmallowよりも高速です。また、Pythonの型ヒントを元にしているので、エディタの補助が素晴らしいです。 -/// check | "**FastAPI**での使用用途" +/// check | **FastAPI**での使用用途 データのバリデーション、データのシリアライゼーション、自動的なモデルの (JSON Schemaに基づいた) ドキュメント化の全てを扱えます。 @@ -419,7 +419,7 @@ Marshmallowに匹敵しますが、ベンチマークではMarshmallowよりも /// -### Starlette +### Starlette Starletteは、軽量なASGIフレームワーク/ツールキットで、高性能な非同期サービスの構築に最適です。 @@ -447,7 +447,7 @@ Starletteは基本的なWebマイクロフレームワークの機能をすべ これは **FastAPI** が追加する主な機能の一つで、すべての機能は Pythonの型ヒントに基づいています (Pydanticを使用しています) 。これに加えて、依存性注入の仕組み、セキュリティユーティリティ、OpenAPIスキーマ生成などがあります。 -/// note | "技術詳細" +/// note | 技術詳細 ASGIはDjangoのコアチームメンバーにより開発された新しい「標準」です。まだ「Pythonの標準 (PEP) 」ではありませんが、現在そうなるように進めています。 @@ -455,7 +455,7 @@ ASGIはDjangoのコアチームメンバーにより開発された新しい「 /// -/// check | "**FastAPI**での使用用途" +/// check | **FastAPI**での使用用途 webに関するコアな部分を全て扱います。その上に機能を追加します。 @@ -465,7 +465,7 @@ webに関するコアな部分を全て扱います。その上に機能を追 /// -### Uvicorn +### Uvicorn Uvicornは非常に高速なASGIサーバーで、uvloopとhttptoolsにより構成されています。 @@ -473,7 +473,7 @@ Uvicornは非常に高速なASGIサーバーで、uvloopとhttptoolsにより構 Starletteや**FastAPI**のサーバーとして推奨されています。 -/// check | "**FastAPI**が推奨する理由" +/// check | **FastAPI**が推奨する理由 **FastAPI**アプリケーションを実行するメインのウェブサーバーである点。 diff --git a/docs/ja/docs/async.md b/docs/ja/docs/async.md index ce9dac56fe..90a2e2ee55 100644 --- a/docs/ja/docs/async.md +++ b/docs/ja/docs/async.md @@ -21,7 +21,7 @@ async def read_results(): return results ``` -/// note | "備考" +/// note | 備考 `async def` を使用して作成された関数の内部でしか `await` は使用できません。 @@ -338,7 +338,7 @@ async def read_burgers(): 以前のバージョンのPythonでは、スレッドやGeventが利用できました。しかし、コードは理解、デバック、そして、考察がはるかに複雑です。 -以前のバージョンのNodeJS / ブラウザJavaScriptでは、「コールバック」を使用していました。これは、コールバック地獄につながります。 +以前のバージョンのNodeJS / ブラウザJavaScriptでは、「コールバック」を使用していました。これは、「コールバック地獄」につながります。 ## コルーチン @@ -358,7 +358,7 @@ async def read_burgers(): ## 非常に発展的な技術的詳細 -/// warning | "注意" +/// warning | 注意 恐らくスキップしても良いでしょう。 diff --git a/docs/ja/docs/contributing.md b/docs/ja/docs/contributing.md deleted file mode 100644 index 86926b2132..0000000000 --- a/docs/ja/docs/contributing.md +++ /dev/null @@ -1,498 +0,0 @@ -# 開発 - 貢献 - -まず、[FastAPIを応援 - ヘルプの入手](help-fastapi.md){.internal-link target=_blank}の基本的な方法を見て、ヘルプを得た方がいいかもしれません。 - -## 開発 - -すでにリポジトリをクローンし、コードを詳しく調べる必要があるとわかっている場合に、環境構築のためのガイドラインをいくつか紹介します。 - -### `venv`を使用した仮想環境 - -Pythonの`venv`モジュールを使用して、ディレクトリに仮想環境を作成します: - -
- -```console -$ python -m venv env -``` - -
- -これにより、Pythonバイナリを含む`./env/`ディレクトリが作成され、その隔離された環境にパッケージのインストールが可能になります。 - -### 仮想環境の有効化 - -新しい環境を有効化するには: - -//// tab | Linux, macOS - -
- -```console -$ source ./env/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .\env\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -もしwindows用のBash (例えば、Git Bash)を使っているなら: - -
- -```console -$ source ./env/Scripts/activate -``` - -
- -//// - -動作の確認には、下記を実行します: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ Get-Command pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -`env/bin/pip`に`pip`バイナリが表示される場合は、正常に機能しています。🎉 - - -/// tip | "豆知識" - -この環境で`pip`を使って新しいパッケージをインストールするたびに、仮想環境を再度有効化します。 - -これにより、そのパッケージによってインストールされたターミナルのプログラム を使用する場合、ローカル環境のものを使用し、グローバルにインストールされたものは使用されなくなります。 - -/// - -### pip - -上記のように環境を有効化した後: - -
- -```console -$ pip install -r requirements.txt - ----> 100% -``` - -
- -これで、すべての依存関係とFastAPIを、ローカル環境にインストールします。 - -#### ローカル環境でFastAPIを使う - -FastAPIをインポートして使用するPythonファイルを作成し、ローカル環境で実行すると、ローカルのFastAPIソースコードが使用されます。 - -そして、`-e` でインストールされているローカルのFastAPIソースコードを更新した場合、そのPythonファイルを再度実行すると、更新したばかりの新しいバージョンのFastAPIが使用されます。 - -これにより、ローカルバージョンを「インストール」しなくても、すべての変更をテストできます。 - -### コードの整形 - -すべてのコードを整形してクリーンにするスクリプトがあります: - -
- -```console -$ bash scripts/format.sh -``` - -
- -また、すべてのインポートを自動でソートします。 - -正しく並べ替えるには、上記セクションのコマンドで `-e` を使い、FastAPIをローカル環境にインストールしている必要があります。 - -### インポートの整形 - -他にも、すべてのインポートを整形し、未使用のインポートがないことを確認するスクリプトがあります: - -
- -```console -$ bash scripts/format-imports.sh -``` - -
- -多くのファイルを編集したり、リバートした後、これらのコマンドを実行すると、少し時間がかかります。なので`scripts/format.sh`を頻繁に使用し、`scripts/format-imports.sh`をコミット前に実行する方が楽でしょう。 - -## ドキュメント - -まず、上記のように環境をセットアップしてください。すべての必要なパッケージがインストールされます。 - -ドキュメントは、MkDocsを使っています。 - -そして、翻訳を処理するためのツール/スクリプトが、`./scripts/docs.py`に用意されています。 - -/// tip | "豆知識" - -`./scripts/docs.py`のコードを見る必要はなく、コマンドラインからただ使うだけです。 - -/// - -すべてのドキュメントが、Markdown形式で`./docs/en/`ディレクトリにあります。 - -多くのチュートリアルには、コードブロックがあります。 - -ほとんどの場合、これらのコードブロックは、実際にそのまま実行できる完全なアプリケーションです。 - -実際、これらのコードブロックはMarkdown内には記述されておらず、`./docs_src/`ディレクトリのPythonファイルです。 - -そして、これらの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 | "豆知識" - -すでにあるプルリクエストに修正提案つきのコメントを追加できます。 - -修正提案の承認のためにプルリクエストのレビューの追加のドキュメントを確認してください。 - -/// - -* issuesをチェックして、あなたの言語に対応する翻訳があるかどうかを確認してください。 - -* 翻訳したページごとに1つのプルリクエストを追加します。これにより、他のユーザーがレビューしやすくなります。 - -私が話さない言語については、他の何人かが翻訳をレビューするのを待って、マージします。 - -* 自分の言語の翻訳があるかどうか確認し、レビューを追加できます。これにより、翻訳が正しく、マージできることがわかります。 - -* 同じPythonの例を使用し、ドキュメント内のテキストのみを翻訳してください。何も変更する必要はありません。 - -* 同じ画像、ファイル名、リンクを使用します。何も変更する必要はありません。 - -* 翻訳する言語の2文字のコードを確認するには、表 ISO 639-1コードのリストが使用できます。 - -#### すでにある言語 - -スペイン語の様に、既に一部のページが翻訳されている言語の翻訳を追加したいとしましょう。 - -スペイン語の場合、2文字のコードは`es`です。したがって、スペイン語のディレクトリは`docs/es/`です。 - -/// tip | "豆知識" - -メイン (「公式」) 言語は英語で、`docs/en/`にあります。 - -/// - -次に、ドキュメントのライブサーバーをスペイン語で実行します: - -
- -```console -// コマンド"live"を使用し、言語コードをCLIに引数で渡します。 -$ 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 docs Webサイトを見ると、すべての言語にすべてのページがあります。しかし、一部のページは翻訳されておらず、翻訳の欠落ページについて通知があります。 - -しかし、このようにローカルで実行すると、翻訳済みのページのみが表示されます。 - -ここで、セクション[Features](features.md){.internal-link target=_blank}の翻訳を追加するとします。 - -* 下記のファイルをコピーします: - -``` -docs/en/docs/features.md -``` - -* 翻訳したい言語のまったく同じところに貼り付けます。例えば: - -``` -docs/es/docs/features.md -``` - -/// tip | "豆知識" - -パスとファイル名の変更は、`en`から`es`への言語コードだけであることに注意してください。 - -/// - -* ここで、英語のMkDocs構成ファイルを開きます: - -``` -docs/en/docs/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/docs/mkdocs.yml -``` - -* 英語とまったく同じ場所に追加します。例えば: - -```YAML hl_lines="8" -site_name: FastAPI -# More stuff -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -他のエントリがある場合は、翻訳を含む新しいエントリが英語版とまったく同じ順序になっていることを確認してください。 - -ブラウザにアクセスすれば、ドキュメントに新しいセクションが表示されています。 🎉 - -これですべて翻訳して、ファイルを保存した状態を確認できます。 - -#### 新しい言語 - -まだ翻訳されていない言語の翻訳を追加したいとしましょう。 - -クレオール語の翻訳を追加したいのですが、それはまだドキュメントにありません。 - -上記のリンクを確認すると、「クレオール語」のコードは`ht`です。 - -次のステップは、スクリプトを実行して新しい翻訳ディレクトリを生成することです: - -
- -```console -// コマンド「new-lang」を使用して、言語コードをCLIに引数で渡します -$ 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 -``` - -これは、テーマがその言語をサポートしていないことを意味します (この場合は、`xx`の2文字の偽のコード) 。 - -ただし、心配しないでください。テーマ言語を英語に設定して、ドキュメントの内容を翻訳できます。 - -その必要がある場合は、新しい言語の`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 -// 「build-all」コマンドの実行の後に、「serve」コマンドを使います -$ 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/ja/docs/deployment/concepts.md b/docs/ja/docs/deployment/concepts.md index c6b21fd1b9..a0d4fb35b1 100644 --- a/docs/ja/docs/deployment/concepts.md +++ b/docs/ja/docs/deployment/concepts.md @@ -219,7 +219,7 @@ FastAPI アプリケーションでは、Uvicorn のようなサーバープロ これらのワーカー・プロセスは、アプリケーションを実行するものであり、**リクエスト**を受けて**レスポンス**を返すための主要な計算を行い、あなたが変数に入れたものは何でもRAMにロードします。 - + そしてもちろん、同じマシンでは、あなたのアプリケーションとは別に、**他のプロセス**も実行されているでしょう。 diff --git a/docs/ja/docs/deployment/https.md b/docs/ja/docs/deployment/https.md index ac40b0982a..7b0f567aa5 100644 --- a/docs/ja/docs/deployment/https.md +++ b/docs/ja/docs/deployment/https.md @@ -92,7 +92,7 @@ DNSサーバーでは、**取得したドメイン**をあなたのサーバー DNSサーバーは、ブラウザに特定の**IPアドレス**を使用するように指示します。このIPアドレスは、DNSサーバーで設定した、あなたのサーバーが使用するパブリックIPアドレスになります。 - + ### TLS Handshake の開始 @@ -100,7 +100,7 @@ DNSサーバーは、ブラウザに特定の**IPアドレス**を使用する 通信の最初の部分は、クライアントとサーバー間の接続を確立し、使用する暗号鍵などを決めるだけです。 - + TLS接続を確立するためのクライアントとサーバー間のこのやりとりは、**TLSハンドシェイク**と呼ばれます。 @@ -120,7 +120,7 @@ TLS Termination Proxyは、1つ以上の**TLS証明書**(HTTPS証明書)に 今回は、`someapp.example.com`の証明書を使うことになります。 - + クライアントは、そのTLS証明書を生成したエンティティ(この場合はLet's Encryptですが、これについては後述します)をすでに**信頼**しているため、その証明書が有効であることを**検証**することができます。 @@ -142,19 +142,19 @@ TLS Termination Proxyは、1つ以上の**TLS証明書**(HTTPS証明書)に そこで、クライアントは**HTTPSリクエスト**を送信します。これは、暗号化されたTLSコネクションを介した単なるHTTPリクエストです。 - + ### リクエストの復号化 TLS Termination Proxy は、合意が取れている暗号化を使用して、**リクエストを復号化**し、**プレーン (復号化された) HTTP リクエスト** をアプリケーションを実行しているプロセス (例えば、FastAPI アプリケーションを実行している Uvicorn を持つプロセス) に送信します。 - + ### HTTP レスポンス アプリケーションはリクエストを処理し、**プレーン(暗号化されていない)HTTPレスポンス** をTLS Termination Proxyに送信します。 - + ### HTTPS レスポンス @@ -162,7 +162,7 @@ TLS Termination Proxyは次に、事前に合意が取れている暗号(`someap その後ブラウザでは、レスポンスが有効で正しい暗号キーで暗号化されていることなどを検証します。そして、ブラウザはレスポンスを**復号化**して処理します。 - + クライアント(ブラウザ)は、レスポンスが正しいサーバーから来たことを知ることができます。 なぜなら、そのサーバーは、以前に**HTTPS証明書**を使って合意した暗号を使っているからです。 @@ -172,7 +172,7 @@ TLS Termination Proxyは次に、事前に合意が取れている暗号(`someap 特定のIPとポート(この例ではTLS Termination Proxy)を扱うことができるのは1つのプロセスだけですが、他のアプリケーション/プロセスも、同じ**パブリックIPとポート**の組み合わせを使用しようとしない限り、サーバー上で実行することができます。 - + そうすれば、TLS Termination Proxy は、**複数のドメイン**や複数のアプリケーションのHTTPSと証明書を処理し、それぞれのケースで適切なアプリケーションにリクエストを送信することができます。 @@ -182,7 +182,7 @@ TLS Termination Proxyは次に、事前に合意が取れている暗号(`someap その後、Let's Encryptと通信する別のプログラム(別のプログラムである場合もあれば、同じTLS Termination Proxyである場合もある)によって、証明書を更新します。 - + **TLS証明書**は、IPアドレスではなく、**ドメイン名に関連付けられて**います。 diff --git a/docs/ja/docs/deployment/manually.md b/docs/ja/docs/deployment/manually.md index c17e637284..da382a9c53 100644 --- a/docs/ja/docs/deployment/manually.md +++ b/docs/ja/docs/deployment/manually.md @@ -6,7 +6,7 @@ //// tab | Uvicorn -* Uvicorn, uvloopとhttptoolsを基にした高速なASGIサーバ。 +* Uvicorn, uvloopとhttptoolsを基にした高速なASGIサーバ。
@@ -20,7 +20,7 @@ $ pip install "uvicorn[standard]" //// -/// tip | "豆知識" +/// tip | 豆知識 `standard` を加えることで、Uvicornがインストールされ、いくつかの推奨される依存関係を利用するようになります。 @@ -78,7 +78,7 @@ Running on 0.0.0.0:8080 over http (CTRL + C to quit) 停止した場合に自動的に再起動させるツールを設定したいかもしれません。 -さらに、GunicornをインストールしてUvicornのマネージャーとして使用したり、複数のワーカーでHypercornを使用したいかもしれません。 +さらに、GunicornをインストールしてUvicornのマネージャーとして使用したり、複数のワーカーでHypercornを使用したいかもしれません。 ワーカー数などの微調整も行いたいかもしれません。 diff --git a/docs/ja/docs/deployment/versions.md b/docs/ja/docs/deployment/versions.md index 941ddb71b4..7575fc4f70 100644 --- a/docs/ja/docs/deployment/versions.md +++ b/docs/ja/docs/deployment/versions.md @@ -42,7 +42,7 @@ PoetryやPipenvなど、他のインストール管理ツールを使用して FastAPIでは「パッチ」バージョンはバグ修正と非破壊的な変更に留めるという規約に従っています。 -/// tip | "豆知識" +/// tip | 豆知識 「パッチ」は最後の数字を指します。例えば、`0.2.3` ではパッチバージョンは `3` です。 @@ -56,7 +56,7 @@ fastapi>=0.45.0,<0.46.0 破壊的な変更と新機能実装は「マイナー」バージョンで加えられます。 -/// tip | "豆知識" +/// tip | 豆知識 「マイナー」は真ん中の数字です。例えば、`0.2.3` ではマイナーバージョンは `2` です。 diff --git a/docs/ja/docs/environment-variables.md b/docs/ja/docs/environment-variables.md new file mode 100644 index 0000000000..507af3a0cc --- /dev/null +++ b/docs/ja/docs/environment-variables.md @@ -0,0 +1,301 @@ +# 環境変数 + +/// tip + +もし、「環境変数」とは何か、それをどう使うかを既に知っている場合は、このセクションをスキップして構いません。 + +/// + +環境変数(**env var**とも呼ばれる)はPythonコードの**外側**、つまり**OS**に存在する変数で、Pythonから読み取ることができます。(他のプログラムでも同様に読み取れます。) + +環境変数は、アプリケーションの**設定**の管理や、Pythonの**インストール**などに役立ちます。 + +## 環境変数の作成と使用 + +環境変数は**シェル(ターミナル)**内で**作成**して使用でき、それらにPythonは不要です。 + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// You could create an env var MY_NAME with +$ export MY_NAME="Wade Wilson" + +// Then you could use it with other programs, like +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ + +```console +// Create an env var MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Use it with other programs, like +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Pythonで環境変数を読み取る + +環境変数をPythonの**外側**、ターミナル(や他の方法)で作成し、**Python内で読み取る**こともできます。 + +例えば、以下のような`main.py`ファイルを用意します: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip + +`os.getenv()` の第2引数は、デフォルトで返される値を指定します。 + +この引数を省略するとデフォルト値として`None`が返されますが、ここではデフォルト値として`"World"`を指定しています。 + +/// + +次に、このPythonプログラムを呼び出します。 + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ export MY_NAME="Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ $Env:MY_NAME = "Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +環境変数はコードの外側で設定し、内側から読み取ることができるので、他のファイルと一緒に(`git`に)保存する必要がありません。そのため、環境変数をコンフィグレーションや**設定**に使用することが一般的です。 + +また、**特定のプログラムの呼び出し**のための環境変数を、そのプログラムのみ、その実行中に限定して利用できるよう作成できます。 + +そのためには、プログラム起動コマンドと同じコマンドライン上の、起動コマンド直前で環境変数を作成してください。 + +
+ +```console +// Create an env var MY_NAME in line for this program call +$ MY_NAME="Wade Wilson" python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python + +// The env var no longer exists afterwards +$ python main.py + +Hello World from Python +``` + +
+ +/// tip + +詳しくは The Twelve-Factor App: Config を参照してください。 + +/// + +## 型とバリデーション + +環境変数は**テキスト文字列**のみを扱うことができます。これは、環境変数がPython外部に存在し、他のプログラムやシステム全体(Linux、Windows、macOS間の互換性を含む)と連携する必要があるためです。 + +つまり、Pythonが環境変数から読み取る**あらゆる値**は **`str`型となり**、他の型への変換やバリデーションはコード内で行う必要があります。 + +環境変数を使用して**アプリケーション設定**を管理する方法については、[高度なユーザーガイド - Settings and Environment Variables](./advanced/settings.md){.internal-link target=_blank}で詳しく学べます。 + +## `PATH`環境変数 + +**`PATH`**という**特別な**環境変数があります。この環境変数は、OS(Linux、macOS、Windows)が実行するプログラムを発見するために使用されます。 + +`PATH`変数は、複数のディレクトリのパスから成る長い文字列です。このパスはLinuxやMacOSの場合は`:`で、Windowsの場合は`;`で区切られています。 + +例えば、`PATH`環境変数は次のような文字列かもしれません: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +これは、OSはプログラムを見つけるために以下のディレクトリを探す、ということを意味します: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +これは、OSはプログラムを見つけるために以下のディレクトリを探す、ということを意味します: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +ターミナル上で**コマンド**を入力すると、 OSはそのプログラムを見つけるために、`PATH`環境変数のリストに記載された**それぞれのディレクトリを探し**ます。 + +例えば、ターミナル上で`python`を入力すると、OSは`python`によって呼ばれるプログラムを見つけるために、そのリストの**先頭のディレクトリ**を最初に探します。 + +OSは、もしそのプログラムをそこで発見すれば**実行し**ますが、そうでなければリストの**他のディレクトリ**を探していきます。 + +### PythonのインストールとPATH環境変数の更新 + +Pythonのインストール時に`PATH`環境変数を更新したいか聞かれるかもしれません。 + +/// tab | Linux, macOS + +Pythonをインストールして、そのプログラムが`/opt/custompython/bin`というディレクトリに配置されたとします。 + +もし、`PATH`環境変数を更新するように答えると、`PATH`環境変数に`/opt/custompython/bin`が追加されます。 + +`PATH`環境変数は以下のように更新されるでしょう: + +``` plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +このようにして、ターミナルで`python`と入力したときに、OSは`/opt/custompython/bin`(リストの末尾のディレクトリ)にあるPythonプログラムを見つけ、使用します。 + +/// + +/// tab | Windows + +Pythonをインストールして、そのプログラムが`C:\opt\custompython\bin`というディレクトリに配置されたとします。 + +もし、`PATH`環境変数を更新するように答えると、`PATH`環境変数に`C:\opt\custompython\bin`が追加されます。 + +`PATH`環境変数は以下のように更新されるでしょう: + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +このようにして、ターミナルで`python`と入力したときに、OSは`C:\opt\custompython\bin\python`(リストの末尾のディレクトリ)にあるPythonプログラムを見つけ、使用します。 + +/// + +つまり、ターミナルで以下のコマンドを入力すると: + +
+ +``` console +$ python +``` + +
+ +/// tab | Linux, macOS + +OSは`/opt/custompython/bin`にある`python`プログラムを**見つけ**て実行します。 + +これは、次のコマンドを入力した場合とほとんど同等です: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +/// + +/// tab | Windows + +OSは`C:\opt\custompython\bin\python`にある`python`プログラムを**見つけ**て実行します。 + +これは、次のコマンドを入力した場合とほとんど同等です: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +/// + +この情報は、[Virtual Environments](virtual-environments.md) について学ぶ際にも役立ちます。 + +## まとめ + +これで、**環境変数**とは何か、Pythonでどのように使用するかについて、基本的な理解が得られたはずです。 + +環境変数についての詳細は、Wikipedia: Environment Variable を参照してください。 + +環境変数の用途や適用方法が最初は直感的ではないかもしれませんが、開発中のさまざまなシナリオで繰り返し登場します。そのため、基本を知っておくことが重要です。 + +たとえば、この情報は次のセクションで扱う[Virtual Environments](virtual-environments.md)にも関連します。 diff --git a/docs/ja/docs/features.md b/docs/ja/docs/features.md index 73c0192c70..f78eab4303 100644 --- a/docs/ja/docs/features.md +++ b/docs/ja/docs/features.md @@ -62,7 +62,7 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` -/// info | "情報" +/// info | 情報 `**second_user_data` は以下を意味します: @@ -160,7 +160,7 @@ FastAPIには非常に使いやすく、非常に強力な**FastAPI** についてツイートし、開発者や他の人にどこが気に入ったのか教えてください。🎉 +**FastAPI** についてツイートし、開発者や他の人にどこが気に入ったのか教えてください。🎉 **FastAPI** がどのように使われ、どこが気に入られ、どんなプロジェクト/会社で使われているかなどについて知りたいです。 diff --git a/docs/ja/docs/history-design-future.md b/docs/ja/docs/history-design-future.md index bc4a160eae..6cfd1894d8 100644 --- a/docs/ja/docs/history-design-future.md +++ b/docs/ja/docs/history-design-future.md @@ -59,7 +59,7 @@ そして、JSON Schemaに完全に準拠するようにしたり、制約宣言を定義するさまざまな方法をサポートしたり、いくつかのエディターでのテストに基づいてエディターのサポート (型チェック、自動補完) を改善するために貢献しました。 -開発中、もう1つの重要な鍵となる**Starlette**、にも貢献しました。 +開発中、もう1つの重要な鍵となる**Starlette**、にも貢献しました。 ## 開発 diff --git a/docs/ja/docs/how-to/conditional-openapi.md b/docs/ja/docs/how-to/conditional-openapi.md index b892ed6c64..bfaa9e6d75 100644 --- a/docs/ja/docs/how-to/conditional-openapi.md +++ b/docs/ja/docs/how-to/conditional-openapi.md @@ -29,9 +29,7 @@ 例えば、 -```Python hl_lines="6 11" -{!../../../docs_src/conditional_openapi/tutorial001.py!} -``` +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} ここでは `openapi_url` の設定を、デフォルトの `"/openapi.json"` のまま宣言しています。 diff --git a/docs/ja/docs/index.md b/docs/ja/docs/index.md index 72a0e98bc0..8dee1ee035 100644 --- a/docs/ja/docs/index.md +++ b/docs/ja/docs/index.md @@ -11,14 +11,17 @@ 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

@@ -85,13 +88,13 @@ FastAPI は、Pythonの標準である型ヒントに基づいてPython 以降 "_私は**FastAPI**にワクワクしています。 めちゃくちゃ楽しいです!_" -
Brian Okken - Python Bytes podcast host (ref)
+
Brian Okken - Python Bytes podcast host (ref)
--- "_正直、超堅実で洗練されているように見えます。いろんな意味で、それは私がハグしたかったものです。_" -
Timothy Crosley - Hug creator (ref)
+
Timothy Crosley - Hug creator (ref)
--- @@ -99,7 +102,7 @@ FastAPI は、Pythonの標準である型ヒントに基づいてPython 以降 "_私たちの**API**は**FastAPI**に切り替えました。[...] きっと気に入ると思います。 [...]_" -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
+
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
--- @@ -115,7 +118,7 @@ FastAPI は、Pythonの標準である型ヒントに基づいてPython 以降 FastAPI は巨人の肩の上に立っています。 -- Web の部分はStarlette +- Web の部分はStarlette - データの部分はPydantic ## インストール @@ -130,7 +133,7 @@ $ pip install fastapi
-本番環境では、Uvicorn または、 Hypercornのような、 ASGI サーバーが必要になります。 +本番環境では、Uvicorn または、 Hypercornのような、 ASGI サーバーが必要になります。
@@ -448,7 +451,7 @@ Starlette によって使用されるもの: FastAPI / Starlette に使用されるもの: -- uvicorn - アプリケーションをロードしてサーブするサーバーのため。 +- uvicorn - アプリケーションをロードしてサーブするサーバーのため。 - orjson - `ORJSONResponse`を使用したい場合は必要です。 - ujson - `UJSONResponse`を使用する場合は必須です。 diff --git a/docs/ja/docs/python-types.md b/docs/ja/docs/python-types.md index 730a209ab1..a847ce5d54 100644 --- a/docs/ja/docs/python-types.md +++ b/docs/ja/docs/python-types.md @@ -12,7 +12,7 @@ しかしたとえまったく **FastAPI** を使用しない場合でも、それらについて少し学ぶことで利点を得ることができるでしょう。 -/// note | "備考" +/// note | 備考 もしあなたがPythonの専門家で、すでに型ヒントについてすべて知っているのであれば、次の章まで読み飛ばしてください。 @@ -22,9 +22,8 @@ 簡単な例から始めてみましょう: -```Python -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py *} + このプログラムを実行すると以下が出力されます: @@ -38,9 +37,8 @@ John Doe * `title()`を用いて、それぞれの最初の文字を大文字に変換します。 * 真ん中にスペースを入れて連結します。 -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py hl[2] *} + ### 編集 @@ -82,9 +80,8 @@ John Doe それが「型ヒント」です: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial002.py!} -``` +{* ../../docs_src/python_types/tutorial002.py hl[1] *} + これは、以下のようにデフォルト値を宣言するのと同じではありません: @@ -112,9 +109,8 @@ John Doe この関数を見てください。すでに型ヒントを持っています: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial003.py!} -``` +{* ../../docs_src/python_types/tutorial003.py hl[1] *} + エディタは変数の型を知っているので、補完だけでなく、エラーチェックをすることもできます。 @@ -122,9 +118,8 @@ John Doe これで`age`を`str(age)`で文字列に変換して修正する必要があることがわかります: -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial004.py!} -``` +{* ../../docs_src/python_types/tutorial004.py hl[2] *} + ## 型の宣言 @@ -143,9 +138,8 @@ John Doe * `bool` * `bytes` -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial005.py!} -``` +{* ../../docs_src/python_types/tutorial005.py hl[1] *} + ### 型パラメータを持つジェネリック型 @@ -161,9 +155,8 @@ John Doe `typing`から`List`をインポートします(大文字の`L`を含む): -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial006.py!} -``` +{* ../../docs_src/python_types/tutorial006.py hl[1] *} + 同じようにコロン(`:`)の構文で変数を宣言します。 @@ -171,11 +164,10 @@ John Doe リストはいくつかの内部の型を含む型なので、それらを角括弧で囲んでいます。 -```Python hl_lines="4" -{!../../../docs_src/python_types/tutorial006.py!} -``` +{* ../../docs_src/python_types/tutorial006.py hl[4] *} -/// tip | "豆知識" + +/// tip | 豆知識 角括弧内の内部の型は「型パラメータ」と呼ばれています。 @@ -199,9 +191,8 @@ John Doe `tuple`と`set`の宣言も同様です: -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial007.py!} -``` +{* ../../docs_src/python_types/tutorial007.py hl[1,4] *} + つまり: @@ -217,9 +208,8 @@ John Doe 2番目の型パラメータは`dict`の値です。 -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial008.py!} -``` +{* ../../docs_src/python_types/tutorial008.py hl[1,4] *} + つまり: @@ -232,7 +222,7 @@ John Doe また、`Optional`を使用して、変数が`str`のような型を持つことを宣言することもできますが、それは「オプション」であり、`None`にすることもできます。 ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009.py!} ``` ただの`str`の代わりに`Optional[str]`を使用することで、エディタは値が常に`str`であると仮定している場合に実際には`None`である可能性があるエラーを検出するのに役立ちます。 @@ -256,15 +246,13 @@ John Doe 例えば、`Person`クラスという名前のクラスがあるとしましょう: -```Python hl_lines="1 2 3" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[1,2,3] *} + 変数の型を`Person`として宣言することができます: -```Python hl_lines="6" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[6] *} + そして、再び、すべてのエディタのサポートを得ることができます: @@ -284,11 +272,10 @@ John Doe Pydanticの公式ドキュメントから引用: -```Python -{!../../../docs_src/python_types/tutorial011.py!} -``` +{* ../../docs_src/python_types/tutorial011.py *} -/// info | "情報" + +/// info | 情報 Pydanticについてより学びたい方はドキュメントを参照してください. @@ -320,7 +307,7 @@ Pydanticについてより学びたい方は`mypy`のチートシートを参照してください diff --git a/docs/ja/docs/tutorial/background-tasks.md b/docs/ja/docs/tutorial/background-tasks.md index 6094c370fd..b289faf12d 100644 --- a/docs/ja/docs/tutorial/background-tasks.md +++ b/docs/ja/docs/tutorial/background-tasks.md @@ -15,9 +15,7 @@ まず初めに、`BackgroundTasks` をインポートし、` BackgroundTasks` の型宣言と共に、*path operation 関数* のパラメーターを定義します: -```Python hl_lines="1 13" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} **FastAPI** は、`BackgroundTasks` 型のオブジェクトを作成し、そのパラメーターに渡します。 @@ -33,17 +31,13 @@ また、書き込み操作では `async` と `await` を使用しないため、通常の `def` で関数を定義します。 -```Python hl_lines="6-9" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} ## バックグラウンドタスクの追加 *path operations 関数* 内で、`.add_task()` メソッドを使用してタスク関数を *background tasks* オブジェクトに渡します。 -```Python hl_lines="14" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} `.add_task()` は以下の引数を受け取ります: @@ -57,9 +51,7 @@ **FastAPI** は、それぞれの場合の処理​​方法と同じオブジェクトの再利用方法を知っているため、すべてのバックグラウンドタスクがマージされ、バックグラウンドで後で実行されます。 -```Python hl_lines="13 15 22 25" -{!../../../docs_src/background_tasks/tutorial002.py!} -``` +{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *} この例では、レスポンスが送信された *後* にメッセージが `log.txt` ファイルに書き込まれます。 @@ -69,7 +61,7 @@ ## 技術的な詳細 -`BackgroundTasks` クラスは、`starlette.background`から直接取得されます。 +`BackgroundTasks` クラスは、`starlette.background`から直接取得されます。 これは、FastAPI に直接インポート/インクルードされるため、`fastapi` からインポートできる上に、`starlette.background`から別の `BackgroundTask` (末尾に `s` がない) を誤ってインポートすることを回避できます。 @@ -77,7 +69,7 @@ それでも、FastAPI で `BackgroundTask` を単独で使用することは可能ですが、コード内でオブジェクトを作成し、それを含むStarlette `Response` を返す必要があります。 -詳細については、バックグラウンドタスクに関する Starlette の公式ドキュメントを参照して下さい。 +詳細については、バックグラウンドタスクに関する Starlette の公式ドキュメントを参照して下さい。 ## 警告 @@ -85,8 +77,6 @@ これらは、より複雑な構成、RabbitMQ や Redis などのメッセージ/ジョブキューマネージャーを必要とする傾向がありますが、複数のプロセス、特に複数のサーバーでバックグラウンドタスクを実行できます。 -例を確認するには、[Project Generators](../project-generation.md){.internal-link target=_blank} を参照してください。これらにはすべて、Celery が構築済みです。 - ただし、同じ **FastAPI** アプリから変数とオブジェクトにアクセスする必要がある場合、または小さなバックグラウンドタスク (電子メール通知の送信など) を実行する必要がある場合は、単に `BackgroundTasks` を使用できます。 ## まとめ diff --git a/docs/ja/docs/tutorial/body-fields.md b/docs/ja/docs/tutorial/body-fields.md index b9f6d694b6..ce5630351e 100644 --- a/docs/ja/docs/tutorial/body-fields.md +++ b/docs/ja/docs/tutorial/body-fields.md @@ -6,11 +6,9 @@ まず、以下のようにインポートします: -```Python hl_lines="4" -{!../../../docs_src/body_fields/tutorial001.py!} -``` +{* ../../docs_src/body_fields/tutorial001.py hl[4] *} -/// warning | "注意" +/// warning | 注意 `Field`は他の全てのもの(`Query`、`Path`、`Body`など)とは違い、`fastapi`からではなく、`pydantic`から直接インポートされていることに注意してください。 @@ -20,13 +18,11 @@ 以下のように`Field`をモデルの属性として使用することができます: -```Python hl_lines="11 12 13 14" -{!../../../docs_src/body_fields/tutorial001.py!} -``` +{* ../../docs_src/body_fields/tutorial001.py hl[11,12,13,14] *} `Field`は`Query`や`Path`、`Body`と同じように動作し、全く同様のパラメータなどを持ちます。 -/// note | "技術詳細" +/// note | 技術詳細 実際には次に見る`Query`や`Path`などは、共通の`Param`クラスのサブクラスのオブジェクトを作成しますが、それ自体はPydanticの`FieldInfo`クラスのサブクラスです。 @@ -38,7 +34,7 @@ /// -/// tip | "豆知識" +/// tip | 豆知識 型、デフォルト値、`Field`を持つ各モデルの属性が、`Path`や`Query`、`Body`の代わりに`Field`を持つ、*path operation 関数の*パラメータと同じ構造になっていることに注目してください。 @@ -48,7 +44,7 @@ 追加情報は`Field`や`Query`、`Body`などで宣言することができます。そしてそれは生成されたJSONスキーマに含まれます。 -後に例を用いて宣言を学ぶ際に、追加情報を句悪方法を学べます。 +後に例を用いて宣言を学ぶ際に、追加情報を追加する方法を学べます。 ## まとめ diff --git a/docs/ja/docs/tutorial/body-multiple-params.md b/docs/ja/docs/tutorial/body-multiple-params.md index c051fde248..cbfdda4b21 100644 --- a/docs/ja/docs/tutorial/body-multiple-params.md +++ b/docs/ja/docs/tutorial/body-multiple-params.md @@ -8,11 +8,9 @@ また、デフォルトの`None`を設定することで、ボディパラメータをオプションとして宣言することもできます: -```Python hl_lines="19 20 21" -{!../../../docs_src/body_multiple_params/tutorial001.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial001.py hl[19,20,21] *} -/// note | "備考" +/// note | 備考 この場合、ボディから取得する`item`はオプションであることに注意してください。デフォルト値は`None`です。 @@ -33,9 +31,7 @@ しかし、`item`と`user`のように複数のボディパラメータを宣言することもできます: -```Python hl_lines="22" -{!../../../docs_src/body_multiple_params/tutorial002.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *} この場合、**FastAPI**は関数内に複数のボディパラメータ(Pydanticモデルである2つのパラメータ)があることに気付きます。 @@ -56,7 +52,7 @@ } ``` -/// note | "備考" +/// note | 備考 以前と同じように`item`が宣言されていたにもかかわらず、`item`はキー`item`を持つボディの内部にあることが期待されていることに注意してください。 @@ -77,9 +73,7 @@ しかし、`Body`を使用して、**FastAPI** に別のボディキーとして扱うように指示することができます: -```Python hl_lines="23" -{!../../../docs_src/body_multiple_params/tutorial003.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial003.py hl[23] *} この場合、**FastAPI** は以下のようなボディを期待します: @@ -114,11 +108,9 @@ q: str = None 以下において: -```Python hl_lines="27" -{!../../../docs_src/body_multiple_params/tutorial004.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *} -/// info | "情報" +/// info | 情報 `Body`もまた、後述する `Query` や `Path` などと同様に、すべての検証パラメータとメタデータパラメータを持っています。 @@ -138,9 +130,7 @@ item: Item = Body(..., embed=True) 以下において: -```Python hl_lines="17" -{!../../../docs_src/body_multiple_params/tutorial005.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *} この場合、**FastAPI** は以下のようなボディを期待します: diff --git a/docs/ja/docs/tutorial/body-nested-models.md b/docs/ja/docs/tutorial/body-nested-models.md index 59ee672954..a1680d10f2 100644 --- a/docs/ja/docs/tutorial/body-nested-models.md +++ b/docs/ja/docs/tutorial/body-nested-models.md @@ -6,9 +6,7 @@ 属性をサブタイプとして定義することができます。例えば、Pythonの`list`は以下のように定義できます: -```Python hl_lines="12" -{!../../../docs_src/body_nested_models/tutorial001.py!} -``` +{* ../../docs_src/body_nested_models/tutorial001.py hl[12] *} これにより、各項目の型は宣言されていませんが、`tags`はある項目のリストになります。 @@ -20,9 +18,7 @@ まず、Pythonの標準の`typing`モジュールから`List`をインポートします: -```Python hl_lines="1" -{!../../../docs_src/body_nested_models/tutorial002.py!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} ### タイプパラメータを持つ`List`の宣言 @@ -43,9 +39,7 @@ my_list: List[str] そのため、以下の例では`tags`を具体的な「文字列のリスト」にすることができます: -```Python hl_lines="14" -{!../../../docs_src/body_nested_models/tutorial002.py!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *} ## セット型 @@ -55,9 +49,7 @@ my_list: List[str] そのため、以下のように、`Set`をインポートして`str`の`set`として`tags`を宣言することができます: -```Python hl_lines="1 14" -{!../../../docs_src/body_nested_models/tutorial003.py!} -``` +{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *} これを使えば、データが重複しているリクエストを受けた場合でも、ユニークな項目のセットに変換されます。 @@ -79,17 +71,13 @@ Pydanticモデルの各属性には型があります。 例えば、`Image`モデルを定義することができます: -```Python hl_lines="9 10 11" -{!../../../docs_src/body_nested_models/tutorial004.py!} -``` +{* ../../docs_src/body_nested_models/tutorial004.py hl[9,10,11] *} ### サブモデルを型として使用 そして、それを属性の型として使用することができます: -```Python hl_lines="20" -{!../../../docs_src/body_nested_models/tutorial004.py!} -``` +{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *} これは **FastAPI** が以下のようなボディを期待することを意味します: @@ -122,9 +110,7 @@ Pydanticモデルの各属性には型があります。 例えば、`Image`モデルのように`url`フィールドがある場合、`str`の代わりにPydanticの`HttpUrl`を指定することができます: -```Python hl_lines="4 10" -{!../../../docs_src/body_nested_models/tutorial005.py!} -``` +{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *} 文字列は有効なURLであることが確認され、そのようにJSONスキーマ・OpenAPIで文書化されます。 @@ -132,9 +118,7 @@ Pydanticモデルの各属性には型があります。 Pydanticモデルを`list`や`set`などのサブタイプとして使用することもできます: -```Python hl_lines="20" -{!../../../docs_src/body_nested_models/tutorial006.py!} -``` +{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *} これは、次のようなJSONボディを期待します(変換、検証、ドキュメントなど): @@ -162,7 +146,7 @@ Pydanticモデルを`list`や`set`などのサブタイプとして使用する } ``` -/// info | "情報" +/// info | 情報 `images`キーが画像オブジェクトのリストを持つようになったことに注目してください。 @@ -172,11 +156,9 @@ Pydanticモデルを`list`や`set`などのサブタイプとして使用する 深くネストされた任意のモデルを定義することができます: -```Python hl_lines="9 14 20 23 27" -{!../../../docs_src/body_nested_models/tutorial007.py!} -``` +{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *} -/// info | "情報" +/// info | 情報 `Offer`は`Item`のリストであり、オプションの`Image`のリストを持っていることに注目してください。 @@ -192,9 +174,7 @@ images: List[Image] 以下のように: -```Python hl_lines="15" -{!../../../docs_src/body_nested_models/tutorial008.py!} -``` +{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *} ## あらゆる場所でのエディタサポート @@ -224,11 +204,9 @@ Pydanticモデルではなく、`dict`を直接使用している場合はこの この場合、`int`のキーと`float`の値を持つものであれば、どんな`dict`でも受け入れることができます: -```Python hl_lines="15" -{!../../../docs_src/body_nested_models/tutorial009.py!} -``` +{* ../../docs_src/body_nested_models/tutorial009.py hl[15] *} -/// tip | "豆知識" +/// tip | 豆知識 JSONはキーとして`str`しかサポートしていないことに注意してください。 diff --git a/docs/ja/docs/tutorial/body-updates.md b/docs/ja/docs/tutorial/body-updates.md index 672a03a642..ffbe52e1db 100644 --- a/docs/ja/docs/tutorial/body-updates.md +++ b/docs/ja/docs/tutorial/body-updates.md @@ -6,9 +6,7 @@ `jsonable_encoder`を用いて、入力データをJSON形式で保存できるデータに変換することができます(例:NoSQLデータベース)。例えば、`datetime`を`str`に変換します。 -```Python hl_lines="30 31 32 33 34 35" -{!../../../docs_src/body_updates/tutorial001.py!} -``` +{* ../../docs_src/body_updates/tutorial001.py hl[30,31,32,33,34,35] *} 既存のデータを置き換えるべきデータを受け取るために`PUT`は使用されます。 @@ -34,7 +32,7 @@ つまり、更新したいデータだけを送信して、残りはそのままにしておくことができます。 -/// note | "備考" +/// note | 備考 `PATCH`は`PUT`よりもあまり使われておらず、知られていません。 @@ -56,9 +54,7 @@ これを使うことで、デフォルト値を省略して、設定された(リクエストで送られた)データのみを含む`dict`を生成することができます: -```Python hl_lines="34" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +{* ../../docs_src/body_updates/tutorial002.py hl[34] *} ### Pydanticの`update`パラメータ @@ -66,9 +62,7 @@ `stored_item_model.copy(update=update_data)`のように: -```Python hl_lines="35" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +{* ../../docs_src/body_updates/tutorial002.py hl[35] *} ### 部分的更新のまとめ @@ -85,11 +79,9 @@ * データをDBに保存します。 * 更新されたモデルを返します。 -```Python hl_lines="30 31 32 33 34 35 36 37" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +{* ../../docs_src/body_updates/tutorial002.py hl[30,31,32,33,34,35,36,37] *} -/// tip | "豆知識" +/// tip | 豆知識 実際には、HTTPの`PUT`操作でも同じテクニックを使用することができます。 @@ -97,7 +89,7 @@ /// -/// note | "備考" +/// note | 備考 入力モデルがまだ検証されていることに注目してください。 diff --git a/docs/ja/docs/tutorial/body.md b/docs/ja/docs/tutorial/body.md index 017ff89863..1298eec7eb 100644 --- a/docs/ja/docs/tutorial/body.md +++ b/docs/ja/docs/tutorial/body.md @@ -8,7 +8,7 @@ APIはほとんどの場合 **レスポンス** ボディを送らなければ **リクエスト** ボディを宣言するために Pydantic モデルを使用します。そして、その全てのパワーとメリットを利用します。 -/// info | "情報" +/// info | 情報 データを送るには、`POST` (もっともよく使われる)、`PUT`、`DELETE` または `PATCH` を使うべきです。 @@ -22,9 +22,7 @@ GET リクエストでボディを送信することは、仕様では未定義 ます初めに、 `pydantic` から `BaseModel` をインポートする必要があります: -```Python hl_lines="2" -{!../../../docs_src/body/tutorial001.py!} -``` +{* ../../docs_src/body/tutorial001.py hl[4] *} ## データモデルの作成 @@ -32,9 +30,7 @@ GET リクエストでボディを送信することは、仕様では未定義 すべての属性にpython標準の型を使用します: -```Python hl_lines="5-9" -{!../../../docs_src/body/tutorial001.py!} -``` +{* ../../docs_src/body/tutorial001.py hl[7:11] *} クエリパラメータの宣言と同様に、モデル属性がデフォルト値をもつとき、必須な属性ではなくなります。それ以外は必須になります。オプショナルな属性にしたい場合は `None` を使用してください。 @@ -62,9 +58,7 @@ GET リクエストでボディを送信することは、仕様では未定義 *パスオペレーション* に加えるために、パスパラメータやクエリパラメータと同じ様に宣言します: -```Python hl_lines="16" -{!../../../docs_src/body/tutorial001.py!} -``` +{* ../../docs_src/body/tutorial001.py hl[18] *} ...そして、作成したモデル `Item` で型を宣言します。 @@ -113,7 +107,7 @@ GET リクエストでボディを送信することは、仕様では未定義 -/// tip | "豆知識" +/// tip | 豆知識 PyCharmエディタを使用している場合は、Pydantic PyCharm Pluginが使用可能です。 @@ -131,9 +125,7 @@ GET リクエストでボディを送信することは、仕様では未定義 関数内部で、モデルの全ての属性に直接アクセスできます: -```Python hl_lines="19" -{!../../../docs_src/body/tutorial002.py!} -``` +{* ../../docs_src/body/tutorial002.py hl[21] *} ## リクエストボディ + パスパラメータ @@ -141,9 +133,7 @@ GET リクエストでボディを送信することは、仕様では未定義 **FastAPI** はパスパラメータである関数パラメータは**パスから受け取り**、Pydanticモデルによって宣言された関数パラメータは**リクエストボディから受け取る**ということを認識します。 -```Python hl_lines="15-16" -{!../../../docs_src/body/tutorial003.py!} -``` +{* ../../docs_src/body/tutorial003.py hl[17:18] *} ## リクエストボディ + パスパラメータ + クエリパラメータ @@ -151,9 +141,7 @@ GET リクエストでボディを送信することは、仕様では未定義 **FastAPI** はそれぞれを認識し、適切な場所からデータを取得します。 -```Python hl_lines="16" -{!../../../docs_src/body/tutorial004.py!} -``` +{* ../../docs_src/body/tutorial004.py hl[18] *} 関数パラメータは以下の様に認識されます: @@ -161,7 +149,7 @@ GET リクエストでボディを送信することは、仕様では未定義 * パラメータが**単数型** (`int`、`float`、`str`、`bool` など)の場合は**クエリ**パラメータとして解釈されます。 * パラメータが **Pydantic モデル**型で宣言された場合、リクエスト**ボディ**として解釈されます。 -/// note | "備考" +/// note | 備考 FastAPIは、`= None`があるおかげで、`q`がオプショナルだとわかります。 diff --git a/docs/ja/docs/tutorial/cookie-param-models.md b/docs/ja/docs/tutorial/cookie-param-models.md new file mode 100644 index 0000000000..8285f44efd --- /dev/null +++ b/docs/ja/docs/tutorial/cookie-param-models.md @@ -0,0 +1,77 @@ +# クッキーパラメータモデル + +もし関連する**複数のクッキー**から成るグループがあるなら、それらを宣言するために、**Pydanticモデル**を作成できます。🍪 + +こうすることで、**複数の場所**で**そのPydanticモデルを再利用**でき、バリデーションやメタデータを、すべてのクッキーパラメータに対して一度に宣言できます。😎 + +/// note | 備考 + +この機能は、FastAPIのバージョン `0.115.0` からサポートされています。🤓 + +/// + +/// tip | 豆知識 + +これと同じテクニックは `Query` 、 `Cookie` 、 `Header` にも適用できます。 😎 + +/// + +## クッキーにPydanticモデルを使用する + +必要な複数の**クッキー**パラメータを**Pydanticモデル**で宣言し、さらに、それを `Cookie` として宣言しましょう: + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +**FastAPI**は、リクエストの**クッキー**から**それぞれのフィールド**のデータを**抽出**し、定義された**Pydanticモデル**を提供します。 + +## ドキュメントの確認 + +対話的APIドキュメントUI `/docs` で、定義されているクッキーを確認できます: + +
+ +
+ +/// info | 備考 + + +**ブラウザがクッキーを処理し**ていますが、特別な方法で内部的に処理を行っているために、**JavaScript**からは簡単に操作**できない**ことに留意してください。 + +**対話的APIドキュメントUI** `/docs` にアクセスすれば、*パスオペレーション*に関するクッキーの**ドキュメンテーション**を確認できます。 + +しかし、たとえ**クッキーデータを入力して**「Execute」をクリックしても、対話的APIドキュメントUIは**JavaScript**で動作しているためクッキーは送信されず、まるで値を入力しなかったかのような**エラー**メッセージが表示されます。 + +/// + +## 余分なクッキーを禁止する + +特定の(あまり一般的ではないかもしれない)ケースで、受け付けるクッキーを**制限**する必要があるかもしれません。 + +あなたのAPIは独自の クッキー同意 を管理する能力を持っています。 🤪🍪 + +Pydanticのモデルの Configuration を利用して、 `extra` フィールドを `forbid` とすることができます。 + +{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} + +もしクライアントが**余分なクッキー**を送ろうとすると、**エラー**レスポンスが返されます。 + +どうせAPIに拒否されるのにあなたの同意を得ようと精一杯努力する可哀想なクッキーバナーたち... 🍪 + +例えば、クライアントがクッキー `santa_tracker` を `good-list-please` という値で送ろうとすると、`santa_tracker` という クッキーが許可されていない ことを通知する**エラー**レスポンスが返されます: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## まとめ + +**FastAPI**では、**クッキー**を宣言するために、**Pydanticモデル**を使用できます。😎 diff --git a/docs/ja/docs/tutorial/cookie-params.md b/docs/ja/docs/tutorial/cookie-params.md index 2128852098..13af6d3c77 100644 --- a/docs/ja/docs/tutorial/cookie-params.md +++ b/docs/ja/docs/tutorial/cookie-params.md @@ -6,9 +6,7 @@ まず、`Cookie`をインポートします: -```Python hl_lines="3" -{!../../../docs_src/cookie_params/tutorial001.py!} -``` +{* ../../docs_src/cookie_params/tutorial001.py hl[3] *} ## `Cookie`のパラメータを宣言 @@ -16,11 +14,9 @@ 最初の値がデフォルト値で、追加の検証パラメータや注釈パラメータをすべて渡すことができます: -```Python hl_lines="9" -{!../../../docs_src/cookie_params/tutorial001.py!} -``` +{* ../../docs_src/cookie_params/tutorial001.py hl[9] *} -/// note | "技術詳細" +/// note | 技術詳細 `Cookie`は`Path`と`Query`の「姉妹」クラスです。また、同じ共通の`Param`クラスを継承しています。 @@ -28,7 +24,7 @@ /// -/// info | "情報" +/// info | 情報 クッキーを宣言するには、`Cookie`を使う必要があります。なぜなら、そうしないとパラメータがクエリのパラメータとして解釈されてしまうからです。 diff --git a/docs/ja/docs/tutorial/cors.md b/docs/ja/docs/tutorial/cors.md index 738240342f..f7bd59b709 100644 --- a/docs/ja/docs/tutorial/cors.md +++ b/docs/ja/docs/tutorial/cors.md @@ -46,9 +46,7 @@ * 特定のHTTPメソッド (`POST`、`PUT`) またはワイルドカード `"*"` を使用してすべて許可。 * 特定のHTTPヘッダー、またはワイルドカード `"*"`を使用してすべて許可。 -```Python hl_lines="2 6-11 13-19" -{!../../../docs_src/cors/tutorial001.py!} -``` +{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} `CORSMiddleware` 実装のデフォルトのパラメータはCORSに関して制限を与えるものになっているので、ブラウザにドメインを跨いで特定のオリジン、メソッド、またはヘッダーを使用可能にするためには、それらを明示的に有効にする必要があります @@ -78,7 +76,7 @@ CORSについてより詳しい情報は、Mozilla CORS documentation を参照して下さい。 -/// note | "技術詳細" +/// note | 技術詳細 `from starlette.middleware.cors import CORSMiddleware` も使用できます。 diff --git a/docs/ja/docs/tutorial/debugging.md b/docs/ja/docs/tutorial/debugging.md index 06b8ad2772..6c29679efd 100644 --- a/docs/ja/docs/tutorial/debugging.md +++ b/docs/ja/docs/tutorial/debugging.md @@ -6,9 +6,7 @@ Visual Studio CodeやPyCharmなどを使用して、エディター上でデバ FastAPIアプリケーション上で、`uvicorn` を直接インポートして実行します: -```Python hl_lines="1 15" -{!../../../docs_src/debugging/tutorial001.py!} -``` +{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} ### `__name__ == "__main__"` について @@ -74,7 +72,7 @@ from myapp import app は実行されません。 -/// info | "情報" +/// info | 情報 より詳しい情報は、公式Pythonドキュメントを参照してください。 diff --git a/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md index 69b67d042a..80153529e5 100644 --- a/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md @@ -6,9 +6,7 @@ 前の例では、依存関係("dependable")から`dict`を返していました: -```Python hl_lines="9" -{!../../../docs_src/dependencies/tutorial001.py!} -``` +{* ../../docs_src/dependencies/tutorial001.py hl[9] *} しかし、*path operation関数*のパラメータ`commons`に`dict`が含まれています。 @@ -71,21 +69,15 @@ FastAPIが実際にチェックしているのは、それが「呼び出し可 そこで、上で紹介した依存関係の`common_parameters`を`CommonQueryParams`クラスに変更します: -```Python hl_lines="11 12 13 14 15" -{!../../../docs_src/dependencies/tutorial002.py!} -``` +{* ../../docs_src/dependencies/tutorial002.py hl[11,12,13,14,15] *} クラスのインスタンスを作成するために使用される`__init__`メソッドに注目してください: -```Python hl_lines="12" -{!../../../docs_src/dependencies/tutorial002.py!} -``` +{* ../../docs_src/dependencies/tutorial002.py hl[12] *} ...以前の`common_parameters`と同じパラメータを持っています: -```Python hl_lines="8" -{!../../../docs_src/dependencies/tutorial001.py!} -``` +{* ../../docs_src/dependencies/tutorial001.py hl[8] *} これらのパラメータは **FastAPI** が依存関係を「解決」するために使用するものです。 @@ -101,9 +93,7 @@ FastAPIが実際にチェックしているのは、それが「呼び出し可 これで、このクラスを使用して依存関係を宣言することができます。 -```Python hl_lines="19" -{!../../../docs_src/dependencies/tutorial002.py!} -``` +{* ../../docs_src/dependencies/tutorial002.py hl[19] *} **FastAPI** は`CommonQueryParams`クラスを呼び出します。これにより、そのクラスの「インスタンス」が作成され、インスタンスはパラメータ`commons`として関数に渡されます。 @@ -143,9 +133,7 @@ commons = Depends(CommonQueryParams) 以下にあるように: -```Python hl_lines="19" -{!../../../docs_src/dependencies/tutorial003.py!} -``` +{* ../../docs_src/dependencies/tutorial003.py hl[19] *} しかし、型を宣言することは推奨されています。そうすれば、エディタは`commons`のパラメータとして何が渡されるかを知ることができ、コードの補完や型チェックなどを行うのに役立ちます: @@ -179,13 +167,11 @@ commons: CommonQueryParams = Depends() 同じ例では以下のようになります: -```Python hl_lines="19" -{!../../../docs_src/dependencies/tutorial004.py!} -``` +{* ../../docs_src/dependencies/tutorial004.py hl[19] *} ...そして **FastAPI** は何をすべきか知っています。 -/// tip | "豆知識" +/// tip | 豆知識 役に立つというよりも、混乱するようであれば無視してください。それをする*必要*はありません。 diff --git a/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index c6472cab5c..0fb15ae02c 100644 --- a/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,13 +14,11 @@ それは`Depends()`の`list`であるべきです: -```Python hl_lines="17" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[17] *} これらの依存関係は、通常の依存関係と同様に実行・解決されます。しかし、それらの値(何かを返す場合)は*path operation関数*には渡されません。 -/// tip | "豆知識" +/// tip | 豆知識 エディタによっては、未使用の関数パラメータをチェックしてエラーとして表示するものもあります。 @@ -38,17 +36,13 @@ これらはリクエストの要件(ヘッダのようなもの)やその他のサブ依存関係を宣言することができます: -```Python hl_lines="6 11" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *} ### 例外の発生 これらの依存関係は通常の依存関係と同じように、例外を`raise`発生させることができます: -```Python hl_lines="8 13" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *} ### 戻り値 @@ -56,9 +50,7 @@ つまり、すでにどこかで使っている通常の依存関係(値を返すもの)を再利用することができ、値は使われなくても依存関係は実行されます: -```Python hl_lines="9 14" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *} ## *path operations*のグループに対する依存関係 diff --git a/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md index 3f22a7a7b0..35a69de0df 100644 --- a/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md @@ -4,13 +4,13 @@ FastAPIは、いくつかのしてカスタムの独自ヘッダーを追加できます。 -ただし、ブラウザのクライアントに表示させたいカスタムヘッダーがある場合は、StarletteのCORSドキュメントに記載されているパラメータ `expose_headers` を使用して、それらをCORS設定に追加する必要があります ([CORS (オリジン間リソース共有)](cors.md){.internal-link target=_blank}) +ただし、ブラウザのクライアントに表示させたいカスタムヘッダーがある場合は、StarletteのCORSドキュメントに記載されているパラメータ `expose_headers` を使用して、それらをCORS設定に追加する必要があります ([CORS (オリジン間リソース共有)](cors.md){.internal-link target=_blank}) /// -/// note | "技術詳細" +/// note | 技術詳細 `from starlette.requests import Request` を使用することもできます。 @@ -59,9 +57,7 @@ 例えば、リクエストの処理とレスポンスの生成にかかった秒数を含むカスタムヘッダー `X-Process-Time` を追加できます: -```Python hl_lines="10 12-13" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} ## その他のミドルウェア diff --git a/docs/ja/docs/tutorial/path-operation-configuration.md b/docs/ja/docs/tutorial/path-operation-configuration.md index def12bd081..0cc38cb255 100644 --- a/docs/ja/docs/tutorial/path-operation-configuration.md +++ b/docs/ja/docs/tutorial/path-operation-configuration.md @@ -2,7 +2,7 @@ *path operationデコレータ*を設定するためのパラメータがいくつかあります。 -/// warning | "注意" +/// warning | 注意 これらのパラメータは*path operation関数*ではなく、*path operationデコレータ*に直接渡されることに注意してください。 @@ -16,13 +16,11 @@ しかし、それぞれの番号コードが何のためのものか覚えていない場合は、`status`のショートカット定数を使用することができます: -```Python hl_lines="3 17" -{!../../../docs_src/path_operation_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *} そのステータスコードはレスポンスで使用され、OpenAPIスキーマに追加されます。 -/// note | "技術詳細" +/// note | 技術詳細 また、`from starlette import status`を使用することもできます。 @@ -34,9 +32,7 @@ `tags`パラメータを`str`の`list`(通常は1つの`str`)と一緒に渡すと、*path operation*にタグを追加できます: -```Python hl_lines="17 22 27" -{!../../../docs_src/path_operation_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *} これらはOpenAPIスキーマに追加され、自動ドキュメントのインターフェースで使用されます: @@ -46,9 +42,7 @@ `summary`と`description`を追加できます: -```Python hl_lines="20-21" -{!../../../docs_src/path_operation_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *} ## docstringを用いた説明 @@ -56,9 +50,7 @@ docstringにMarkdownを記述すれば、正しく解釈されて表示されます。(docstringのインデントを考慮して) -```Python hl_lines="19-27" -{!../../../docs_src/path_operation_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} これは対話的ドキュメントで使用されます: @@ -68,17 +60,15 @@ docstringにdeprecatedとしてマークする必要があるが、それを削除しない場合は、`deprecated`パラメータを渡します: -```Python hl_lines="16" -{!../../../docs_src/path_operation_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} 対話的ドキュメントでは非推奨と明記されます: diff --git a/docs/ja/docs/tutorial/path-params-numeric-validations.md b/docs/ja/docs/tutorial/path-params-numeric-validations.md index 9f0b72585f..a1810ae37b 100644 --- a/docs/ja/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ja/docs/tutorial/path-params-numeric-validations.md @@ -6,9 +6,7 @@ まず初めに、`fastapi`から`Path`をインポートします: -```Python hl_lines="1" -{!../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[1] *} ## メタデータの宣言 @@ -16,11 +14,9 @@ 例えば、パスパラメータ`item_id`に対して`title`のメタデータを宣言するには以下のようにします: -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[8] *} -/// note | "備考" +/// note | 備考 パスの一部でなければならないので、パスパラメータは常に必須です。 @@ -46,9 +42,7 @@ Pythonは「デフォルト」を持たない値の前に「デフォルト」 そのため、以下のように関数を宣言することができます: -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[8] *} ## 必要に応じてパラメータを並び替えるトリック @@ -58,19 +52,15 @@ Pythonは「デフォルト」を持たない値の前に「デフォルト」 Pythonはその`*`で何かをすることはありませんが、それ以降のすべてのパラメータがキーワード引数(キーと値のペア)として呼ばれるべきものであると知っているでしょう。それはkwargsとしても知られています。たとえデフォルト値がなくても。 -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[8] *} ## 数値の検証: 以上 `Query`と`Path`(、そして後述する他のもの)を用いて、文字列の制約を宣言することができますが、数値の制約も同様に宣言できます。 -ここで、`ge=1`の場合、`item_id`は`1`「より大きい`g`か、同じ`e`」整数でなれけばなりません。 +ここで、`ge=1`の場合、`item_id`は`1`「より大きい`g`か、同じ`e`」整数でなれけばなりません。 -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} ## 数値の検証: より大きいと小なりイコール @@ -79,9 +69,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降 * `gt`: より大きい(`g`reater `t`han) * `le`: 小なりイコール(`l`ess than or `e`qual) -```Python hl_lines="9" -{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *} ## 数値の検証: 浮動小数点、 大なり小なり @@ -93,9 +81,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降 これはltも同じです。 -```Python hl_lines="11" -{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} ## まとめ @@ -108,7 +94,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降 * `lt`: より小さい(`l`ess `t`han) * `le`: 以下(`l`ess than or `e`qual) -/// info | "情報" +/// info | 情報 `Query`、`Path`などは後に共通の`Param`クラスのサブクラスを見ることになります。(使う必要はありません) @@ -116,9 +102,9 @@ Pythonはその`*`で何かをすることはありませんが、それ以降 /// -/// note | "技術詳細" +/// note | 技術詳細 -`fastapi`から`Query`、`Path`などをインポートすると、これらは実際には関数です。 +`fastapi`から`Query`、`Path`などをインポートすると、これらは実際には関数です。 呼び出されると、同じ名前のクラスのインスタンスを返します。 diff --git a/docs/ja/docs/tutorial/path-params.md b/docs/ja/docs/tutorial/path-params.md index 0a79160127..1893ec12f4 100644 --- a/docs/ja/docs/tutorial/path-params.md +++ b/docs/ja/docs/tutorial/path-params.md @@ -2,9 +2,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメータ」や「パス変数」を宣言できます: -```Python hl_lines="6 7" -{!../../../docs_src/path_params/tutorial001.py!} -``` +{* ../../docs_src/path_params/tutorial001.py hl[6,7] *} パスパラメータ `item_id` の値は、引数 `item_id` として関数に渡されます。 @@ -18,13 +16,11 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー 標準のPythonの型アノテーションを使用して、関数内のパスパラメータの型を宣言できます: -```Python hl_lines="7" -{!../../../docs_src/path_params/tutorial002.py!} -``` +{* ../../docs_src/path_params/tutorial002.py hl[7] *} ここでは、 `item_id` は `int` として宣言されています。 -/// check | "確認" +/// check | 確認 これにより、関数内でのエディターサポート (エラーチェックや補完など) が提供されます。 @@ -38,7 +34,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー {"item_id":3} ``` -/// check | "確認" +/// check | 確認 関数が受け取った(および返した)値は、文字列の `"3"` ではなく、Pythonの `int` としての `3` であることに注意してください。 @@ -69,7 +65,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー http://127.0.0.1:8000/items/4.2 で見られるように、intのかわりに `float` が与えられた場合にも同様なエラーが表示されます。 -/// check | "確認" +/// check | 確認 したがって、Pythonの型宣言を使用することで、**FastAPI**はデータのバリデーションを行います。 @@ -85,7 +81,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー -/// check | "確認" +/// check | 確認 繰り返しになりますが、Python型宣言を使用するだけで、**FastAPI**は対話的なAPIドキュメントを自動的に生成します(Swagger UIを統合)。 @@ -121,9 +117,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー *path operations* は順に評価されるので、 `/users/me` が `/users/{user_id}` よりも先に宣言されているか確認する必要があります: -```Python hl_lines="6 11" -{!../../../docs_src/path_params/tutorial003.py!} -``` +{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} それ以外の場合、 `/users/{users_id}` は `/users/me` としてもマッチします。値が「"me"」であるパラメータ `user_id` を受け取ると「考え」ます。 @@ -139,17 +133,15 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー そして、固定値のクラス属性を作ります。すると、その値が使用可能な値となります: -```Python hl_lines="1 6 7 8 9" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[1,6,7,8,9] *} -/// info | "情報" +/// info | 情報 Enumerations (もしくは、enums)はPython 3.4以降で利用できます。 /// -/// tip | "豆知識" +/// tip | 豆知識 "AlexNet"、"ResNet"そして"LeNet"は機械学習モデルの名前です。 @@ -159,9 +151,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー 次に、作成したenumクラスである`ModelName`を使用した型アノテーションをもつ*パスパラメータ*を作成します: -```Python hl_lines="16" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[16] *} ### ドキュメントの確認 @@ -177,19 +167,15 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー これは、作成した列挙型 `ModelName` の*列挙型メンバ*と比較できます: -```Python hl_lines="17" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[17] *} #### *列挙値*の取得 `model_name.value` 、もしくは一般に、 `your_enum_member.value` を使用して実際の値 (この場合は `str`) を取得できます。 -```Python hl_lines="20" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[20] *} -/// tip | "豆知識" +/// tip | 豆知識 `ModelName.lenet.value` でも `"lenet"` 値にアクセスできます。 @@ -201,9 +187,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー それらはクライアントに返される前に適切な値 (この場合は文字列) に変換されます。 -```Python hl_lines="18 21 23" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} クライアントは以下の様なJSONレスポンスを得ます: @@ -242,11 +226,9 @@ Starletteのオプションを直接使用することで、以下のURLの様 したがって、以下の様に使用できます: -```Python hl_lines="6" -{!../../../docs_src/path_params/tutorial004.py!} -``` +{* ../../docs_src/path_params/tutorial004.py hl[6] *} -/// tip | "豆知識" +/// tip | 豆知識 最初のスラッシュ (`/`)が付いている `/home/johndoe/myfile.txt` をパラメータが含んでいる必要があります。 diff --git a/docs/ja/docs/tutorial/query-param-models.md b/docs/ja/docs/tutorial/query-param-models.md new file mode 100644 index 0000000000..053d0740bc --- /dev/null +++ b/docs/ja/docs/tutorial/query-param-models.md @@ -0,0 +1,68 @@ +# クエリパラメータモデル + +もし関連する**複数のクエリパラメータ**から成るグループがあるなら、それらを宣言するために、**Pydanticモデル**を作成できます。 + +こうすることで、**複数の場所**で**そのPydanticモデルを再利用**でき、バリデーションやメタデータを、すべてのクエリパラメータに対して一度に宣言できます。😎 + +/// note | 備考 + +この機能は、FastAPIのバージョン `0.115.0` からサポートされています。🤓 + +/// + +## クエリパラメータにPydanticモデルを使用する + +必要な**複数のクエリパラメータ**を**Pydanticモデル**で宣言し、さらに、それを `Query` として宣言しましょう: + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +**FastAPI**は、リクエストの**クエリパラメータ**からそれぞれの**フィールド**のデータを**抽出**し、定義された**Pydanticモデル**を提供します。 + +## ドキュメントの確認 + +対話的APIドキュメント `/docs` でクエリパラメータを確認できます: + +
+ +
+ +## 余分なクエリパラメータを禁止する + +特定の(あまり一般的ではないかもしれない)ケースで、受け付けるクエリパラメータを**制限**する必要があるかもしれません。 + +Pydanticのモデルの Configuration を利用して、 `extra` フィールドを `forbid` とすることができます。 + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +もしクライアントが**クエリパラメータ**として**余分な**データを送ろうとすると、**エラー**レスポンスが返されます。 + +例えば、クライアントがクエリパラメータ `tool` に、値 `plumbus` を設定して送ろうとすると: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +クエリパラメータ `tool` が許可されていないことを通知する**エラー**レスポンスが返されます。 + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## まとめ + +**FastAPI**では、**クエリパラメータ**を宣言するために、**Pydanticモデル**を使用できます。😎 + +/// tip | 豆知識 + +ネタバレ注意: Pydanticモデルはクッキーやヘッダーの宣言にも使用できますが、その内容については後のチュートリアルで学びます。🤫 + +/// diff --git a/docs/ja/docs/tutorial/query-params-str-validations.md b/docs/ja/docs/tutorial/query-params-str-validations.md index ada0488449..22b89e452f 100644 --- a/docs/ja/docs/tutorial/query-params-str-validations.md +++ b/docs/ja/docs/tutorial/query-params-str-validations.md @@ -4,13 +4,11 @@ 以下のアプリケーションを例にしてみましょう: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial001.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *} クエリパラメータ `q` は `Optional[str]` 型で、`None` を許容する `str` 型を意味しており、デフォルトは `None` です。そのため、FastAPIはそれが必須ではないと理解します。 -/// note | "備考" +/// note | 備考 FastAPIは、 `q` はデフォルト値が `=None` であるため、必須ではないと理解します。 @@ -26,17 +24,13 @@ FastAPIは、 `q` はデフォルト値が `=None` であるため、必須で そのために、まずは`fastapi`から`Query`をインポートします: -```Python hl_lines="3" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *} ## デフォルト値として`Query`を使用 パラメータのデフォルト値として使用し、パラメータ`max_length`を50に設定します: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} デフォルト値`None`を`Query(default=None)`に置き換える必要があるので、`Query`の最初の引数はデフォルト値を定義するのと同じです。 @@ -54,7 +48,7 @@ q: Optional[str] = None しかし、これはクエリパラメータとして明示的に宣言しています。 -/// info | "情報" +/// info | 情報 FastAPIは以下の部分を気にすることを覚えておいてください: @@ -86,17 +80,13 @@ q: Union[str, None] = Query(default=None, max_length=50) パラメータ`min_length`も追加することができます: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial003.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *} ## 正規表現の追加 パラメータが一致するべき正規表現を定義することができます: -```Python hl_lines="11" -{!../../../docs_src/query_params_str_validations/tutorial004.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *} この特定の正規表現は受け取ったパラメータの値をチェックします: @@ -114,11 +104,9 @@ q: Union[str, None] = Query(default=None, max_length=50) クエリパラメータ`q`の`min_length`を`3`とし、デフォルト値を`fixedquery`としてみましょう: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial005.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} -/// note | "備考" +/// note | 備考 デフォルト値を指定すると、パラメータは任意になります。 @@ -146,11 +134,9 @@ q: Union[str, None] = Query(default=None, min_length=3) そのため、`Query`を使用して必須の値を宣言する必要がある場合は、第一引数に`...`を使用することができます: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial006.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} -/// info | "情報" +/// info | 情報 これまで`...`を見たことがない方へ: これは特殊な単一値です。Pythonの一部であり、"Ellipsis"と呼ばれています。 @@ -164,9 +150,7 @@ q: Union[str, None] = Query(default=None, min_length=3) 例えば、URL内に複数回出現するクエリパラメータ`q`を宣言するには以下のように書きます: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial011.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} そしてURLは以下です: @@ -187,7 +171,7 @@ http://localhost:8000/items/?q=foo&q=bar } ``` -/// tip | "豆知識" +/// tip | 豆知識 上述の例のように、`list`型のクエリパラメータを宣言するには明示的に`Query`を使用する必要があります。そうしない場合、リクエストボディと解釈されます。 @@ -201,9 +185,7 @@ http://localhost:8000/items/?q=foo&q=bar また、値が指定されていない場合はデフォルトの`list`を定義することもできます。 -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial012.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} 以下のURLを開くと: @@ -226,11 +208,9 @@ http://localhost:8000/items/ `List[str]`の代わりに直接`list`を使うこともできます: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial013.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} -/// note | "備考" +/// note | 備考 この場合、FastAPIはリストの内容をチェックしないことを覚えておいてください。 @@ -244,7 +224,7 @@ http://localhost:8000/items/ その情報は、生成されたOpenAPIに含まれ、ドキュメントのユーザーインターフェースや外部のツールで使用されます。 -/// note | "備考" +/// note | 備考 ツールによってOpenAPIのサポートのレベルが異なる可能性があることを覚えておいてください。 @@ -254,15 +234,11 @@ http://localhost:8000/items/ `title`を追加できます: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial007.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial007.py hl[9] *} `description`を追加できます: -```Python hl_lines="13" -{!../../../docs_src/query_params_str_validations/tutorial008.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} ## エイリアスパラメータ @@ -282,9 +258,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems それならば、`alias`を宣言することができます。エイリアスはパラメータの値を見つけるのに使用されます: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial009.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} ## 非推奨パラメータ @@ -294,9 +268,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems その場合、`Query`にパラメータ`deprecated=True`を渡します: -```Python hl_lines="18" -{!../../../docs_src/query_params_str_validations/tutorial010.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} ドキュメントは以下のようになります: diff --git a/docs/ja/docs/tutorial/query-params.md b/docs/ja/docs/tutorial/query-params.md index c0eb2d0964..74e455579e 100644 --- a/docs/ja/docs/tutorial/query-params.md +++ b/docs/ja/docs/tutorial/query-params.md @@ -2,9 +2,7 @@ パスパラメータではない関数パラメータを宣言すると、それらは自動的に "クエリ" パラメータとして解釈されます。 -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial001.py!} -``` +{* ../../docs_src/query_params/tutorial001.py hl[9] *} クエリはURL内で `?` の後に続くキーとバリューの組で、 `&` で区切られています。 @@ -63,13 +61,11 @@ http://127.0.0.1:8000/items/?skip=20 同様に、デフォルト値を `None` とすることで、オプショナルなクエリパラメータを宣言できます: -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial002.py!} -``` +{* ../../docs_src/query_params/tutorial002.py hl[9] *} この場合、関数パラメータ `q` はオプショナルとなり、デフォルトでは `None` になります。 -/// check | "確認" +/// check | 確認 パスパラメータ `item_id` はパスパラメータであり、`q` はそれとは違ってクエリパラメータであると判別できるほど**FastAPI** が賢いということにも注意してください。 @@ -79,9 +75,7 @@ http://127.0.0.1:8000/items/?skip=20 `bool` 型も宣言できます。これは以下の様に変換されます: -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial003.py!} -``` +{* ../../docs_src/query_params/tutorial003.py hl[9] *} この場合、以下にアクセスすると: @@ -123,9 +117,7 @@ http://127.0.0.1:8000/items/foo?short=yes 名前で判別されます: -```Python hl_lines="8 10" -{!../../../docs_src/query_params/tutorial004.py!} -``` +{* ../../docs_src/query_params/tutorial004.py hl[8,10] *} ## 必須のクエリパラメータ @@ -135,9 +127,7 @@ http://127.0.0.1:8000/items/foo?short=yes しかしクエリパラメータを必須にしたい場合は、ただデフォルト値を宣言しなければよいです: -```Python hl_lines="6-7" -{!../../../docs_src/query_params/tutorial005.py!} -``` +{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} ここで、クエリパラメータ `needy` は `str` 型の必須のクエリパラメータです @@ -181,9 +171,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy そして当然、あるパラメータを必須に、別のパラメータにデフォルト値を設定し、また別のパラメータをオプショナルにできます: -```Python hl_lines="10" -{!../../../docs_src/query_params/tutorial006.py!} -``` +{* ../../docs_src/query_params/tutorial006.py hl[10] *} この場合、3つのクエリパラメータがあります。: @@ -191,7 +179,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy * `skip`、デフォルト値を `0` とする `int` 。 * `limit`、オプショナルな `int` 。 -/// tip | "豆知識" +/// tip | 豆知識 [パスパラメータ](path-params.md#_8){.internal-link target=_blank}と同様に `Enum` を使用できます。 diff --git a/docs/ja/docs/tutorial/request-forms-and-files.md b/docs/ja/docs/tutorial/request-forms-and-files.md index d8effc219e..110e3106a6 100644 --- a/docs/ja/docs/tutorial/request-forms-and-files.md +++ b/docs/ja/docs/tutorial/request-forms-and-files.md @@ -2,7 +2,7 @@ `File`と`Form`を同時に使うことでファイルとフォームフィールドを定義することができます。 -/// info | "情報" +/// info | 情報 アップロードされたファイルやフォームデータを受信するには、まず`python-multipart`をインストールします。 @@ -12,23 +12,19 @@ ## `File`と`Form`のインポート -```Python hl_lines="1" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *} ## `File`と`Form`のパラメータの定義 ファイルやフォームのパラメータは`Body`や`Query`の場合と同じように作成します: -```Python hl_lines="8" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *} ファイルとフォームフィールドがフォームデータとしてアップロードされ、ファイルとフォームフィールドを受け取ります。 また、いくつかのファイルを`bytes`として、いくつかのファイルを`UploadFile`として宣言することができます。 -/// warning | "注意" +/// warning | 注意 *path operation*で複数の`File`と`Form`パラメータを宣言することができますが、JSONとして受け取ることを期待している`Body`フィールドを宣言することはできません。なぜなら、リクエストのボディは`application/json`の代わりに`multipart/form-data`を使ってエンコードされているからです。 diff --git a/docs/ja/docs/tutorial/request-forms.md b/docs/ja/docs/tutorial/request-forms.md index d04dc810ba..eca2cd6dc7 100644 --- a/docs/ja/docs/tutorial/request-forms.md +++ b/docs/ja/docs/tutorial/request-forms.md @@ -2,7 +2,7 @@ JSONの代わりにフィールドを受け取る場合は、`Form`を使用します。 -/// info | "情報" +/// info | 情報 フォームを使うためには、まず`python-multipart`をインストールします。 @@ -14,17 +14,13 @@ JSONの代わりにフィールドを受け取る場合は、`Form`を使用し `fastapi`から`Form`をインポートします: -```Python hl_lines="1" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[1] *} ## `Form`のパラメータの定義 `Body`や`Query`の場合と同じようにフォームパラメータを作成します: -```Python hl_lines="7" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[7] *} 例えば、OAuth2仕様が使用できる方法の1つ(「パスワードフロー」と呼ばれる)では、フォームフィールドとして`username`と`password`を送信する必要があります。 @@ -32,13 +28,13 @@ JSONの代わりにフィールドを受け取る場合は、`Form`を使用し `Form`では`Body`(および`Query`や`Path`、`Cookie`)と同じメタデータとバリデーションを宣言することができます。 -/// info | "情報" +/// info | 情報 `Form`は`Body`を直接継承するクラスです。 /// -/// tip | "豆知識" +/// tip | 豆知識 フォームのボディを宣言するには、明示的に`Form`を使用する必要があります。なぜなら、これを使わないと、パラメータはクエリパラメータやボディ(JSON)パラメータとして解釈されるからです。 @@ -50,7 +46,7 @@ HTMLフォーム(`
`)がサーバにデータを送信する方 **FastAPI** は、JSONの代わりにそのデータを適切な場所から読み込むようにします。 -/// note | "技術詳細" +/// note | 技術詳細 フォームからのデータは通常、`application/x-www-form-urlencoded`の「media type」を使用してエンコードされます。 @@ -60,7 +56,7 @@ HTMLフォーム(`
`)がサーバにデータを送信する方 /// -/// warning | "注意" +/// warning | 注意 *path operation*で複数の`Form`パラメータを宣言することができますが、JSONとして受け取ることを期待している`Body`フィールドを宣言することはできません。なぜなら、リクエストは`application/json`の代わりに`application/x-www-form-urlencoded`を使ってボディをエンコードするからです。 diff --git a/docs/ja/docs/tutorial/response-model.md b/docs/ja/docs/tutorial/response-model.md index 7bb5e28251..b8464a4c73 100644 --- a/docs/ja/docs/tutorial/response-model.md +++ b/docs/ja/docs/tutorial/response-model.md @@ -8,11 +8,9 @@ * `@app.delete()` * など。 -```Python hl_lines="17" -{!../../../docs_src/response_model/tutorial001.py!} -``` +{* ../../docs_src/response_model/tutorial001.py hl[17] *} -/// note | "備考" +/// note | 備考 `response_model`は「デコレータ」メソッド(`get`、`post`など)のパラメータであることに注意してください。すべてのパラメータやボディのように、*path operation関数* のパラメータではありません。 @@ -31,7 +29,7 @@ FastAPIは`response_model`を使って以下のことをします: * 出力データをモデルのデータに限定します。これがどのように重要なのか以下で見ていきましょう。 -/// note | "技術詳細" +/// note | 技術詳細 レスポンスモデルは、関数の戻り値のアノテーションではなく、このパラメータで宣言されています。なぜなら、パス関数は実際にはそのレスポンスモデルを返すのではなく、`dict`やデータベースオブジェクト、あるいは他のモデルを返し、`response_model`を使用してフィールドの制限やシリアライズを行うからです。 @@ -41,15 +39,11 @@ FastAPIは`response_model`を使って以下のことをします: ここでは`UserIn`モデルを宣言しています。それには平文のパスワードが含まれています: -```Python hl_lines="9 11" -{!../../../docs_src/response_model/tutorial002.py!} -``` +{* ../../docs_src/response_model/tutorial002.py hl[9,11] *} そして、このモデルを使用して入力を宣言し、同じモデルを使って出力を宣言しています: -```Python hl_lines="17 18" -{!../../../docs_src/response_model/tutorial002.py!} -``` +{* ../../docs_src/response_model/tutorial002.py hl[17,18] *} これで、ブラウザがパスワードを使ってユーザーを作成する際に、APIがレスポンスで同じパスワードを返すようになりました。 @@ -57,7 +51,7 @@ FastAPIは`response_model`を使って以下のことをします: しかし、同じモデルを別の*path operation*に使用すると、すべてのクライアントにユーザーのパスワードを送信してしまうことになります。 -/// danger | "危険" +/// danger | 危険 ユーザーの平文のパスワードを保存したり、レスポンスで送信したりすることは絶対にしないでください。 @@ -67,21 +61,15 @@ FastAPIは`response_model`を使って以下のことをします: 代わりに、平文のパスワードを持つ入力モデルと、パスワードを持たない出力モデルを作成することができます: -```Python hl_lines="9 11 16" -{!../../../docs_src/response_model/tutorial003.py!} -``` +{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *} ここでは、*path operation関数*がパスワードを含む同じ入力ユーザーを返しているにもかかわらず: -```Python hl_lines="24" -{!../../../docs_src/response_model/tutorial003.py!} -``` +{* ../../docs_src/response_model/tutorial003.py hl[24] *} ...`response_model`を`UserOut`と宣言したことで、パスワードが含まれていません: -```Python hl_lines="22" -{!../../../docs_src/response_model/tutorial003.py!} -``` +{* ../../docs_src/response_model/tutorial003.py hl[22] *} そのため、**FastAPI** は出力モデルで宣言されていない全てのデータをフィルタリングしてくれます(Pydanticを使用)。 @@ -99,9 +87,7 @@ FastAPIは`response_model`を使って以下のことをします: レスポンスモデルにはデフォルト値を設定することができます: -```Python hl_lines="11 13 14" -{!../../../docs_src/response_model/tutorial004.py!} -``` +{* ../../docs_src/response_model/tutorial004.py hl[11,13,14] *} * `description: str = None`は`None`がデフォルト値です。 * `tax: float = 10.5`は`10.5`がデフォルト値です。 @@ -115,9 +101,7 @@ FastAPIは`response_model`を使って以下のことをします: *path operation デコレータ*に`response_model_exclude_unset=True`パラメータを設定することができます: -```Python hl_lines="24" -{!../../../docs_src/response_model/tutorial004.py!} -``` +{* ../../docs_src/response_model/tutorial004.py hl[24] *} そして、これらのデフォルト値はレスポンスに含まれず、実際に設定された値のみが含まれます。 @@ -130,13 +114,13 @@ FastAPIは`response_model`を使って以下のことをします: } ``` -/// info | "情報" +/// info | 情報 FastAPIはこれをするために、Pydanticモデルの`.dict()`をその`exclude_unset`パラメータで使用しています。 /// -/// info | "情報" +/// info | 情報 以下も使用することができます: @@ -180,7 +164,7 @@ FastAPIは十分に賢いので(実際には、Pydanticが十分に賢い)`d そのため、それらはJSONレスポンスに含まれることになります。 -/// tip | "豆知識" +/// tip | 豆知識 デフォルト値は`None`だけでなく、なんでも良いことに注意してください。 例えば、リスト(`[]`)や`10.5`の`float`などです。 @@ -195,7 +179,7 @@ FastAPIは十分に賢いので(実際には、Pydanticが十分に賢い)`d これは、Pydanticモデルが1つしかなく、出力からいくつかのデータを削除したい場合のクイックショートカットとして使用することができます。 -/// tip | "豆知識" +/// tip | 豆知識 それでも、これらのパラメータではなく、複数のクラスを使用して、上記のようなアイデアを使うことをおすすめします。 @@ -205,11 +189,9 @@ FastAPIは十分に賢いので(実際には、Pydanticが十分に賢い)`d /// -```Python hl_lines="31 37" -{!../../../docs_src/response_model/tutorial005.py!} -``` +{* ../../docs_src/response_model/tutorial005.py hl[31,37] *} -/// tip | "豆知識" +/// tip | 豆知識 `{"name", "description"}`の構文はこれら2つの値をもつ`set`を作成します。 @@ -221,9 +203,7 @@ FastAPIは十分に賢いので(実際には、Pydanticが十分に賢い)`d もし`set`を使用することを忘れて、代わりに`list`や`tuple`を使用しても、FastAPIはそれを`set`に変換して正しく動作します: -```Python hl_lines="31 37" -{!../../../docs_src/response_model/tutorial006.py!} -``` +{* ../../docs_src/response_model/tutorial006.py hl[31,37] *} ## まとめ diff --git a/docs/ja/docs/tutorial/response-status-code.md b/docs/ja/docs/tutorial/response-status-code.md index 945767894f..6d197d543d 100644 --- a/docs/ja/docs/tutorial/response-status-code.md +++ b/docs/ja/docs/tutorial/response-status-code.md @@ -8,11 +8,9 @@ * `@app.delete()` * など。 -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} -/// note | "備考" +/// note | 備考 `status_code`は「デコレータ」メソッド(`get`、`post`など)のパラメータであることに注意してください。すべてのパラメータやボディのように、*path operation関数*のものではありません。 @@ -20,7 +18,7 @@ `status_code`パラメータはHTTPステータスコードを含む数値を受け取ります。 -/// info | "情報" +/// info | 情報 `status_code`は代わりに、Pythonの`http.HTTPStatus`のように、`IntEnum`を受け取ることもできます。 @@ -33,7 +31,7 @@ -/// note | "備考" +/// note | 備考 いくつかのレスポンスコード(次のセクションを参照)は、レスポンスにボディがないことを示しています。 @@ -43,7 +41,7 @@ FastAPIはこれを知っていて、レスポンスボディがないというO ## HTTPステータスコードについて -/// note | "備考" +/// note | 備考 すでにHTTPステータスコードが何であるかを知っている場合は、次のセクションにスキップしてください。 @@ -66,7 +64,7 @@ HTTPでは、レスポンスの一部として3桁の数字のステータス * クライアントからの一般的なエラーについては、`400`を使用することができます。 * `500`以上はサーバーエラーのためのものです。これらを直接使うことはほとんどありません。アプリケーションコードやサーバーのどこかで何か問題が発生した場合、これらのステータスコードのいずれかが自動的に返されます。 -/// tip | "豆知識" +/// tip | 豆知識 それぞれのステータスコードとどのコードが何のためのコードなのかについて詳細はMDN HTTP レスポンスステータスコードについてのドキュメントを参照してください。 @@ -76,9 +74,7 @@ HTTPでは、レスポンスの一部として3桁の数字のステータス 先ほどの例をもう一度見てみましょう: -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} `201`は「作成完了」のためのステータスコードです。 @@ -86,15 +82,13 @@ HTTPでは、レスポンスの一部として3桁の数字のステータス `fastapi.status`の便利な変数を利用することができます。 -```Python hl_lines="1 6" -{!../../../docs_src/response_status_code/tutorial002.py!} -``` +{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} それらは便利です。それらは同じ番号を保持しており、その方法ではエディタの自動補完を使用してそれらを見つけることができます。 -/// note | "技術詳細" +/// note | 技術詳細 また、`from starlette import status`を使うこともできます。 diff --git a/docs/ja/docs/tutorial/schema-extra-example.md b/docs/ja/docs/tutorial/schema-extra-example.md index a3cd5eb549..1834e67b27 100644 --- a/docs/ja/docs/tutorial/schema-extra-example.md +++ b/docs/ja/docs/tutorial/schema-extra-example.md @@ -10,9 +10,7 @@ JSON Schemaの追加情報を宣言する方法はいくつかあります。 Pydanticのドキュメント: スキーマのカスタマイズで説明されているように、`Config`と`schema_extra`を使ってPydanticモデルの例を宣言することができます: -```Python hl_lines="15 16 17 18 19 20 21 22 23" -{!../../../docs_src/schema_extra_example/tutorial001.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial001.py hl[15,16,17,18,19,20,21,22,23] *} その追加情報はそのまま出力され、JSON Schemaに追加されます。 @@ -20,11 +18,9 @@ JSON Schemaの追加情報を宣言する方法はいくつかあります。 後述する`Field`、`Path`、`Query`、`Body`などでは、任意の引数を関数に渡すことでJSON Schemaの追加情報を宣言することもできます: -```Python hl_lines="4 10 11 12 13" -{!../../../docs_src/schema_extra_example/tutorial002.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial002.py hl[4,10,11,12,13] *} -/// warning | "注意" +/// warning | 注意 これらの追加引数が渡されても、文書化のためのバリデーションは追加されず、注釈だけが追加されることを覚えておいてください。 @@ -36,9 +32,7 @@ JSON Schemaの追加情報を宣言する方法はいくつかあります。 例えば、`Body`にボディリクエストの`example`を渡すことができます: -```Python hl_lines="21 22 23 24 25 26" -{!../../../docs_src/schema_extra_example/tutorial003.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial003.py hl[21,22,23,24,25,26] *} ## ドキュメントのUIの例 diff --git a/docs/ja/docs/tutorial/security/first-steps.md b/docs/ja/docs/tutorial/security/first-steps.md index c78a3755e9..0ce0f929be 100644 --- a/docs/ja/docs/tutorial/security/first-steps.md +++ b/docs/ja/docs/tutorial/security/first-steps.md @@ -20,13 +20,11 @@ `main.py`に、下記の例をコピーします: -```Python -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py *} ## 実行 -/// info | "情報" +/// info | 情報 まず`python-multipart`をインストールします。 @@ -56,7 +54,7 @@ $ uvicorn main:app --reload -/// check | "Authorizeボタン!" +/// check | Authorizeボタン! すでにピカピカの新しい「Authorize」ボタンがあります。 @@ -68,7 +66,7 @@ $ uvicorn main:app --reload -/// note | "備考" +/// note | 備考 フォームに何を入力しても、まだうまくいきません。ですが、これから動くようになります。 @@ -114,7 +112,7 @@ OAuth2は、バックエンドやAPIがユーザーを認証するサーバー この例では、**Bearer**トークンを使用して**OAuth2**を**パスワード**フローで使用します。これには`OAuth2PasswordBearer`クラスを使用します。 -/// info | "情報" +/// info | 情報 「bearer」トークンが、唯一の選択肢ではありません。 @@ -128,11 +126,9 @@ OAuth2は、バックエンドやAPIがユーザーを認証するサーバー `OAuth2PasswordBearer` クラスのインスタンスを作成する時に、パラメーター`tokenUrl`を渡します。このパラメーターには、クライアント (ユーザーのブラウザで動作するフロントエンド) がトークンを取得するために`ユーザー名`と`パスワード`を送信するURLを指定します。 -```Python hl_lines="6" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[6] *} -/// tip | "豆知識" +/// tip | 豆知識 ここで、`tokenUrl="token"`は、まだ作成していない相対URL`token`を指します。相対URLなので、`./token`と同じです。 @@ -146,7 +142,7 @@ OAuth2は、バックエンドやAPIがユーザーを認証するサーバー 実際のpath operationもすぐに作ります。 -/// info | "情報" +/// info | 情報 非常に厳格な「Pythonista」であれば、パラメーター名のスタイルが`token_url`ではなく`tokenUrl`であることを気に入らないかもしれません。 @@ -168,15 +164,13 @@ oauth2_scheme(some, parameters) これで`oauth2_scheme`を`Depends`で依存関係に渡すことができます。 -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} この依存関係は、*path operation function*のパラメーター`token`に代入される`str`を提供します。 **FastAPI**は、この依存関係を使用してOpenAPIスキーマ (および自動APIドキュメント) で「セキュリティスキーム」を定義できることを知っています。 -/// info | "技術詳細" +/// info | 技術詳細 **FastAPI**は、`OAuth2PasswordBearer` クラス (依存関係で宣言されている) を使用してOpenAPIのセキュリティスキームを定義できることを知っています。これは`fastapi.security.oauth2.OAuth2`、`fastapi.security.base.SecurityBase`を継承しているからです。 diff --git a/docs/ja/docs/tutorial/security/get-current-user.md b/docs/ja/docs/tutorial/security/get-current-user.md index 250f66b818..9fc46c07c5 100644 --- a/docs/ja/docs/tutorial/security/get-current-user.md +++ b/docs/ja/docs/tutorial/security/get-current-user.md @@ -2,9 +2,7 @@ 一つ前の章では、(依存性注入システムに基づいた)セキュリティシステムは、 *path operation関数* に `str` として `token` を与えていました: -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} しかし、それはまだそんなに有用ではありません。 @@ -16,9 +14,7 @@ ボディを宣言するのにPydanticを使用するのと同じやり方で、Pydanticを別のどんなところでも使うことができます: -```Python hl_lines="5 12-16" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[5,12:16] *} ## 依存関係 `get_current_user` を作成 @@ -30,31 +26,25 @@ 以前直接 *path operation* の中でしていたのと同じように、新しい依存関係である `get_current_user` は `str` として `token` を受け取るようになります: -```Python hl_lines="25" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[25] *} ## ユーザーの取得 `get_current_user` は作成した(偽物の)ユーティリティ関数を使って、 `str` としてトークンを受け取り、先ほどのPydanticの `User` モデルを返却します: -```Python hl_lines="19-22 26-27" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *} ## 現在のユーザーの注入 ですので、 `get_current_user` に対して同様に *path operation* の中で `Depends` を利用できます。 -```Python hl_lines="31" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[31] *} Pydanticモデルの `User` として、 `current_user` の型を宣言することに注意してください。 その関数の中ですべての入力補完や型チェックを行う際に役に立ちます。 -/// tip | "豆知識" +/// tip | 豆知識 リクエストボディはPydanticモデルでも宣言できることを覚えているかもしれません。 @@ -62,7 +52,7 @@ Pydanticモデルの `User` として、 `current_user` の型を宣言するこ /// -/// check | "確認" +/// check | 確認 依存関係システムがこのように設計されているおかげで、 `User` モデルを返却する別の依存関係(別の"dependables")を持つことができます。 @@ -103,9 +93,7 @@ Pydanticモデルの `User` として、 `current_user` の型を宣言するこ さらに、こうした何千もの *path operations* は、たった3行で表現できるのです: -```Python hl_lines="30-32" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[30:32] *} ## まとめ diff --git a/docs/ja/docs/tutorial/security/index.md b/docs/ja/docs/tutorial/security/index.md index c68e7e7f2f..14f2c8f448 100644 --- a/docs/ja/docs/tutorial/security/index.md +++ b/docs/ja/docs/tutorial/security/index.md @@ -22,7 +22,7 @@ OAuth2は、認証と認可を処理するためのいくつかの方法を定 これには「サードパーティ」を使用して認証する方法が含まれています。 -これが、「Facebook、Google、Twitter、GitHubを使ってログイン」を使用したすべてのシステムの背後で使われている仕組みです。 +これが、「Facebook、Google、X (Twitter)、GitHubを使ってログイン」を使用したすべてのシステムの背後で使われている仕組みです。 ### OAuth 1 @@ -32,7 +32,7 @@ OAuth 1というものもありましたが、これはOAuth2とは全く異な OAuth2は、通信を暗号化する方法を指定せず、アプリケーションがHTTPSで提供されることを想定しています。 -/// tip | "豆知識" +/// tip | 豆知識 **デプロイ**のセクションでは、TraefikとLet's Encryptを使用して、無料でHTTPSを設定する方法が紹介されています。 @@ -79,7 +79,7 @@ OpenAPIでは、以下のセキュリティスキームを定義しています: * HTTP Basic認証 * HTTP ダイジェスト認証など * `oauth2`: OAuth2のセキュリティ処理方法(「フロー」と呼ばれます)のすべて。 - * これらのフローのいくつかは、OAuth 2.0認証プロバイダ(Google、Facebook、Twitter、GitHubなど)を構築するのに適しています。 + * これらのフローのいくつかは、OAuth 2.0認証プロバイダ(Google、Facebook、X (Twitter)、GitHubなど)を構築するのに適しています。 * `implicit` * `clientCredentials` * `authorizationCode` @@ -89,9 +89,9 @@ OpenAPIでは、以下のセキュリティスキームを定義しています: * この自動検出メカニズムは、OpenID Connectの仕様で定義されているものです。 -/// tip | "豆知識" +/// tip | 豆知識 -Google、Facebook、Twitter、GitHubなど、他の認証/認可プロバイダを統合することも可能で、比較的簡単です。 +Google、Facebook、X (Twitter)、GitHubなど、他の認証/認可プロバイダを統合することも可能で、比較的簡単です。 最も複雑な問題は、それらのような認証/認可プロバイダを構築することですが、**FastAPI**は、あなたのために重い仕事をこなしながら、それを簡単に行うためのツールを提供します。 diff --git a/docs/ja/docs/tutorial/security/oauth2-jwt.md b/docs/ja/docs/tutorial/security/oauth2-jwt.md index 4f6aebd4c3..599fc7b069 100644 --- a/docs/ja/docs/tutorial/security/oauth2-jwt.md +++ b/docs/ja/docs/tutorial/security/oauth2-jwt.md @@ -44,7 +44,7 @@ $ pip install python-jose[cryptography] ここでは、推奨されているものを使用します:pyca/cryptography。 -/// tip | "豆知識" +/// tip | 豆知識 このチュートリアルでは以前、PyJWTを使用していました。 @@ -86,7 +86,7 @@ $ pip install passlib[bcrypt]
-/// tip | "豆知識" +/// tip | 豆知識 `passlib`を使用すると、**Django**や**Flask**のセキュリティプラグインなどで作成されたパスワードを読み取れるように設定できます。 @@ -102,7 +102,7 @@ $ pip install passlib[bcrypt] PassLib の「context」を作成します。これは、パスワードのハッシュ化と検証に使用されるものです。 -/// tip | "豆知識" +/// tip | 豆知識 PassLibのcontextには、検証だけが許された非推奨の古いハッシュアルゴリズムを含む、様々なハッシュアルゴリズムを使用した検証機能もあります。 @@ -118,11 +118,9 @@ PassLibのcontextには、検証だけが許された非推奨の古いハッシ さらに、ユーザーを認証して返す関数も作成します。 -```Python hl_lines="7 48 55-56 59-60 69-75" -{!../../../docs_src/security/tutorial004.py!} -``` +{* ../../docs_src/security/tutorial004.py hl[7,48,55:56,59:60,69:75] *} -/// note | "備考" +/// note | 備考 新しい(偽の)データベース`fake_users_db`を確認すると、ハッシュ化されたパスワードが次のようになっていることがわかります:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"` @@ -156,9 +154,7 @@ JWTトークンの署名に使用するアルゴリズム`"HS256"`を指定し 新しいアクセストークンを生成するユーティリティ関数を作成します。 -```Python hl_lines="6 12-14 28-30 78-86" -{!../../../docs_src/security/tutorial004.py!} -``` +{* ../../docs_src/security/tutorial004.py hl[6,12:14,28:30,78:86] *} ## 依存関係の更新 @@ -168,9 +164,7 @@ JWTトークンの署名に使用するアルゴリズム`"HS256"`を指定し トークンが無効な場合は、すぐにHTTPエラーを返します。 -```Python hl_lines="89-106" -{!../../../docs_src/security/tutorial004.py!} -``` +{* ../../docs_src/security/tutorial004.py hl[89:106] *} ## `/token` パスオペレーションの更新 @@ -178,9 +172,7 @@ JWTトークンの署名に使用するアルゴリズム`"HS256"`を指定し JWTアクセストークンを作成し、それを返します。 -```Python hl_lines="115-130" -{!../../../docs_src/security/tutorial004.py!} -``` +{* ../../docs_src/security/tutorial004.py hl[115:130] *} ### JWTの"subject" `sub` についての技術的な詳細 @@ -219,7 +211,7 @@ IDの衝突を回避するために、ユーザーのJWTトークンを作成す Username: `johndoe` Password: `secret` -/// check | "確認" +/// check | 確認 コードのどこにも平文のパスワード"`secret`"はなく、ハッシュ化されたものしかないことを確認してください。 @@ -244,7 +236,7 @@ Password: `secret` -/// note | "備考" +/// note | 備考 ヘッダーの`Authorization`には、`Bearer`で始まる値があります。 @@ -280,4 +272,4 @@ OAuth2には、「スコープ」という概念があります。 また、OAuth2のような安全で標準的なプロトコルを比較的簡単な方法で使用できるだけではなく、実装することもできます。 -OAuth2の「スコープ」を使って、同じ基準でより細かい権限システムを実現する方法については、**高度なユーザーガイド**で詳しく説明しています。スコープ付きのOAuth2は、Facebook、Google、GitHub、Microsoft、Twitterなど、多くの大手認証プロバイダが、サードパーティのアプリケーションと自社のAPIとのやり取りをユーザーに代わって認可するために使用している仕組みです。 +OAuth2の「スコープ」を使って、同じ基準でより細かい権限システムを実現する方法については、**高度なユーザーガイド**で詳しく説明しています。スコープ付きのOAuth2は、Facebook、Google、GitHub、Microsoft、X (Twitter)など、多くの大手認証プロバイダが、サードパーティのアプリケーションと自社のAPIとのやり取りをユーザーに代わって認可するために使用している仕組みです。 diff --git a/docs/ja/docs/tutorial/static-files.md b/docs/ja/docs/tutorial/static-files.md index c9d95bc347..f910d7e36d 100644 --- a/docs/ja/docs/tutorial/static-files.md +++ b/docs/ja/docs/tutorial/static-files.md @@ -7,11 +7,9 @@ * `StaticFiles` をインポート。 * `StaticFiles()` インスタンスを生成し、特定のパスに「マウント」。 -```Python hl_lines="2 6" -{!../../../docs_src/static_files/tutorial001.py!} -``` +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} -/// note | "技術詳細" +/// note | 技術詳細 `from starlette.staticfiles import StaticFiles` も使用できます。 @@ -39,4 +37,4 @@ ## より詳しい情報 -詳細とオプションについては、Starletteの静的ファイルに関するドキュメントを確認してください。 +詳細とオプションについては、Starletteの静的ファイルに関するドキュメントを確認してください。 diff --git a/docs/ja/docs/tutorial/testing.md b/docs/ja/docs/tutorial/testing.md index 3ed03ebeaa..4e8ad4f7cc 100644 --- a/docs/ja/docs/tutorial/testing.md +++ b/docs/ja/docs/tutorial/testing.md @@ -1,6 +1,6 @@ # テスト -Starlette のおかげで、**FastAPI** アプリケーションのテストは簡単で楽しいものになっています。 +Starlette のおかげで、**FastAPI** アプリケーションのテストは簡単で楽しいものになっています。 HTTPX がベースなので、非常に使いやすく直感的です。 @@ -18,11 +18,9 @@ チェックしたい Python の標準的な式と共に、シンプルに `assert` 文を記述します。 -```Python hl_lines="2 12 15-18" -{!../../../docs_src/app_testing/tutorial001.py!} -``` +{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} -/// tip | "豆知識" +/// tip | 豆知識 テスト関数は `async def` ではなく、通常の `def` であることに注意してください。 @@ -32,7 +30,7 @@ /// -/// note | "技術詳細" +/// note | 技術詳細 `from starlette.testclient import TestClient` も使用できます。 @@ -40,7 +38,7 @@ /// -/// tip | "豆知識" +/// tip | 豆知識 FastAPIアプリケーションへのリクエストの送信とは別に、テストで `async` 関数 (非同期データベース関数など) を呼び出したい場合は、高度なチュートリアルの[Async Tests](../advanced/async-tests.md){.internal-link target=_blank} を参照してください。 @@ -56,17 +54,13 @@ FastAPIアプリケーションへのリクエストの送信とは別に、テ **FastAPI** アプリに `main.py` ファイルがあるとします: -```Python -{!../../../docs_src/app_testing/main.py!} -``` +{* ../../docs_src/app_testing/main.py *} ### テストファイル 次に、テストを含む `test_main.py` ファイルを作成し、`main` モジュール (`main.py`) から `app` をインポートします: -```Python -{!../../../docs_src/app_testing/test_main.py!} -``` +{* ../../docs_src/app_testing/test_main.py *} ## テスト: 例の拡張 @@ -83,29 +77,13 @@ FastAPIアプリケーションへのリクエストの送信とは別に、テ これらの *path operation* には `X-Token` ヘッダーが必要です。 -//// tab | Python 3.10+ - -```Python -{!> ../../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// - -//// tab | Python 3.6+ - -```Python -{!> ../../../docs_src/app_testing/app_b/main.py!} -``` - -//// +{* ../../docs_src/app_testing/app_b_py310/main.py *} ### 拡張版テストファイル 次に、先程のものに拡張版のテストを加えた、`test_main_b.py` を作成します。 -```Python -{!> ../../../docs_src/app_testing/app_b/test_main.py!} -``` +{* ../../docs_src/app_testing/app_b/test_main.py *} リクエストに情報を渡せるクライアントが必要で、その方法がわからない場合はいつでも、`httpx` での実現方法を検索 (Google) できます。 @@ -121,7 +99,7 @@ FastAPIアプリケーションへのリクエストの送信とは別に、テ (`httpx` または `TestClient` を使用して) バックエンドにデータを渡す方法の詳細は、HTTPXのドキュメントを確認してください。 -/// info | "情報" +/// info | 情報 `TestClient` は、Pydanticモデルではなく、JSONに変換できるデータを受け取ることに注意してください。 diff --git a/docs/ja/docs/virtual-environments.md b/docs/ja/docs/virtual-environments.md new file mode 100644 index 0000000000..791cf64a84 --- /dev/null +++ b/docs/ja/docs/virtual-environments.md @@ -0,0 +1,831 @@ +# 仮想環境 + +Pythonプロジェクトの作業では、**仮想環境**(または類似の仕組み)を使用し、プロジェクトごとにインストールするパッケージを分離するべきでしょう。 + +/// info | 情報 + +もし、仮想環境の概要や作成方法、使用方法について既にご存知なら、このセクションをスキップすることができます。🤓 + +/// + +/// tip | 豆知識 + +**仮想環境**は、**環境変数**とは異なります。 + +**環境変数**は、プログラムが使用できるシステム内の変数です。 + +**仮想環境**は、ファイルをまとめたディレクトリのことです。 + +/// + +/// info | 情報 +このページでは、**仮想環境**の使用方法と、そのはたらきについて説明します。 + +もし**すべてを管理するツール**(Pythonのインストールも含む)を導入する準備ができているなら、uv をお試しください。 + +/// + +## プロジェクトの作成 + +まず、プロジェクト用のディレクトリを作成します。 + +私は通常 home/user ディレクトリの中に `code` というディレクトリを用意していて、プロジェクトごとに1つのディレクトリをその中に作成しています。 + +
+ +```console +// Go to the home directory +$ cd +// Create a directory for all your code projects +$ mkdir code +// Enter into that code directory +$ cd code +// Create a directory for this project +$ mkdir awesome-project +// Enter into that project directory +$ cd awesome-project +``` + +
+ +## 仮想環境の作成 + +Pythonプロジェクトでの**初めての**作業を開始する際には、**プロジェクト内**に仮想環境を作成してください。 + +/// tip | 豆知識 + +これを行うのは、**プロジェクトごとに1回だけ**です。作業のたびに行う必要はありません。 + +/// + +//// tab | `venv` + +仮想環境を作成するには、Pythonに付属している `venv` モジュールを使用できます。 + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | このコマンドの意味 + +- `python` : `python` というプログラムを呼び出します +- `-m` : モジュールをスクリプトとして呼び出します。どのモジュールを呼び出すのか、この次に指定します +- `venv` : 通常Pythonに付随してインストールされる `venv`モジュールを使用します +- `.venv` : 仮想環境を`.venv`という新しいディレクトリに作成します + +/// + +//// + +//// tab | `uv` + +もし `uv` をインストール済みなら、仮想環境を作成するために `uv` を使うこともできます。 + +
+ +```console +$ uv venv +``` + +
+ +/// tip | 豆知識 + +デフォルトでは、 `uv` は `.venv` というディレクトリに仮想環境を作成します。 + +ただし、追加の引数にディレクトリ名を与えてカスタマイズすることもできます。 + +/// + +//// + +このコマンドは `.venv` というディレクトリに新しい仮想環境を作成します。 + +/// details | `.venv` またはその他の名前 + +仮想環境を別のディレクトリに作成することも可能ですが、 `.venv` と名付けるのが一般的な慣習です。 + +/// + +## 仮想環境の有効化 + +実行されるPythonコマンドやインストールされるパッケージが新しく作成した仮想環境を使用するよう、その仮想環境を有効化しましょう。 + +/// tip | 豆知識 + +そのプロジェクトの作業で**新しいターミナルセッション**を開始する際には、**毎回**有効化が必要です。 + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +もしWindowsでBashを使用している場合 (Git Bashなど): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip | 豆知識 + +**新しいパッケージ**を仮想環境にインストールするときには、再度**有効化**してください。 + +こうすることで、そのパッケージがインストールした**ターミナル(CLI)プログラム**を使用する場合に、仮想環境内のものが確実に使われ、グローバル環境にインストールされている別のもの(おそらく必要なものとは異なるバージョン)を誤って使用することを防ぎます。 + +/// + +## 仮想環境が有効であることを確認する + +仮想環境が有効である(前のコマンドが正常に機能した)ことを確認します。 + +/// tip | 豆知識 + +これは**任意**ですが、すべてが期待通りに機能し、意図した仮想環境を使用していることを**確認する**良い方法です。 + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +`.venv/bin/python` にある `python` バイナリが、プロジェクト(この場合は `awesome-project` )内に表示されていれば、正常に動作しています 🎉。 + +//// + +//// tab | Windows PowerShell + +
+ +``` console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +`.venv\Scripts\python` にある `python` バイナリが、プロジェクト(この場合は `awesome-project` )内に表示されていれば、正常に動作しています 🎉。 + +//// + +## `pip` をアップグレードする + +/// tip | 豆知識 + +もし `uv` を使用している場合は、 `pip` の代わりに `uv` を使ってインストールを行うため、 `pip` をアップグレードする必要はありません 😎。 + +/// + +もしパッケージのインストールに `pip`(Pythonに標準で付属しています)を使用しているなら、 `pip` を最新バージョンに**アップグレード**しましょう。 + +パッケージのインストール中に発生する想定外のエラーの多くは、最初に `pip` をアップグレードしておくだけで解決されます。 + +/// tip | 豆知識 + +通常、これは仮想環境を作成した直後に**一度だけ**実行します。 + +/// + +仮想環境が有効であることを(上で説明したコマンドで)確認し、アップグレードを実行しましょう: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## `.gitignore` を追加する + +**Git**を使用している場合(使用するべきでしょう)、 `.gitignore` ファイルを追加して、 `.venv` 内のあらゆるファイルをGitの管理対象から除外します。 + +/// tip | 豆知識 + +もし `uv` を使用して仮想環境を作成した場合、すでにこの作業は済んでいるので、この手順をスキップできます 😎。 + +/// + +/// tip | 豆知識 + +これも、仮想環境を作成した直後に**一度だけ**実行します。 + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | このコマンドの意味 + +- `echo "*"` : ターミナルに `*` というテキストを「表示」しようとします。(次の部分によってその動作が少し変わります) +- `>` : `>` の左側のコマンドがターミナルに表示しようとする内容を、ターミナルには表示せず、 `>` の右側のファイルに書き込みます。 +- `.gitignore` : `*` を書き込むファイル名。 + +ここで、Gitにおける `*` は「すべて」を意味するので、このコマンドによって `.venv` ディレクトリ内のすべてがGitに無視されるようになります。 + +このコマンドは以下のテキストを持つ `.gitignore` ファイルを作成します: + +```gitignore +* +``` + +/// + +## パッケージのインストール + +仮想環境を有効化した後、その中でパッケージをインストールできます。 + +/// tip | 豆知識 + +プロジェクトに必要なパッケージをインストールまたはアップグレードする場合、これを**一度**実行します。 + +もし新しいパッケージを追加したり、バージョンをアップグレードする必要がある場合は、もう**一度この手順を繰り返し**ます。 + +/// + +### パッケージを直接インストールする + +急いでいて、プロジェクトのパッケージ要件を宣言するファイルを使いたくない場合、パッケージを直接インストールできます。 + +/// tip | 豆知識 + +プログラムが必要とするパッケージとバージョンをファイル(例えば `requirements.txt` や `pyproject.toml` )に記載しておくのは、(とても)良い考えです。 + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +もし `uv` を使用できるなら: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### `requirements.txt` からインストールする + +もし `requirements.txt` があるなら、パッケージのインストールに使用できます。 + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +もし `uv` を使用できるなら: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +パッケージが記載された `requirements.txt` は以下のようになっています: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## プログラムを実行する + +仮想環境を有効化した後、プログラムを実行できます。この際、仮想環境内のPythonと、そこにインストールしたパッケージが使用されます。 + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## エディタの設定 + +プロジェクトではおそらくエディタを使用するでしょう。コード補完やインラインエラーの表示ができるように、作成した仮想環境をエディタでも使えるよう設定してください。(多くの場合、自動検出されます) + +設定例: + +* VS Code +* PyCharm + +/// tip | 豆知識 + +この設定は通常、仮想環境を作成した際に**一度だけ**行います。 + +/// + +## 仮想環境の無効化 + +プロジェクトの作業が終了したら、その仮想環境を**無効化**できます。 + +
+ +```console +$ deactivate +``` + +
+ +これにより、 `python` コマンドを実行しても、そのプロジェクト用(のパッケージがインストールされた)仮想環境から `python` プログラムを呼び出そうとはしなくなります。 + +## 作業準備完了 + +ここまでで、プロジェクトの作業を始める準備が整いました。 + +/// tip | 豆知識 + +上記の内容を理解したいですか? + +もしそうなら、以下を読み進めてください。👇🤓 + +/// + +## なぜ仮想環境? + +FastAPIを使った作業をするには、 [Python](https://www.python.org/) のインストールが必要です。 + +それから、FastAPIや、使用したいその他の**パッケージ**を**インストール**する必要があります。 + +パッケージをインストールするには、通常、Python に付属する `pip` コマンド (または同様の代替コマンド) を使用します。 + +ただし、`pip` を直接使用すると、パッケージは**グローバルなPython環境**(OS全体にインストールされたPython環境)にインストールされます。 + +### 問題点 + +では、グローバルPython環境にパッケージをインストールすることの問題点は何でしょうか? + +ある時点で、あなたは**異なるパッケージ**に依存する多くのプログラムを書くことになるでしょう。そして、これらの中には同じパッケージの**異なるバージョン**に依存するものも出てくるでしょう。😱 + +例えば、 `philosophers-stone` (賢者の石)というプロジェクトを作成するとします。このプログラムは **`harry` (ハリー)というパッケージのバージョン `1`**に依存しています。そのため、 `harry` (ハリー)をインストールする必要があります。 + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +それから、 `prisoner-of-azkaban` (アズカバンの囚人)という別のプロジェクトを作成したとします。このプロジェクトも `harry` (ハリー)に依存していますが、**`harry` (ハリー)のバージョン `3`**が必要です。 + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +しかし、ここで問題になるのは、もしローカルの**仮想環境**ではなくグローバル(環境)にパッケージをインストールするなら、 `harry` (ハリー)のどのバージョンをインストールするか選ばないといけないことです。 + +例えば、 `philosophers-stone` (賢者の石)を実行するには、まず `harry` (ハリー)のバージョン `1` をインストールする必要があります: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +これにより、`harry` (ハリー)バージョン1がグローバルなPython環境にインストールされます。 + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +しかし、 `prisoner-of-azkaban` (アズカバンの囚人)を実行したい場合は、`harry` (ハリー)のバージョン `1` をアンインストールし、`harry` (ハリー)のバージョン `3` をインストールし直す必要があります。(あるいは、単に`harry` (ハリー)のバージョン `3` をインストールすることで、自動的にバージョン `1` がアンインストールされます) + +
+ +```console +$ pip install "harry==3" +``` + +
+ +このようにして、グローバル環境への `harry` (ハリー)のバージョン `3` のインストールが完了します。 + +それから、 `philosophers-stone` (賢者の石)を再び実行しようとすると、このプログラムは `harry` (ハリー)のバージョン `1` が必要なため、**動作しなくなる**可能性があります。 + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip | 豆知識 + +Pythonのパッケージでは、**新しいバージョン**で**互換性を損なう変更を避ける**よう努めるのが一般的ですが、それでも注意が必要です。すべてが正常に動作することをテストで確認してから、意図的に指定して新しいバージョンをインストールするのが良いでしょう。 + +/// + +あなたのすべての**プロジェクトが依存している**、**多数の**他の**パッケージ**が上記の問題を抱えていると想像してください。これは管理が非常に困難です。そして、**互換性のないバージョン**のパッケージを使ってプロジェクトを実行し、なぜ動作しないのか分からなくなるでしょう。 + +また、使用しているOS(Linux、Windows、macOS など)によっては、Pythonがすでにインストールされていることがあります。この場合、特定のバージョンのパッケージが**OSの動作に必要である**ことがあります。グローバル環境にパッケージをインストールすると、OSに付属するプログラムを**壊してしまう**可能性があります。 + +## パッケージのインストール先 + +Pythonをインストールしたとき、ファイルを含んだいくつかのディレクトリが作成されます。 + +これらの中には、インストールされたパッケージを保存するためのものもあります。 + +以下のコマンドを実行したとき: + +
+ +```console +// Don't run this now, it's just an example 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +FastAPIのコードを含む圧縮ファイルが、通常は [PyPI](https://pypi.org/project/fastapi/) からダウンロードされます。 + +また、FastAPIが依存する他のパッケージも**ダウンロード**されます。 + +それから、これらのファイルは**解凍**され、コンピュータのあるディレクトリに配置されます。 + +デフォルトでは、これらのファイルはPythonのインストール時に作成されるディレクトリ、つまり**グローバル環境**に配置されます。 + +## 仮想環境とは + +すべてのパッケージをグローバル環境に配置することによって生じる問題の解決策は、作業する**プロジェクトごとの仮想環境**を使用することです。 + +仮想環境は**ディレクトリ**であり、グローバル環境と非常に似ていて、一つのプロジェクトで使う特定のパッケージ群をインストールできる場所です。 + +このようにして、それぞれのプロジェクトが独自の仮想環境(`.venv` ディレクトリ)に独自のパッケージ群を持つことができます。 + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## 仮想環境の有効化とは + +仮想環境を有効にしたとき、例えば次のコマンドを実行した場合を考えます: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +あるいは、WindowsでBashを使用している場合 (Git Bashなど): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +これによって、いくつかの [環境変数](environment-variables.md){.internal-link target=_blank} が作成・修正され、次に実行されるコマンドで使用できるようになります。 + +これらの環境変数のひとつに、 `PATH` 変数があります。 + +/// tip | 豆知識 + +`PATH` 変数についての詳細は [環境変数](environment-variables.md#path環境変数){.internal-link target=_blank} を参照してください。 + +/// + +仮想環境を有効にすると、その仮想環境のパス `.venv/bin` (LinuxとmacOS)、あるいは `.venv\Scripts` (Windows)が `PATH` 変数に追加されます。 + +その環境を有効にする前の `PATH` 変数が次のようになっているとします。 + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +これは、OSが以下のディレクトリ中でプログラムを探すことを意味します: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +これは、OSが以下のディレクトリ中でプログラムを探すことを意味します: + +* `C:\Windows\System32` + +//// + +仮想環境を有効にすると、 `PATH` 変数は次のようになります。 + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +これは、OSが他のディレクトリを探すより前に、最初に以下のディレクトリ中でプログラムを探し始めることを意味します: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +そのため、ターミナルで `python` と入力した際に、OSはPythonプログラムを以下のパスで発見し、使用します。 + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +これは、OSが他のディレクトリを探すより前に、最初に以下のディレクトリ中でプログラムを探し始めることを意味します: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +そのため、ターミナルで `python` と入力した際に、OSはPythonプログラムを以下のパスで発見し、使用します。 + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +//// + +重要な点は、仮想環境のパスを `PATH` 変数の**先頭**に配置することです。OSは利用可能な他のPythonを見つけるより**前に**、この仮想環境のPythonを見つけるようになります。このようにして、 `python` を実行したときに、他の `python` (例えばグローバル環境の `python` )ではなく、**その仮想環境の**Pythonを使用するようになります。 + +仮想環境を有効にして変更されることは他にもありますが、これが最も重要な変更のひとつです。 + +## 仮想環境の確認 + +仮想環境が有効かどうか、例えば次のように確認できます。: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +これは、使用される `python` プログラムが**その仮想環境の**ものであることを意味します。 + +LinuxやmacOSでは `which` を、Windows PowerShellでは `Get-Command` を使用します。 + +このコマンドの動作は、 `PATH`変数に設定された**それぞれのパスを順に**確認していき、呼ばれている `python` プログラムを探します。そして、見つかり次第そのプログラムへの**パスを表示します**。 + +最も重要なことは、 `python` が呼ばれたときに、まさにこのコマンドで確認した "`python`" が実行されることです。 + +こうして、自分が想定通りの仮想環境にいるかを確認できます。 + +/// tip | 豆知識 + +ある仮想環境を有効にし、そのPythonを使用したまま**他のプロジェクトに移動して**しまうことは簡単に起こり得ます。 + +そして、その第二のプロジェクトは動作しないでしょう。なぜなら別のプロジェクトの仮想環境の**誤ったPython**を使用しているからです。 + +そのため、どの `python` が使用されているのか確認できることは役立ちます。🤓 + +/// + +## なぜ仮想環境を無効化するのか + +例えば、`philosophers-stone` (賢者の石)というプロジェクトで作業をしていて、**その仮想環境を有効にし**、必要なパッケージをインストールしてその環境内で作業を進めているとします。 + +それから、**別のプロジェクト**、 `prisoner-of-azkaban` (アズカバンの囚人)に取り掛かろうとします。 + +そのプロジェクトディレクトリへ移動します: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +もし `philosophers-stone` (賢者の石)の仮想環境を無効化していないと、`python` を実行したとき、 ターミナルは `philosophers-stone` (賢者の石)のPythonを使用しようとします。 + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Error importing sirius, it's not installed 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +しかし、その仮想環境を無効化し、 `prisoner-of-azkaban` (アズカバンの囚人)のための新しい仮想環境を有効にすれば、 `python` を実行したときに `prisoner-of-azkaban` (アズカバンの囚人)の仮想環境の Python が使用されるようになります。 + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎 +$ deactivate + +// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀 +$ source .venv/bin/activate + +// Now when you run python, it will find the package sirius installed in this virtual environment ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## 代替手段 + +これは、あらゆる仕組みを**根本から**学ぶためのシンプルな入門ガイドです。 + +仮想環境、パッケージの依存関係(requirements)、プロジェクトの管理には、多くの**代替手段**があります。 + +準備が整い、パッケージの依存関係、仮想環境など**プロジェクト全体の管理**ツールを使いたいと考えたら、uv を試してみることをおすすめします。 + +`uv` では以下のような多くのことができます: + +* 異なるバージョンも含めた**Python のインストール** +* プロジェクトごとの**仮想環境**の管理 +* **パッケージ**のインストール +* プロジェクトのパッケージの**依存関係やバージョン**の管理 +* パッケージとそのバージョンの、依存関係を含めた**厳密な**組み合わせを保持し、これによって、本番環境で、開発環境と全く同じようにプロジェクトを実行できる(これは**locking**と呼ばれます) +* その他のさまざまな機能 + +## まとめ + +ここまで読みすべて理解したなら、世間の多くの開発者と比べて、仮想環境について**あなたはより多くのことを知っています**。🤓 + +これらの詳細を知ることは、将来、複雑に見える何かのデバッグにきっと役立つでしょう。しかし、その頃には、あなたは**そのすべての動作を根本から**理解しているでしょう。😎 diff --git a/docs/ko/docs/advanced/additional-status-codes.md b/docs/ko/docs/advanced/additional-status-codes.md new file mode 100644 index 0000000000..da06cb778a --- /dev/null +++ b/docs/ko/docs/advanced/additional-status-codes.md @@ -0,0 +1,41 @@ +# 추가 상태 코드 + +기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 작업(path operation)*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다. + +기본 상태 코드 또는 *경로 작업*에서 설정한 상태 코드를 사용합니다. + +## 추가 상태 코드 + +기본 상태 코드와 별도로 추가 상태 코드를 반환하려면 `JSONResponse`와 같이 `Response`를 직접 반환하고 추가 상태 코드를 직접 설정할 수 있습니다. + +예를 들어 항목을 업데이트할 수 있는 *경로 작업*이 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다. + +하지만 새로운 항목을 허용하기를 원할 것입니다. 항목이 이전에 존재하지 않았다면 이를 생성하고 HTTP 상태 코드 201 "Created"를 반환합니다. + +이를 위해서는 `JSONResponse`를 가져와서 원하는 `status_code`를 설정하여 콘텐츠를 직접 반환합니다: + +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} + +/// warning | 경고 + +위의 예제처럼 `Response`를 직접 반환하면 바로 반환됩니다. + +모델 등과 함께 직렬화되지 않습니다. + +원하는 데이터가 있는지, 값이 유효한 JSON인지 확인합니다(`JSONResponse`를 사용하는 경우). + +/// + +/// note | 기술적 세부 정보 + +`from starlette.responses import JSONResponse`를 사용할 수도 있습니다. + +**FastAPI**는 개발자 여러분을 위한 편의성으로 `fastapi.responses`와 동일한 `starlette.responses`를 제공합니다. 그러나 사용 가능한 응답의 대부분은 Starlette에서 직접 제공됩니다. `status` 또한 마찬가지입니다. + +/// + +## OpenAPI 및 API 문서 + +추가 상태 코드와 응답을 직접 반환하는 경우, FastAPI는 반환할 내용을 미리 알 수 있는 방법이 없기 때문에 OpenAPI 스키마(API 문서)에 포함되지 않습니다. + +하지만 다음을 사용하여 코드에 이를 문서화할 수 있습니다: [추가 응답](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/ko/docs/advanced/advanced-dependencies.md b/docs/ko/docs/advanced/advanced-dependencies.md new file mode 100644 index 0000000000..7fa043fa3a --- /dev/null +++ b/docs/ko/docs/advanced/advanced-dependencies.md @@ -0,0 +1,67 @@ +# 고급 의존성 + +## 매개변수화된 의존성 + +지금까지 본 모든 의존성은 고정된 함수 또는 클래스입니다. + +하지만 여러 개의 함수나 클래스를 선언하지 않고도 의존성에 매개변수를 설정해야 하는 경우가 있을 수 있습니다. + +예를 들어, `q` 쿼리 매개변수가 특정 고정된 내용을 포함하고 있는지 확인하는 의존성을 원한다고 가정해 봅시다. + +이때 해당 고정된 내용을 매개변수화할 수 있길 바랍니다. + +## "호출 가능한" 인스턴스 + +Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법이 있습니다. + +클래스 자체(이미 호출 가능함)가 아니라 해당 클래스의 인스턴스에 대해 호출 가능하게 하는 것입니다. + +이를 위해 `__call__` 메서드를 선언합니다: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} + +이 경우, **FastAPI**는 추가 매개변수와 하위 의존성을 확인하기 위해 `__call__`을 사용하게 되며, +나중에 *경로 연산 함수*에서 매개변수에 값을 전달할 때 이를 호출하게 됩니다. + +## 인스턴스 매개변수화하기 + +이제 `__init__`을 사용하여 의존성을 "매개변수화"할 수 있는 인스턴스의 매개변수를 선언할 수 있습니다: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} + +이 경우, **FastAPI**는 `__init__`에 전혀 관여하지 않으며, 우리는 이 메서드를 코드에서 직접 사용하게 됩니다. + +## 인스턴스 생성하기 + +다음과 같이 이 클래스의 인스턴스를 생성할 수 있습니다: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} + +이렇게 하면 `checker.fixed_content` 속성에 `"bar"`라는 값을 담아 의존성을 "매개변수화"할 수 있습니다. + +## 인스턴스를 의존성으로 사용하기 + +그런 다음, `Depends(FixedContentQueryChecker)` 대신 `Depends(checker)`에서 이 `checker` 인스턴스를 사용할 수 있으며, +클래스 자체가 아닌 인스턴스 `checker`가 의존성이 됩니다. + +의존성을 해결할 때 **FastAPI**는 이 `checker`를 다음과 같이 호출합니다: + +```Python +checker(q="somequery") +``` + +...그리고 이때 반환되는 값을 *경로 연산 함수*의 `fixed_content_included` 매개변수로 전달합니다: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} + +/// tip | 참고 + +이 모든 과정이 복잡하게 느껴질 수 있습니다. 그리고 지금은 이 방법이 얼마나 유용한지 명확하지 않을 수도 있습니다. + +이 예시는 의도적으로 간단하게 만들었지만, 전체 구조가 어떻게 작동하는지 보여줍니다. + +보안 관련 장에서는 이와 같은 방식으로 구현된 편의 함수들이 있습니다. + +이 모든 과정을 이해했다면, 이러한 보안 도구들이 내부적으로 어떻게 작동하는지 이미 파악한 것입니다. + +/// diff --git a/docs/ko/docs/advanced/async-tests.md b/docs/ko/docs/advanced/async-tests.md new file mode 100644 index 0000000000..37dfe29792 --- /dev/null +++ b/docs/ko/docs/advanced/async-tests.md @@ -0,0 +1,108 @@ +# 비동기 테스트 코드 작성 + +이전 장에서 `TestClient` 를 이용해 **FastAPI** 어플리케이션 테스트를 작성하는 법을 배우셨을텐데요. +지금까지는 `async` 키워드 사용없이 동기 함수의 테스트 코드를 작성하는 법만 익혔습니다. + +하지만 비동기 함수를 사용하여 테스트 코드를 작성하는 것은 매우 유용할 수 있습니다. +예를 들면 데이터베이스에 비동기로 쿼리하는 경우를 생각해봅시다. +FastAPI 애플리케이션에 요청을 보내고, 비동기 데이터베이스 라이브러리를 사용하여 백엔드가 데이터베이스에 올바르게 데이터를 기록했는지 확인하고 싶을 때가 있을 겁니다. + +이런 경우의 테스트 코드를 어떻게 비동기로 작성하는지 알아봅시다. + +## pytest.mark.anyio + +앞에서 작성한 테스트 함수에서 비동기 함수를 호출하고 싶다면, 테스트 코드도 비동기 함수여야합니다. +AnyIO는 특정 테스트 함수를 비동기 함수로 호출 할 수 있는 깔끔한 플러그인을 제공합니다. + + +## HTTPX + +**FastAPI** 애플리케이션이 `async def` 대신 `def` 키워드로 선언된 함수를 사용하더라도, 내부적으로는 여전히 `비동기` 애플리케이션입니다. + +`TestClient`는 pytest 표준을 사용하여 비동기 FastAPI 애플리케이션을 일반적인 `def` 테스트 함수 내에서 호출할 수 있도록 내부에서 마술을 부립니다. 하지만 이 마술은 비동기 함수 내부에서 사용할 때는 더 이상 작동하지 않습니다. 테스트를 비동기로 실행하면, 더 이상 테스트 함수 내부에서 `TestClient`를 사용할 수 없습니다. + +`TestClient`는 HTTPX를 기반으로 하고 있으며, 다행히 이를 직접 사용하여 API를 테스트할 수 있습니다. + +## 예시 + +간단한 예시를 위해 [더 큰 어플리케이션 만들기](../ko/tutorial/bigger-applications.md){.internal-link target=_blank} 와 [테스트](../ko/tutorial/testing.md){.internal-link target=_blank}:에서 다룬 파일 구조와 비슷한 형태를 확인해봅시다: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + + `main.py`는 아래와 같아야 합니다: + +{* ../../docs_src/async_tests/main.py *} + +`test_main.py` 파일은 `main.py`에 대한 테스트가 있을 텐데, 다음과 같을 수 있습니다: + +{* ../../docs_src/async_tests/test_main.py *} + +## 실행하기 + +아래의 명령어로 테스트 코드를 실행합니다: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## 자세히 보기 + +`@pytest.mark.anyio` 마커는 pytest에게 이 테스트 함수가 비동기로 호출되어야 함을 알려줍니다: + +{* ../../docs_src/async_tests/test_main.py hl[7] *} + +/// tip | 팁 + +테스트 함수가 이제 `TestClient`를 사용할 때처럼 단순히 `def`가 아니라 `async def`로 작성된 점에 주목해주세요. + +/// + +그 다음에 `AsyncClient` 로 앱을 만들고 비동기 요청을 `await` 키워드로 보낼 수 있습니다: + +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} + +위의 코드는: + +```Python +response = client.get('/') +``` + +`TestClient` 에 요청을 보내던 것과 동일합니다. + +/// tip | 팁 + +새로운 `AsyncClient`를 사용할 때 async/await를 사용하고 있다는 점에 주목하세요. 이 요청은 비동기적으로 처리됩니다. + +/// + +/// warning | 경고 + +만약의 어플리케이션이 Lifespan 이벤트에 의존성을 갖고 있다면 `AsyncClient` 가 이러한 이벤트를 실행시키지 않습니다. +`AsyncClient` 가 테스트를 실행시켰다는 것을 확인하기 위해 +`LifespanManager` from florimondmanca/asgi-lifespan.확인해주세요. + + +/// + +## 그 외의 비동기 함수 호출 + +테스트 함수가 이제 비동기 함수이므로, FastAPI 애플리케이션에 요청을 보내는 것 외에도 다른 `async` 함수를 호출하고 `await` 키워드를 사용 할 수 있습니다. + +/// tip | 팁 + +테스트에 비동기 함수 호출을 통합할 때 (예: MongoDB의 MotorClient를 사용할 때) `RuntimeError: Task attached to a different loop` 오류가 발생한다면, 이벤트 루프가 필요한 객체는 반드시 비동기 함수 내에서만 인스턴스화해야 한다는 점을 주의하세요! +예를 들어 `@app.on_event("startup")` 콜백 내에서 인스턴스화하는 것이 좋습니다. + +/// diff --git a/docs/ko/docs/advanced/custom-response.md b/docs/ko/docs/advanced/custom-response.md new file mode 100644 index 0000000000..2001956fa2 --- /dev/null +++ b/docs/ko/docs/advanced/custom-response.md @@ -0,0 +1,313 @@ +# 사용자 정의 응답 - HTML, Stream, 파일, 기타 + +기본적으로, **FastAPI** 응답을 `JSONResponse`를 사용하여 반환합니다. + +이를 재정의 하려면 [응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 본 것처럼 `Response`를 직접 반환하면 됩니다. + +그러나 `Response` (또는 `JSONResponse`와 같은 하위 클래스)를 직접 반환하면, 데이터가 자동으로 변환되지 않으며 (심지어 `response_model`을 선언했더라도), 문서화가 자동으로 생성되지 않습니다(예를 들어, 생성된 OpenAPI의 일부로 HTTP 헤더 `Content-Type`에 특정 "미디어 타입"을 포함하는 경우). + +하지만 *경로 작업 데코레이터*에서 `response_class` 매개변수를 사용하여 원하는 `Response`(예: 모든 `Response` 하위 클래스)를 선언할 수도 있습니다. + +*경로 작업 함수*에서 반환하는 내용은 해당 `Response`안에 포함됩니다. + +그리고 만약 그 `Response`가 `JSONResponse`와 `UJSONResponse`의 경우 처럼 JSON 미디어 타입(`application/json`)을 가지고 있다면, *경로 작업 데코레이터*에서 선언한 Pydantic의 `response_model`을 사용해 자동으로 변환(및 필터링) 됩니다. + +/// note | 참고 + +미디어 타입이 없는 응답 클래스를 사용하는 경우, FastAPI는 응답에 내용이 없을 것으로 예상하므로 생성된 OpenAPI 문서에서 응답 형식을 문서화하지 않습니다. + +/// + +## `ORJSONResponse` 사용하기 + +예를 들어, 성능을 극대화하려는 경우, orjson을 설치하여 사용하고 응답을 `ORJSONResponse`로 설정할 수 있습니다. + +사용하고자 하는 `Response` 클래스(하위 클래스)를 임포트한 후, **경로 작업 데코레이터*에서 선언하세요. + +대규모 응답의 경우, 딕셔너리를 반환하는 것보다 `Response`를 반환하는 것이 훨씬 빠릅니다. + +이유는 기본적으로, FastAPI가 내부의 모든 항목을 검사하고 JSON으로 직렬화할 수 있는지 확인하기 때문입니다. 이는 사용자 안내서에서 설명된 [JSON 호환 가능 인코더](../tutorial/encoder.md){.internal-link target=_blank}를 사용하는 방식과 동일합니다. 이를 통해 데이터베이스 모델과 같은 **임의의 객체**를 반환할 수 있습니다. + +하지만 반환하는 내용이 **JSON으로 직렬화 가능**하다고 확신하는 경우, 해당 내용을 응답 클래스에 직접 전달할 수 있으며, FastAPI가 반환 내용을 `jsonable_encoder`를 통해 처리한 뒤 응답 클래스에 전달하는 오버헤드를 피할 수 있습니다. + +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} + +/// info | 정보 + +`response_class` 매개변수는 응답의 "미디어 타입"을 정의하는 데에도 사용됩니다. + +이 경우, HTTP 헤더 `Content-Type`은 `application/json`으로 설정됩니다. + +그리고 이는 OpenAPI에 그대로 문서화됩니다. + +/// + +/// tip | 팁 + +`ORJSONResponse`는 FastAPI에서만 사용할 수 있고 Starlette에서는 사용할 수 없습니다. + +/// + +## HTML 응답 + +**FastAPI**에서 HTML 응답을 직접 반환하려면 `HTMLResponse`를 사용하세요. + +* `HTMLResponse`를 임포트 합니다. +* *경로 작업 데코레이터*의 `response_class` 매개변수로 `HTMLResponse`를 전달합니다. + +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} + +/// info | 정보 + +`response_class` 매개변수는 응답의 "미디어 타입"을 정의하는 데에도 사용됩니다. + +이 경우, HTTP 헤더 `Content-Type`은 `text/html`로 설정됩니다. + +그리고 이는 OpenAPI에 그대로 문서화 됩니다. + +/// + +### `Response` 반환하기 + +[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 본 것 처럼, *경로 작업*에서 응답을 직접 반환하여 재정의할 수도 있습니다. + +위의 예제와 동일하게 `HTMLResponse`를 반환하는 코드는 다음과 같을 수 있습니다: + +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} + +/// warning | 경고 + +*경로 작업 함수*에서 직접 반환된 `Response`는 OpenAPI에 문서화되지 않습니다(예를들어, `Content-Type`이 문서화되지 않음) 자동 대화형 문서에서도 표시되지 않습니다. + +/// + +/// info | 정보 + +물론 실제 `Content-Type` 헤더, 상태 코드 등은 반환된 `Response` 객체에서 가져옵니다. + +/// + +### OpenAPI에 문서화하고 `Response` 재정의 하기 + +함수 내부에서 응답을 재정의하면서 동시에 OpenAPI에서 "미디어 타입"을 문서화하고 싶다면, `response_class` 매게변수를 사용하면서 `Response` 객체를 반환할 수 있습니다. + +이 경우 `response_class`는 OpenAPI *경로 작업*을 문서화하는 데만 사용되고, 실제로는 여러분이 반환한 `Response`가 그대로 사용됩니다. + +### `HTMLResponse`직접 반환하기 + +예를 들어, 다음과 같이 작성할 수 있습니다: + +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} + +이 예제에서, `generate_html_response()` 함수는 HTML을 `str`로 반환하는 대신 이미 `Response`를 생성하고 반환합니다. + +`generate_html_response()`를 호출한 결과를 반환함으로써, 기본적인 **FastAPI** 기본 동작을 재정의 하는 `Response`를 이미 반환하고 있습니다. + +하지만 `response_class`에 `HTMLResponse`를 함께 전달했기 때문에, FastAPI는 이를 OpenAPI 및 대화형 문서에서 `text/html`로 HTML을 문서화 하는 방법을 알 수 있습니다. + + + +## 사용 가능한 응답들 + +다음은 사용할 수 있는 몇가지 응답들 입니다. + +`Response`를 사용하여 다른 어떤 것도 반환 할수 있으며, 직접 하위 클래스를 만들 수도 있습니다. + +/// note | 기술 세부사항 + +`from starlette.responses import HTMLResponse`를 사용할 수도 있습니다. + +**FastAPI**는 개발자인 여러분의 편의를 위해 `starlette.responses`를 `fastapi.responses`로 제공 하지만, 대부분의 사용 가능한 응답은 Starlette에서 직접 가져옵니다. + +/// + +### `Response` + +기본 `Response` 클래스는 다른 모든 응답 클래스의 부모 클래스 입니다. + +이 클래스를 직접 반환할 수 있습니다. + +다음 매개변수를 받을 수 있습니다: + +* `content` - `str` 또는 `bytes`. +* `status_code` - HTTP 상태코드를 나타내는 `int`. +* `headers` - 문자열로 이루어진 `dict`. +* `media_type` - 미디어 타입을 나타내는 `str` 예: `"text/html"`. + +FastAPI (실제로는 Starlette)가 자동으로 `Content-Length` 헤더를 포함시킵니다. 또한 `media_type`에 기반하여 `Content-Type` 헤더를 포함하며, 텍스트 타입의 경우 문자 집합을 추가 합니다. + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +### `HTMLResponse` + +텍스트 또는 바이트를 받아 HTML 응답을 반환합니다. 위에서 설명한 내용과 같습니다. + +### `PlainTextResponse` + +텍스트 또는 바이트를 받아 일반 텍스트 응답을 반환합니다. + +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} + +### `JSONResponse` + +데이터를 받아 `application/json`으로 인코딩된 응답을 반환합니다. + +이는 위에서 설명했듯이 **FastAPI**에서 기본적으로 사용되는 응답 형식입니다. + +### `ORJSONResponse` + + `orjson`을 사용하여 빠른 JSON 응답을 제공하는 대안입니다. 위에서 설명한 내용과 같습니다. + +/// info | 정보 + +이를 사용하려면 `orjson`을 설치해야합니다. 예: `pip install orjson`. + +/// + +### `UJSONResponse` + +`ujson`을 사용한 또 다른 JSON 응답 형식입니다. + +/// info | 정보 + +이 응답을 사용하려면 `ujson`을 설치해야합니다. 예: 'pip install ujson`. + +/// + +/// warning | 경고 + +`ujson` 은 일부 예외 경우를 처리하는 데 있어 Python 내장 구현보다 덜 엄격합니다. + +/// + +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} + +/// tip | 팁 + +`ORJSONResponse`가 더 빠른 대안일 가능성이 있습니다. + +/// + +### `RedirectResponse` + +HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 307(임시 리디렉션)으로 설정됩니다. + +`RedirectResponse`를 직접 반환할 수 있습니다. + +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} + +--- + +또는 `response_class` 매개변수에서 사용할 수도 있습니다: + + +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} + +이 경우, *경로 작업* 함수에서 URL을 직접 반환할 수 있습니다. + +이 경우, 사용되는 `status_code`는 `RedirectResponse`의 기본값인 `307` 입니다. + +--- + +`status_code` 매개변수를 `response_class` 매개변수와 함께 사용할 수도 있습니다: + +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} + +### `StreamingResponse` + +비동기 제너레이터 또는 일반 제너레이터/이터레이터를 받아 응답 본문을 스트리밍 합니다. + +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} + +#### 파일과 같은 객체를 사용한 `StreamingResponse` + +파일과 같은 객체(예: `open()`으로 반환된 객체)가 있는 경우, 해당 파일과 같은 객체를 반복(iterate)하는 제너레이터 함수를 만들 수 있습니다. + +이 방식으로, 파일 전체를 메모리에 먼저 읽어들일 필요 없이, 제너레이터 함수를 `StreamingResponse`에 전달하여 반환할 수 있습니다. + +이 방식은 클라우드 스토리지, 비디오 처리 등의 다양한 라이브러리와 함께 사용할 수 있습니다. + +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} + +1. 이것이 제너레이터 함수입니다. `yield` 문을 포함하고 있으므로 "제너레이터 함수"입니다. +2. `with` 블록을 사용함으로써, 제너레이터 함수가 완료된 후 파일과 같은 객체가 닫히도록 합니다. 즉, 응답 전송이 끝난 후 닫힙니다. +3. 이 `yield from`은 함수가 `file_like`라는 객체를 반복(iterate)하도록 합니다. 반복된 각 부분은 이 제너레이터 함수(`iterfile`)에서 생성된 것처럼 `yield` 됩니다. + + 이렇게 하면 "생성(generating)" 작업을 내부적으로 다른 무언가에 위임하는 제너레이터 함수가 됩니다. + + 이 방식을 사용하면 `with` 블록 안에서 파일을 열 수 있어, 작업이 완료된 후 파일과 같은 객체가 닫히는 것을 보장할 수 있습니다. + +/// tip | 팁 + +여기서 표준 `open()`을 사용하고 있기 때문에 `async`와 `await`를 지원하지 않습니다. 따라서 경로 작업은 일반 `def`로 선언합니다. + +/// + +### `FileResponse` + +파일을 비동기로 스트리밍하여 응답합니다. + +다른 응답 유형과는 다른 인수를 사용하여 객체를 생성합니다: + +* `path` - 스트리밍할 파일의 경로. +* `headers` - 딕셔너리 형식의 사용자 정의 헤더. +* `media_type` - 미디어 타입을 나타내는 문자열. 설정되지 않은 경우 파일 이름이나 경로를 사용하여 추론합니다. +* `filename` - 설정된 경우 응답의 `Content-Disposition`에 포함됩니다. + +파일 응답에는 적절한 `Content-Length`, `Last-Modified`, 및 `ETag` 헤더가 포함됩니다. + +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} + +또한 `response_class` 매개변수를 사용할 수도 있습니다: + +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} + +이 경우, 경로 작업 함수에서 파일 경로를 직접 반환할 수 있습니다. + +## 사용자 정의 응답 클래스 + +`Response`를 상속받아 사용자 정의 응답 클래스를 생성하고 사용할 수 있습니다. + +예를 들어, 포함된 `ORJSONResponse` 클래스에서 사용되지 않는 설정으로 orjson을 사용하고 싶다고 가정해봅시다. + +만약 들여쓰기 및 포맷된 JSON을 반환하고 싶다면, `orjson.OPT_INDENT_2` 옵션을 사용할 수 있습니다. + +`CustomORJSONResponse`를 생성할 수 있습니다. 여기서 핵심은 `Response.render(content)` 메서드를 생성하여 내용을 `bytes`로 반환하는 것입니다: + +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} + +이제 다음 대신: + +```json +{"message": "Hello World"} +``` + +이 응답은 이렇게 반환됩니다: + +```json +{ + "message": "Hello World" +} +``` + +물론 JSON 포맷팅보다 더 유용하게 활용할 방법을 찾을 수 있을 것입니다. 😉 + +## 기본 응답 클래스 + +**FastAPI** 클래스 객체 또는 `APIRouter`를 생성할 때 기본적으로 사용할 응답 클래스를 지정할 수 있습니다. + +이를 정의하는 매개변수는 `default_response_class`입니다. + +아래 예제에서 **FastAPI**는 모든 경로 작업에서 기본적으로 `JSONResponse` 대신 `ORJSONResponse`를 사용합니다. + +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} + +/// tip | 팁 + +여전히 이전처럼 *경로 작업*에서 `response_class`를 재정의할 수 있습니다. + +/// + +## 추가 문서화 + +OpenAPI에서 `responses`를 사용하여 미디어 타입 및 기타 세부 정보를 선언할 수도 있습니다: [OpenAPI에서 추가 응답](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/ko/docs/advanced/events.md b/docs/ko/docs/advanced/events.md index e155f41f10..4318ada54e 100644 --- a/docs/ko/docs/advanced/events.md +++ b/docs/ko/docs/advanced/events.md @@ -1,57 +1,165 @@ -# 이벤트: startup과 shutdown +# Lifespan 이벤트 -필요에 따라 응용 프로그램이 시작되기 전이나 종료될 때 실행되는 이벤트 핸들러(함수)를 정의할 수 있습니다. +애플리케이션 **시작 전**에 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이는 이 코드가 **한 번**만 실행되며, **애플리케이션이 요청을 받기 시작하기 전**에 실행된다는 의미입니다. -이 함수들은 `async def` 또는 평범하게 `def`으로 선언할 수 있습니다. +마찬가지로, 애플리케이션이 **종료될 때** 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이 경우, 이 코드는 **한 번**만 실행되며, **여러 요청을 처리한 후**에 실행됩니다. -/// warning | "경고" +이 코드가 애플리케이션이 **요청을 받기 시작하기 전에** 실행되고, 요청 처리가 끝난 후 **종료 직전에** 실행되기 때문에 전체 애플리케이션의 **수명(Lifespan)**을 다룹니다. (잠시 후 "수명"이라는 단어가 중요해집니다 😉) -이벤트 핸들러는 주 응용 프로그램에서만 작동합니다. [하위 응용 프로그램 - 마운트](./sub-applications.md){.internal-link target=_blank}에서는 작동하지 않습니다. +이 방법은 전체 애플리케이션에서 사용해야 하는 **자원**을 설정하거나 요청 간에 **공유되는** 자원을 설정하고, 또는 그 후에 **정리**하는 데 매우 유용할 수 있습니다. 예를 들어, 데이터베이스 연결 풀 또는 공유되는 머신러닝 모델을 로드하는 경우입니다. + + +## 사용 사례 + +먼저 **사용 사례**를 예로 들어보고, 이를 어떻게 해결할 수 있는지 살펴보겠습니다. + +우리가 요청을 처리하기 위해 사용하고 싶은 **머신러닝 모델**이 있다고 상상해 봅시다. 🤖 + +이 모델들은 요청 간에 공유되므로, 요청마다 모델이 하나씩 있는 것이 아니라, 여러 요청에서 동일한 모델을 사용합니다. + +모델을 로드하는 데 **상당한 시간이 걸린다고 상상해 봅시다**, 왜냐하면 모델이 **디스크에서 많은 데이터를 읽어야** 하기 때문입니다. 따라서 모든 요청에 대해 모델을 매번 로드하고 싶지 않습니다. + +모듈/파일의 최상위에서 모델을 로드할 수도 있지만, 그러면 **모델을 로드하는데** 시간이 걸리기 때문에, 단순한 자동화된 테스트를 실행할 때도 모델이 로드될 때까지 기다려야 해서 **테스트 속도가 느려집니다**. + +이 문제를 해결하려고 하는 것입니다. 요청을 처리하기 전에 모델을 로드하되, 애플리케이션이 요청을 받기 시작하기 직전에만 로드하고, 코드가 로드되는 동안은 로드하지 않도록 하겠습니다. + +## Lifespan + +`FastAPI` 애플리케이션의 `lifespan` 매개변수와 "컨텍스트 매니저"를 사용하여 *시작*과 *종료* 로직을 정의할 수 있습니다. (컨텍스트 매니저가 무엇인지 잠시 후에 설명드리겠습니다.) + +예제를 통해 시작하고, 그 후에 자세히 살펴보겠습니다. + +우리는 `yield`를 사용하여 비동기 함수 `lifespan()`을 다음과 같이 생성합니다: + +{* ../../docs_src/events/tutorial003.py hl[16,19] *} + +여기서 우리는 모델을 로드하는 비싼 *시작* 작업을 시뮬레이션하고 있습니다. `yield` 앞에서 (가짜) 모델 함수를 머신러닝 모델이 담긴 딕셔너리에 넣습니다. 이 코드는 **애플리케이션이 요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다. + +그리고 `yield` 직후에는 모델을 언로드합니다. 이 코드는 **애플리케이션이 요청 처리 완료 후**, *종료* 직전에 실행됩니다. 예를 들어, 메모리나 GPU와 같은 자원을 해제하는 작업을 할 수 있습니다. + +/// tip | 팁 + +`shutdown`은 애플리케이션을 **종료**할 때 발생합니다. + +새로운 버전을 시작해야 하거나, 그냥 실행을 멈추고 싶을 수도 있습니다. 🤷 /// -## `startup` 이벤트 +### Lifespan 함수 -응용 프로그램을 시작하기 전에 실행하려는 함수를 "startup" 이벤트로 선언합니다: +먼저 주목할 점은, `yield`를 사용하여 비동기 함수(async function)를 정의하고 있다는 것입니다. 이는 `yield`를 사용한 의존성과 매우 유사합니다. -```Python hl_lines="8" -{!../../../docs_src/events/tutorial001.py!} +{* ../../docs_src/events/tutorial003.py hl[14:19] *} + +함수의 첫 번째 부분, 즉 `yield` 이전의 코드는 애플리케이션이 시작되기 **전에** 실행됩니다. + +그리고 `yield` 이후의 부분은 애플리케이션이 완료된 후 **나중에** 실행됩니다. + +### 비동기 컨텍스트 매니저 + +함수를 확인해보면, `@asynccontextmanager`로 장식되어 있습니다. + +이것은 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환시킵니다. + +{* ../../docs_src/events/tutorial003.py hl[1,13] *} + +파이썬에서 **컨텍스트 매니저**는 `with` 문에서 사용할 수 있는 것입니다. 예를 들어, `open()`은 컨텍스트 매니저로 사용할 수 있습니다: + +```Python +with open("file.txt") as file: + file.read() +``` +최근 버전의 파이썬에서는 **비동기 컨텍스트 매니저**도 있습니다. 이를 `async with`와 함께 사용합니다: + +```Python +async with lifespan(app): + await do_stuff() ``` -이 경우 `startup` 이벤트 핸들러 함수는 단순히 몇 가지 값으로 구성된 `dict` 형식의 "데이터베이스"를 초기화합니다. +컨텍스트 매니저나 위와 같은 비동기 컨텍스트 매니저를 만들면, `with` 블록에 들어가기 전에 `yield` 이전의 코드가 실행되고, `with` 블록을 벗어난 후에는 `yield` 이후의 코드가 실행됩니다. -하나 이상의 이벤트 핸들러 함수를 추가할 수도 있습니다. +위의 코드 예제에서는 직접 사용하지 않고, FastAPI에 전달하여 사용하도록 합니다. -그리고 응용 프로그램은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받지 않습니다. +`FastAPI` 애플리케이션의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받기 때문에, 새로운 `lifespan` 비동기 컨텍스트 매니저를 FastAPI에 전달할 수 있습니다. -## `shutdown` 이벤트 +{* ../../docs_src/events/tutorial003.py hl[22] *} -응용 프로그램이 종료될 때 실행하려는 함수를 추가하려면 `"shutdown"` 이벤트로 선언합니다: +## 대체 이벤트 (사용 중단) -```Python hl_lines="6" -{!../../../docs_src/events/tutorial002.py!} -``` +/// warning | 경고 -이 예제에서 `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트가 적힌 `log.txt` 파일을 추가할 것입니다. +*시작*과 *종료*를 처리하는 권장 방법은 위에서 설명한 대로 `FastAPI` 애플리케이션의 `lifespan` 매개변수를 사용하는 것입니다. `lifespan` 매개변수를 제공하면 `startup`과 `shutdown` 이벤트 핸들러는 더 이상 호출되지 않습니다. `lifespan`을 사용할지, 모든 이벤트를 사용할지 선택해야 하며 둘 다 사용할 수는 없습니다. -/// info | "정보" - -`open()` 함수에서 `mode="a"`는 "추가"를 의미합니다. 따라서 이미 존재하는 파일의 내용을 덮어쓰지 않고 새로운 줄을 추가합니다. +이 부분은 건너뛰셔도 좋습니다. /// -/// tip | "팁" +*시작*과 *종료* 동안 실행될 이 로직을 정의하는 대체 방법이 있습니다. -이 예제에서는 파일과 상호작용 하기 위해 파이썬 표준 함수인 `open()`을 사용하고 있습니다. +애플리케이션이 시작되기 전에 또는 종료될 때 실행해야 하는 이벤트 핸들러(함수)를 정의할 수 있습니다. -따라서 디스크에 데이터를 쓰기 위해 "대기"가 필요한 I/O (입력/출력) 작업을 수행합니다. +이 함수들은 `async def` 또는 일반 `def`로 선언할 수 있습니다. -그러나 `open()`은 `async`와 `await`을 사용하지 않기 때문에 이벤트 핸들러 함수는 `async def`가 아닌 표준 `def`로 선언하고 있습니다. +### `startup` 이벤트 + +애플리케이션이 시작되기 전에 실행되어야 하는 함수를 추가하려면, `"startup"` 이벤트로 선언합니다: + +{* ../../docs_src/events/tutorial001.py hl[8] *} + +이 경우, `startup` 이벤트 핸들러 함수는 "database"라는 항목(단지 `dict`)을 일부 값으로 초기화합니다. + +여러 개의 이벤트 핸들러 함수를 추가할 수 있습니다. + +애플리케이션은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받기 시작하지 않습니다. + +### `shutdown` 이벤트 + +애플리케이션이 종료될 때 실행되어야 하는 함수를 추가하려면, `"shutdown"` 이벤트로 선언합니다: + +{* ../../docs_src/events/tutorial002.py hl[6] *} + +여기서, `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트를 `log.txt` 파일에 기록합니다. + +/// info | 정보 + +`open()` 함수에서 `mode="a"`는 "추가"를 의미하므로, 파일에 있는 기존 내용은 덮어쓰지 않고 새로운 줄이 추가됩니다. /// -/// info | "정보" +/// tip | 팁 -이벤트 핸들러에 관한 내용은 Starlette 이벤트 문서에서 추가로 확인할 수 있습니다. +이 경우, 우리는 표준 파이썬 `open()` 함수를 사용하여 파일과 상호작용하고 있습니다. + +따라서 I/O(입출력) 작업이 포함되어 있어 디스크에 기록되는 것을 "기다리는" 과정이 필요합니다. + +하지만 `open()`은 `async`와 `await`를 사용하지 않습니다. + +그래서 우리는 이벤트 핸들러 함수를 `async def` 대신 일반 `def`로 선언합니다. /// + +### `startup`과 `shutdown`을 함께 사용 + +*시작*과 *종료* 로직이 연결될 가능성이 높습니다. 예를 들어, 무언가를 시작한 후 끝내거나, 자원을 획득한 후 해제하는 등의 작업을 할 수 있습니다. + +이러한 작업을 별도의 함수로 처리하면 서로 로직이나 변수를 공유하지 않기 때문에 더 어려워집니다. 값들을 전역 변수에 저장하거나 비슷한 트릭을 사용해야 할 수 있습니다. + +그렇기 때문에 위에서 설명한 대로 `lifespan`을 사용하는 것이 권장됩니다. + +## 기술적 세부사항 + +호기심 많은 분들을 위한 기술적인 세부사항입니다. 🤓 + +ASGI 기술 사양에 따르면, 이는 Lifespan Protocol의 일부이며, `startup`과 `shutdown`이라는 이벤트를 정의합니다. + +/// info | 정보 + +Starlette의 `lifespan` 핸들러에 대해 더 읽고 싶다면 Starlette의 Lifespan 문서에서 확인할 수 있습니다. + +이 문서에는 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다. + +/// + +## 서브 애플리케이션 + +🚨 이 lifespan 이벤트(`startup`과 `shutdown`)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요. diff --git a/docs/ko/docs/advanced/index.md b/docs/ko/docs/advanced/index.md index cb628fa10b..31704727ca 100644 --- a/docs/ko/docs/advanced/index.md +++ b/docs/ko/docs/advanced/index.md @@ -6,7 +6,7 @@ 이어지는 장에서는 여러분이 다른 옵션, 구성 및 추가 기능을 보실 수 있습니다. -/// tip | "팁" +/// tip | 팁 다음 장들이 **반드시 "심화"**인 것은 아닙니다. diff --git a/docs/ko/docs/advanced/middlewares.md b/docs/ko/docs/advanced/middlewares.md new file mode 100644 index 0000000000..5778528a8e --- /dev/null +++ b/docs/ko/docs/advanced/middlewares.md @@ -0,0 +1,96 @@ +# 고급 미들웨어 + +메인 튜토리얼에서 [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank}를 응용프로그램에 추가하는 방법을 읽으셨습니다. + +그리고 [CORS with the `CORSMiddleware`](){.internal-link target=_blank}하는 방법도 보셨습니다. + +이 섹션에서는 다른 미들웨어들을 사용하는 방법을 알아보겠습니다. + +## ASGI 미들웨어 추가하기 + +**FastAPI**는 Starlette을 기반으로 하고 있으며, ASGI 사양을 구현하므로 ASGI 미들웨어를 사용할 수 있습니다. + +미들웨어가 FastAPI나 Starlette용으로 만들어지지 않아도 ASGI 사양을 준수하는 한 동작할 수 있습니다. + +일반적으로 ASGI 미들웨어는 첫 번째 인수로 ASGI 앱을 받는 클래스들입니다. + +따라서 타사 ASGI 미들웨어 문서에서 일반적으로 다음과 같이 사용하도록 안내할 것입니다. + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +하지만 내부 미들웨어가 서버 오류를 처리하고 사용자 정의 예외 처리기가 제대로 작동하도록 하는 더 간단한 방법을 제공하는 FastAPI(실제로는 Starlette)가 있습니다. + +이를 위해 `app.add_middleware()`를 사용합니다(CORS의 예에서와 같이). + +```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`에 여러 미들웨어를 제공합니다. 그러나 사용 가능한 대부분의 미들웨어는 Starlette에서 직접 제공합니다. + +/// + +## `HTTPSRedirectMiddleware` + +들어오는 모든 요청이 `https` 또는 `wss`여야 합니다. + +`http` 또는 `ws`로 들어오는 모든 요청은 대신 보안 체계로 리디렉션됩니다. + +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} + +## `TrustedHostMiddleware` + +HTTP 호스트 헤더 공격을 방지하기 위해 모든 수신 요청에 올바르게 설정된 `Host` 헤더를 갖도록 강제합니다. + +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} + +다음 인수가 지원됩니다: + +* `allowed_hosts` - 호스트 이름으로 허용해야 하는 도메인 이름 목록입니다. 일치하는 하위 도메인에 대해 `*.example.com`과 같은 와일드카드 도메인이 지원됩니다. 모든 호스트 이름을 허용하려면 `allowed_hosts=[“*”]`를 사용하거나 미들웨어를 생략하세요. + +수신 요청의 유효성이 올바르게 확인되지 않으면 `400`이라는 응답이 전송됩니다. + +## `GZipMiddleware` + +`Accept-Encoding` 헤더에 `“gzip”`이 포함된 모든 요청에 대해 GZip 응답을 처리합니다. + +미들웨어는 표준 응답과 스트리밍 응답을 모두 처리합니다. + +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} + +지원되는 인수는 다음과 같습니다: + +* `minimum_size` - 이 최소 크기(바이트)보다 작은 응답은 GZip하지 않습니다. 기본값은 `500`입니다. +* `compresslevel` - GZip 압축 중에 사용됩니다. 1에서 9 사이의 정수입니다. 기본값은 `9`입니다. 값이 낮을수록 압축 속도는 빨라지지만 파일 크기는 커지고, 값이 높을수록 압축 속도는 느려지지만 파일 크기는 작아집니다. + +## 기타 미들웨어 + +다른 많은 ASGI 미들웨어가 있습니다. + +예를 들어: + +유비콘의 `ProxyHeadersMiddleware`> +MessagePack + +사용 가능한 다른 미들웨어를 확인하려면 스타렛의 미들웨어 문서ASGI Awesome List를 참조하세요. diff --git a/docs/ko/docs/advanced/response-change-status-code.md b/docs/ko/docs/advanced/response-change-status-code.md new file mode 100644 index 0000000000..1ba9aa3ccb --- /dev/null +++ b/docs/ko/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` 타입의 파라미터를 선언할 수 있습니다. (쿠키와 헤더에 대해 선언하는 것과 유사하게) + +그리고 이 *임시* 응답 객체에서 `status_code`를 설정할 수 있습니다. + +{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} + +그리고 평소처럼 원하는 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다. + +`response_model`을 선언했다면 반환된 객체는 여전히 필터링되고 변환됩니다. + +**FastAPI**는 이 *임시* 응답 객체에서 상태 코드(쿠키와 헤더 포함)를 추출하여, `response_model`로 필터링된 반환 값을 최종 응답에 넣습니다. + +또한, 의존성에서도 `Response` 파라미터를 선언하고 그 안에서 상태 코드를 설정할 수 있습니다. 단, 마지막으로 설정된 상태 코드가 우선 적용된다는 점을 유의하세요. diff --git a/docs/ko/docs/advanced/response-cookies.md b/docs/ko/docs/advanced/response-cookies.md new file mode 100644 index 0000000000..50da713fe6 --- /dev/null +++ b/docs/ko/docs/advanced/response-cookies.md @@ -0,0 +1,49 @@ +# 응답 쿠키 + +## `Response` 매개변수 사용하기 + +*경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다. + +그런 다음 해당 *임시* 응답 객체에서 쿠키를 설정할 수 있습니다. + +{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *} + +그런 다음 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다. + +그리고 `response_model`을 선언했다면 반환한 객체를 거르고 변환하는 데 여전히 사용됩니다. + +**FastAPI**는 그 *임시* 응답에서 쿠키(또한 헤더 및 상태 코드)를 추출하고, 반환된 값이 포함된 최종 응답에 이를 넣습니다. 이 값은 `response_model`로 걸러지게 됩니다. + +또한 의존관계에서 `Response` 매개변수를 선언하고, 해당 의존성에서 쿠키(및 헤더)를 설정할 수도 있습니다. + +## `Response`를 직접 반환하기 + +코드에서 `Response`를 직접 반환할 때도 쿠키를 생성할 수 있습니다. + +이를 위해 [Response를 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성할 수 있습니다. + +그런 다음 쿠키를 설정하고 반환하면 됩니다: +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} +/// tip + +`Response` 매개변수를 사용하지 않고 응답을 직접 반환하는 경우, FastAPI는 이를 직접 반환한다는 점에 유의하세요. + +따라서 데이터가 올바른 유형인지 확인해야 합니다. 예: `JSONResponse`를 반환하는 경우, JSON과 호환되는지 확인하세요. + +또한 `response_model`로 걸러져야 할 데이터가 전달되지 않도록 확인하세요. + +/// + +### 추가 정보 + +/// note | 기술적 세부사항 + +`from starlette.responses import Response` 또는 `from starlette.responses import JSONResponse`를 사용할 수도 있습니다. + +**FastAPI**는 개발자의 편의를 위해 `fastapi.responses`로 동일한 `starlette.responses`를 제공합니다. 그러나 대부분의 응답은 Starlette에서 직접 제공됩니다. + +또한 `Response`는 헤더와 쿠키를 설정하는 데 자주 사용되므로, **FastAPI**는 이를 `fastapi.Response`로도 제공합니다. + +/// + +사용 가능한 모든 매개변수와 옵션은 Starlette 문서에서 확인할 수 있습니다. diff --git a/docs/ko/docs/advanced/response-directly.md b/docs/ko/docs/advanced/response-directly.md new file mode 100644 index 0000000000..08d63c43ce --- /dev/null +++ b/docs/ko/docs/advanced/response-directly.md @@ -0,0 +1,63 @@ +# 응답을 직접 반환하기 + +**FastAPI**에서 *경로 작업(path operation)*을 생성할 때, 일반적으로 `dict`, `list`, Pydantic 모델, 데이터베이스 모델 등의 데이터를 반환할 수 있습니다. + +기본적으로 **FastAPI**는 [JSON 호환 가능 인코더](../tutorial/encoder.md){.internal-link target=_blank}에 설명된 `jsonable_encoder`를 사용해 해당 반환 값을 자동으로 `JSON`으로 변환합니다. + +그런 다음, JSON 호환 데이터(예: `dict`)를 `JSONResponse`에 넣어 사용자의 응답을 전송하는 방식으로 처리됩니다. + +그러나 *경로 작업*에서 `JSONResponse`를 직접 반환할 수도 있습니다. + +예를 들어, 사용자 정의 헤더나 쿠키를 반환해야 하는 경우에 유용할 수 있습니다. + +## `Response` 반환하기 + +사실, `Response` 또는 그 하위 클래스를 반환할 수 있습니다. + +/// tip + +`JSONResponse` 자체도 `Response`의 하위 클래스입니다. + +/// + +그리고 `Response`를 반환하면 **FastAPI**가 이를 그대로 전달합니다. + +Pydantic 모델로 데이터 변환을 수행하지 않으며, 내용을 다른 형식으로 변환하지 않습니다. + +이로 인해 많은 유연성을 얻을 수 있습니다. 어떤 데이터 유형이든 반환할 수 있고, 데이터 선언이나 유효성 검사를 재정의할 수 있습니다. + +## `Response`에서 `jsonable_encoder` 사용하기 + +**FastAPI**는 반환하는 `Response`에 아무런 변환을 하지 않으므로, 그 내용이 준비되어 있어야 합니다. + +예를 들어, Pydantic 모델을 `dict`로 변환해 `JSONResponse`에 넣지 않으면 JSON 호환 유형으로 변환된 데이터 유형(예: `datetime`, `UUID` 등)이 사용되지 않습니다. + +이러한 경우, 데이터를 응답에 전달하기 전에 `jsonable_encoder`를 사용하여 변환할 수 있습니다: + +{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} + +/// note | 기술적 세부 사항 + +`from starlette.responses import JSONResponse`를 사용할 수도 있습니다. + +**FastAPI**는 개발자의 편의를 위해 `starlette.responses`를 `fastapi.responses`로 제공합니다. 그러나 대부분의 가능한 응답은 Starlette에서 직접 제공합니다. + +/// + +## 사용자 정의 `Response` 반환하기 +위 예제는 필요한 모든 부분을 보여주지만, 아직 유용하지는 않습니다. 사실 데이터를 직접 반환하면 **FastAPI**가 이를 `JSONResponse`에 넣고 `dict`로 변환하는 등 모든 작업을 자동으로 처리합니다. + +이제, 사용자 정의 응답을 반환하는 방법을 알아보겠습니다. + +예를 들어 XML 응답을 반환하고 싶다고 가정해보겠습니다. + +XML 내용을 문자열에 넣고, 이를 `Response`에 넣어 반환할 수 있습니다: + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +## 참고 사항 +`Response`를 직접 반환할 때, 그 데이터는 자동으로 유효성 검사되거나, 변환(직렬화)되거나, 문서화되지 않습니다. + +그러나 [OpenAPI에서 추가 응답](additional-responses.md){.internal-link target=_blank}에서 설명된 대로 문서화할 수 있습니다. + +이후 단락에서 자동 데이터 변환, 문서화 등을 사용하면서 사용자 정의 `Response`를 선언하는 방법을 확인할 수 있습니다. diff --git a/docs/ko/docs/advanced/response-headers.md b/docs/ko/docs/advanced/response-headers.md new file mode 100644 index 0000000000..e4e022c9b7 --- /dev/null +++ b/docs/ko/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# 응답 헤더 + +## `Response` 매개변수 사용하기 + +여러분은 *경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다 (쿠키와 같이 사용할 수 있습니다). + +그런 다음, 여러분은 해당 *임시* 응답 객체에서 헤더를 설정할 수 있습니다. + +{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *} + +그 후, 일반적으로 사용하듯이 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다. + +`response_model`을 선언한 경우, 반환한 객체를 필터링하고 변환하는 데 여전히 사용됩니다. + +**FastAPI**는 해당 *임시* 응답에서 헤더(쿠키와 상태 코드도 포함)를 추출하여, 여러분이 반환한 값을 포함하는 최종 응답에 `response_model`로 필터링된 값을 넣습니다. + +또한, 종속성에서 `Response` 매개변수를 선언하고 그 안에서 헤더(및 쿠키)를 설정할 수 있습니다. + +## `Response` 직접 반환하기 + +`Response`를 직접 반환할 때에도 헤더를 추가할 수 있습니다. + +[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성하고, 헤더를 추가 매개변수로 전달하세요. + +{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} + +/// note | 기술적 세부사항 + +`from starlette.responses import Response`나 `from starlette.responses import JSONResponse`를 사용할 수도 있습니다. + +**FastAPI**는 `starlette.responses`를 `fastapi.responses`로 개발자의 편의를 위해 직접 제공하지만, 대부분의 응답은 Starlette에서 직접 제공됩니다. + +그리고 `Response`는 헤더와 쿠키를 설정하는 데 자주 사용될 수 있으므로, **FastAPI**는 `fastapi.Response`로도 이를 제공합니다. + +/// + +## 커스텀 헤더 + +‘X-’ 접두어를 사용하여 커스텀 사설 헤더를 추가할 수 있습니다. + +하지만, 여러분이 브라우저에서 클라이언트가 볼 수 있기를 원하는 커스텀 헤더가 있는 경우, CORS 설정에 이를 추가해야 합니다([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}에서 자세히 알아보세요). `expose_headers` 매개변수를 사용하여 Starlette의 CORS 설명서에 문서화된 대로 설정할 수 있습니다. diff --git a/docs/ko/docs/advanced/sub-applications.md b/docs/ko/docs/advanced/sub-applications.md new file mode 100644 index 0000000000..c5835de15c --- /dev/null +++ b/docs/ko/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# 하위 응용프로그램 - 마운트 + +만약 각각의 독립적인 OpenAPI와 문서 UI를 갖는 두 개의 독립적인 FastAPI 응용프로그램이 필요하다면, 메인 어플리케이션에 하나 (또는 그 이상의) 하위-응용프로그램(들)을 “마운트"해서 사용할 수 있습니다. + +## **FastAPI** 응용프로그램 마운트 + +“마운트"이란 완전히 “독립적인" 응용프로그램을 특정 경로에 추가하여 해당 하위 응용프로그램에서 선언된 *경로 동작*을 통해 해당 경로 아래에 있는 모든 작업들을 처리할 수 있도록 하는 것을 의미합니다. + +### 최상단 응용프로그램 + +먼저, 메인, 최상단의 **FastAPI** 응용프로그램과 이것의 *경로 동작*을 생성합니다: + +{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} + +### 하위 응용프로그램 + +다음으로, 하위 응용프로그램과 이것의 *경로 동작*을 생성합니다: + +이 하위 응용프로그램은 또 다른 표준 FastAPI 응용프로그램입니다. 다만 이것은 “마운트”될 것입니다: + +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} + +### 하위 응용프로그램 마운트 + +최상단 응용프로그램, `app`에 하위 응용프로그램, `subapi`를 마운트합니다. + +이 예시에서, 하위 응용프로그램션은 `/subapi` 경로에 마운트 될 것입니다: + +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} + +### 자동으로 생성된 API 문서 확인 + +이제, `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에서 문서를 여십시오. + +메인 응용프로그램의 *경로 동작*만을 포함하는, 메인 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다: + + + +다음으로, http://127.0.0.1:8000/subapi/docs에서 하위 응용프로그램의 문서를 여십시오. + +하위 경로 접두사 `/subapi` 아래에 선언된 *경로 동작* 을 포함하는, 하위 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다: + + + +두 사용자 인터페이스 중 어느 하나를 사용해야하는 경우, 브라우저는 특정 응용프로그램 또는 하위 응용프로그램과 각각 통신할 수 있기 때문에 올바르게 동작할 것입니다. + +### 기술적 세부사항: `root_path` + +위에 설명된 것과 같이 하위 응용프로그램을 마운트하는 경우, FastAPI는 `root_path`라고 하는 ASGI 명세의 매커니즘을 사용하여 하위 응용프로그램에 대한 마운트 경로 통신을 처리합니다. + +이를 통해, 하위 응용프로그램은 문서 UI를 위해 경로 접두사를 사용해야 한다는 사실을 인지합니다. + +하위 응용프로그램에도 역시 다른 하위 응용프로그램을 마운트하는 것이 가능하며 FastAPI가 모든 `root_path` 들을 자동적으로 처리하기 때문에 모든 것은 올바르게 동작할 것입니다. + +`root_path`와 이것을 사용하는 방법에 대해서는 [프록시의 뒷단](./behind-a-proxy.md){.internal-link target=_blank} 섹션에서 배울 수 있습니다. diff --git a/docs/ko/docs/advanced/templates.md b/docs/ko/docs/advanced/templates.md new file mode 100644 index 0000000000..6126357132 --- /dev/null +++ b/docs/ko/docs/advanced/templates.md @@ -0,0 +1,127 @@ +# 템플릿 + +**FastAPI**와 함께 원하는 어떤 템플릿 엔진도 사용할 수 있습니다. + +일반적인 선택은 Jinja2로, Flask와 다른 도구에서도 사용됩니다. + +설정을 쉽게 할 수 있는 유틸리티가 있으며, 이를 **FastAPI** 애플리케이션에서 직접 사용할 수 있습니다(Starlette 제공). + +## 의존성 설치 + +가상 환경을 생성하고(virtual environment{.internal-link target=_blank}), 활성화한 후 jinja2를 설치해야 합니다: + + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## 사용하기 `Jinja2Templates` + +* `Jinja2Templates`를 가져옵니다. +* 나중에 재사용할 수 있는 `templates` 객체를 생성합니다. +* 템플릿을 반환할 경로 작업에 `Request` 매개변수를 선언합니다. +* 생성한 `templates`를 사용하여 `TemplateResponse`를 렌더링하고 반환합니다. 템플릿의 이름, 요청 객체 및 Jinja2 템플릿 내에서 사용될 키-값 쌍이 포함된 "컨텍스트" 딕셔너리도 전달합니다. + + +```Python hl_lines="4 11 15-18" +{!../../docs_src/templates/tutorial001.py!} +``` + +/// note | 참고 + +FastAPI 0.108.0 이전과 Starlette 0.29.0에서는 `name`이 첫 번째 매개변수였습니다. + +또한 이전 버전에서는 `request` 객체가 Jinja2의 컨텍스트에서 키-값 쌍의 일부로 전달되었습니다. + +/// + +/// tip | 팁 + +`response_class=HTMLResponse`를 선언하면 문서 UI 응답이 HTML임을 알 수 있습니다. + +/// + +/// note | 기술 세부 사항 +`from starlette.templating import Jinja2Templates`를 사용할 수도 있습니다. + +**FastAPI**는 개발자를 위한 편리함으로 `fastapi.templating` 대신 `starlette.templating`을 제공합니다. 하지만 대부분의 사용 가능한 응답은 Starlette에서 직접 옵니다. `Request` 및 `StaticFiles`도 마찬가지입니다. +/// + +## 템플릿 작성하기 + +그런 다음 `templates/item.html`에 템플릿을 작성할 수 있습니다. 예를 들면: + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### 템플릿 컨텍스트 값 + +다음과 같은 HTML에서: + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +...이는 전달한 "컨텍스트" `dict`에서 가져온 `id`를 표시합니다: + +```Python +{"id": id} +``` + +예를 들어, ID가 `42`일 경우, 이는 다음과 같이 렌더링됩니다: + +```html +Item ID: 42 +``` + +### 템플릿 `url_for` 인수 + +템플릿 내에서 `url_for()`를 사용할 수도 있으며, 이는 *경로 작업 함수*에서 사용될 인수와 동일한 인수를 받습니다. + +따라서 다음과 같은 부분에서: + +{% raw %} + +```jinja + +``` + +{% endraw %} + +...이는 *경로 작업 함수* `read_item(id=id)`가 처리할 동일한 URL로 링크를 생성합니다. + +예를 들어, ID가 `42`일 경우, 이는 다음과 같이 렌더링됩니다: +```html + +``` + +## 템플릿과 정적 파일 + +템플릿 내에서 `url_for()`를 사용할 수 있으며, 예를 들어 `name="static"`으로 마운트한 `StaticFiles`와 함께 사용할 수 있습니다. + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +이 예제에서는 `static/styles.css`에 있는 CSS 파일에 연결될 것입니다: + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +그리고 `StaticFiles`를 사용하고 있으므로, 해당 CSS 파일은 **FastAPI** 애플리케이션에서 `/static/styles.css` URL로 자동 제공됩니다. + +## 더 많은 세부 사항 + +템플릿 테스트를 포함한 더 많은 세부 사항은 Starlette의 템플릿 문서를 확인하세요. diff --git a/docs/ko/docs/advanced/testing-dependencies.md b/docs/ko/docs/advanced/testing-dependencies.md new file mode 100644 index 0000000000..780e19431f --- /dev/null +++ b/docs/ko/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# 테스트 의존성 오버라이드 + +## 테스트 중 의존성 오버라이드하기 + +테스트를 진행하다 보면 의존성을 오버라이드해야 하는 경우가 있습니다. + +원래 의존성을 실행하고 싶지 않을 수도 있습니다(또는 그 의존성이 가지고 있는 하위 의존성까지도 실행되지 않길 원할 수 있습니다). + +대신, 테스트 동안(특정 테스트에서만) 사용될 다른 의존성을 제공하고, 원래 의존성이 사용되던 곳에서 사용할 수 있는 값을 제공하기를 원할 수 있습니다. + +### 사용 사례: 외부 서비스 + +예를 들어, 외부 인증 제공자를 호출해야 하는 경우를 생각해봅시다. + +토큰을 보내면 인증된 사용자를 반환합니다. + +제공자는 요청당 요금을 부과할 수 있으며, 테스트를 위해 고정된 모의 사용자가 있는 경우보다 호출하는 데 시간이 더 걸릴 수 있습니다. + +외부 제공자를 한 번만 테스트하고 싶을 수도 있지만 테스트를 실행할 때마다 반드시 호출할 필요는 없습니다. + +이 경우 해당 공급자를 호출하는 종속성을 오버라이드하고 테스트에 대해서만 모의 사용자를 반환하는 사용자 지정 종속성을 사용할 수 있습니다. + +### `app.dependency_overrides` 속성 사용하기 + +이런 경우를 위해 **FastAPI** 응용 프로그램에는 `app.dependency_overrides`라는 속성이 있습니다. 이는 간단한 `dict`입니다. + +테스트를 위해 의존성을 오버라이드하려면, 원래 의존성(함수)을 키로 설정하고 오버라이드할 의존성(다른 함수)을 값으로 설정합니다. + +그럼 **FastAPI**는 원래 의존성 대신 오버라이드된 의존성을 호출합니다. + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | 팁 + +**FastAPI** 애플리케이션 어디에서든 사용된 의존성에 대해 오버라이드를 설정할 수 있습니다. + +원래 의존성은 *경로 동작 함수*, *경로 동작 데코레이터*(반환값을 사용하지 않는 경우), `.include_router()` 호출 등에서 사용될 수 있습니다. + +FastAPI는 여전히 이를 오버라이드할 수 있습니다. + +/// + +그런 다음, `app.dependency_overrides`를 빈 `dict`로 설정하여 오버라이드를 재설정(제거)할 수 있습니다: + +```python +app.dependency_overrides = {} +``` + +/// tip | 팁 + +특정 테스트에서만 의존성을 오버라이드하고 싶다면, 테스트 시작 시(테스트 함수 내부) 오버라이드를 설정하고 테스트 종료 시(테스트 함수 끝부분) 재설정하면 됩니다. + +/// diff --git a/docs/ko/docs/advanced/testing-events.md b/docs/ko/docs/advanced/testing-events.md new file mode 100644 index 0000000000..502762f23b --- /dev/null +++ b/docs/ko/docs/advanced/testing-events.md @@ -0,0 +1,5 @@ +# 이벤트 테스트: 시작 - 종료 + +테스트에서 이벤트 핸들러(`startup` 및 `shutdown`)를 실행해야 하는 경우, `with` 문과 함께 `TestClient`를 사용할 수 있습니다. + +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/ko/docs/advanced/testing-websockets.md b/docs/ko/docs/advanced/testing-websockets.md new file mode 100644 index 0000000000..9b67824295 --- /dev/null +++ b/docs/ko/docs/advanced/testing-websockets.md @@ -0,0 +1,13 @@ +# WebSocket 테스트하기 + +`TestClient`를 사용하여 WebSocket을 테스트할 수 있습니다. + +이를 위해 `with` 문에서 `TestClient`를 사용하여 WebSocket에 연결합니다: + +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} + +/// note | 참고 + +자세한 내용은 Starlette의 WebSocket 테스트에 관한 설명서를 참고하시길 바랍니다. + +/// diff --git a/docs/ko/docs/advanced/using-request-directly.md b/docs/ko/docs/advanced/using-request-directly.md new file mode 100644 index 0000000000..b88a83bf42 --- /dev/null +++ b/docs/ko/docs/advanced/using-request-directly.md @@ -0,0 +1,56 @@ +# `Request` 직접 사용하기 + +지금까지 요청에서 필요한 부분을 각 타입으로 선언하여 사용해 왔습니다. + +다음과 같은 곳에서 데이터를 가져왔습니다: + +* 경로의 파라미터로부터. +* 헤더. +* 쿠키. +* 기타 등등. + +이렇게 함으로써, **FastAPI**는 데이터를 검증하고 변환하며, API에 대한 문서를 자동화로 생성합니다. + +하지만 `Request` 객체에 직접 접근해야 하는 상황이 있을 수 있습니다. + +## `Request` 객체에 대한 세부 사항 + +**FastAPI**는 실제로 내부에 **Starlette**을 사용하며, 그 위에 여러 도구를 덧붙인 구조입니다. 따라서 여러분이 필요할 때 Starlette의 `Request` 객체를 직접 사용할 수 있습니다. + +`Request` 객체에서 데이터를 직접 가져오는 경우(예: 본문을 읽기)에는 FastAPI가 해당 데이터를 검증하거나 변환하지 않으며, 문서화(OpenAPI를 통한 문서 자동화(로 생성된) API 사용자 인터페이스)도 되지 않습니다. + +그러나 다른 매개변수(예: Pydantic 모델을 사용한 본문)는 여전히 검증, 변환, 주석 추가 등이 이루어집니다. + +하지만 특정한 경우에는 `Request` 객체에 직접 접근하는 것이 유용할 수 있습니다. + +## `Request` 객체를 직접 사용하기 + +여러분이 클라이언트의 IP 주소/호스트 정보를 *경로 작동 함수* 내부에서 가져와야 한다고 가정해 보겠습니다. + +이를 위해서는 요청에 직접 접근해야 합니다. + +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} + +*경로 작동 함수* 매개변수를 `Request` 타입으로 선언하면 **FastAPI**가 해당 매개변수에 `Request` 객체를 전달하는 것을 알게 됩니다. + +/// tip | 팁 + +이 경우, 요청 매개변수와 함께 경로 매개변수를 선언한 것을 볼 수 있습니다. + +따라서, 경로 매개변수는 추출되고 검증되며 지정된 타입으로 변환되고 OpenAPI로 주석이 추가됩니다. + +이와 같은 방식으로, 다른 매개변수들을 평소처럼 선언하면서, 부가적으로 `Request`도 가져올 수 있습니다. + +/// + +## `Request` 설명서 + +여러분은 `Request` 객체에 대한 더 자세한 내용을 공식 Starlette 설명서 사이트에서 읽어볼 수 있습니다. + +/// note | 기술 세부사항 + +`from starlette.requests import Request`를 사용할 수도 있습니다. + +**FastAPI**는 여러분(개발자)를 위한 편의를 위해 이를 직접 제공하지만, 실제로는 Starlette에서 가져온 것입니다. + +/// diff --git a/docs/ko/docs/advanced/websockets.md b/docs/ko/docs/advanced/websockets.md new file mode 100644 index 0000000000..d9d0dd95c4 --- /dev/null +++ b/docs/ko/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSockets + +여러분은 **FastAPI**에서 WebSockets를 사용할 수 있습니다. + +## `WebSockets` 설치 + +[가상 환경](../virtual-environments.md){.internal-link target=_blank)를 생성하고 활성화한 다음, `websockets`를 설치하세요: + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## WebSockets 클라이언트 + +### 프로덕션 환경에서 + +여러분의 프로덕션 시스템에서는 React, Vue.js 또는 Angular와 같은 최신 프레임워크로 생성된 프런트엔드를 사용하고 있을 가능성이 높습니다. + +백엔드와 WebSockets을 사용해 통신하려면 아마도 프런트엔드의 유틸리티를 사용할 것입니다. + +또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 응용 프로그램을 가질 수도 있습니다. + +혹은 WebSocket 엔드포인트와 통신할 수 있는 다른 방법이 있을 수도 있습니다. + +--- + +하지만 이번 예제에서는 일부 자바스크립트를 포함한 간단한 HTML 문서를 사용하겠습니다. 모든 것을 긴 문자열 안에 넣습니다. + +물론, 이는 최적의 방법이 아니며 프로덕션 환경에서는 사용하지 않을 것입니다. + +프로덕션 환경에서는 위에서 설명한 옵션 중 하나를 사용하는 것이 좋습니다. + +그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다: + +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} + +## `websocket` 생성하기 + +**FastAPI** 응용 프로그램에서 `websocket`을 생성합니다: + +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} + +/// note | 기술적 세부사항 + +`from starlette.websockets import WebSocket`을 사용할 수도 있습니다. + +**FastAPI**는 개발자를 위한 편의를 위해 동일한 `WebSocket`을 직접 제공합니다. 하지만 이는 Starlette에서 가져옵니다. + +/// + +## 메시지를 대기하고 전송하기 + +WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다. + +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} + +여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다. + +## 시도해보기 + +파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +브라우저에서 http://127.0.0.1:8000을 열어보세요. + +간단한 페이지가 나타날 것입니다: + + + +입력창에 메시지를 입력하고 전송할 수 있습니다: + + + +**FastAPI** WebSocket 응용 프로그램이 응답을 돌려줄 것입니다: + + + +여러 메시지를 전송(그리고 수신)할 수 있습니다: + + + +모든 메시지는 동일한 WebSocket 연결을 사용합니다. + +## `Depends` 및 기타 사용하기 + +WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할 수 있습니다: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +이들은 다른 FastAPI 엔드포인트/*경로 작동*과 동일하게 동작합니다: + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info | 정보 + +WebSocket에서는 `HTTPException`을 발생시키는 것이 적합하지 않습니다. 대신 `WebSocketException`을 발생시킵니다. + +명세서에 정의된 유효한 코드를 사용하여 종료 코드를 설정할 수 있습니다. + +/// + +### 종속성을 가진 WebSockets 테스트 + +파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +브라우저에서 http://127.0.0.1:8000을 열어보세요. + +다음과 같은 값을 설정할 수 있습니다: + +* 경로에 사용된 "Item ID". +* 쿼리 매개변수로 사용된 "Token". + +/// tip | 팁 + +쿼리 `token`은 종속성에 의해 처리됩니다. + +/// + +이제 WebSocket에 연결하고 메시지를 전송 및 수신할 수 있습니다: + + + +## 연결 해제 및 다중 클라이언트 처리 + +WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 이를 잡아 처리할 수 있습니다: + +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} + +테스트해보기: + +* 여러 브라우저 탭에서 앱을 엽니다. +* 각 탭에서 메시지를 작성합니다. +* 한 탭을 닫아보세요. + +`WebSocketDisconnect` 예외가 발생하며, 다른 모든 클라이언트가 다음과 같은 메시지를 수신합니다: + +``` +Client #1596980209979 left the chat +``` + +/// tip | 팁 + +위 응용 프로그램은 여러 WebSocket 연결에 메시지를 브로드캐스트하는 방법을 보여주는 간단한 예제입니다. + +그러나 모든 것을 메모리의 단일 리스트로 처리하므로, 프로세스가 실행 중인 동안만 동작하며 단일 프로세스에서만 작동합니다. + +FastAPI와 쉽게 통합할 수 있으면서 더 견고하고 Redis, PostgreSQL 등을 지원하는 도구를 찾고 있다면, encode/broadcaster를 확인하세요. + +/// + +## 추가 정보 + +다음 옵션에 대한 자세한 내용을 보려면 Starlette의 문서를 확인하세요: + +* `WebSocket` 클래스. +* 클래스 기반 WebSocket 처리. diff --git a/docs/ko/docs/advanced/wsgi.md b/docs/ko/docs/advanced/wsgi.md new file mode 100644 index 0000000000..3e9de3e6ca --- /dev/null +++ b/docs/ko/docs/advanced/wsgi.md @@ -0,0 +1,35 @@ +# WSGI 포함하기 - Flask, Django 그 외 + +[서브 응용 프로그램 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤편에서](behind-a-proxy.md){.internal-link target=_blank}에서 보았듯이 WSGI 응용 프로그램들을 다음과 같이 마운트 할 수 있습니다. + +`WSGIMiddleware`를 사용하여 WSGI 응용 프로그램(예: Flask, Django 등)을 감쌀 수 있습니다. + +## `WSGIMiddleware` 사용하기 + +`WSGIMiddleware`를 불러와야 합니다. + +그런 다음, WSGI(예: Flask) 응용 프로그램을 미들웨어로 포장합니다. + +그 후, 해당 경로에 마운트합니다. + +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,23] *} + +## 확인하기 + +이제 `/v1/` 경로에 있는 모든 요청은 Flask 응용 프로그램에서 처리됩니다. + +그리고 나머지는 **FastAPI**에 의해 처리됩니다. + +실행하면 http://localhost:8000/v1/으로 이동해서 Flask의 응답을 볼 수 있습니다: + +```txt +Hello, World from Flask! +``` + +그리고 다음으로 이동하면 http://localhost:8000/v2 Flask의 응답을 볼 수 있습니다: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/ko/docs/async.md b/docs/ko/docs/async.md index dfc2caa789..ec503d5406 100644 --- a/docs/ko/docs/async.md +++ b/docs/ko/docs/async.md @@ -21,7 +21,7 @@ async def read_results(): return results ``` -/// note | "참고" +/// note | 참고 `async def`로 생성된 함수 내부에서만 `await`를 사용할 수 있습니다. @@ -349,7 +349,7 @@ FastAPI를 사용하지 않더라도, 높은 호환성 및 Gevent를 사용할 수 있을 것입니다. 하지만 코드를 이해하고, 디버깅하고, 이에 대해 생각하는게 훨씬 복잡합니다. -예전 버전의 NodeJS / 브라우저 자바스크립트라면, "콜백 함수"를 사용했을 것입니다. 그리고 이로 인해 콜백 지옥에 빠지게 될 수 있습니다. +예전 버전의 NodeJS / 브라우저 자바스크립트라면, "콜백 함수"를 사용했을 것입니다. 그리고 이로 인해 "콜백 지옥"에 빠지게 될 수 있습니다. ## 코루틴 @@ -369,7 +369,7 @@ FastAPI를 사용하지 않더라도, 높은 호환성 및 가장 빠른 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보다 "느려질" 수밖에 없습니다. + * 하지만 경로 기반 라우팅 등 간단한 웹 응용 프로그램을 구축할 수 있는 도구를 제공합니다. + * Starlette를 비교할 때는 Sanic, Flask, Django 등의 웹 프레임워크(또는 마이크로 프레임워크)와 비교하세요. +* **FastAPI**: + * Starlette가 Uvicorn을 사용하므로 Uvicorn보다 빨라질 수 없는 것과 마찬가지로, **FastAPI**는 Starlette를 사용하므로 더 빠를 수 없습니다. + * FastAPI는 Starlette에 추가적으로 더 많은 기능을 제공합니다. API를 구축할 때 거의 항상 필요한 데이터 검증 및 직렬화와 같은 기능들이 포함되어 있습니다. 그리고 이를 사용하면 문서 자동화 기능도 제공됩니다(문서 자동화는 응용 프로그램 실행 시 오버헤드를 추가하지 않고 시작 시 생성됩니다). + * FastAPI를 사용하지 않고 직접 Starlette(또는 Sanic, Flask, Responder 등)를 사용했다면 데이터 검증 및 직렬화를 직접 구현해야 합니다. 따라서 최종 응용 프로그램은 FastAPI를 사용한 것과 동일한 오버헤드를 가지게 될 것입니다. 많은 경우 데이터 검증 및 직렬화가 응용 프로그램에서 작성된 코드 중 가장 많은 부분을 차지합니다. + * 따라서 FastAPI를 사용함으로써 개발 시간, 버그, 코드 라인을 줄일 수 있으며, FastAPI를 사용하지 않았을 때와 동일하거나 더 나은 성능을 얻을 수 있습니다(코드에서 모두 구현해야 하기 때문에). + * FastAPI를 비교할 때는 Flask-apispec, NestJS, Molten 등 데이터 검증, 직렬화 및 문서화가 통합된 자동 데이터 검증, 직렬화 및 문서화를 제공하는 웹 응용 프로그램 프레임워크(또는 도구 집합)와 비교하세요. diff --git a/docs/ko/docs/deployment/cloud.md b/docs/ko/docs/deployment/cloud.md index 2d6938e200..dbc814bbdb 100644 --- a/docs/ko/docs/deployment/cloud.md +++ b/docs/ko/docs/deployment/cloud.md @@ -10,7 +10,4 @@ 이는 FastAPI와 **커뮤니티** (여러분)에 대한 진정한 헌신을 보여줍니다. 그들은 여러분에게 **좋은 서비스**를 제공할 뿐 만이 아니라 여러분이 **훌륭하고 건강한 프레임워크인** FastAPI 를 사용하길 원하기 때문입니다. 🙇 -아래와 같은 서비스를 사용해보고 각 서비스의 가이드를 따를 수도 있습니다: - -* Platform.sh -* Porter +아래와 같은 서비스를 사용해보고 각 서비스의 가이드를 따를 수도 있습니다. diff --git a/docs/ko/docs/deployment/docker.md b/docs/ko/docs/deployment/docker.md index 502a36fc05..e8b2746c5f 100644 --- a/docs/ko/docs/deployment/docker.md +++ b/docs/ko/docs/deployment/docker.md @@ -4,7 +4,7 @@ FastAPI 어플리케이션을 배포할 때 일반적인 접근 방법은 **리 리눅스 컨테이너를 사용하는 데에는 **보안**, **반복 가능성**, **단순함** 등의 장점이 있습니다. -/// tip | "팁" +/// tip | 팁 시간에 쫓기고 있고 이미 이런것들을 알고 있다면 [`Dockerfile`👇](#build-a-docker-image-for-fastapi)로 점프할 수 있습니다. @@ -133,7 +133,7 @@ Successfully installed fastapi pydantic uvicorn -/// info | "정보" +/// info | 정보 패키지 종속성을 정의하고 설치하기 위한 방법과 도구는 다양합니다. @@ -231,7 +231,7 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 프로그램이 `/code`에서 시작하고 그 속에 `./app` 디렉터리가 여러분의 코드와 함께 들어있기 때문에, **Uvicorn**은 이를 보고 `app`을 `app.main`으로부터 **불러 올** 것입니다. -/// tip | "팁" +/// tip | 팁 각 코드 라인을 코드의 숫자 버블을 클릭하여 리뷰할 수 있습니다. 👆 @@ -305,7 +305,7 @@ $ docker build -t myimage . -/// tip | "팁" +/// tip | 팁 맨 끝에 있는 `.` 에 주목합시다. 이는 `./`와 동등하며, 도커에게 컨테이너 이미지를 빌드하기 위한 디렉터리를 알려줍니다. @@ -409,7 +409,7 @@ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] **HTTPS**와 **인증서**의 **자동** 취득을 다루는 것은 다른 컨테이너가 될 수 있는데, 예를 들어 Traefik을 사용하는 것입니다. -/// tip | "팁" +/// tip | 팁 Traefik은 도커, 쿠버네티스, 그리고 다른 도구와 통합되어 있어 여러분의 컨테이너를 포함하는 HTTPS를 셋업하고 설정하는 것이 매우 쉽습니다. @@ -441,7 +441,7 @@ Traefik은 도커, 쿠버네티스, 그리고 다른 도구와 통합되어 있 이 요소가 요청들의 **로드**를 읽어들이고 각 워커에게 (바라건대) **균형적으로** 분배한다면, 이 요소는 일반적으로 **로드 밸런서**라고 불립니다. -/// tip | "팁" +/// tip | 팁 HTTPS를 위해 사용된 **TLS 종료 프록시** 요소 또한 **로드 밸런서**가 될 수 있습니다. @@ -524,7 +524,7 @@ HTTPS를 위해 사용된 **TLS 종료 프록시** 요소 또한 **로드 밸런 만약 여러분이 **여러개의 컨테이너**를 가지고 있다면, 아마도 각각의 컨테이너는 **하나의 프로세스**를 가지고 있을 것입니다(예를 들어, **쿠버네티스** 클러스터에서). 그러면 여러분은 복제된 워커 컨테이너를 실행하기 **이전에**, 하나의 컨테이너에 있는 **이전의 단계들을** 수행하는 단일 프로세스를 가지는 **별도의 컨테이너들**을 가지고 싶을 것입니다. -/// info | "정보" +/// info | 정보 만약 여러분이 쿠버네티스를 사용하고 있다면, 아마도 이는 Init Container일 것입니다. @@ -544,7 +544,7 @@ HTTPS를 위해 사용된 **TLS 종료 프록시** 요소 또한 **로드 밸런 * tiangolo/uvicorn-gunicorn-fastapi. -/// warning | "경고" +/// warning | 경고 여러분이 이 베이스 이미지 또는 다른 유사한 이미지를 필요로 하지 **않을** 높은 가능성이 있으며, [위에서 설명된 것처럼: FastAPI를 위한 도커 이미지 빌드하기](#build-a-docker-image-for-fastapi) 처음부터 이미지를 빌드하는 것이 더 나을 수 있습니다. @@ -556,7 +556,7 @@ HTTPS를 위해 사용된 **TLS 종료 프록시** 요소 또한 **로드 밸런 또한 스크립트를 통해 **시작하기 전 사전 단계**를 실행하는 것을 지원합니다. -/// tip | "팁" +/// tip | 팁 모든 설정과 옵션을 보려면, 도커 이미지 페이지로 이동합니다: tiangolo/uvicorn-gunicorn-fastapi. @@ -687,7 +687,7 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 11. `uvicorn` 커맨드를 실행하여, `app.main`에서 불러온 `app` 객체를 사용하도록 합니다. -/// tip | "팁" +/// tip | 팁 버블 숫자를 클릭해 각 줄이 하는 일을 알아볼 수 있습니다. diff --git a/docs/ko/docs/deployment/server-workers.md b/docs/ko/docs/deployment/server-workers.md index 39976faf5b..b40b25cd8f 100644 --- a/docs/ko/docs/deployment/server-workers.md +++ b/docs/ko/docs/deployment/server-workers.md @@ -17,7 +17,7 @@ 지금부터 **구니콘**을 **유비콘 워커 프로세스**와 함께 사용하는 방법을 알려드리겠습니다. -/// info | "정보" +/// info | 정보 만약 도커와 쿠버네티스 같은 컨테이너를 사용하고 있다면 다음 챕터 [FastAPI와 컨테이너 - 도커](docker.md){.internal-link target=_blank}에서 더 많은 정보를 얻을 수 있습니다. diff --git a/docs/ko/docs/deployment/versions.md b/docs/ko/docs/deployment/versions.md index f3b3c2d7b8..559a892ab9 100644 --- a/docs/ko/docs/deployment/versions.md +++ b/docs/ko/docs/deployment/versions.md @@ -43,7 +43,7 @@ fastapi>=0.45.0,<0.46.0 FastAPI는 오류를 수정하고, 일반적인 변경사항을 위해 "패치"버전의 관습을 따릅니다. -/// tip | "팁" +/// tip | 팁 여기서 말하는 "패치"란 버전의 마지막 숫자로, 예를 들어 `0.2.3` 버전에서 "패치"는 `3`을 의미합니다. @@ -57,7 +57,7 @@ fastapi>=0.45.0,<0.46.0 수정된 사항과 새로운 요소들이 "마이너" 버전에 추가되었습니다. -/// tip | "팁" +/// tip | 팁 "마이너"란 버전 넘버의 가운데 숫자로, 예를 들어서 `0.2.3`의 "마이너" 버전은 `2`입니다. diff --git a/docs/ko/docs/environment-variables.md b/docs/ko/docs/environment-variables.md new file mode 100644 index 0000000000..1e6af3ceba --- /dev/null +++ b/docs/ko/docs/environment-variables.md @@ -0,0 +1,298 @@ +# 환경 변수 + +/// tip | 팁 + +만약 "환경 변수"가 무엇이고, 어떻게 사용하는지 알고 계시다면, 이 챕터를 스킵하셔도 좋습니다. + +/// + +환경 변수는 파이썬 코드의 **바깥**인, **운영 체제**에 존재하는 변수입니다. 파이썬 코드나 다른 프로그램에서 읽을 수 있습니다. + +환경 변수는 애플리케이션 **설정**을 처리하거나, 파이썬의 **설치** 과정의 일부로 유용합니다. + +## 환경 변수를 만들고 사용하기 + +파이썬 없이도, **셸 (터미널)** 에서 환경 변수를 **생성** 하고 사용할 수 있습니다. + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// You could create an env var MY_NAME with +$ export MY_NAME="Wade Wilson" + +// Then you could use it with other programs, like +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Create an env var MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Use it with other programs, like +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## 파이썬에서 환경 변수 읽기 + +파이썬 **바깥**인 터미널에서(다른 도구로도 가능) 환경 변수를 생성도 할 수도 있고, 이를 **파이썬에서 읽을 수 있습니다.** + +예를 들어 다음과 같은 `main.py` 파일이 있다고 합시다: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip | 팁 + +`os.getenv()` 의 두 번째 인자는 반환할 기본값입니다. + +여기서는 `"World"`를 넣었기에 기본값으로써 사용됩니다. 넣지 않으면 `None` 이 기본값으로 사용됩니다. + +/// + +그러면 해당 파이썬 프로그램을 다음과 같이 호출할 수 있습니다: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ export MY_NAME="Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ $Env:MY_NAME = "Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +환경변수는 코드 바깥에서 설정될 수 있지만, 코드에서 읽을 수 있고, 나머지 파일과 함께 저장(`git`에 커밋)할 필요가 없으므로, 구성이나 **설정** 에 사용하는 것이 일반적입니다. + +**특정 프로그램 호출**에 대해서만 사용할 수 있는 환경 변수를 만들 수도 있습니다. 해당 프로그램에서만 사용할 수 있고, 해당 프로그램이 실행되는 동안만 사용할 수 있습니다. + +그렇게 하려면 프로그램 바로 앞, 같은 줄에 환경 변수를 만들어야 합니다: + +
+ +```console +// Create an env var MY_NAME in line for this program call +$ MY_NAME="Wade Wilson" python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python + +// The env var no longer exists afterwards +$ python main.py + +Hello World from Python +``` + +
+ +/// tip | 팁 + +The Twelve-Factor App: Config 에서 좀 더 자세히 알아볼 수 있습니다. + +/// + +## 타입과 검증 + +이 환경변수들은 오직 **텍스트 문자열**로만 처리할 수 있습니다. 텍스트 문자열은 파이썬 외부에 있으며 다른 프로그램 및 나머지 시스템(Linux, Windows, macOS 등 다른 운영 체제)과 호환되어야 합니다. + +즉, 파이썬에서 환경 변수로부터 읽은 **모든 값**은 **`str`**이 되고, 다른 타입으로의 변환이나 검증은 코드에서 수행해야 합니다. + +**애플리케이션 설정**을 처리하기 위한 환경 변수 사용에 대한 자세한 내용은 [고급 사용자 가이드 - 설정 및 환경 변수](./advanced/settings.md){.internal-link target=\_blank} 에서 확인할 수 있습니다. + +## `PATH` 환경 변수 + +**`PATH`**라고 불리는, **특별한** 환경변수가 있습니다. 운영체제(Linux, Windows, macOS 등)에서 실행할 프로그램을 찾기위해 사용됩니다. + +변수 `PATH`의 값은 Linux와 macOS에서는 콜론 `:`, Windows에서는 세미콜론 `;`으로 구분된 디렉토리로 구성된 긴 문자열입니다. + +예를 들어, `PATH` 환경 변수는 다음과 같습니다: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +이는 시스템이 다음 디렉토리에서 프로그램을 찾아야 함을 의미합니다: + +- `/usr/local/bin` +- `/usr/bin` +- `/bin` +- `/usr/sbin` +- `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +이는 시스템이 다음 디렉토리에서 프로그램을 찾아야 함을 의미합니다: + +- `C:\Program Files\Python312\Scripts` +- `C:\Program Files\Python312` +- `C:\Windows\System32` + +//// + +터미널에 **명령어**를 입력하면 운영 체제는 `PATH` 환경 변수에 나열된 **각 디렉토리**에서 프로그램을 **찾습니다.** + +예를 들어 터미널에 `python`을 입력하면 운영 체제는 해당 목록의 **첫 번째 디렉토리**에서 `python`이라는 프로그램을 찾습니다. + +찾으면 **사용합니다**. 그렇지 않으면 **다른 디렉토리**에서 계속 찾습니다. + +### 파이썬 설치와 `PATH` 업데이트 + +파이썬을 설치할 때, 아마 `PATH` 환경 변수를 업데이트 할 것이냐고 물어봤을 겁니다. + +//// tab | Linux, macOS + +파이썬을 설치하고 그것이 `/opt/custompython/bin` 디렉토리에 있다고 가정해 보겠습니다. + +`PATH` 환경 변수를 업데이트하도록 "예"라고 하면 설치 관리자가 `/opt/custompython/bin`을 `PATH` 환경 변수에 추가합니다. + +다음과 같이 보일 수 있습니다: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +이렇게 하면 터미널에 `python`을 입력할 때, 시스템이 `/opt/custompython/bin`(마지막 디렉토리)에서 파이썬 프로그램을 찾아 사용합니다. + +//// + +//// tab | Windows + +파이썬을 설치하고 그것이 `C:\opt\custompython\bin` 디렉토리에 있다고 가정해 보겠습니다. + +`PATH` 환경 변수를 업데이트하도록 "예"라고 하면 설치 관리자가 `C:\opt\custompython\bin`을 `PATH` 환경 변수에 추가합니다. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +이렇게 하면 터미널에 `python`을 입력할 때, 시스템이 `C:\opt\custompython\bin`(마지막 디렉토리)에서 파이썬 프로그램을 찾아 사용합니다. + +//// + +그래서, 다음과 같이 입력한다면: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +시스템은 `/opt/custompython/bin`에서 `python` 프로그램을 **찾아** 실행합니다. + +다음과 같이 입력하는 것과 거의 같습니다: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +시스템은 `C:\opt\custompython\bin\python`에서 `python` 프로그램을 **찾아** 실행합니다. + +다음과 같이 입력하는 것과 거의 같습니다: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +이 정보는 [가상 환경](virtual-environments.md){.internal-link target=\_blank} 에 대해 알아볼 때 유용할 것입니다. + +## 결론 + +이 문서를 읽고 **환경 변수**가 무엇이고 파이썬에서 어떻게 사용하는지 기본적으로 이해하셨을 겁니다. + +또한 환경 변수에 대한 위키피디아(한국어)에서 이에 대해 자세히 알아볼 수 있습니다. + +많은 경우에서, 환경 변수가 어떻게 유용하고 적용 가능한지 바로 명확하게 알 수는 없습니다. 하지만 개발할 때 다양한 시나리오에서 계속 나타나므로 이에 대해 아는 것이 좋습니다. + +예를 들어, 다음 섹션인 [가상 환경](virtual-environments.md)에서 이 정보가 필요합니다. diff --git a/docs/ko/docs/fastapi-cli.md b/docs/ko/docs/fastapi-cli.md new file mode 100644 index 0000000000..a1160c71fc --- /dev/null +++ b/docs/ko/docs/fastapi-cli.md @@ -0,0 +1,83 @@ +# FastAPI CLI + +**FastAPI CLI**는 FastAPI 애플리케이션을 실행하고, 프로젝트를 관리하는 등 다양한 작업을 수행할 수 있는 커맨드 라인 프로그램입니다. + +FastAPI를 설치할 때 (예: `pip install "fastapi[standard]"` 명령어를 사용할 경우), `fastapi-cli`라는 패키지가 포함됩니다. 이 패키지는 터미널에서 사용할 수 있는 `fastapi` 명령어를 제공합니다. + +개발용으로 FastAPI 애플리케이션을 실행하려면 다음과 같이 `fastapi dev` 명령어를 사용할 수 있습니다: + +
+ +```console +$ fastapi dev main.py +INFO Using path main.py +INFO Resolved absolute path /home/user/code/awesomeapp/main.py +INFO Searching for package file structure from directories with __init__.py files +INFO Importing from /home/user/code/awesomeapp + + ╭─ Python module file ─╮ + │ │ + │ 🐍 main.py │ + │ │ + ╰──────────────────────╯ + +INFO Importing module main +INFO Found importable FastAPI app + + ╭─ Importable FastAPI app ─╮ + │ │ + │ from main import app │ + │ │ + ╰──────────────────────────╯ + +INFO Using import string main:app + + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + fastapi run + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [2265862] using WatchFiles +INFO: Started server process [2265873] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +`fastapi`라고 불리는 명령어 프로그램은 **FastAPI CLI**입니다. + +FastAPI CLI는 Python 프로그램의 경로(예: `main.py`)를 인수로 받아, `FastAPI` 인스턴스(일반적으로 `app`으로 명명)를 자동으로 감지하고 올바른 임포트 과정을 결정한 후 이를 실행합니다. + +프로덕션 환경에서는 `fastapi run` 명령어를 사용합니다. 🚀 + +내부적으로, **FastAPI CLI**는 고성능의, 프로덕션에 적합한, ASGI 서버인 Uvicorn을 사용합니다. 😎 + +## `fastapi dev` + +`fastapi dev` 명령을 실행하면 개발 모드가 시작됩니다. + +기본적으로 **자동 재시작(auto-reload)** 기능이 활성화되어, 코드에 변경이 생기면 서버를 자동으로 다시 시작합니다. 하지만 이 기능은 리소스를 많이 사용하며, 비활성화했을 때보다 안정성이 떨어질 수 있습니다. 따라서 개발 환경에서만 사용하는 것이 좋습니다. 또한, 서버는 컴퓨터가 자체적으로 통신할 수 있는 IP 주소(`localhost`)인 `127.0.0.1`에서 연결을 대기합니다. + +## `fastapi run` + +`fastapi run` 명령을 실행하면 기본적으로 프로덕션 모드로 FastAPI가 시작됩니다. + +기본적으로 **자동 재시작(auto-reload)** 기능이 비활성화되어 있습니다. 또한, 사용 가능한 모든 IP 주소인 `0.0.0.0`에서 연결을 대기하므로 해당 컴퓨터와 통신할 수 있는 모든 사람이 공개적으로 액세스할 수 있습니다. 이는 일반적으로 컨테이너와 같은 프로덕션 환경에서 실행하는 방법입니다. + +애플리케이션을 배포하는 방식에 따라 다르지만, 대부분 "종료 프록시(termination proxy)"를 활용해 HTTPS를 처리하는 것이 좋습니다. 배포 서비스 제공자가 이 작업을 대신 처리해줄 수도 있고, 직접 설정해야 할 수도 있습니다. + +/// tip + +자세한 내용은 [deployment documentation](deployment/index.md){.internal-link target=\_blank}에서 확인할 수 있습니다. + +/// diff --git a/docs/ko/docs/features.md b/docs/ko/docs/features.md index b6f6f7af2e..dfbf479998 100644 --- a/docs/ko/docs/features.md +++ b/docs/ko/docs/features.md @@ -63,7 +63,7 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` -/// info | "정보" +/// info | 정보 `**second_user_data`가 뜻하는 것: @@ -159,7 +159,7 @@ FastAPI는 사용하기 매우 간편하지만, 엄청난 **FastAPI**에 대해 트윗 하고 FastAPI가 마음에 드는 이유를 알려주세요. 🎉 - - [**Dev.to**](https://dev.to/tiangolo) 또는 [**Medium**](https://medium.com/@tiangolo)에서 제가 작성한 내용을 읽어 보십시오(또는 팔로우). - - 다른 기사나 아이디어들을 읽고, 제가 만들어왔던 툴에 대해서도 읽으십시오. - - 새로운 기사를 읽기 위해 팔로우 하십시오. +**FastAPI**가 어떻게 사용되고 있는지, 어떤 점이 마음에 들었는지, 어떤 프로젝트/회사에서 사용하고 있는지 등에 대해 듣고 싶습니다. - ## **FastAPI**에 대한 트윗 +## FastAPI에 투표하기 - [**FastAPI**에 대해 트윗](https://twitter.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi) 하고 FastAPI가 마음에 드는 이유를 알려주세요. 🎉 +* Slant에서 **FastAPI** 에 대해 투표하십시오. +* AlternativeTo에서 **FastAPI** 에 대해 투표하십시오. +* StackShare에서 **FastAPI** 에 대해 투표하십시오. - **FastAPI**가 어떻게 사용되고 있는지, 어떤 점이 마음에 들었는지, 어떤 프로젝트/회사에서 사용하고 있는지 등에 대해 듣고 싶습니다. +## GitHub의 이슈로 다른사람 돕기 - ## FastAPI에 투표하기 +다른 사람들의 질문에 도움을 줄 수 있습니다: - - [Slant에서 **FastAPI** 에 대해 투표하십시오](https://www.slant.co/options/34241/~fastapi-review). - - [AlternativeTo**FastAPI** 에 대해 투표하십시오](https://alternativeto.net/software/fastapi/). +* GitHub 디스커션 +* GitHub 이슈 - ## GitHub의 이슈로 다른사람 돕기 +많은 경우, 여러분은 이미 그 질문에 대한 답을 알고 있을 수도 있습니다. 🤓 - [존재하는 이슈](https://github.com/fastapi/fastapi/issues)를 확인하고 그것을 시도하고 도와줄 수 있습니다. 대부분의 경우 이미 답을 알고 있는 질문입니다. 🤓 +만약 많은 사람들의 문제를 도와준다면, 공식적인 [FastAPI 전문가](fastapi-people.md#fastapi-experts){.internal-link target=\_blank} 가 될 것입니다. 🎉 - 많은 사람들의 문제를 도와준다면, 공식적인 [FastAPI 전문가](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) 가 될 수 있습니다{.internal-link target=_blank}. 🎉 +가장 중요한 점은: 친절하려고 노력하는 것입니다. 사람들은 좌절감을 안고 오며, 많은 경우 최선의 방식으로 질문하지 않을 수도 있습니다. 하지만 최대한 친절하게 대하려고 노력하세요. 🤗 - ## GitHub 저장소 보기 +**FastAPI** 커뮤니티의 목표는 친절하고 환영하는 것입니다. 동시에, 괴롭힘이나 무례한 행동을 받아들이지 마세요. 우리는 서로를 돌봐야 합니다. - GitHub에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 watch 버튼을 클릭): https://github.com/fastapi/fastapi. 👀 +--- - "Releases only" 대신 "Watching"을 선택하면 다른 사용자가 새로운 issue를 생성할 때 알림이 수신됩니다. +다른 사람들의 질문 (디스커션 또는 이슈에서) 해결을 도울 수 있는 방법은 다음과 같습니다. - 그런 다음 이런 issues를 해결 할 수 있도록 도움을 줄 수 있습니다. +### 질문 이해하기 - ## 이슈 생성하기 +* 질문하는 사람이 가진 **목적**과 사용 사례를 이해할 수 있는지 확인하세요. - GitHub 저장소에 [새로운 이슈 생성](https://github.com/fastapi/fastapi/issues/new/choose) 을 할 수 있습니다, 예를들면 다음과 같습니다: +* 질문 (대부분은 질문입니다)이 **명확**한지 확인하세요. - - **질문**을 하거나 **문제**에 대해 질문합니다. - - 새로운 **기능**을 제안 합니다. +* 많은 경우, 사용자가 가정한 해결책에 대한 질문을 하지만, 더 **좋은** 해결책이 있을 수 있습니다. 문제와 사용 사례를 더 잘 이해하면 더 나은 **대안적인 해결책**을 제안할 수 있습니다. - **참고**: 만약 이슈를 생성한다면, 저는 여러분에게 다른 사람들을 도와달라고 부탁할 것입니다. 😉 +* 질문을 이해할 수 없다면, 더 **자세한 정보**를 요청하세요. - ## Pull Request를 만드십시오 +### 문제 재현하기 - Pull Requests를 이용하여 소스코드에 [컨트리뷰트](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/contributing.md){.internal-link target=_blank} 할 수 있습니다. 예를 들면 다음과 같습니다: +대부분의 경우, 질문은 질문자의 **원본 코드**와 관련이 있습니다. - - 문서에서 찾은 오타를 수정할 때. +많은 경우, 코드의 일부만 복사해서 올리지만, 그것만으로는 **문제를 재현**하기에 충분하지 않습니다. - - FastAPI를 [편집하여](https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml) 작성했거나 찾은 문서, 비디오 또는 팟캐스트를 공유할 때. +* 질문자에게 최소한의 재현 가능한 예제를 제공해달라고 요청하세요. 이렇게 하면 코드를 **복사-붙여넣기**하여 직접 실행하고, 동일한 오류나 동작을 확인하거나 사용 사례를 더 잘 이해할 수 있습니다. - - 해당 섹션의 시작 부분에 링크를 추가했는지 확인하십시오. +* 너그러운 마음이 든다면, 문제 설명만을 기반으로 직접 **예제를 만들어**볼 수도 있습니다. 하지만, 이는 시간이 많이 걸릴 수 있으므로, 먼저 질문을 명확히 해달라고 요청하는 것이 좋습니다. - - 당신의 언어로 [문서 번역하는데](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/contributing.md#translations){.internal-link target=_blank} 기여할 때. +### 해결책 제안하기 - - 또한 다른 사용자가 만든 번역을 검토하는데 도움을 줄 수도 있습니다. +* 질문을 충분히 이해한 후에는 가능한 **답변**을 제공할 수 있습니다. - - 새로운 문서의 섹션을 제안할 때. +* 많은 경우, 질문자의 **근본적인 문제나 사용 사례**를 이해하는 것이 중요합니다. 그들이 시도하는 방법보다 더 나은 해결책이 있을 수 있기 때문입니다. - - 기존 문제/버그를 수정할 때. +### 해결 요청하기 - - 새로운 feature를 추가할 때. +질문자가 답변을 확인하고 나면, 당신이 문제를 해결했을 가능성이 높습니다. 축하합니다, **당신은 영웅입니다**! 🦸 - ## 채팅에 참여하십시오 +* 이제 문제를 해결했다면, 질문자에게 다음을 요청할 수 있습니다. - 👥 [디스코드 채팅 서버](https://discord.gg/VQjSZaeJmf) 👥 에 가입하고 FastAPI 커뮤니티에서 다른 사람들과 어울리세요. + * GitHub 디스커션에서: 댓글을 **답변**으로 표시하도록 요청하세요. + * GitHub 이슈에서: 이슈를 **닫아달라고** 요청하세요. - /// tip +## GitHub 저장소 보기 - 질문이 있는 경우, [GitHub 이슈 ](https://github.com/fastapi/fastapi/issues/new/choose) 에서 질문하십시오, [FastAPI 전문가](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) 의 도움을 받을 가능성이 높습니다{.internal-link target=_blank} . +GitHub에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 watch 버튼을 클릭): https://github.com/fastapi/fastapi. 👀 - /// +"Releases only" 대신 "Watching"을 선택하면, 새로운 이슈나 질문이 생성될 때 알림을 받을 수 있습니다. 또한, 특정하게 새로운 이슈, 디스커션, PR 등만 알림 받도록 설정할 수도 있습니다. - ``` - 다른 일반적인 대화에서만 채팅을 사용하십시오. - ``` +그런 다음 이런 이슈들을 해결 할 수 있도록 도움을 줄 수 있습니다. - 기존 [지터 채팅](https://gitter.im/fastapi/fastapi) 이 있지만 채널과 고급기능이 없어서 대화를 하기가 조금 어렵기 때문에 지금은 디스코드가 권장되는 시스템입니다. +## 이슈 생성하기 - ### 질문을 위해 채팅을 사용하지 마십시오 +GitHub 저장소에 새로운 이슈 생성을 할 수 있습니다, 예를들면 다음과 같습니다: - 채팅은 더 많은 "자유로운 대화"를 허용하기 때문에, 너무 일반적인 질문이나 대답하기 어려운 질문을 쉽게 질문을 할 수 있으므로, 답변을 받지 못할 수 있습니다. +* **질문**을 하거나 **문제**에 대해 질문합니다. +* 새로운 **기능**을 제안 합니다. - GitHub 이슈에서의 템플릿은 올바른 질문을 작성하도록 안내하여 더 쉽게 좋은 답변을 얻거나 질문하기 전에 스스로 문제를 해결할 수도 있습니다. 그리고 GitHub에서는 시간이 조금 걸리더라도 항상 모든 것에 답할 수 있습니다. 채팅 시스템에서는 개인적으로 그렇게 할 수 없습니다. 😅 +**참고**: 만약 이슈를 생성한다면, 저는 여러분에게 다른 사람들을 도와달라고 부탁할 것입니다. 😉 - 채팅 시스템에서의 대화 또한 GitHub에서 처럼 쉽게 검색할 수 없기 때문에 대화 중에 질문과 답변이 손실될 수 있습니다. 그리고 GitHub 이슈에 있는 것만 [FastAPI 전문가](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts)가 되는 것으로 간주되므로{.internal-link target=_blank} , GitHub 이슈에서 더 많은 관심을 받을 것입니다. +## Pull Requests 리뷰하기 - 반면, 채팅 시스템에는 수천 명의 사용자가 있기 때문에, 거의 항상 대화 상대를 찾을 가능성이 높습니다. 😄 +다른 사람들의 pull request를 리뷰하는 데 도움을 줄 수 있습니다. - ## 개발자 스폰서가 되십시오 +다시 한번 말하지만, 최대한 친절하게 리뷰해 주세요. 🤗 - [GitHub 스폰서](https://github.com/sponsors/tiangolo) 를 통해 개발자를 경제적으로 지원할 수 있습니다. +--- - 감사하다는 말로 커피를 ☕️ 한잔 사줄 수 있습니다. 😄 +Pull Rrquest를 리뷰할 때 고려해야 할 사항과 방법은 다음과 같습니다: - 또한 FastAPI의 실버 또는 골드 스폰서가 될 수 있습니다. 🏅🎉 +### 문제 이해하기 - ## FastAPI를 강화하는 도구의 스폰서가 되십시오 +* 먼저, 해당 pull request가 해결하려는 **문제를 이해하는지** 확인하세요. GitHub 디스커션 또는 이슈에서 더 긴 논의가 있었을 수도 있습니다. - 문서에서 보았듯이, FastAPI는 Starlette과 Pydantic 라는 거인의 어깨에 타고 있습니다. +* Pull request가 필요하지 않을 가능성도 있습니다. **다른 방식**으로 문제를 해결할 수 있다면, 그 방법을 제안하거나 질문할 수 있습니다. - 다음의 스폰서가 될 수 있습니다 +### 스타일에 너무 신경 쓰지 않기 - - [Samuel Colvin (Pydantic)](https://github.com/sponsors/samuelcolvin) - - [Encode (Starlette, Uvicorn)](https://github.com/sponsors/encode) +* 커밋 메시지 스타일 같은 것에 너무 신경 쓰지 않아도 됩니다. 저는 직접 커밋을 수정하여 squash and merge를 수행할 것입니다. - ------ +* 코드 스타일 규칙도 걱정할 필요 없습니다. 이미 자동화된 도구들이 이를 검사하고 있습니다. - 감사합니다! 🚀 +스타일이나 일관성 관련 요청이 필요한 경우, 제가 직접 요청하거나 필요한 변경 사항을 추가 커밋으로 수정할 것입니다. + +### 코드 확인하기 + +* 코드를 읽고, **논리적으로 타당**한지 확인한 후 로컬에서 실행하여 문제가 해결되는지 확인하세요. + +* 그런 다음, 확인했다고 **댓글**을 남겨 주세요. 그래야 제가 검토했음을 알 수 있습니다. + +/// info + +불행히도, 제가 단순히 여러 개의 승인만으로 PR을 신뢰할 수는 없습니다. + +3개, 5개 이상의 승인이 달린 PR이 실제로는 깨져 있거나, 버그가 있거나, 주장하는 문제를 해결하지 못하는 경우가 여러 번 있었습니다. 😅 + +따라서, 정말로 코드를 읽고 실행한 뒤, 댓글로 확인 내용을 남겨 주는 것이 매우 중요합니다. 🤓 + +/// + +* PR을 더 단순하게 만들 수 있다면 그렇게 요청할 수 있지만, 너무 까다로울 필요는 없습니다. 주관적인 견해가 많이 있을 수 있기 때문입니다 (그리고 저도 제 견해가 있을 거예요 🙈). 따라서 핵심적인 부분에 집중하는 것이 좋습니다. + +### 테스트 + +* PR에 **테스트**가 포함되어 있는지 확인하는 데 도움을 주세요. + +* PR을 적용하기 전에 테스트가 **실패**하는지 확인하세요. 🚨 + +* PR을 적용한 후 테스트가 **통과**하는지 확인하세요. ✅ + +* 많은 PR에는 테스트가 없습니다. 테스트를 추가하도록 **상기**시켜줄 수도 있고, 직접 테스트를 **제안**할 수도 있습니다. 이는 시간이 많이 소요되는 부분 중 하나이며, 그 부분을 많이 도와줄 수 있습니다. + +* 그리고 시도한 내용을 댓글로 남겨주세요. 그러면 제가 확인했다는 걸 알 수 있습니다. 🤓 + +## Pull Request를 만드십시오 + +Pull Requests를 이용하여 소스코드에 [컨트리뷰트](contributing.md){.internal-link target=\_blank} 할 수 있습니다. 예를 들면 다음과 같습니다: + +* 문서에서 발견한 오타를 수정할 때. +* FastAPI 관련 문서, 비디오 또는 팟캐스트를 작성했거나 발견하여 이 파일을 편집하여 공유할 때. + * 해당 섹션의 시작 부분에 링크를 추가해야 합니다. +* 당신의 언어로 [문서 번역하는데](contributing.md#translations){.internal-link target=\_blank} 기여할 때. + * 다른 사람이 작성한 번역을 검토하는 것도 도울 수 있습니다. +* 새로운 문서의 섹션을 제안할 때. +* 기존 문제/버그를 수정할 때. + * 테스트를 반드시 추가해야 합니다. +* 새로운 feature를 추가할 때. + * 테스트를 반드시 추가해야 합니다. + * 관련 문서가 필요하다면 반드시 추가해야 합니다. + +## FastAPI 유지 관리에 도움 주기 + +**FastAPI**의 유지 관리를 도와주세요! 🤓 + +할 일이 많고, 그 중 대부분은 **여러분**이 할 수 있습니다. + +지금 할 수 있는 주요 작업은: + +* [GitHub에서 다른 사람들의 질문에 도움 주기](#github_1){.internal-link target=_blank} (위의 섹션을 참조하세요). +* [Pull Request 리뷰하기](#pull-requests){.internal-link target=_blank} (위의 섹션을 참조하세요). + +이 두 작업이 **가장 많은 시간을 소모**하는 일입니다. 그것이 FastAPI 유지 관리의 주요 작업입니다. + +이 작업을 도와주신다면, **FastAPI 유지 관리에 도움을 주는 것**이며 그것이 **더 빠르고 더 잘 발전하는 것**을 보장하는 것입니다. 🚀 + +## 채팅에 참여하십시오 + +👥 디스코드 채팅 서버 👥 에 가입하고 FastAPI 커뮤니티에서 다른 사람들과 어울리세요. + +/// tip + +질문이 있는 경우, GitHub 디스커션 에서 질문하십시오, [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank} 의 도움을 받을 가능성이 높습니다. + +다른 일반적인 대화에서만 채팅을 사용하십시오. + +/// + +### 질문을 위해 채팅을 사용하지 마십시오 + +채팅은 더 많은 "자유로운 대화"를 허용하기 때문에, 너무 일반적인 질문이나 대답하기 어려운 질문을 쉽게 질문을 할 수 있으므로, 답변을 받지 못할 수 있습니다. + +GitHub 이슈에서의 템플릿은 올바른 질문을 작성하도록 안내하여 더 쉽게 좋은 답변을 얻거나 질문하기 전에 스스로 문제를 해결할 수도 있습니다. 그리고 GitHub에서는 시간이 조금 걸리더라도 항상 모든 것에 답할 수 있습니다. 채팅 시스템에서는 개인적으로 그렇게 할 수 없습니다. 😅 + +채팅 시스템에서의 대화 또한 GitHub에서 처럼 쉽게 검색할 수 없기 때문에 대화 중에 질문과 답변이 손실될 수 있습니다. 그리고 GitHub 이슈에 있는 것만 [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}가 되는 것으로 간주되므로, GitHub 이슈에서 더 많은 관심을 받을 것입니다. + +반면, 채팅 시스템에는 수천 명의 사용자가 있기 때문에, 거의 항상 대화 상대를 찾을 가능성이 높습니다. 😄 + +## 개발자 스폰서가 되십시오 + +GitHub 스폰서 를 통해 개발자를 경제적으로 지원할 수 있습니다. + +감사하다는 말로 커피를 ☕️ 한잔 사줄 수 있습니다. 😄 + +또한 FastAPI의 실버 또는 골드 스폰서가 될 수 있습니다. 🏅🎉 + +## FastAPI를 강화하는 도구의 스폰서가 되십시오 + +문서에서 보았듯이, FastAPI는 Starlette과 Pydantic 라는 거인의 어깨에 타고 있습니다. + +다음의 스폰서가 될 수 있습니다 + +* Samuel Colvin (Pydantic) +* Encode (Starlette, Uvicorn) + +--- + +감사합니다! 🚀 diff --git a/docs/ko/docs/history-design-future.md b/docs/ko/docs/history-design-future.md new file mode 100644 index 0000000000..98f01d70d3 --- /dev/null +++ b/docs/ko/docs/history-design-future.md @@ -0,0 +1,81 @@ +# 역사, 디자인 그리고 미래 + +어느 날, [한 FastAPI 사용자](https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920)가 이렇게 물었습니다: + +> 이 프로젝트의 역사를 알려 주실 수 있나요? 몇 주 만에 멋진 결과를 낸 것 같아요. [...] + +여기서 그 역사에 대해 간단히 설명하겠습니다. + +--- + +## 대안 + +저는 여러 해 동안 머신러닝, 분산 시스템, 비동기 작업, NoSQL 데이터베이스 같은 복잡한 요구사항을 가진 API를 개발하며 여러 팀을 이끌어 왔습니다. + +이 과정에서 많은 대안을 조사하고, 테스트하며, 사용해야 했습니다. **FastAPI**의 역사는 그 이전에 나왔던 여러 도구의 역사와 밀접하게 연관되어 있습니다. + +[대안](alternatives.md){.internal-link target=_blank} 섹션에서 언급된 것처럼: + +> **FastAPI**는 이전에 나왔던 많은 도구들의 노력 없이는 존재하지 않았을 것입니다. +> +> 이전에 개발된 여러 도구들이 이 프로젝트에 영감을 주었습니다. +> +> 저는 오랫동안 새로운 프레임워크를 만드는 것을 피하고자 했습니다. 처음에는 **FastAPI**가 제공하는 기능들을 다양한 프레임워크와 플러그인, 도구들을 조합해 해결하려 했습니다. +> +> 하지만 결국에는 이 모든 기능을 통합하는 도구가 필요해졌습니다. 이전 도구들로부터 최고의 아이디어들을 모으고, 이를 최적의 방식으로 조합해야만 했습니다. 이는 :term:Python 3.6+ 타입 힌트 와 같은, 이전에는 사용할 수 없었던 언어 기능이 가능했기 때문입니다. + +--- + +## 조사 + +여러 대안을 사용해 보며 다양한 도구에서 배운 점들을 모아 저와 개발팀에게 가장 적합한 방식을 찾았습니다. + +예를 들어, 표준 :term:Python 타입 힌트 에 기반하는 것이 이상적이라는 점이 명확했습니다. + +또한, 이미 존재하는 표준을 활용하는 것이 가장 좋은 접근법이라 판단했습니다. + +그래서 **FastAPI**의 코드를 작성하기 전에 몇 달 동안 OpenAPI, JSON Schema, OAuth2 명세를 연구하며 이들의 관계와 겹치는 부분, 차이점을 이해했습니다. + +--- + +## 디자인 + +그 후, **FastAPI** 사용자가 될 개발자로서 사용하고 싶은 개발자 "API"를 디자인했습니다. + +[Python Developer Survey](https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools)에 따르면 약 80%의 Python 개발자가 PyCharm, VS Code, Jedi 기반 편집기 등에서 개발합니다. 이 과정에서 여러 아이디어를 테스트했습니다. + +대부분의 다른 편집기도 유사하게 동작하기 때문에, **FastAPI**의 이점은 거의 모든 편집기에서 누릴 수 있습니다. + +이 과정을 통해 코드 중복을 최소화하고, 모든 곳에서 자동 완성, 타입 검사, 에러 확인 기능이 제공되는 최적의 방식을 찾아냈습니다. + +이 모든 것은 개발자들에게 최고의 개발 경험을 제공하기 위해 설계되었습니다. + +--- + +## 필요조건 + +여러 대안을 테스트한 후, [Pydantic](https://docs.pydantic.dev/)을 사용하기로 결정했습니다. + +이후 저는 **Pydantic**이 JSON Schema와 완벽히 호환되도록 개선하고, 다양한 제약 조건 선언을 지원하며, 여러 편집기에서의 자동 완성과 타입 검사 기능을 향상하기 위해 기여했습니다. + +또한, 또 다른 주요 필요조건이었던 [Starlette](https://www.starlette.dev/)에도 기여했습니다. + +--- + +## 개발 + +**FastAPI**를 개발하기 시작할 즈음에는 대부분의 준비가 이미 완료된 상태였습니다. 설계가 정의되었고, 필요조건과 도구가 준비되었으며, 표준과 명세에 대한 지식도 충분했습니다. + +--- + +## 미래 + +현시점에서 **FastAPI**가 많은 사람들에게 유용하다는 것이 명백해졌습니다. + +여러 용도에 더 적합한 도구로서 기존 대안보다 선호되고 있습니다. +이미 많은 개발자와 팀들이 **FastAPI**에 의존해 프로젝트를 진행 중입니다 (저와 제 팀도 마찬가지입니다). + +하지만 여전히 개선해야 할 점과 추가할 기능들이 많이 남아 있습니다. + +**FastAPI**는 밝은 미래로 나아가고 있습니다. +그리고 [여러분의 도움](help-fastapi.md){.internal-link target=_blank}은 큰 힘이 됩니다. diff --git a/docs/ko/docs/how-to/conditional-openapi.md b/docs/ko/docs/how-to/conditional-openapi.md new file mode 100644 index 0000000000..79c7f0dd2d --- /dev/null +++ b/docs/ko/docs/how-to/conditional-openapi.md @@ -0,0 +1,61 @@ +# 조건부적인 OpenAPI + +필요한 경우, 설정 및 환경 변수를 사용하여 환경에 따라 조건부로 OpenAPI를 구성하고 완전히 비활성화할 수도 있습니다. + +## 보안, API 및 docs에 대해서 + +프로덕션에서, 문서화된 사용자 인터페이스(UI)를 숨기는 것이 API를 보호하는 방법이 *되어서는 안 됩니다*. + +이는 API에 추가적인 보안을 제공하지 않으며, *경로 작업*은 여전히 동일한 위치에서 사용 할 수 있습니다. + +코드에 보안 결함이 있다면, 그 결함은 여전히 존재할 것입니다. + +문서를 숨기는 것은 API와 상호작용하는 방법을 이해하기 어렵게 만들며, 프로덕션에서 디버깅을 더 어렵게 만들 수 있습니다. 이는 단순히 '모호성에 의한 보안'의 한 형태로 간주될 수 있습니다. + +API를 보호하고 싶다면, 예를 들어 다음과 같은 더 나은 방법들이 있습니다: + +* 요청 본문과 응답에 대해 잘 정의된 Pydantic 모델을 사용하도록 하세요. + +* 종속성을 사용하여 필요한 권한과 역할을 구성하세요. + +* 평문 비밀번호를 절대 저장하지 말고, 오직 암호화된 비밀번호만 저장하세요. + +* Passlib과 JWT 토큰과 같은 잘 알려진 암호화 도구들을 구현하고 사용하세요. + +* 필요한 곳에 OAuth2 범위를 사용하여 더 세분화된 권한 제어를 추가하세요. + +* 등등.... + +그럼에도 불구하고, 특정 환경(예: 프로덕션)에서 또는 환경 변수의 설정에 따라 API 문서를 비활성화해야 하는 매우 특정한 사용 사례가 있을 수 있습니다. + +## 설정 및 환경변수의 조건부 OpenAPI + +동일한 Pydantic 설정을 사용하여 생성된 OpenAPI 및 문서 UI를 쉽게 구성할 수 있습니다. + +예를 들어: + +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} + +여기서 `openapi_url` 설정을 기본값인 `"/openapi.json"`으로 선언합니다. + +그런 뒤, 우리는 `FastAPI` 앱을 만들 때 그것을 사용합니다. + +환경 변수 `OPENAPI_URL`을 빈 문자열로 설정하여 OpenAPI(문서 UI 포함)를 비활성화할 수도 있습니다. 예를 들어: + +
+ +```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`의 URL로 이동하면 `404 Not Found`라는 오류가 다음과 같이 표시됩니다: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/ko/docs/how-to/configure-swagger-ui.md b/docs/ko/docs/how-to/configure-swagger-ui.md new file mode 100644 index 0000000000..5a57342cf2 --- /dev/null +++ b/docs/ko/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Swagger UI 구성 + +추가적인 Swagger UI 매개변수를 구성할 수 있습니다. + +구성을 하려면, `FastAPI()` 앱 객체를 생성할 때 또는 `get_swagger_ui_html()` 함수에 `swagger_ui_parameters` 인수를 전달하십시오. + +`swagger_ui_parameters`는 Swagger UI에 직접 전달된 구성을 포함하는 딕셔너리를 받습니다. + +FastAPI는 이 구성을 **JSON** 형식으로 변환하여 JavaScript와 호환되도록 합니다. 이는 Swagger UI에서 필요로 하는 형식입니다. + +## 구문 강조 비활성화 + +예를 들어, Swagger UI에서 구문 강조 기능을 비활성화할 수 있습니다. + +설정을 변경하지 않으면, 기본적으로 구문 강조 기능이 활성화되어 있습니다: + + + +그러나 `syntaxHighlight`를 `False`로 설정하여 구문 강조 기능을 비활성화할 수 있습니다: + +{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} + +...그럼 Swagger UI에서 더 이상 구문 강조 기능이 표시되지 않습니다: + + + +## 테마 변경 + +동일한 방식으로 `"syntaxHighlight.theme"` 키를 사용하여 구문 강조 테마를 설정할 수 있습니다 (중간에 점이 포함된 것을 참고하십시오). + +{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} + +이 설정은 구문 강조 색상 테마를 변경합니다: + + + +## 기본 Swagger UI 매개변수 변경 + +FastAPI는 대부분의 사용 사례에 적합한 몇 가지 기본 구성 매개변수를 포함하고 있습니다. + +기본 구성에는 다음이 포함됩니다: + +{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} + +`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 일부를 재정의할 수 있습니다. + +예를 들어, `deepLinking`을 비활성화하려면 `swagger_ui_parameters`에 다음 설정을 전달할 수 있습니다: + +{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} + +## 기타 Swagger UI 매개변수 + +사용할 수 있는 다른 모든 구성 옵션을 확인하려면, Swagger UI 매개변수에 대한 공식 문서를 참조하십시오. + +## JavaScript 전용 설정 + +Swagger UI는 **JavaScript 전용** 객체(예: JavaScript 함수)로 다른 구성을 허용하기도 합니다. + +FastAPI는 이러한 JavaScript 전용 `presets` 설정을 포함하고 있습니다: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +이들은 문자열이 아닌 **JavaScript** 객체이므로 Python 코드에서 직접 전달할 수 없습니다. + +이와 같은 JavaScript 전용 구성을 사용해야 하는 경우, 위의 방법 중 하나를 사용하여 모든 Swagger UI 경로 작업을 재정의하고 필요한 JavaScript를 수동으로 작성할 수 있습니다. diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md index 8b00d90bc5..b6b4765dad 100644 --- a/docs/ko/docs/index.md +++ b/docs/ko/docs/index.md @@ -11,15 +11,18 @@ FastAPI 프레임워크, 고성능, 간편한 학습, 빠른 코드 작성, 준비된 프로덕션

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

--- @@ -85,7 +88,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트 "_**FastAPI**가 너무 좋아서 구름 위를 걷는듯 합니다. 정말 즐겁습니다!_" -
Brian Okken - Python Bytes 팟캐스트 호스트 (ref)
+
Brian Okken - Python Bytes 팟캐스트 호스트 (ref)
--- @@ -99,7 +102,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트 "_우리 **API**를 **FastAPI**로 바꿨습니다 [...] 아마 여러분도 좋아하실 것입니다 [...]_" -
Ines Montani - Matthew Honnibal - Explosion AI 설립자 - spaCy 제작자 (ref) - (ref)
+
Ines Montani - Matthew Honnibal - Explosion AI 설립자 - spaCy 제작자 (ref) - (ref)
--- @@ -115,7 +118,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트 FastAPI는 거인들의 어깨 위에 서 있습니다: -* 웹 부분을 위한 Starlette. +* 웹 부분을 위한 Starlette. * 데이터 부분을 위한 Pydantic. ## 설치 @@ -130,7 +133,7 @@ $ pip install fastapi -프로덕션을 위해 Uvicorn 또는 Hypercorn과 같은 ASGI 서버도 필요할 겁니다. +프로덕션을 위해 Uvicorn 또는 Hypercorn과 같은 ASGI 서버도 필요할 겁니다.
@@ -454,7 +457,7 @@ Starlette이 사용하는: FastAPI / Starlette이 사용하는: -* uvicorn - 애플리케이션을 로드하고 제공하는 서버. +* uvicorn - 애플리케이션을 로드하고 제공하는 서버. * orjson - `ORJSONResponse`을 사용하려면 필요. * ujson - `UJSONResponse`를 사용하려면 필요. diff --git a/docs/ko/docs/openapi-webhooks.md b/docs/ko/docs/openapi-webhooks.md new file mode 100644 index 0000000000..96339aa961 --- /dev/null +++ b/docs/ko/docs/openapi-webhooks.md @@ -0,0 +1,55 @@ +# OpenAPI 웹훅(Webhooks) + +API **사용자**에게 특정 **이벤트**가 발생할 때 *그들*의 앱(시스템)에 요청을 보내 **알림**을 전달할 수 있다는 것을 알리고 싶은 경우가 있습니다. + +즉, 일반적으로 사용자가 API에 요청을 보내는 것과는 반대로, **API**(또는 앱)가 **사용자의 시스템**(그들의 API나 앱)으로 **요청을 보내는** 상황을 의미합니다. + +이를 흔히 **웹훅(Webhook)**이라고 부릅니다. + +## 웹훅 스텝 + +**코드에서** 웹훅으로 보낼 메시지, 즉 요청의 **바디(body)**를 정의하는 것이 일반적인 프로세스입니다. + +앱에서 해당 요청이나 이벤트를 전송할 **시점**을 정의합니다. + +**사용자**는 앱이 해당 요청을 보낼 **URL**을 정의합니다. (예: 웹 대시보드에서 설정) + +웹훅의 URL을 등록하는 방법과 이러한 요청을 실제로 전송하는 코드에 대한 모든 로직은 여러분에게 달려 있습니다. 원하는대로 **고유의 코드**를 작성하면 됩니다. + +## **FastAPI**와 OpenAPI로 웹훅 문서화하기 + +**FastAPI**를 사용하여 OpenAPI와 함께 웹훅의 이름, 앱이 보낼 수 있는 HTTP 작업 유형(예: `POST`, `PUT` 등), 그리고 보낼 요청의 **바디**를 정의할 수 있습니다. + +이를 통해 사용자가 **웹훅** 요청을 수신할 **API 구현**을 훨씬 쉽게 할 수 있으며, 경우에 따라 사용자 API 코드의 일부를 자동 생성할 수도 있습니다. + +/// info + +웹훅은 OpenAPI 3.1.0 이상에서 지원되며, FastAPI `0.99.0` 이상 버전에서 사용할 수 있습니다. + +/// + +## 웹훅이 포함된 앱 만들기 + +**FastAPI** 애플리케이션을 만들 때, `webhooks` 속성을 사용하여 *웹훅*을 정의할 수 있습니다. 이는 `@app.webhooks.post()`와 같은 방식으로 *경로(path) 작업*을 정의하는 것과 비슷합니다. + +{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} + +이렇게 정의한 웹훅은 **OpenAPI** 스키마와 자동 **문서화 UI**에 표시됩니다. + +/// info + +`app.webhooks` 객체는 사실 `APIRouter`일 뿐이며, 여러 파일로 앱을 구성할 때 사용하는 것과 동일한 타입입니다. + +/// + +웹훅에서는 실제 **경로(path)** (예: `/items/`)를 선언하지 않는 점에 유의해야 합니다. 여기서 전달하는 텍스트는 **식별자**로, 웹훅의 이름(이벤트 이름)입니다. 예를 들어, `@app.webhooks.post("new-subscription")`에서 웹훅 이름은 `new-subscription`입니다. + +이는 실제 **URL 경로**는 **사용자**가 다른 방법(예: 웹 대시보드)을 통해 지정하도록 기대되기 때문입니다. + +### 문서 확인하기 + +이제 앱을 시작하고 http://127.0.0.1:8000/docs로 이동해 봅시다. + +문서에서 기존 *경로 작업*뿐만 아니라 **웹훅**도 표시된 것을 확인할 수 있습니다: + + diff --git a/docs/ko/docs/project-generation.md b/docs/ko/docs/project-generation.md new file mode 100644 index 0000000000..dd11fca703 --- /dev/null +++ b/docs/ko/docs/project-generation.md @@ -0,0 +1,28 @@ +# Full Stack FastAPI 템플릿 + +템플릿은 일반적으로 특정 설정과 함께 제공되지만, 유연하고 커스터마이징이 가능하게 디자인 되었습니다. 이 특성들은 여러분이 프로젝트의 요구사항에 맞춰 수정, 적용을 할 수 있게 해주고, 템플릿이 완벽한 시작점이 되게 해줍니다. 🏁 + +많은 초기 설정, 보안, 데이터베이스 및 일부 API 엔드포인트가 이미 준비되어 있으므로, 여러분은 이 템플릿을 (프로젝트를) 시작하는 데 사용할 수 있습니다. + +GitHub 저장소: Full Stack FastAPI 템플릿 + +## Full Stack FastAPI 템플릿 - 기술 스택과 기능들 + +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com): Python 백엔드 API. + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com): Python SQL 데이터 상호작용을 위한 (ORM). + - 🔍 [Pydantic](https://docs.pydantic.dev): FastAPI에 의해 사용되는, 데이터 검증과 설정관리. + - 💾 [PostgreSQL](https://www.postgresql.org): SQL 데이터베이스. +- 🚀 [React](https://react.dev): 프론트엔드. + - 💃 TypeScript, hooks, [Vite](https://vitejs.dev) 및 기타 현대적인 프론트엔드 스택을 사용. + - 🎨 [Chakra UI](https://chakra-ui.com): 프론트엔드 컴포넌트. + - 🤖 자동으로 생성된 프론트엔드 클라이언트. + - 🧪 E2E 테스트를 위한 [Playwright](https://playwright.dev). + - 🦇 다크 모드 지원. +- 🐋 [Docker Compose](https://www.docker.com): 개발 환경과 프로덕션(운영). +- 🔒 기본으로 지원되는 안전한 비밀번호 해싱. +- 🔑 JWT 토큰 인증. +- 📫 이메일 기반 비밀번호 복구. +- ✅ [Pytest]를 이용한 테스트(https://pytest.org). +- 📞 [Traefik](https://traefik.io): 리버스 프록시 / 로드 밸런서. +- 🚢 Docker Compose를 이용한 배포 지침: 자동 HTTPS 인증서를 처리하기 위한 프론트엔드 Traefik 프록시 설정 방법을 포함. +- 🏭 GitHub Actions를 기반으로 CI (지속적인 통합) 및 CD (지속적인 배포). diff --git a/docs/ko/docs/python-types.md b/docs/ko/docs/python-types.md index 5c458e48de..18d4b341e4 100644 --- a/docs/ko/docs/python-types.md +++ b/docs/ko/docs/python-types.md @@ -12,7 +12,7 @@ 비록 **FastAPI**를 쓰지 않는다고 하더라도, 조금이라도 알아두면 도움이 될 것입니다. -/// note | "참고" +/// note | 참고 파이썬에 능숙하셔서 타입 힌트에 대해 모두 아신다면, 다음 챕터로 건너뛰세요. @@ -22,9 +22,8 @@ 간단한 예제부터 시작해봅시다: -```Python -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py *} + 이 프로그램을 실행한 결과값: @@ -38,9 +37,8 @@ John Doe * `title()`로 각 첫 문자를 대문자로 변환시킵니다. * 두 단어를 중간에 공백을 두고 연결합니다. -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py hl[2] *} + ### 코드 수정 @@ -82,9 +80,8 @@ John Doe 이게 "타입 힌트"입니다: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial002.py!} -``` +{* ../../docs_src/python_types/tutorial002.py hl[1] *} + 타입힌트는 다음과 같이 기본 값을 선언하는 것과는 다릅니다: @@ -112,9 +109,8 @@ John Doe 아래 함수를 보면, 이미 타입 힌트가 적용되어 있는 걸 볼 수 있습니다: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial003.py!} -``` +{* ../../docs_src/python_types/tutorial003.py hl[1] *} + 편집기가 변수의 타입을 알고 있기 때문에, 자동완성 뿐 아니라 에러도 확인할 수 있습니다: @@ -122,9 +118,8 @@ John Doe 이제 고쳐야하는 걸 알기 때문에, `age`를 `str(age)`과 같이 문자열로 바꾸게 됩니다: -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial004.py!} -``` +{* ../../docs_src/python_types/tutorial004.py hl[2] *} + ## 타입 선언 @@ -143,9 +138,8 @@ John Doe * `bool` * `bytes` -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial005.py!} -``` +{* ../../docs_src/python_types/tutorial005.py hl[1] *} + ### 타입 매개변수를 활용한 Generic(제네릭) 타입 @@ -161,9 +155,8 @@ John Doe `typing`에서 `List`(대문자 `L`)를 import 합니다. -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial006.py!} -``` +{* ../../docs_src/python_types/tutorial006.py hl[1] *} + 콜론(`:`) 문법을 이용하여 변수를 선언합니다. @@ -171,11 +164,10 @@ John Doe 이때 배열은 내부 타입을 포함하는 타입이기 때문에 대괄호 안에 넣어줍니다. -```Python hl_lines="4" -{!../../../docs_src/python_types/tutorial006.py!} -``` +{* ../../docs_src/python_types/tutorial006.py hl[4] *} -/// tip | "팁" + +/// tip | 팁 대괄호 안의 내부 타입은 "타입 매개변수(type paramters)"라고 합니다. @@ -199,9 +191,8 @@ John Doe `tuple`과 `set`도 동일하게 선언할 수 있습니다. -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial007.py!} -``` +{* ../../docs_src/python_types/tutorial007.py hl[1,4] *} + 이 뜻은 아래와 같습니다: @@ -216,9 +207,8 @@ John Doe 두 번째 매개변수는 `dict`의 값(value)입니다. -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial008.py!} -``` +{* ../../docs_src/python_types/tutorial008.py hl[1,4] *} + 이 뜻은 아래와 같습니다: @@ -231,7 +221,7 @@ John Doe `str`과 같이 타입을 선언할 때 `Optional`을 쓸 수도 있는데, "선택적(Optional)"이기때문에 `None`도 될 수 있습니다: ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009.py!} ``` `Optional[str]`을 `str` 대신 쓰게 되면, 특정 값이 실제로는 `None`이 될 수도 있는데 항상 `str`이라고 가정하는 상황에서 에디터가 에러를 찾게 도와줄 수 있습니다. @@ -255,15 +245,13 @@ John Doe 이름(name)을 가진 `Person` 클래스가 있다고 해봅시다. -```Python hl_lines="1-3" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} + 그렇게 하면 변수를 `Person`이라고 선언할 수 있게 됩니다. -```Python hl_lines="6" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[6] *} + 그리고 역시나 모든 에디터 도움을 받게 되겠죠. @@ -283,11 +271,10 @@ John Doe Pydantic 공식 문서 예시: -```Python -{!../../../docs_src/python_types/tutorial011.py!} -``` +{* ../../docs_src/python_types/tutorial011.py *} -/// info | "정보" + +/// info | 정보 Pydantic<에 대해 더 배우고 싶다면 공식 문서를 참고하세요. @@ -319,7 +306,7 @@ Pydantic<에 대해 더 배우고 싶다면 `mypy`에서 제공하는 "cheat sheet"이 좋은 자료가 될 겁니다. diff --git a/docs/ko/docs/resources/index.md b/docs/ko/docs/resources/index.md new file mode 100644 index 0000000000..e804dd4d56 --- /dev/null +++ b/docs/ko/docs/resources/index.md @@ -0,0 +1,3 @@ +# 리소스 + +추가 리소스, 외부 링크, 기사 등. ✈️ diff --git a/docs/ko/docs/security/index.md b/docs/ko/docs/security/index.md new file mode 100644 index 0000000000..5a6c733f05 --- /dev/null +++ b/docs/ko/docs/security/index.md @@ -0,0 +1,19 @@ +# 고급 보안 + +## 추가 기능 + +[자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서에서 다룬 내용 외에도 보안 처리를 위한 몇 가지 추가 기능이 있습니다. + +/// tip + +다음 섹션은 **반드시 "고급"** 기능은 아닙니다. + +그리고 여러분의 사용 사례에 따라, 적합한 해결책이 그 중 하나에 있을 가능성이 있습니다. + +/// + +## 먼저 자습서 읽기 + +다음 섹션은 이미 [자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서를 읽었다고 가정합니다. + +이 섹션들은 모두 동일한 개념을 바탕으로 하며, 추가 기능을 제공합니다. diff --git a/docs/ko/docs/tutorial/background-tasks.md b/docs/ko/docs/tutorial/background-tasks.md index 880a1c198b..9c4d57481f 100644 --- a/docs/ko/docs/tutorial/background-tasks.md +++ b/docs/ko/docs/tutorial/background-tasks.md @@ -15,9 +15,7 @@ FastAPI에서는 응답을 반환한 후에 실행할 백그라운드 작업을 먼저 아래와 같이 `BackgroundTasks`를 임포트하고, `BackgroundTasks`를 _경로 작동 함수_ 에서 매개변수로 가져오고 정의합니다. -```Python hl_lines="1 13" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} **FastAPI** 는 `BackgroundTasks` 개체를 생성하고, 매개 변수로 전달합니다. @@ -33,17 +31,13 @@ FastAPI에서는 응답을 반환한 후에 실행할 백그라운드 작업을 그리고 이 작업은 `async`와 `await`를 사용하지 않으므로 일반 `def` 함수로 선언합니다. -```Python hl_lines="6-9" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} ## 백그라운드 작업 추가 _경로 작동 함수_ 내에서 작업 함수를 `.add_task()` 함수 통해 _백그라운드 작업_ 개체에 전달합니다. -```Python hl_lines="14" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} `.add_task()` 함수는 다음과 같은 인자를 받습니다 : @@ -57,21 +51,7 @@ _경로 작동 함수_ 내에서 작업 함수를 `.add_task()` 함수 통해 _ **FastAPI**는 각 경우에 수행할 작업과 동일한 개체를 내부적으로 재사용하기에, 모든 백그라운드 작업이 함께 병합되고 나중에 백그라운드에서 실행됩니다. -//// tab | Python 3.6 and above - -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002.py!} -``` - -//// - -//// tab | Python 3.10 and above - -```Python hl_lines="11 13 20 23" -{!> ../../../docs_src/background_tasks/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *} 이 예제에서는 응답이 반환된 후에 `log.txt` 파일에 메시지가 기록됩니다. @@ -81,7 +61,7 @@ _경로 작동 함수_ 내에서 작업 함수를 `.add_task()` 함수 통해 _ ## 기술적 세부사항 -`BackgroundTasks` 클래스는 `starlette.background`에서 직접 가져옵니다. +`BackgroundTasks` 클래스는 `starlette.background`에서 직접 가져옵니다. `BackgroundTasks` 클래스는 FastAPI에서 직접 임포트하거나 포함하기 때문에 실수로 `BackgroundTask` (끝에 `s`가 없음)을 임포트하더라도 starlette.background에서 `BackgroundTask`를 가져오는 것을 방지할 수 있습니다. @@ -89,7 +69,7 @@ _경로 작동 함수_ 내에서 작업 함수를 `.add_task()` 함수 통해 _ FastAPI에서 `BackgroundTask`를 단독으로 사용하는 것은 여전히 가능합니다. 하지만 객체를 코드에서 생성하고, 이 객체를 포함하는 Starlette `Response`를 반환해야 합니다. -`Starlette의 공식 문서`에서 백그라운드 작업에 대한 자세한 내용을 확인할 수 있습니다. +`Starlette의 공식 문서`에서 백그라운드 작업에 대한 자세한 내용을 확인할 수 있습니다. ## 경고 @@ -97,8 +77,6 @@ FastAPI에서 `BackgroundTask`를 단독으로 사용하는 것은 여전히 가 RabbitMQ 또는 Redis와 같은 메시지/작업 큐 시스템 보다 복잡한 구성이 필요한 경향이 있지만, 여러 작업 프로세스를 특히 여러 서버의 백그라운드에서 실행할 수 있습니다. -예제를 보시려면 [프로젝트 생성기](../project-generation.md){.internal-link target=\_blank} 를 참고하세요. 해당 예제에는 이미 구성된 `Celery`가 포함되어 있습니다. - 그러나 동일한 FastAPI 앱에서 변수 및 개체에 접근해야햐는 작은 백그라운드 수행이 필요한 경우 (예 : 알림 이메일 보내기) 간단하게 `BackgroundTasks`를 사용해보세요. ## 요약 diff --git a/docs/ko/docs/tutorial/body-fields.md b/docs/ko/docs/tutorial/body-fields.md index b74722e266..4708e7099f 100644 --- a/docs/ko/docs/tutorial/body-fields.md +++ b/docs/ko/docs/tutorial/body-fields.md @@ -6,59 +6,9 @@ 먼저 이를 임포트해야 합니다: -//// tab | Python 3.10+ +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *} -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="2" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// - -/// warning | "경고" +/// warning | 경고 `Field`는 다른 것들처럼 (`Query`, `Path`, `Body` 등) `fastapi`에서가 아닌 `pydantic`에서 바로 임포트 되는 점에 주의하세요. @@ -68,61 +18,11 @@ 그 다음 모델 어트리뷰트와 함께 `Field`를 사용할 수 있습니다: -//// tab | Python 3.10+ - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12-15" -{!> ../../../docs_src/body_fields/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="9-12" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *} `Field`는 `Query`, `Path`와 `Body`와 같은 방식으로 동작하며, 모두 같은 매개변수들 등을 가집니다. -/// note | "기술적 세부사항" +/// note | 기술적 세부사항 실제로 `Query`, `Path`등, 여러분이 앞으로 볼 다른 것들은 공통 클래스인 `Param` 클래스의 서브클래스 객체를 만드는데, 그 자체로 Pydantic의 `FieldInfo` 클래스의 서브클래스입니다. @@ -134,7 +34,7 @@ /// -/// tip | "팁" +/// tip | 팁 주목할 점은 타입, 기본 값 및 `Field`로 이루어진 각 모델 어트리뷰트가 `Path`, `Query`와 `Body`대신 `Field`를 사용하는 *경로 작동 함수*의 매개변수와 같은 구조를 가진다는 점 입니다. @@ -146,7 +46,7 @@ 여러분이 예제를 선언할 때 나중에 이 공식 문서에서 별도 정보를 추가하는 방법을 배울 것입니다. -/// warning | "경고" +/// warning | 경고 별도 키가 전달된 `Field` 또한 여러분의 어플리케이션의 OpenAPI 스키마에 나타날 것입니다. 이런 키가 OpenAPI 명세서, [the OpenAPI validator](https://validator.swagger.io/)같은 몇몇 OpenAPI 도구들에 포함되지 못할 수 있으며, 여러분이 생성한 스키마와 호환되지 않을 수 있습니다. diff --git a/docs/ko/docs/tutorial/body-multiple-params.md b/docs/ko/docs/tutorial/body-multiple-params.md index 023575e1bb..edf892dfab 100644 --- a/docs/ko/docs/tutorial/body-multiple-params.md +++ b/docs/ko/docs/tutorial/body-multiple-params.md @@ -10,11 +10,9 @@ 또한, 기본 값을 `None`으로 설정해 본문 매개변수를 선택사항으로 선언할 수 있습니다. -```Python hl_lines="19-21" -{!../../../docs_src/body_multiple_params/tutorial001.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial001.py hl[19:21] *} -/// note | "참고" +/// note | 참고 이 경우에는 본문으로 부터 가져온 ` item`은 기본값이 `None`이기 때문에, 선택사항이라는 점을 유의해야 합니다. @@ -35,9 +33,7 @@ 하지만, 다중 본문 매개변수 역시 선언할 수 있습니다. 예. `item`과 `user`: -```Python hl_lines="22" -{!../../../docs_src/body_multiple_params/tutorial002.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *} 이 경우에, **FastAPI**는 이 함수 안에 한 개 이상의 본문 매개변수(Pydantic 모델인 두 매개변수)가 있다고 알 것입니다. @@ -58,7 +54,7 @@ } ``` -/// note | "참고" +/// note | 참고 이전과 같이 `item`이 선언 되었더라도, 본문 내의 `item` 키가 있을 것이라고 예측합니다. @@ -79,9 +75,7 @@ FastAPI는 요청을 자동으로 변환해, 매개변수의 `item`과 `user`를 하지만, **FastAPI**의 `Body`를 사용해 다른 본문 키로 처리하도록 제어할 수 있습니다: -```Python hl_lines="23" -{!../../../docs_src/body_multiple_params/tutorial003.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial003.py hl[23] *} 이 경우에는 **FastAPI**는 본문을 이와 같이 예측할 것입니다: @@ -110,9 +104,7 @@ FastAPI는 요청을 자동으로 변환해, 매개변수의 `item`과 `user`를 기본적으로 단일 값은 쿼리 매개변수로 해석되므로, 명시적으로 `Query`를 추가할 필요가 없고, 아래처럼 할 수 있습니다: -```Python hl_lines="27" -{!../../../docs_src/body_multiple_params/tutorial004.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *} 이렇게: @@ -120,7 +112,7 @@ FastAPI는 요청을 자동으로 변환해, 매개변수의 `item`과 `user`를 q: Optional[str] = None ``` -/// info | "정보" +/// info | 정보 `Body` 또한 `Query`, `Path` 그리고 이후에 볼 다른 것들처럼 동일한 추가 검증과 메타데이터 매개변수를 갖고 있습니다. @@ -134,9 +126,7 @@ Pydantic 모델 `Item`의 `item`을 본문 매개변수로 오직 한개만 갖 하지만, 만약 모델 내용에 `item `키를 가진 JSON으로 예측하길 원한다면, 추가적인 본문 매개변수를 선언한 것처럼 `Body`의 특별한 매개변수인 `embed`를 사용할 수 있습니다: -```Python hl_lines="17" -{!../../../docs_src/body_multiple_params/tutorial005.py!} -``` +{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *} 아래 처럼: diff --git a/docs/ko/docs/tutorial/body-nested-models.md b/docs/ko/docs/tutorial/body-nested-models.md index 4d785f64b1..ebd7b3ba6a 100644 --- a/docs/ko/docs/tutorial/body-nested-models.md +++ b/docs/ko/docs/tutorial/body-nested-models.md @@ -5,9 +5,7 @@ 어트리뷰트를 서브타입으로 정의할 수 있습니다. 예를 들어 파이썬 `list`는: -```Python hl_lines="14" -{!../../../docs_src/body_nested_models/tutorial001.py!} -``` +{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *} 이는 `tags`를 항목 리스트로 만듭니다. 각 항목의 타입을 선언하지 않더라도요. @@ -19,9 +17,7 @@ 먼저, 파이썬 표준 `typing` 모듈에서 `List`를 임포트합니다: -```Python hl_lines="1" -{!../../../docs_src/body_nested_models/tutorial002.py!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} ### 타입 매개변수로 `List` 선언 @@ -42,9 +38,7 @@ my_list: List[str] 마찬가지로 예제에서 `tags`를 구체적으로 "문자열의 리스트"로 만들 수 있습니다: -```Python hl_lines="14" -{!../../../docs_src/body_nested_models/tutorial002.py!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *} ## 집합 타입 @@ -54,9 +48,7 @@ my_list: List[str] 그렇다면 `Set`을 임포트 하고 `tags`를 `str`의 `set`으로 선언할 수 있습니다: -```Python hl_lines="1 14" -{!../../../docs_src/body_nested_models/tutorial003.py!} -``` +{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *} 덕분에 중복 데이터가 있는 요청을 수신하더라도 고유한 항목들의 집합으로 변환됩니다. @@ -78,17 +70,13 @@ Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다. 예를 들어, `Image` 모델을 선언할 수 있습니다: -```Python hl_lines="9-11" -{!../../../docs_src/body_nested_models/tutorial004.py!} -``` +{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *} ### 서브모듈을 타입으로 사용 그리고 어트리뷰트의 타입으로 사용할 수 있습니다: -```Python hl_lines="20" -{!../../../docs_src/body_nested_models/tutorial004.py!} -``` +{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *} 이는 **FastAPI**가 다음과 유사한 본문을 기대한다는 것을 의미합니다: @@ -121,9 +109,7 @@ Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다. 예를 들어 `Image` 모델 안에 `url` 필드를 `str` 대신 Pydantic의 `HttpUrl`로 선언할 수 있습니다: -```Python hl_lines="4 10" -{!../../../docs_src/body_nested_models/tutorial005.py!} -``` +{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *} 이 문자열이 유효한 URL인지 검사하고 JSON 스키마/OpenAPI로 문서화 됩니다. @@ -131,9 +117,7 @@ Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다. `list`, `set` 등의 서브타입으로 Pydantic 모델을 사용할 수도 있습니다: -```Python hl_lines="20" -{!../../../docs_src/body_nested_models/tutorial006.py!} -``` +{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *} 아래와 같은 JSON 본문으로 예상(변환, 검증, 문서화 등을)합니다: @@ -161,7 +145,7 @@ Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다. } ``` -/// info | "정보" +/// info | 정보 `images` 키가 어떻게 이미지 객체 리스트를 갖는지 주목하세요. @@ -171,11 +155,9 @@ Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다. 단독으로 깊게 중첩된 모델을 정의할 수 있습니다: -```Python hl_lines="9 14 20 23 27" -{!../../../docs_src/body_nested_models/tutorial007.py!} -``` +{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *} -/// info | "정보" +/// info | 정보 `Offer`가 선택사항 `Image` 리스트를 차례로 갖는 `Item` 리스트를 어떻게 가지고 있는지 주목하세요 @@ -191,9 +173,7 @@ images: List[Image] 이를 아래처럼: -```Python hl_lines="15" -{!../../../docs_src/body_nested_models/tutorial008.py!} -``` +{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *} ## 어디서나 편집기 지원 @@ -223,11 +203,9 @@ Pydantic 모델 대신에 `dict`를 직접 사용하여 작업할 경우, 이러 이 경우, `float` 값을 가진 `int` 키가 있는 모든 `dict`를 받아들입니다: -```Python hl_lines="15" -{!../../../docs_src/body_nested_models/tutorial009.py!} -``` +{* ../../docs_src/body_nested_models/tutorial009.py hl[15] *} -/// tip | "팁" +/// tip | 팁 JSON은 오직 `str`형 키만 지원한다는 것을 염두에 두세요. diff --git a/docs/ko/docs/tutorial/body.md b/docs/ko/docs/tutorial/body.md index 337218eb46..b3914fa4b4 100644 --- a/docs/ko/docs/tutorial/body.md +++ b/docs/ko/docs/tutorial/body.md @@ -8,7 +8,7 @@ **요청** 본문을 선언하기 위해서 모든 강력함과 이점을 갖춘 Pydantic 모델을 사용합니다. -/// info | "정보" +/// info | 정보 데이터를 보내기 위해, (좀 더 보편적인) `POST`, `PUT`, `DELETE` 혹은 `PATCH` 중에 하나를 사용하는 것이 좋습니다. @@ -22,21 +22,7 @@ 먼저 `pydantic`에서 `BaseModel`를 임포트해야 합니다: -//// tab | Python 3.10+ - -```Python hl_lines="2" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// +{* ../../docs_src/body/tutorial001_py310.py hl[2] *} ## 여러분의 데이터 모델 만들기 @@ -44,21 +30,7 @@ 모든 어트리뷰트에 대해 표준 파이썬 타입을 사용합니다: -//// tab | Python 3.10+ - -```Python hl_lines="5-9" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7-11" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// +{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} 쿼리 매개변수를 선언할 때와 같이, 모델 어트리뷰트가 기본 값을 가지고 있어도 이는 필수가 아닙니다. 그외에는 필수입니다. 그저 `None`을 사용하여 선택적으로 만들 수 있습니다. @@ -86,21 +58,7 @@ 여러분의 *경로 작동*에 추가하기 위해, 경로 매개변수 그리고 쿼리 매개변수에서 선언했던 것과 같은 방식으로 선언하면 됩니다. -//// tab | Python 3.10+ - -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// +{* ../../docs_src/body/tutorial001_py310.py hl[16] *} ...그리고 만들어낸 모델인 `Item`으로 타입을 선언합니다. @@ -149,7 +107,7 @@ -/// tip | "팁" +/// tip | 팁 만약 PyCharm를 편집기로 사용한다면, Pydantic PyCharm Plugin을 사용할 수 있습니다. @@ -167,21 +125,7 @@ 함수 안에서 모델 객체의 모든 어트리뷰트에 직접 접근 가능합니다: -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/body/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/body/tutorial002.py!} -``` - -//// +{* ../../docs_src/body/tutorial002_py310.py hl[19] *} ## 요청 본문 + 경로 매개변수 @@ -189,21 +133,7 @@ **FastAPI**는 경로 매개변수와 일치하는 함수 매개변수가 **경로에서 가져와야 한다**는 것을 인지하며, Pydantic 모델로 선언된 그 함수 매개변수는 **요청 본문에서 가져와야 한다**는 것을 인지할 것입니다. -//// tab | Python 3.10+ - -```Python hl_lines="15-16" -{!> ../../../docs_src/body/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17-18" -{!> ../../../docs_src/body/tutorial003.py!} -``` - -//// +{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} ## 요청 본문 + 경로 + 쿼리 매개변수 @@ -211,21 +141,7 @@ **FastAPI**는 각각을 인지하고 데이터를 옳바른 위치에 가져올 것입니다. -//// tab | Python 3.10+ - -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial004.py!} -``` - -//// +{* ../../docs_src/body/tutorial004_py310.py hl[16] *} 함수 매개변수는 다음을 따라서 인지하게 됩니다: @@ -233,7 +149,7 @@ * 만약 매개변수가 (`int`, `float`, `str`, `bool` 등과 같은) **유일한 타입**으로 되어있으면, **쿼리** 매개변수로 해석될 것입니다. * 만약 매개변수가 **Pydantic 모델** 타입으로 선언되어 있으면, 요청 **본문**으로 해석될 것입니다. -/// note | "참고" +/// note | 참고 FastAPI는 `q`의 값이 필요없음을 알게 될 것입니다. 기본 값이 `= None`이기 때문입니다. diff --git a/docs/ko/docs/tutorial/cookie-param-models.md b/docs/ko/docs/tutorial/cookie-param-models.md new file mode 100644 index 0000000000..e7eef0b1de --- /dev/null +++ b/docs/ko/docs/tutorial/cookie-param-models.md @@ -0,0 +1,76 @@ +# 쿠키 매개변수 모델 + +관련있는 **쿠키**들의 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다. 🍪 + +이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😍 + +/// note | 참고 + +이 기능은 FastAPI 버전 `0.115.0` 이후부터 지원됩니다. 🤓 + +/// + +/// tip | 팁 + +동일한 기술이 `Query`, `Cookie`, 그리고 `Header`에 적용됩니다. 😎 + +/// + +## Pydantic 모델을 사용한 쿠키 + +**Pydantic 모델**에 필요한 **쿠키** 매개변수를 선언한 다음, 해당 매개변수를 `Cookie`로 선언합니다: + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +**FastAPI**는 요청에서 받은 **쿠키**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다. + +## 문서 확인하기 + +문서 UI `/docs`에서 정의한 쿠키를 볼 수 있습니다: + +
+ +
+ +/// info | 정보 + +명심하세요, 내부적으로 **브라우저는 쿠키를 특별한 방식으로 처리**하기 때문에 **자바스크립트**가 쉽게 쿠키를 건드릴 수 **없습니다**. + +`/docs`에서 **API 문서 UI**로 이동하면 *경로 작업*에 대한 쿠키의 **문서**를 볼 수 있습니다. + +하지만 아무리 **데이터를 입력**하고 "실행(Execute)"을 클릭해도, 문서 UI는 **자바스크립트**로 작동하기 때문에 쿠키는 전송되지 않고, 아무 값도 쓰지 않은 것처럼 **오류** 메시지를 보게 됩니다. + +/// + +## 추가 쿠키 금지하기 + +일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 쿠키를 **제한**할 수 있습니다. + +이제 API는 자신의 쿠키 동의를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪 + +Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다: + +{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} + +클라이언트가 **추가 쿠키**를 보내려고 시도하면, **오류** 응답을 받게 됩니다. + +API가 거부하는데도 동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들. 🍪 + +예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` 쿠키가 허용되지 않는다는 **오류** 응답을 받게 됩니다: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## 요약 + +**Pydantic 모델**을 사용하여 **FastAPI**에서 **쿠키**를 선언할 수 있습니다. 😍 diff --git a/docs/ko/docs/tutorial/cookie-params.md b/docs/ko/docs/tutorial/cookie-params.md index 5f129b63fc..fba756d498 100644 --- a/docs/ko/docs/tutorial/cookie-params.md +++ b/docs/ko/docs/tutorial/cookie-params.md @@ -6,57 +6,7 @@ 먼저 `Cookie`를 임포트합니다: -//// tab | Python 3.10+ - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` - -//// +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} ## `Cookie` 매개변수 선언 @@ -64,59 +14,9 @@ 첫 번째 값은 기본값이며, 추가 검증이나 어노테이션 매개변수 모두 전달할 수 있습니다: -//// tab | Python 3.10+ +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/cookie_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` - -//// - -/// note | "기술 세부사항" +/// note | 기술 세부사항 `Cookie`는 `Path` 및 `Query`의 "자매"클래스입니다. 이 역시 동일한 공통 `Param` 클래스를 상속합니다. @@ -124,7 +24,7 @@ /// -/// info | "정보" +/// info | 정보 쿠키를 선언하기 위해서는 `Cookie`를 사용해야 합니다. 그렇지 않으면 해당 매개변수를 쿼리 매개변수로 해석하기 때문입니다. diff --git a/docs/ko/docs/tutorial/cors.md b/docs/ko/docs/tutorial/cors.md index 312fdee1ba..1ef5a74803 100644 --- a/docs/ko/docs/tutorial/cors.md +++ b/docs/ko/docs/tutorial/cors.md @@ -46,9 +46,7 @@ * 특정한 HTTP 메소드(`POST`, `PUT`) 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 메소드. * 특정한 HTTP 헤더 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 헤더. -```Python hl_lines="2 6-11 13-19" -{!../../../docs_src/cors/tutorial001.py!} -``` +{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} `CORSMiddleware` 에서 사용하는 기본 매개변수는 제한적이므로, 브라우저가 교차-도메인 상황에서 특정한 출처, 메소드, 헤더 등을 사용할 수 있도록 하려면 이들을 명시적으로 허용해야 합니다. @@ -78,7 +76,7 @@ CORS에 대한 더 많은 정보를 알고싶다면, Mozilla CORS 문서를 참고하기 바랍니다. -/// note | "기술적 세부 사항" +/// note | 기술적 세부 사항 `from starlette.middleware.cors import CORSMiddleware` 역시 사용할 수 있습니다. diff --git a/docs/ko/docs/tutorial/debugging.md b/docs/ko/docs/tutorial/debugging.md index cb45e51692..e42f1ba880 100644 --- a/docs/ko/docs/tutorial/debugging.md +++ b/docs/ko/docs/tutorial/debugging.md @@ -6,9 +6,7 @@ FastAPI 애플리케이션에서 `uvicorn`을 직접 임포트하여 실행합니다 -```Python hl_lines="1 15" -{!../../../docs_src/debugging/tutorial001.py!} -``` +{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} ### `__name__ == "__main__"` 에 대하여 @@ -74,7 +72,7 @@ from myapp import app 은 실행되지 않습니다. -/// info | "정보" +/// info | 정보 자세한 내용은 공식 Python 문서를 확인하세요 diff --git a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md index 259fe4b6d6..3e5cdcc8c8 100644 --- a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md @@ -6,21 +6,7 @@ 이전 예제에서, 우리는 의존성(의존 가능한) 함수에서 `딕셔너리`객체를 반환하고 있었습니다: -//// tab | 파이썬 3.6 이상 - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001.py hl[9] *} 우리는 *경로 작동 함수*의 매개변수 `commons`에서 `딕셔너리` 객체를 얻습니다. @@ -81,57 +67,15 @@ FastAPI가 실질적으로 확인하는 것은 "호출 가능성"(함수, 클래 그래서, 우리는 위 예제에서의 `common_paramenters` 의존성을 클래스 `CommonQueryParams`로 바꿀 수 있습니다. -//// tab | 파이썬 3.6 이상 - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="9-13" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002.py hl[11:15] *} 클래스의 인스턴스를 생성하는 데 사용되는 `__init__` 메서드에 주목하기 바랍니다: -//// tab | 파이썬 3.6 이상 - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002.py hl[12] *} ...이전 `common_parameters`와 동일한 매개변수를 가집니다: -//// tab | 파이썬 3.6 이상 - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="6" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001.py hl[9] *} 이 매개변수들은 **FastAPI**가 의존성을 "해결"하기 위해 사용할 것입니다 @@ -147,21 +91,7 @@ FastAPI가 실질적으로 확인하는 것은 "호출 가능성"(함수, 클래 이제 아래의 클래스를 이용해서 의존성을 정의할 수 있습니다. -//// tab | 파이썬 3.6 이상 - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002.py hl[19] *} **FastAPI**는 `CommonQueryParams` 클래스를 호출합니다. 이것은 해당 클래스의 "인스턴스"를 생성하고 그 인스턴스는 함수의 매개변수 `commons`로 전달됩니다. @@ -200,21 +130,7 @@ commons = Depends(CommonQueryParams) ..전체적인 코드는 아래와 같습니다: -//// tab | 파이썬 3.6 이상 - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial003.py hl[19] *} 그러나 자료형을 선언하면 에디터가 매개변수 `commons`로 전달될 것이 무엇인지 알게 되고, 이를 통해 코드 완성, 자료형 확인 등에 도움이 될 수 있으므로 권장됩니다. @@ -248,25 +164,11 @@ commons: CommonQueryParams = Depends() 아래에 같은 예제가 있습니다: -//// tab | 파이썬 3.6 이상 - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial004.py hl[19] *} ...이렇게 코드를 단축하여도 **FastAPI**는 무엇을 해야하는지 알고 있습니다. -/// tip | "팁" +/// tip | 팁 만약 이것이 도움이 되기보다 더 헷갈리게 만든다면, 잊어버리십시오. 이것이 반드시 필요한 것은 아닙니다. diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index bc8af488da..4a3854cefa 100644 --- a/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,39 +14,11 @@ `Depends()`로 된 `list`이어야합니다: -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} 이러한 의존성들은 기존 의존성들과 같은 방식으로 실행/해결됩니다. 그러나 값은 (무엇이든 반환한다면) *경로 작동 함수*에 제공되지 않습니다. -/// tip | "팁" +/// tip | 팁 일부 편집기에서는 사용되지 않는 함수 매개변수를 검사하고 오류로 표시합니다. @@ -56,7 +28,7 @@ /// -/// info | "정보" +/// info | 정보 이 예시에서 `X-Key`와 `X-Token`이라는 커스텀 헤더를 만들어 사용했습니다. @@ -72,69 +44,13 @@ (헤더같은) 요청 요구사항이나 하위-의존성을 선언할 수 있습니다: -//// tab | Python 3.9+ - -```Python hl_lines="8 13" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7 12" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="6 11" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} ### 오류 발생시키기 다음 의존성은 기존 의존성과 동일하게 예외를 `raise`를 일으킬 수 있습니다: -//// tab | Python 3.9+ - -```Python hl_lines="10 15" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 14" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="8 13" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} ### 값 반환하기 @@ -142,35 +58,7 @@ 그래서 이미 다른 곳에서 사용된 (값을 반환하는) 일반적인 의존성을 재사용할 수 있고, 비록 값은 사용되지 않지만 의존성은 실행될 것입니다: -//// tab | Python 3.9+ - -```Python hl_lines="11 16" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10 15" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="9 14" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} ## *경로 작동* 모음에 대한 의존성 diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 0000000000..ff174937de --- /dev/null +++ b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,275 @@ +# yield를 사용하는 의존성 + +FastAPI는 작업 완료 후 추가 단계를 수행하는 의존성을 지원합니다. + +이를 구현하려면 `return` 대신 `yield`를 사용하고, 추가로 실행할 단계 (코드)를 그 뒤에 작성하세요. + +/// tip | 팁 + +각 의존성마다 `yield`는 한 번만 사용해야 합니다. + +/// + +/// note | 기술 세부사항 + +다음과 함께 사용할 수 있는 모든 함수: + +* `@contextlib.contextmanager` 또는 +* `@contextlib.asynccontextmanager` + +는 **FastAPI**의 의존성으로 사용할 수 있습니다. + +사실, FastAPI는 내부적으로 이 두 데코레이터를 사용합니다. + +/// + +## `yield`를 사용하는 데이터베이스 의존성 + +예를 들어, 이 기능을 사용하면 데이터베이스 세션을 생성하고 작업이 끝난 후에 세션을 종료할 수 있습니다. + +응답을 생성하기 전에는 `yield`문을 포함하여 그 이전의 코드만이 실행됩니다: + +{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} + +yield된 값은 *경로 작업* 및 다른 의존성들에 주입되는 값 입니다: + +{* ../../docs_src/dependencies/tutorial007.py hl[4] *} + +`yield`문 다음의 코드는 응답을 생성한 후 보내기 전에 실행됩니다: + +{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} + +/// tip | 팁 + +`async` 함수와 일반 함수 모두 사용할 수 있습니다. + +**FastAPI**는 일반 의존성과 마찬가지로 각각의 함수를 올바르게 처리할 것입니다. + +/// + +## `yield`와 `try`를 사용하는 의존성 + +`yield`를 사용하는 의존성에서 `try` 블록을 사용한다면, 의존성을 사용하는 도중 발생한 모든 예외를 받을 수 있습니다. + +예를 들어, 다른 의존성이나 *경로 작업*의 중간에 데이터베이스 트랜잭션 "롤백"이 발생하거나 다른 오류가 발생한다면, 해당 예외를 의존성에서 받을 수 있습니다. + +따라서, 의존성 내에서 `except SomeException`을 사용하여 특정 예외를 처리할 수 있습니다. + +마찬가지로, `finally`를 사용하여 예외 발생 여부와 관계 없이 종료 단계까 실행되도록 할 수 있습니다. + +{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} + +## `yield`를 사용하는 하위 의존성 + +모든 크기와 형태의 하위 의존성과 하위 의존성의 "트리"도 가질 수 있으며, 이들 모두가 `yield`를 사용할 수 있습니다. + +**FastAPI**는 `yield`를 사용하는 각 의존성의 "종료 코드"가 올바른 순서로 실행되도록 보장합니다. + +예를 들어, `dependency_c`는 `dependency_b`에 의존할 수 있고, `dependency_b`는 `dependency_a`에 의존할 수 있습니다. + +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} + +이들 모두는 `yield`를 사용할 수 있습니다. + +이 경우 `dependency_c`는 종료 코드를 실행하기 위해, `dependency_b`의 값 (여기서는 `dep_b`로 명명)이 여전히 사용 가능해야 합니다. + +그리고, `dependency_b`는 종료 코드를 위해 `dependency_a`의 값 (여기서는 `dep_a`로 명명) 이 사용 가능해야 합니다. + +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} + +같은 방식으로, `yield`를 사용하는 의존성과 `return`을 사용하는 의존성을 함께 사용할 수 있으며, 이들 중 일부가 다른 것들에 의존할 수 있습니다. + +그리고 `yield`를 사용하는 다른 여러 의존성을 필요로 하는 단일 의존성을 가질 수도 있습니다. + +원하는 의존성을 원하는 대로 조합할 수 있습니다. + +**FastAPI**는 모든 것이 올바른 순서로 실행되도록 보장합니다. + +/// note | 기술 세부사항 + +파이썬의 Context Managers 덕분에 이 기능이 작동합니다. + +**FastAPI**는 이를 내부적으로 컨텍스트 관리자를 사용하여 구현합니다. + +/// + +## `yield`와 `HTTPException`를 사용하는 의존성 + +`yield`와 `try` 블록이 있는 의존성을 사용하여 예외를 처리할 수 있다는 것을 알게 되었습니다. + +같은 방식으로, `yield` 이후의 종료 코드에서 `HTTPException`이나 유사한 예외를 발생시킬 수 있습니다. + +/// tip | 팁 + +이는 다소 고급 기술이며, 대부분의 경우 경로 연산 함수 등 나머지 애플리케이션 코드 내부에서 예외 (`HTTPException` 포함)를 발생시킬 수 있으므로 실제로는 필요하지 않을 것입니다. + +하지만 필요한 경우 사용할 수 있습니다. 🤓 + +/// + +{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} + +예외를 처리하고(또는 추가로 다른 `HTTPException`을 발생시키기 위해) 사용할 수 있는 또 다른 방법은 [사용자 정의 예외 처리기](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}를 생성하는 것 입니다. + +## `yield`와 `except`를 사용하는 의존성 + +`yield`를 사용하는 의존성에서 `except`를 사용하여 예외를 포착하고 예외를 다시 발생시키지 않거나 (또는 새 예외를 발생시키지 않으면), FastAPI는 해당 예외가 발생했는지 알 수 없습니다. 이는 일반적인 Python 방식과 동일합니다: + +{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} + +이 경우, `HTTPException`이나 유사한 예외를 발생시키지 않기 때문에 클라이언트는 HTTP 500 Internal Server Error 응답을 보게 되지만, 서버는 어떤 오류가 발생했는지에 대한 **로그**나 다른 표시를 전혀 가지지 않게 됩니다. 😱 + +### `yield`와 `except`를 사용하는 의존성에서 항상 `raise` 하기 + +`yield`가 있는 의존성에서 예외를 잡았을 때는 `HTTPException`이나 유사한 예외를 새로 발생시키지 않는 한, 반드시 원래의 예외를 다시 발생시켜야 합니다. + +`raise`를 사용하여 동일한 예외를 다시 발생시킬 수 있습니다: + +{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} + +이제 클라이언트는 동일한 *HTTP 500 Internal Server Error* 오류 응답을 받게 되지만, 서버 로그에는 사용자 정의 예외인 `InternalError"가 기록됩니다. 😎 + +## `yield`를 사용하는 의존성의 실행 순서 + +실행 순서는 아래 다이어그램과 거의 비슷합니다. 시간은 위에서 아래로 흐릅니다. 그리고 각 열은 상호 작용하거나 코드를 실행하는 부분 중 하나입니다. + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,operation: Can raise exceptions, including HTTPException + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise Exception + dep -->> handler: Raise Exception + handler -->> client: HTTP error response + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise Exception (e.g. HTTPException) + opt handle + dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception + end + handler -->> client: HTTP error response + end + + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> tasks: Handle exceptions in the background task code + end +``` + +/// info | 정보 + +클라이언트에 **하나의 응답** 만 전송됩니다. 이는 오류 응답 중 하나일 수도 있고,*경로 작업*에서 생성된 응답일 수도 있습니다. + +이러한 응답 중 하나가 전송된 후에는 다른 응답을 보낼 수 없습니다. + +/// + +/// tip | 팁 + +이 다이어그램은 `HTTPException`을 보여주지만, `yield`를 사용하는 의존성에서 처리한 예외나 [사용자 정의 예외처리기](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.를 사용하여 처리한 다른 예외도 발생시킬 수 있습니다. + +어떤 예외가 발생하든, `HTTPException`을 포함하여 yield를 사용하는 의존성으로 전달됩니다. 대부분의 경우 예외를 다시 발생시키거나 새로운 예외를 발생시켜야 합니다. + +/// + +## `yield`, `HTTPException`, `except` 및 백그라운드 작업을 사용하는 의존성 + +/// warning | 경고 + +이러한 기술적 세부 사항은 대부분 필요하지 않으므로 이 섹션을 건너뛰고 아래에서 계속 진행해도 됩니다. + +이러한 세부 정보는 주로 FastAPI 0.106.0 이전 버전에서 `yield`가 있는 의존성의 리소스를 백그라운드 작업에서 사용했던 경우메 유용합니다. + +/// + +### `yield`와 `except`를 사용하는 의존성, 기술 세부사항 + +FastAPI 0.110.0 이전에는 `yield`가 포함된 의존성을 사용한 후 해당 의존성에서 `except`가 포함된 예외를 캡처하고 다시 예외를 발생시키지 않으면 예외가 자동으로 예외 핸들러 또는 내부 서버 오류 핸들러로 발생/전달되었습니다. + +이는 처리기 없이 전달된 예외(내부 서버 오류)에서 처리되지 않은 메모리 소비를 수정하고 일반 파이썬 코드의 동작과 일치하도록 하기 위해 0.110.0 버전에서 변경되었습니다. + +### 백그라운드 작업과 `yield`를 사용하는 의존성, 기술 세부사항 + +FastAPI 0.106.0 이전에는 `yield` 이후에 예외를 발생시키는 것이 불가능했습니다. `yield`가 있는 의존성 종료 코드는 응답이 전송된 이후에 실행되었기 때문에, [예외 처리기](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}가 이미 실행된 상태였습니다. + +이는 주로 백그라운드 작업 내에서 의존성에서 "yield된" 동일한 객체를 사용할 수 있도록 하기 위해 이런 방식으로 설계되었습니다. 종료 코드는 백그라운드 작업이 완료된 후에 실행되었기 때문입니다 + +하지만 이렇게 하면 리소스를 불필요하게 양보한 의존성(예: 데이터베이스 연결)에서 보유하면서 응답이 네트워크를 통해 이동할 때까지 기다리는 것을 의미하기 때문에 FastAPI 0.106.0에서 변경되었습니다. + +/// tip | 팁 + +또한 백그라운드 작업은 일반적으로 자체 리소스(예: 자체 데이터베이스 연결)를 사용하여 별도로 처리해야 하는 독립적인 로직 집합입니다. + +따라서 이렇게 하면 코드가 더 깔끔해집니다. + +/// + +만약 이전에 이러한 동작에 의존했다면, 이제는 백그라운드 작업 내부에서 백그라운드 작업을 위한 리소스를 생성하고, `yield`가 있는 의존성의 리소스에 의존하지 않는 데이터만 내부적으로 사용해야합니다. + +예를 들어, 동일한 데이터베이스 세션을 사용하는 대신, 백그라운드 작업 내부에서 새로운 데이터베이스 세션을 생성하고 이 새로운 세션을 사용하여 데이터베이스에서 객체를 가져와야 합니다. 그리고 데이터베이스 객체를 백그라운드 작업 함수의 매개변수로 직접 전달하는 대신, 해당 객체의 ID를 전달한 다음 백그라운드 작업 함수 내부에서 객체를 다시 가져와야 합니다 + +## 컨텍스트 관리자 + +### "컨텍스트 관리자"란? + +"컨텍스트 관리자"는 Python에서 `with` 문에서 사용할 수 있는 모든 객체를 의미합니다. + +예를 들어, `with`를 사용하여 파일을 읽을 수 있습니다: + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +내부적으로 `open("./somefile.txt")` 는 "컨텍스트 관리자(Context Manager)"라고 불리는 객체를 생성합니다. + +`with` 블록이 끝나면, 예외가 발생했더라도 파일을 닫도록 보장합니다. + +`yield`가 있는 의존성을 생성하면 **FastAPI**는 내부적으로 이를 위한 컨텍스트 매니저를 생성하고 다른 관련 도구들과 결합합니다. + +### `yield`를 사용하는 의존성에서 컨텍스트 관리자 사용하기 + +/// warning | 경고 + +이것은 어느 정도 "고급" 개념입니다. + +**FastAPI**를 처음 시작하는 경우 지금은 이 부분을 건너뛰어도 좋습니다. + +/// + +Python에서는 다음을 통해 컨텍스트 관리자를 생성할 수 있습니다. 두 가지 메서드가 있는 클래스를 생성합니다: `__enter__()` and `__exit__()`. + +**FastAPI**의 `yield`가 있는 의존성 내에서 +`with` 또는 `async with`문을 사용하여 이들을 활용할 수 있습니다: + +{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *} + +/// tip | 팁 + +컨텍스트 관리자를 생성하는 또 다른 방법은 다음과 같습니다: + +* `@contextlib.contextmanager` 또는 +* `@contextlib.asynccontextmanager` + +이들은 단일 `yield`가 있는 함수를 꾸미는 데 사용합니다. + +이것이 **FastAPI**가 `yield`가 있는 의존성을 위해 내부적으로 사용하는 방식입니다. + +하지만 FastAPI 의존성에는 이러한 데코레이터를 사용할 필요가 없습니다(그리고 사용해서도 안됩니다). + +FastAPI가 내부적으로 이를 처리해 줄 것입니다. + +/// diff --git a/docs/ko/docs/tutorial/dependencies/global-dependencies.md b/docs/ko/docs/tutorial/dependencies/global-dependencies.md index 2ce2cf4f25..0d0e7684db 100644 --- a/docs/ko/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/ko/docs/tutorial/dependencies/global-dependencies.md @@ -6,35 +6,7 @@ 그런 경우에, 애플리케이션의 모든 *경로 작동*에 적용될 것입니다: -//// tab | Python 3.9+ - -```Python hl_lines="16" -{!> ../../../docs_src/dependencies/tutorial012_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="16" -{!> ../../../docs_src/dependencies/tutorial012_an.py!} -``` - -//// - -//// tab | Python 3.8 Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="15" -{!> ../../../docs_src/dependencies/tutorial012.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *} 그리고 [*경로 작동 데코레이터*에 `dependencies` 추가하기](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}에 대한 아이디어는 여전히 적용되지만 여기에서는 앱에 있는 모든 *경로 작동*에 적용됩니다. diff --git a/docs/ko/docs/tutorial/dependencies/index.md b/docs/ko/docs/tutorial/dependencies/index.md index 361989e2bf..b35a41e37a 100644 --- a/docs/ko/docs/tutorial/dependencies/index.md +++ b/docs/ko/docs/tutorial/dependencies/index.md @@ -31,57 +31,7 @@ *경로 작동 함수*가 가질 수 있는 모든 매개변수를 갖는 단순한 함수입니다: -//// tab | Python 3.10+ - -```Python hl_lines="8-9" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="8-11" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-12" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="6-7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="8-11" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} 이게 다입니다. @@ -101,7 +51,7 @@ 그 후 위의 값을 포함한 `dict` 자료형으로 반환할 뿐입니다. -/// info | "정보" +/// info | 정보 FastAPI는 0.95.0 버전부터 `Annotated`에 대한 지원을 (그리고 이를 사용하기 권장합니다) 추가했습니다. @@ -113,113 +63,13 @@ FastAPI는 0.95.0 버전부터 `Annotated`에 대한 지원을 (그리고 이를 ### `Depends` 불러오기 -//// tab | Python 3.10+ - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="3" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} ### "의존자"에 의존성 명시하기 *경로 작동 함수*의 매개변수로 `Body`, `Query` 등을 사용하는 방식과 같이 새로운 매개변수로 `Depends`를 사용합니다: -//// tab | Python 3.10+ - -```Python hl_lines="13 18" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="15 20" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="16 21" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="11 16" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="15 20" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} 비록 `Body`, `Query` 등을 사용하는 것과 같은 방식으로 여러분의 함수의 매개변수에 있는 `Depends`를 사용하지만, `Depends`는 약간 다르게 작동합니다. @@ -231,7 +81,7 @@ FastAPI는 0.95.0 버전부터 `Annotated`에 대한 지원을 (그리고 이를 그리고 그 함수는 *경로 작동 함수*가 작동하는 것과 같은 방식으로 매개변수를 받습니다. -/// tip | "팁" +/// tip | 팁 여러분은 다음 장에서 함수를 제외하고서, "다른 것들"이 어떻게 의존성으로 사용되는지 알게 될 것입니다. @@ -256,7 +106,7 @@ common_parameters --> read_users 이렇게 하면 공용 코드를 한번만 적어도 되며, **FastAPI**는 *경로 작동*을 위해 이에 대한 호출을 처리합니다. -/// check | "확인" +/// check | 확인 특별한 클래스를 만들지 않아도 되며, 이러한 것 혹은 비슷한 종류를 **FastAPI**에 "등록"하기 위해 어떤 곳에 넘겨주지 않아도 됩니다. @@ -276,31 +126,9 @@ commons: Annotated[dict, Depends(common_parameters)] 하지만 `Annotated`를 사용하고 있기에, `Annotated` 값을 변수에 저장하고 여러 장소에서 사용할 수 있습니다: -//// tab | Python 3.10+ +{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *} -```Python hl_lines="12 16 21" -{!> ../../../docs_src/dependencies/tutorial001_02_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="14 18 23" -{!> ../../../docs_src/dependencies/tutorial001_02_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15 19 24" -{!> ../../../docs_src/dependencies/tutorial001_02_an.py!} -``` - -//// - -/// tip | "팁" +/// tip | 팁 이는 그저 표준 파이썬이고 "type alias"라고 부르며 사실 **FastAPI**에 국한되는 것은 아닙니다. @@ -322,7 +150,7 @@ commons: Annotated[dict, Depends(common_parameters)] 아무 문제 없습니다. **FastAPI**는 무엇을 할지 알고 있습니다. -/// note | "참고" +/// note | 참고 잘 모르시겠다면, [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} 문서에서 `async`와 `await`에 대해 확인할 수 있습니다. diff --git a/docs/ko/docs/tutorial/encoder.md b/docs/ko/docs/tutorial/encoder.md index b8e87449c1..4323957f41 100644 --- a/docs/ko/docs/tutorial/encoder.md +++ b/docs/ko/docs/tutorial/encoder.md @@ -20,9 +20,7 @@ JSON 호환 가능 데이터만 수신하는 `fake_db` 데이터베이스가 존 Pydantic 모델과 같은 객체를 받고 JSON 호환 가능한 버전으로 반환합니다: -```Python hl_lines="5 22" -{!../../../docs_src/encoder/tutorial001.py!} -``` +{* ../../docs_src/encoder/tutorial001.py hl[5,22] *} 이 예시는 Pydantic 모델을 `dict`로, `datetime` 형식을 `str`로 변환합니다. @@ -30,7 +28,7 @@ Pydantic 모델과 같은 객체를 받고 JSON 호환 가능한 버전으로 길이가 긴 문자열 형태의 JSON 형식(문자열)의 데이터가 들어있는 상황에서는 `str`로 반환하지 않습니다. JSON과 모두 호환되는 값과 하위 값이 있는 Python 표준 데이터 구조 (예: `dict`)를 반환합니다. -/// note | "참고" +/// note | 참고 실제로 `jsonable_encoder`는 **FastAPI** 에서 내부적으로 데이터를 변환하는 데 사용하지만, 다른 많은 곳에서도 이는 유용합니다. diff --git a/docs/ko/docs/tutorial/extra-data-types.md b/docs/ko/docs/tutorial/extra-data-types.md index df3c7a06e7..4a41ba0dc6 100644 --- a/docs/ko/docs/tutorial/extra-data-types.md +++ b/docs/ko/docs/tutorial/extra-data-types.md @@ -55,108 +55,8 @@ 위의 몇몇 자료형을 매개변수로 사용하는 *경로 작동* 예시입니다. -//// tab | Python 3.10+ - -```Python hl_lines="1 3 12-16" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 3 12-16" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 3 13-17" -{!> ../../../docs_src/extra_data_types/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="1 2 11-15" -{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="1 2 12-16" -{!> ../../../docs_src/extra_data_types/tutorial001.py!} -``` - -//// +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *} 함수 안의 매개변수가 그들만의 데이터 자료형을 가지고 있으며, 예를 들어, 다음과 같이 날짜를 조작할 수 있음을 참고하십시오: -//// tab | Python 3.10+ - -```Python hl_lines="18-19" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="18-19" -{!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="19-20" -{!> ../../../docs_src/extra_data_types/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="17-18" -{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="18-19" -{!> ../../../docs_src/extra_data_types/tutorial001.py!} -``` - -//// +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *} diff --git a/docs/ko/docs/tutorial/extra-models.md b/docs/ko/docs/tutorial/extra-models.md new file mode 100644 index 0000000000..8e45590613 --- /dev/null +++ b/docs/ko/docs/tutorial/extra-models.md @@ -0,0 +1,223 @@ +# 추가 모델 + +지난 예제에 이어서, 연관된 모델을 여러개 갖는 것은 흔한 일입니다. + +특히 사용자 모델의 경우에 그러한데, 왜냐하면: + +* **입력 모델** 은 비밀번호를 가져야 합니다. +* **출력 모델** 은 비밀번호를 가지면 안됩니다. +* **데이터베이스 모델** 은 해시처리된 비밀번호를 가질 것입니다. + +/// danger | 위험 + +절대 사용자의 비밀번호를 평문으로 저장하지 마세요. 항상 이후에 검증 가능한 "안전한 해시(secure hash)"로 저장하세요. + +만약 이게 무엇인지 모르겠다면, [security chapters](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.에서 비밀번호 해시에 대해 배울 수 있습니다. + +/// + +## 다중 모델 + +아래는 비밀번호 필드와 해당 필드가 사용되는 위치를 포함하여, 각 모델들이 어떤 형태를 가질 수 있는지 전반적인 예시입니다: + +{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} + + +/// info | 정보 + +Pydantic v1에서는 해당 메서드가 `.dict()`로 불렸으며, Pydantic v2에서는 `.model_dump()`로 이름이 변경되었습니다. `.dict()`는 여전히 지원되지만 더 이상 권장되지 않습니다. + +여기에서 사용하는 예제는 Pydantic v1과의 호환성을 위해 `.dict()`를 사용하지만, Pydantic v2를 사용할 수 있다면 `.model_dump()`를 사용하는 것이 좋습니다. + +/// + +### `**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() +``` + +이제 변수 `user_dict`에 데이터가 포함된 `dict`를 가지게 됩니다(이는 Pydantic 모델 객체가 아닌 `dict`입니다). + +그리고 다음과 같이 호출하면: + +```Python +print(user_dict) +``` + +Python의 `dict`가 다음과 같이 출력됩니다: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### `dict` 언패킹(Unpacking) + +`user_dict`와 같은 `dict`를 함수(또는 클래스)에 `**user_dict`로 전달하면, Python은 이를 "언팩(unpack)"합니다. 이 과정에서 `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_in.dict()`로부터 `user_dict`를 생성한 것처럼, 아래 코드는: + +```Python +user_dict = user_in.dict() +UserInDB(**user_dict) +``` + +다음과 동일합니다: + +```Python +UserInDB(**user_in.dict()) +``` + +...왜냐하면 `user_in.dict()`는 `dict`이며, 이를 `**`로 Python이 "언팩(unpack)"하도록 하여 `UserInDB`에 전달하기 때문입니다. + +따라서, 다른 Pydantic 모델의 데이터를 사용하여 새로운 Pydantic 모델을 생성할 수 있습니다. + +#### `dict` 언패킹(Unpacking)과 추가 키워드 + +그리고 다음과 같이 추가 키워드 인자 `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 | 경고 + +추가적으로 제공된 함수 `fake_password_hasher`와 `fake_save_user`는 데이터 흐름을 시연하기 위한 예제일 뿐이며, 실제 보안을 제공하지 않습니다. + +/// + +## 중복 줄이기 + +코드 중복을 줄이는 것은 **FastAPI**의 핵심 아이디어 중 하나입니다. + +코드 중복은 버그, 보안 문제, 코드 비동기화 문제(한 곳은 업데이트되었지만 다른 곳은 업데이트되지 않는 문제) 등의 가능성을 증가시킵니다. + +그리고 이 모델들은 많은 데이터를 공유하면서 속성 이름과 타입을 중복하고 있습니다. + +더 나은 방법이 있습니다. + +`UserBase` 모델을 선언하여 다른 모델들의 기본(base)으로 사용할 수 있습니다. 그런 다음 이 모델을 상속받아 속성과 타입 선언(유형 선언, 검증 등)을 상속하는 서브클래스를 만들 수 있습니다. + +모든 데이터 변환, 검증, 문서화 등은 정상적으로 작동할 것입니다. + +이렇게 하면 각 모델 간의 차이점만 선언할 수 있습니다(평문 `password`가 있는 경우, `hashed_password`만 있는 경우, 혹은 비밀번호가 없는 경우): + +{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} + +## `Union` 또는 `anyOf` + +두 가지 이상의 타입을 포함하는 `Union`으로 응답을 선언할 수 있습니다. 이는 응답이 그 중 하나의 타입일 수 있음을 의미합니다. + +OpenAPI에서는 이를 `anyOf`로 정의합니다. + +이를 위해 표준 Python 타입 힌트인 `typing.Union`을 사용할 수 있습니다: + +/// note | 참고 + +`Union`을 정의할때는 더 구체적인 타입을 먼저 포함하고, 덜 구체적인 타입을 그 뒤에 나열해야합니다. 아래 예제에서는 `Union[PlaneItem, CarItem]` 를 보면, 더 구체적인 `PlaneItem`이 `CarItem`보다 앞에 위치합니다. + +/// + +{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} + + +### Python 3.10에서 `Union` + +위의 예제에서는 `response_model` 인자 값으로 `Union[PlaneItem, CarItem]`을 전달합니다. + +이 경우, 이를 **타입 어노테이션(type annotation)** 이 아닌 **인자 값(argument value)** 으로 전달하고 있기 때문에 Python 3.10에서도 `Union`을 사용해야 합니다. + +만약 타입 어노테이션에 사용한다면, 다음과 같이 수직 막대(|)를 사용할 수 있습니다: + +```Python +some_variable: PlaneItem | CarItem +``` + +하지만 이를 `response_model=PlaneItem | CarItem`과 같이 할당하면 에러가 발생합니다. 이는 Python이 이를 타입 어노테이션으로 해석하지 않고, `PlaneItem`과 `CarItem` 사이의 **잘못된 연산(invalid operation)**을 시도하기 때문입니다 + +## 모델 리스트 + +마찬가지로, 객체 리스트 형태의 응답을 선언할 수도 있습니다. + +이를 위해 표준 Python의 `typing.List`를 사용하세요(또는 Python 3.9 이상에서는 단순히 `list`를 사용할 수 있습니다): + +{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} + + +## 임의의 `dict` 응답 + +Pydantic 모델을 사용하지 않고, 키와 값의 타입만 선언하여 평범한 임의의 `dict`로 응답을 선언할 수도 있습니다. + +이는 Pydantic 모델에 필요한 유효한 필드/속성 이름을 사전에 알 수 없는 경우에 유용합니다. + +이 경우, `typing.Dict`를 사용할 수 있습니다(또는 Python 3.9 이상에서는 단순히 `dict`를 사용할 수 있습니다): + +{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} + + +## 요약 + +여러 Pydantic 모델을 사용하고, 각 경우에 맞게 자유롭게 상속하세요. + +엔터티가 서로 다른 "상태"를 가져야 하는 경우, 엔터티당 단일 데이터 모델을 사용할 필요는 없습니다. 예를 들어, 사용자 "엔터티"가 `password`, `password_hash`, 또는 비밀번호가 없는 상태를 포함할 수 있는 경우처럼 말입니다. diff --git a/docs/ko/docs/tutorial/first-steps.md b/docs/ko/docs/tutorial/first-steps.md index 52e53fd89b..20ce0d6469 100644 --- a/docs/ko/docs/tutorial/first-steps.md +++ b/docs/ko/docs/tutorial/first-steps.md @@ -2,9 +2,7 @@ 가장 단순한 FastAPI 파일은 다음과 같이 보일 것입니다: -```Python -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py *} 위 코드를 `main.py`에 복사합니다. @@ -24,7 +22,7 @@ $ uvicorn main:app --reload
-/// note | "참고" +/// note | 참고 `uvicorn main:app` 명령은 다음을 의미합니다: @@ -133,25 +131,21 @@ API와 통신하는 클라이언트(프론트엔드, 모바일, IoT 애플리케 ### 1 단계: `FastAPI` 임포트 -```Python hl_lines="1" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[1] *} `FastAPI`는 당신의 API를 위한 모든 기능을 제공하는 파이썬 클래스입니다. -/// note | "기술 세부사항" +/// note | 기술 세부사항 `FastAPI`는 `Starlette`를 직접 상속하는 클래스입니다. -`FastAPI`로 Starlette의 모든 기능을 사용할 수 있습니다. +`FastAPI`로 Starlette의 모든 기능을 사용할 수 있습니다. /// ### 2 단계: `FastAPI` "인스턴스" 생성 -```Python hl_lines="3" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[3] *} 여기에서 `app` 변수는 `FastAPI` 클래스의 "인스턴스"가 됩니다. @@ -171,9 +165,7 @@ $ uvicorn main:app --reload 아래처럼 앱을 만든다면: -```Python hl_lines="3" -{!../../../docs_src/first_steps/tutorial002.py!} -``` +{* ../../docs_src/first_steps/tutorial002.py hl[3] *} 이를 `main.py` 파일에 넣고, `uvicorn`을 아래처럼 호출해야 합니다: @@ -205,7 +197,7 @@ https://example.com/items/foo /items/foo ``` -/// info | "정보" +/// info | 정보 "경로"는 일반적으로 "엔드포인트" 또는 "라우트"라고도 불립니다. @@ -250,16 +242,14 @@ API를 설계할 때 일반적으로 특정 행동을 수행하기 위해 특정 #### *경로 작동 데코레이터* 정의 -```Python hl_lines="6" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[6] *} `@app.get("/")`은 **FastAPI**에게 바로 아래에 있는 함수가 다음으로 이동하는 요청을 처리한다는 것을 알려줍니다. * 경로 `/` * get 작동 사용 -/// info | "`@decorator` 정보" +/// info | `@decorator` 정보 이 `@something` 문법은 파이썬에서 "데코레이터"라 부릅니다. @@ -286,7 +276,7 @@ API를 설계할 때 일반적으로 특정 행동을 수행하기 위해 특정 * `@app.patch()` * `@app.trace()` -/// tip | "팁" +/// tip | 팁 각 작동(HTTP 메소드)을 원하는 대로 사용해도 됩니다. @@ -306,9 +296,7 @@ API를 설계할 때 일반적으로 특정 행동을 수행하기 위해 특정 * **작동**: 은 `get`입니다. * **함수**: 는 "데코레이터" 아래에 있는 함수입니다 (`@app.get("/")` 아래). -```Python hl_lines="7" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[7] *} 이것은 파이썬 함수입니다. @@ -320,11 +308,9 @@ URL "`/`"에 대한 `GET` 작동을 사용하는 요청을 받을 때마다 **Fa `async def`을 이용하는 대신 일반 함수로 정의할 수 있습니다: -```Python hl_lines="7" -{!../../../docs_src/first_steps/tutorial003.py!} -``` +{* ../../docs_src/first_steps/tutorial003.py hl[7] *} -/// note | "참고" +/// note | 참고 차이점을 모르겠다면 [Async: *"바쁘신 경우"*](../async.md#_1){.internal-link target=_blank}을 확인하세요. @@ -332,9 +318,7 @@ URL "`/`"에 대한 `GET` 작동을 사용하는 요청을 받을 때마다 **Fa ### 5 단계: 콘텐츠 반환 -```Python hl_lines="8" -{!../../../docs_src/first_steps/tutorial001.py!} -``` +{* ../../docs_src/first_steps/tutorial001.py hl[8] *} `dict`, `list`, 단일값을 가진 `str`, `int` 등을 반환할 수 있습니다. diff --git a/docs/ko/docs/tutorial/header-param-models.md b/docs/ko/docs/tutorial/header-param-models.md new file mode 100644 index 0000000000..bab7291e3c --- /dev/null +++ b/docs/ko/docs/tutorial/header-param-models.md @@ -0,0 +1,56 @@ +# 헤더 매개변수 모델 + +관련 있는 **헤더 매개변수** 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다. + +이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😎 + +/// note | 참고 + +이 기능은 FastAPI 버전 `0.115.0` 이후부터 지원됩니다. 🤓 + +/// + +## Pydantic 모델을 사용한 헤더 매개변수 + +**Pydantic 모델**에 필요한 **헤더 매개변수**를 선언한 다음, 해당 매개변수를 `Header`로 선언합니다: + +{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} + +**FastAPI**는 요청에서 받은 **헤더**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다. + +## 문서 확인하기 + +문서 UI `/docs`에서 필요한 헤더를 볼 수 있습니다: + +
+ +
+ +## 추가 헤더 금지하기 + +일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 헤더를 **제한**할 수 있습니다. + +Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다: + +{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} + +클라이언트가 **추가 헤더**를 보내려고 시도하면, **오류** 응답을 받게 됩니다. + +예를 들어, 클라이언트가 `plumbus` 값으로 `tool` 헤더를 보내려고 하면, 클라이언트는 헤더 매개변수 `tool`이 허용 되지 않는다는 **오류** 응답을 받게 됩니다: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ] +} +``` + +## 요약 + +**Pydantic 모델**을 사용하여 **FastAPI**에서 **헤더**를 선언할 수 있습니다. 😎 diff --git a/docs/ko/docs/tutorial/header-params.md b/docs/ko/docs/tutorial/header-params.md index d403b91751..7379eb2a07 100644 --- a/docs/ko/docs/tutorial/header-params.md +++ b/docs/ko/docs/tutorial/header-params.md @@ -6,9 +6,7 @@ 먼저 `Header`를 임포트합니다: -```Python hl_lines="3" -{!../../../docs_src/header_params/tutorial001.py!} -``` +{* ../../docs_src/header_params/tutorial001.py hl[3] *} ## `Header` 매개변수 선언 @@ -16,11 +14,9 @@ 첫 번째 값은 기본값이며, 추가 검증이나 어노테이션 매개변수 모두 전달할 수 있습니다: -```Python hl_lines="9" -{!../../../docs_src/header_params/tutorial001.py!} -``` +{* ../../docs_src/header_params/tutorial001.py hl[9] *} -/// note | "기술 세부사항" +/// note | 기술 세부사항 `Header`는 `Path`, `Query` 및 `Cookie`의 "자매"클래스입니다. 이 역시 동일한 공통 `Param` 클래스를 상속합니다. @@ -28,7 +24,7 @@ /// -/// info | "정보" +/// info | 정보 헤더를 선언하기 위해서 `Header`를 사용해야 합니다. 그렇지 않으면 해당 매개변수를 쿼리 매개변수로 해석하기 때문입니다. @@ -50,11 +46,9 @@ 만약 언더스코어를 하이픈으로 자동 변환을 비활성화해야 할 어떤 이유가 있다면, `Header`의 `convert_underscores` 매개변수를 `False`로 설정하십시오: -```Python hl_lines="10" -{!../../../docs_src/header_params/tutorial002.py!} -``` +{* ../../docs_src/header_params/tutorial002.py hl[10] *} -/// warning | "경고" +/// warning | 경고 `convert_underscore`를 `False`로 설정하기 전에, 어떤 HTTP 프록시들과 서버들은 언더스코어가 포함된 헤더 사용을 허락하지 않는다는 것을 명심하십시오. @@ -70,9 +64,7 @@ 예를 들어, 두 번 이상 나타날 수 있는 `X-Token`헤더를 선언하려면, 다음과 같이 작성합니다: -```Python hl_lines="9" -{!../../../docs_src/header_params/tutorial003.py!} -``` +{* ../../docs_src/header_params/tutorial003.py hl[9] *} 다음과 같은 두 개의 HTTP 헤더를 전송하여 해당 *경로* 와 통신할 경우: diff --git a/docs/ko/docs/tutorial/index.md b/docs/ko/docs/tutorial/index.md index d00a942f0e..9f5328992f 100644 --- a/docs/ko/docs/tutorial/index.md +++ b/docs/ko/docs/tutorial/index.md @@ -30,7 +30,7 @@ $ uvicorn main:app --reload 코드를 작성하거나 복사, 편집할 때, 로컬 환경에서 실행하는 것을 **강력히 권장**합니다. -로컬 편집기에서 사용한다면, 모든 타입 검사와 자동완성 등 작성해야 하는 코드가 얼마나 적은지 보면서 FastAPI의 비로소 경험할 수 있습니다. +로컬 편집기에서 사용한다면, 모든 타입 검사와 자동완성 등 작성해야 하는 코드가 얼마나 적은지 보면서 FastAPI의 이점을 비로소 경험할 수 있습니다. --- @@ -53,7 +53,7 @@ $ pip install "fastapi[all]" ...이는 코드를 실행하는 서버로 사용할 수 있는 `uvicorn` 또한 포함하고 있습니다. -/// note | "참고" +/// note | 참고 부분적으로 설치할 수도 있습니다. diff --git a/docs/ko/docs/tutorial/metadata.md b/docs/ko/docs/tutorial/metadata.md new file mode 100644 index 0000000000..a50dfa2e77 --- /dev/null +++ b/docs/ko/docs/tutorial/metadata.md @@ -0,0 +1,118 @@ +# 메타데이터 및 문서화 URL + +**FastAPI** 응용 프로그램에서 다양한 메타데이터 구성을 사용자 맞춤 설정할 수 있습니다. + +## API에 대한 메타데이터 + +OpenAPI 명세 및 자동화된 API 문서 UI에 사용되는 다음 필드를 설정할 수 있습니다: + +| 매개변수 | 타입 | 설명 | +|----------|------|-------| +| `title` | `str` | API의 제목입니다. | +| `summary` | `str` | API에 대한 짧은 요약입니다. OpenAPI 3.1.0, FastAPI 0.99.0부터 사용 가능 | +| `description` | `str` | API에 대한 짧은 설명입니다. 마크다운을 사용할 수 있습니다. | +| `version` | `string` | API의 버전입니다. OpenAPI의 버전이 아닌, 여러분의 애플리케이션의 버전을 나타냅니다. 예: `2.5.0` | +| `terms_of_service` | `str` | API 이용 약관의 URL입니다. 제공하는 경우 URL 형식이어야 합니다. | +| `contact` | `dict` | 노출된 API에 대한 연락처 정보입니다. 여러 필드를 포함할 수 있습니다.
contact 필드
매개변수타입설명
namestr연락처 인물/조직의 식별명입니다.
urlstr연락처 정보가 담긴 URL입니다. URL 형식이어야 합니다.
emailstr연락처 인물/조직의 이메일 주소입니다. 이메일 주소 형식이어야 합니다.
| +| `license_info` | `dict` | 노출된 API의 라이선스 정보입니다. 여러 필드를 포함할 수 있습니다.
license_info 필드
매개변수타입설명
namestr필수 (license_info가 설정된 경우). API에 사용된 라이선스 이름입니다.
identifierstrAPI에 대한 SPDX 라이선스 표현입니다. identifier 필드는 url 필드와 상호 배타적입니다. OpenAPI 3.1.0, FastAPI 0.99.0부터 사용 가능
urlstrAPI에 사용된 라이선스의 URL입니다. URL 형식이어야 합니다.
| + +다음과 같이 설정할 수 있습니다: + +{* ../../docs_src/metadata/tutorial001.py hl[3:16,19:32] *} + +/// tip + +`description` 필드에 마크다운을 사용할 수 있으며, 출력에서 렌더링됩니다. + +/// + +이 구성을 사용하면 문서 자동화(로 생성된) API 문서는 다음과 같이 보입니다: + + + +## 라이선스 식별자 + +OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `identifier`를 URL 대신 설정할 수 있습니다. + +예: + +{* ../../docs_src/metadata/tutorial001_1.py hl[31] *} + +## 태그에 대한 메타데이터 + +`openapi_tags` 매개변수를 사용하여 경로 작동을 그룹화하는 데 사용되는 태그에 추가 메타데이터를 추가할 수 있습니다. + +리스트는 각 태그에 대해 하나의 딕셔너리를 포함해야 합니다. + +각 딕셔너리에는 다음이 포함될 수 있습니다: + +* `name` (**필수**): `tags` 매개변수에서 *경로 작동*과 `APIRouter`에 사용된 태그 이름과 동일한 `str`입니다. +* `description`: 태그에 대한 간단한 설명을 담은 `str`입니다. 마크다운을 사용할 수 있으며 문서 UI에 표시됩니다. +* `externalDocs`: 외부 문서를 설명하는 `dict`이며: + * `description`: 외부 문서에 대한 간단한 설명을 담은 `str`입니다. + * `url` (**필수**): 외부 문서의 URL을 담은 `str`입니다. + +### 태그에 대한 메타데이터 생성 + +`users` 및 `items`에 대한 태그 예시와 함께 메타데이터를 생성하고 이를 `openapi_tags` 매개변수로 전달해 보겠습니다: + +{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} + +설명 안에 마크다운을 사용할 수 있습니다. 예를 들어 "login"은 굵게(**login**) 표시되고, "fancy"는 기울임꼴(_fancy_)로 표시됩니다. + +/// tip + +사용 중인 모든 태그에 메타데이터를 추가할 필요는 없습니다. + +/// + +### 태그 사용 + +`tags` 매개변수를 *경로 작동* 및 `APIRouter`와 함께 사용하여 태그에 할당할 수 있습니다: + +{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} + +/// info + +태그에 대한 자세한 내용은 [경로 작동 구성](path-operation-configuration.md#tags){.internal-link target=_blank}에서 읽어보세요. + +/// + +### 문서 확인 + +이제 문서를 확인하면 모든 추가 메타데이터가 표시됩니다: + + + +### 태그 순서 + +각 태그 메타데이터 딕셔너리의 순서는 문서 UI에 표시되는 순서를 정의합니다. + +예를 들어, 알파벳 순서상 `users`는 `items` 뒤에 오지만, 우리는 `users` 메타데이터를 리스트의 첫 번째 딕셔너리로 추가했기 때문에 먼저 표시됩니다. + +## OpenAPI URL + +OpenAPI 구조는 기본적으로 `/openapi.json`에서 제공됩니다. + +`openapi_url` 매개변수를 통해 이를 설정할 수 있습니다. + +예를 들어, 이를 `/api/v1/openapi.json`에 제공하도록 설정하려면: + +{* ../../docs_src/metadata/tutorial002.py hl[3] *} + +OpenAPI 구조를 완전히 비활성화하려면 `openapi_url=None`으로 설정할 수 있으며, 이를 사용하여 문서화 사용자 인터페이스도 비활성화됩니다. + +## 문서화 URL + +포함된 두 가지 문서화 사용자 인터페이스를 설정할 수 있습니다: + +* **Swagger UI**: `/docs`에서 제공됩니다. + * `docs_url` 매개변수로 URL을 설정할 수 있습니다. + * `docs_url=None`으로 설정하여 비활성화할 수 있습니다. +* **ReDoc**: `/redoc`에서 제공됩니다. + * `redoc_url` 매개변수로 URL을 설정할 수 있습니다. + * `redoc_url=None`으로 설정하여 비활성화할 수 있습니다. + +예를 들어, Swagger UI를 `/documentation`에서 제공하고 ReDoc을 비활성화하려면: + +{* ../../docs_src/metadata/tutorial003.py hl[3] *} diff --git a/docs/ko/docs/tutorial/middleware.md b/docs/ko/docs/tutorial/middleware.md index 84f67bd26f..e0daa3c99b 100644 --- a/docs/ko/docs/tutorial/middleware.md +++ b/docs/ko/docs/tutorial/middleware.md @@ -11,7 +11,7 @@ * **응답** 또는 다른 필요한 코드를 실행시키는 동작을 할 수 있습니다. * **응답**를 반환합니다. -/// note | "기술 세부사항" +/// note | 기술 세부사항 만약 `yield`를 사용한 의존성을 가지고 있다면, 미들웨어가 실행되고 난 후에 exit이 실행됩니다. @@ -31,19 +31,17 @@ * 그런 다음, *경로 작업*에 의해 생성된 `response` 를 반환합니다. * `response`를 반환하기 전에 추가로 `response`를 수정할 수 있습니다. -```Python hl_lines="8-9 11 14" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} -/// tip | "팁" +/// tip | 팁 사용자 정의 헤더는 'X-' 접두사를 사용하여 추가할 수 있습니다. -그러나 만약 클라이언트의 브라우저에서 볼 수 있는 사용자 정의 헤더를 가지고 있다면, 그것들을 CORS 설정([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})에 Starlette CORS 문서에 명시된 `expose_headers` 매개변수를 이용하여 헤더들을 추가하여야합니다. +그러나 만약 클라이언트의 브라우저에서 볼 수 있는 사용자 정의 헤더를 가지고 있다면, 그것들을 CORS 설정([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})에 Starlette CORS 문서에 명시된 `expose_headers` 매개변수를 이용하여 헤더들을 추가하여야합니다. /// -/// note | "기술적 세부사항" +/// note | 기술적 세부사항 `from starlette.requests import request`를 사용할 수도 있습니다. @@ -59,9 +57,7 @@ 예를 들어, 요청을 수행하고 응답을 생성하는데 까지 걸린 시간 값을 가지고 있는 `X-Process-Time` 같은 사용자 정의 헤더를 추가할 수 있습니다. -```Python hl_lines="10 12-13" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} ## 다른 미들웨어 diff --git a/docs/ko/docs/tutorial/path-operation-configuration.md b/docs/ko/docs/tutorial/path-operation-configuration.md index b6608a14d6..81914182af 100644 --- a/docs/ko/docs/tutorial/path-operation-configuration.md +++ b/docs/ko/docs/tutorial/path-operation-configuration.md @@ -2,7 +2,7 @@ *경로 작동 데코레이터*를 설정하기 위해서 전달할수 있는 몇 가지 매개변수가 있습니다. -/// warning | "경고" +/// warning | 경고 아래 매개변수들은 *경로 작동 함수*가 아닌 *경로 작동 데코레이터*에 직접 전달된다는 사실을 기억하십시오. @@ -16,13 +16,11 @@ 하지만 각 코드의 의미를 모른다면, `status`에 있는 단축 상수들을 사용할수 있습니다: -```Python hl_lines="3 17" -{!../../../docs_src/path_operation_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *} 각 상태 코드들은 응답에 사용되며, OpenAPI 스키마에 추가됩니다. -/// note | "기술적 세부사항" +/// note | 기술적 세부사항 다음과 같이 임포트하셔도 좋습니다. `from starlette import status`. @@ -34,9 +32,7 @@ (보통 단일 `str`인) `str`로 구성된 `list`와 함께 매개변수 `tags`를 전달하여, `경로 작동`에 태그를 추가할 수 있습니다: -```Python hl_lines="17 22 27" -{!../../../docs_src/path_operation_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *} 전달된 태그들은 OpenAPI의 스키마에 추가되며, 자동 문서 인터페이스에서 사용됩니다: @@ -46,9 +42,7 @@ `summary`와 `description`을 추가할 수 있습니다: -```Python hl_lines="20-21" -{!../../../docs_src/path_operation_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *} ## 독스트링으로 만든 기술 @@ -56,9 +50,7 @@ 마크다운 문법으로 독스트링을 작성할 수 있습니다, 작성된 마크다운 형식의 독스트링은 (마크다운의 들여쓰기를 고려하여) 올바르게 화면에 출력됩니다. -```Python hl_lines="19-27" -{!../../../docs_src/path_operation_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} 이는 대화형 문서에서 사용됩니다: @@ -68,17 +60,15 @@ `response_description` 매개변수로 응답에 관한 설명을 명시할 수 있습니다: -```Python hl_lines="21" -{!../../../docs_src/path_operation_configuration/tutorial005.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *} -/// info | "정보" +/// info | 정보 `response_description`은 구체적으로 응답을 지칭하며, `description`은 일반적인 *경로 작동*을 지칭합니다. /// -/// check | "확인" +/// check | 확인 OpenAPI는 각 *경로 작동*이 응답에 관한 설명을 요구할 것을 명시합니다. @@ -92,9 +82,7 @@ OpenAPI는 각 *경로 작동*이 응답에 관한 설명을 요구할 것을 단일 *경로 작동*을 없애지 않고 지원중단을 해야한다면, `deprecated` 매개변수를 전달하면 됩니다. -```Python hl_lines="16" -{!../../../docs_src/path_operation_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} 대화형 문서에 지원중단이라고 표시됩니다. diff --git a/docs/ko/docs/tutorial/path-params-numeric-validations.md b/docs/ko/docs/tutorial/path-params-numeric-validations.md index 6d3215c245..f21c9290ed 100644 --- a/docs/ko/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ko/docs/tutorial/path-params-numeric-validations.md @@ -6,9 +6,7 @@ 먼저 `fastapi`에서 `Path`를 임포트합니다: -```Python hl_lines="3" -{!../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[3] *} ## 메타데이터 선언 @@ -16,11 +14,9 @@ 예를 들어, `title` 메타데이터 값을 경로 매개변수 `item_id`에 선언하려면 다음과 같이 입력할 수 있습니다: -```Python hl_lines="10" -{!../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *} -/// note | "참고" +/// note | 참고 경로 매개변수는 경로의 일부여야 하므로 언제나 필수적입니다. @@ -46,9 +42,7 @@ 따라서 함수를 다음과 같이 선언 할 수 있습니다: -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} ## 필요한 경우 매개변수 정렬하기, 트릭 @@ -58,9 +52,7 @@ 파이썬은 `*`으로 아무런 행동도 하지 않지만, 따르는 매개변수들은 kwargs로도 알려진 키워드 인자(키-값 쌍)여야 함을 인지합니다. 기본값을 가지고 있지 않더라도 그렇습니다. -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} ## 숫자 검증: 크거나 같음 @@ -68,9 +60,7 @@ 여기서 `ge=1`인 경우, `item_id`는 `1`보다 "크거나(`g`reater) 같은(`e`qual)" 정수형 숫자여야 합니다. -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} ## 숫자 검증: 크거나 같음 및 작거나 같음 @@ -79,9 +69,7 @@ * `gt`: 크거나(`g`reater `t`han) * `le`: 작거나 같은(`l`ess than or `e`qual) -```Python hl_lines="9" -{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *} ## 숫자 검증: 부동소수, 크거나 및 작거나 @@ -93,9 +81,7 @@ lt 역시 마찬가지입니다. -```Python hl_lines="11" -{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} ## 요약 @@ -108,7 +94,7 @@ * `lt`: 작거나(`l`ess `t`han) * `le`: 작거나 같은(`l`ess than or `e`qual) -/// info | "정보" +/// info | 정보 `Query`, `Path`, 그리고 나중에게 보게될 것들은 (여러분이 사용할 필요가 없는) 공통 `Param` 클래스의 서브 클래스입니다. @@ -116,7 +102,7 @@ /// -/// note | "기술 세부사항" +/// note | 기술 세부사항 `fastapi`에서 `Query`, `Path` 등을 임포트 할 때, 이것들은 실제로 함수입니다. diff --git a/docs/ko/docs/tutorial/path-params.md b/docs/ko/docs/tutorial/path-params.md index 67a2d899d4..b72787e0b2 100644 --- a/docs/ko/docs/tutorial/path-params.md +++ b/docs/ko/docs/tutorial/path-params.md @@ -2,9 +2,7 @@ 파이썬의 포맷 문자열 리터럴에서 사용되는 문법을 이용하여 경로 "매개변수" 또는 "변수"를 선언할 수 있습니다: -```Python hl_lines="6-7" -{!../../../docs_src/path_params/tutorial001.py!} -``` +{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} 경로 매개변수 `item_id`의 값은 함수의 `item_id` 인자로 전달됩니다. @@ -18,13 +16,11 @@ 파이썬 표준 타입 어노테이션을 사용하여 함수에 있는 경로 매개변수의 타입을 선언할 수 있습니다: -```Python hl_lines="7" -{!../../../docs_src/path_params/tutorial002.py!} -``` +{* ../../docs_src/path_params/tutorial002.py hl[7] *} 위의 예시에서, `item_id`는 `int`로 선언되었습니다. -/// check | "확인" +/// check | 확인 이 기능은 함수 내에서 오류 검사, 자동완성 등의 편집기 기능을 활용할 수 있게 해줍니다. @@ -38,7 +34,7 @@ {"item_id":3} ``` -/// check | "확인" +/// check | 확인 함수가 받은(반환도 하는) 값은 문자열 `"3"`이 아니라 파이썬 `int` 형인 `3`입니다. @@ -69,7 +65,7 @@ `int`가 아닌 `float`을 전달하는 경우에도 동일한 오류가 나타납니다: http://127.0.0.1:8000/items/4.2 -/// check | "확인" +/// check | 확인 즉, 파이썬 타입 선언을 하면 **FastAPI**는 데이터 검증을 합니다. @@ -85,7 +81,7 @@ -/// check | "확인" +/// check | 확인 그저 파이썬 타입 선언을 하기만 하면 **FastAPI**는 자동 대화형 API 문서(Swagger UI)를 제공합니다. @@ -121,9 +117,7 @@ *경로 작동*은 순차적으로 실행되기 때문에 `/users/{user_id}` 이전에 `/users/me`를 먼저 선언해야 합니다: -```Python hl_lines="6 11" -{!../../../docs_src/path_params/tutorial003.py!} -``` +{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} 그렇지 않으면 `/users/{user_id}`는 `/users/me` 요청 또한 매개변수 `user_id`의 값이 `"me"`인 것으로 "생각하게" 됩니다. @@ -139,17 +133,15 @@ 가능한 값들에 해당하는 고정된 값의 클래스 어트리뷰트들을 만듭니다: -```Python hl_lines="1 6-9" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} -/// info | "정보" +/// info | 정보 열거형(또는 enums)은 파이썬 버전 3.4 이후로 사용 가능합니다. /// -/// tip | "팁" +/// tip | 팁 혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 기계 학습 모델들의 이름입니다. @@ -159,9 +151,7 @@ 생성한 열거형 클래스(`ModelName`)를 사용하는 타입 어노테이션으로 *경로 매개변수*를 만듭니다: -```Python hl_lines="16" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[16] *} ### 문서 확인 @@ -177,19 +167,15 @@ 열거형 `ModelName`의 *열거형 멤버*를 비교할 수 있습니다: -```Python hl_lines="17" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[17] *} #### *열거형 값* 가져오기 `model_name.value` 또는 일반적으로 `your_enum_member.value`를 이용하여 실제 값(위 예시의 경우 `str`)을 가져올 수 있습니다: -```Python hl_lines="20" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[20] *} -/// tip | "팁" +/// tip | 팁 `ModelName.lenet.value`로도 값 `"lenet"`에 접근할 수 있습니다. @@ -201,9 +187,7 @@ 클라이언트에 반환하기 전에 해당 값(이 경우 문자열)으로 변환됩니다: -```Python hl_lines="18 21 23" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} 클라이언트는 아래의 JSON 응답을 얻습니다: @@ -242,11 +226,9 @@ Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으 따라서 다음과 같이 사용할 수 있습니다: -```Python hl_lines="6" -{!../../../docs_src/path_params/tutorial004.py!} -``` +{* ../../docs_src/path_params/tutorial004.py hl[6] *} -/// tip | "팁" +/// tip | 팁 매개변수가 가져야 하는 값이 `/home/johndoe/myfile.txt`와 같이 슬래시로 시작(`/`)해야 할 수 있습니다. diff --git a/docs/ko/docs/tutorial/query-param-models.md b/docs/ko/docs/tutorial/query-param-models.md new file mode 100644 index 0000000000..2ca65a3319 --- /dev/null +++ b/docs/ko/docs/tutorial/query-param-models.md @@ -0,0 +1,68 @@ +# 쿼리 매개변수 모델 + +연관된 쿼리 **매개변수** 그룹이 있다면 **Pydantic 모델** 을 사용해 선언할 수 있습니다. + +이렇게 하면 **여러 곳**에서 **모델을 재사용**할 수 있을 뿐만 아니라, 매개변수에 대한 검증 및 메타데이터도 한 번에 선언할 수 있습니다. 😎 + +/// note | 참고 + +이 기능은 FastAPI 버전 `0.115.0`부터 제공됩니다. 🤓 + +/// + +## 쿼리 매개변수와 Pydantic 모델 + +필요한 **쿼리 매개변수**를 **Pydantic 모델** 안에 선언한 다음, 모델을 `Query`로 선언합니다. + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +**FastAPI**는 요청의 **쿼리 매개변수**에서 **각 필드**의 데이터를 **추출**해 정의한 Pydantic 모델로 제공합니다. + +## 문서 확인하기 + +`/docs` 경로의 API 문서에서 매개변수를 확인할 수 있습니다. + +
+ +
+ +## 추가 쿼리 매개변수 금지 + +몇몇의 특이한 경우에 (흔치 않지만), 허용할 쿼리 매개변수를 **제한**해야할 수 있습니다. + +Pydantic 모델 설정에서 `extra` 필드를 `forbid` 로 설정할 수 있습니다. + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +만약 클라이언트가 쿼리 매개변수로 **추가적인** 데이터를 보내려고 하면, 클라이언트는 **에러** 응답을 받게 됩니다. + +예를 들어, 아래와 같이 만약 클라이언트가 `tool` 쿼리 매개변수에 `plumbus` 라는 값을 추가해서 보내려고 하면, + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +클라이언트는 쿼리 매개변수 `tool` 이 허용되지 않는다는 **에러** 응답을 받게 됩니다. + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## 요약 + +**FastAPI** 에서 **쿼리 매개변수** 를 선언할 때 **Pydantic 모델** 을 사용할 수 있습니다. 😎 + +/// tip | 팁 + +스포일러 경고: Pydantic 모델을 쿠키와 헤더에도 적용할 수 있습니다. 이에 대해서는 이후 튜토리얼에서 다룰 예정입니다. 🤫 + +/// diff --git a/docs/ko/docs/tutorial/query-params-str-validations.md b/docs/ko/docs/tutorial/query-params-str-validations.md index 11193950b2..f2ca453ac5 100644 --- a/docs/ko/docs/tutorial/query-params-str-validations.md +++ b/docs/ko/docs/tutorial/query-params-str-validations.md @@ -4,13 +4,11 @@ 이 응용 프로그램을 예로 들어보겠습니다: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial001.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *} 쿼리 매개변수 `q`는 `Optional[str]` 자료형입니다. 즉, `str` 자료형이지만 `None` 역시 될 수 있음을 뜻하고, 실제로 기본값은 `None`이기 때문에 FastAPI는 이 매개변수가 필수가 아니라는 것을 압니다. -/// note | "참고" +/// note | 참고 FastAPI는 `q`의 기본값이 `= None`이기 때문에 필수가 아님을 압니다. @@ -26,17 +24,13 @@ FastAPI는 `q`의 기본값이 `= None`이기 때문에 필수가 아님을 압 이를 위해 먼저 `fastapi`에서 `Query`를 임포트합니다: -```Python hl_lines="3" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *} ## 기본값으로 `Query` 사용 이제 `Query`를 매개변수의 기본값으로 사용하여 `max_length` 매개변수를 50으로 설정합니다: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} 기본값 `None`을 `Query(None)`으로 바꿔야 하므로, `Query`의 첫 번째 매개변수는 기본값을 정의하는 것과 같은 목적으로 사용됩니다. @@ -54,7 +48,7 @@ q: Optional[str] = None 하지만 명시적으로 쿼리 매개변수를 선언합니다. -/// info | "정보" +/// info | 정보 FastAPI는 다음 부분에 관심이 있습니다: @@ -86,17 +80,13 @@ q: str = Query(None, max_length=50) 매개변수 `min_length` 또한 추가할 수 있습니다: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial003.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial003.py hl[9] *} ## 정규식 추가 매개변수와 일치해야 하는 정규표현식을 정의할 수 있습니다: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial004.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial004.py hl[10] *} 이 특정 정규표현식은 전달 받은 매개변수 값을 검사합니다: @@ -114,11 +104,9 @@ q: str = Query(None, max_length=50) `min_length`가 `3`이고, 기본값이 `"fixedquery"`인 쿼리 매개변수 `q`를 선언해봅시다: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial005.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} -/// note | "참고" +/// note | 참고 기본값을 갖는 것만으로 매개변수는 선택적이 됩니다. @@ -146,11 +134,9 @@ q: Optional[str] = Query(None, min_length=3) 그래서 `Query`를 필수값으로 만들어야 할 때면, 첫 번째 인자로 `...`를 사용할 수 있습니다: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial006.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} -/// info | "정보" +/// info | 정보 이전에 `...`를 본적이 없다면: 특별한 단일값으로, 파이썬의 일부이며 "Ellipsis"라 부릅니다. @@ -164,9 +150,7 @@ q: Optional[str] = Query(None, min_length=3) 예를 들어, URL에서 여러번 나오는 `q` 쿼리 매개변수를 선언하려면 다음과 같이 작성할 수 있습니다: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial011.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} 아래와 같은 URL을 사용합니다: @@ -187,7 +171,7 @@ http://localhost:8000/items/?q=foo&q=bar } ``` -/// tip | "팁" +/// tip | 팁 위의 예와 같이 `list` 자료형으로 쿼리 매개변수를 선언하려면 `Query`를 명시적으로 사용해야 합니다. 그렇지 않으면 요청 본문으로 해석됩니다. @@ -201,9 +185,7 @@ http://localhost:8000/items/?q=foo&q=bar 그리고 제공된 값이 없으면 기본 `list` 값을 정의할 수도 있습니다: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial012.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} 아래로 이동한다면: @@ -226,11 +208,9 @@ http://localhost:8000/items/ `List[str]` 대신 `list`를 직접 사용할 수도 있습니다: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial013.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} -/// note | "참고" +/// note | 참고 이 경우 FastAPI는 리스트의 내용을 검사하지 않음을 명심하기 바랍니다. @@ -244,7 +224,7 @@ http://localhost:8000/items/ 해당 정보는 생성된 OpenAPI에 포함되고 문서 사용자 인터페이스 및 외부 도구에서 사용됩니다. -/// note | "참고" +/// note | 참고 도구에 따라 OpenAPI 지원 수준이 다를 수 있음을 명심하기 바랍니다. @@ -254,15 +234,11 @@ http://localhost:8000/items/ `title`을 추가할 수 있습니다: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial007.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *} 그리고 `description`도 추가할 수 있습니다: -```Python hl_lines="13" -{!../../../docs_src/query_params_str_validations/tutorial008.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} ## 별칭 매개변수 @@ -282,9 +258,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 이럴 경우 `alias`를 선언할 수 있으며, 해당 별칭은 매개변수 값을 찾는 데 사용됩니다: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial009.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} ## 매개변수 사용하지 않게 하기 @@ -294,9 +268,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 그렇다면 `deprecated=True` 매개변수를 `Query`로 전달합니다: -```Python hl_lines="18" -{!../../../docs_src/query_params_str_validations/tutorial010.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} 문서가 아래와 같이 보일겁니다: diff --git a/docs/ko/docs/tutorial/query-params.md b/docs/ko/docs/tutorial/query-params.md index e71444c188..d5b9837c4e 100644 --- a/docs/ko/docs/tutorial/query-params.md +++ b/docs/ko/docs/tutorial/query-params.md @@ -2,9 +2,7 @@ 경로 매개변수의 일부가 아닌 다른 함수 매개변수를 선언하면 "쿼리" 매개변수로 자동 해석합니다. -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial001.py!} -``` +{* ../../docs_src/query_params/tutorial001.py hl[9] *} 쿼리는 URL에서 `?` 후에 나오고 `&`으로 구분되는 키-값 쌍의 집합입니다. @@ -63,19 +61,17 @@ http://127.0.0.1:8000/items/?skip=20 같은 방법으로 기본값을 `None`으로 설정하여 선택적 매개변수를 선언할 수 있습니다: -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial002.py!} -``` +{* ../../docs_src/query_params/tutorial002.py hl[9] *} 이 경우 함수 매개변수 `q`는 선택적이며 기본값으로 `None` 값이 됩니다. -/// check | "확인" +/// check | 확인 **FastAPI**는 `item_id`가 경로 매개변수이고 `q`는 경로 매개변수가 아닌 쿼리 매개변수라는 것을 알 정도로 충분히 똑똑합니다. /// -/// note | "참고" +/// note | 참고 FastAPI는 `q`가 `= None`이므로 선택적이라는 것을 인지합니다. @@ -87,9 +83,7 @@ FastAPI는 `q`가 `= None`이므로 선택적이라는 것을 인지합니다. `bool` 형으로 선언할 수도 있고, 아래처럼 변환됩니다: -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial003.py!} -``` +{* ../../docs_src/query_params/tutorial003.py hl[9] *} 이 경우, 아래로 이동하면: @@ -132,9 +126,7 @@ http://127.0.0.1:8000/items/foo?short=yes 매개변수들은 이름으로 감지됩니다: -```Python hl_lines="8 10" -{!../../../docs_src/query_params/tutorial004.py!} -``` +{* ../../docs_src/query_params/tutorial004.py hl[8,10] *} ## 필수 쿼리 매개변수 @@ -144,9 +136,7 @@ http://127.0.0.1:8000/items/foo?short=yes 그러나 쿼리 매개변수를 필수로 만들려면 단순히 기본값을 선언하지 않으면 됩니다: -```Python hl_lines="6-7" -{!../../../docs_src/query_params/tutorial005.py!} -``` +{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} 여기 쿼리 매개변수 `needy`는 `str`형인 필수 쿼리 매개변수입니다. @@ -190,9 +180,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy 그리고 물론, 일부 매개변수는 필수로, 다른 일부는 기본값을, 또 다른 일부는 선택적으로 선언할 수 있습니다: -```Python hl_lines="10" -{!../../../docs_src/query_params/tutorial006.py!} -``` +{* ../../docs_src/query_params/tutorial006.py hl[10] *} 위 예시에서는 3가지 쿼리 매개변수가 있습니다: @@ -200,7 +188,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy * `skip`, 기본값이 `0`인 `int`. * `limit`, 선택적인 `int`. -/// tip | "팁" +/// tip | 팁 [경로 매개변수](path-params.md#_8){.internal-link target=_blank}와 마찬가지로 `Enum`을 사용할 수 있습니다. diff --git a/docs/ko/docs/tutorial/request-files.md b/docs/ko/docs/tutorial/request-files.md index b7781ef5ea..9162b353cd 100644 --- a/docs/ko/docs/tutorial/request-files.md +++ b/docs/ko/docs/tutorial/request-files.md @@ -2,7 +2,7 @@ `File`을 사용하여 클라이언트가 업로드할 파일들을 정의할 수 있습니다. -/// info | "정보" +/// info | 정보 업로드된 파일을 전달받기 위해 먼저 `python-multipart`를 설치해야합니다. @@ -16,19 +16,15 @@ `fastapi` 에서 `File` 과 `UploadFile` 을 임포트 합니다: -```Python hl_lines="1" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[1] *} ## `File` 매개변수 정의 `Body` 및 `Form` 과 동일한 방식으로 파일의 매개변수를 생성합니다: -```Python hl_lines="7" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[7] *} -/// info | "정보" +/// info | 정보 `File` 은 `Form` 으로부터 직접 상속된 클래스입니다. @@ -36,7 +32,7 @@ /// -/// tip | "팁" +/// tip | 팁 File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본문(JSON) 매개변수로 해석되는 것을 방지하기 위해 `File` 을 사용해야합니다. @@ -54,9 +50,7 @@ File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본 `File` 매개변수를 `UploadFile` 타입으로 정의합니다: -```Python hl_lines="12" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[12] *} `UploadFile` 을 사용하는 것은 `bytes` 과 비교해 다음과 같은 장점이 있습니다: @@ -104,7 +98,7 @@ contents = myfile.file.read() /// -/// note | "Starlette 기술적 세부사항" +/// note | Starlette 기술적 세부사항 **FastAPI**의 `UploadFile` 은 **Starlette**의 `UploadFile` 을 직접적으로 상속받지만, **Pydantic** 및 FastAPI의 다른 부분들과의 호환성을 위해 필요한 부분들이 추가되었습니다. @@ -116,7 +110,7 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 **FastAPI**는 JSON 대신 올바른 위치에서 데이터를 읽을 수 있도록 합니다. -/// note | "기술적 세부사항" +/// note | 기술적 세부사항 폼의 데이터는 파일이 포함되지 않은 경우 일반적으로 "미디어 유형" `application/x-www-form-urlencoded` 을 사용해 인코딩 됩니다. @@ -126,7 +120,7 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 /// -/// warning | "경고" +/// warning | 경고 다수의 `File` 과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json` 가 아닌 `multipart/form-data` 로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다. @@ -142,13 +136,11 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다: -```Python hl_lines="10 15" -{!../../../docs_src/request_files/tutorial002.py!} -``` +{* ../../docs_src/request_files/tutorial002.py hl[10,15] *} 선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다. -/// note | "참고" +/// note | 참고 2019년 4월 14일부터 Swagger UI가 하나의 폼 필드로 다수의 파일을 업로드하는 것을 지원하지 않습니다. 더 많은 정보를 원하면, #4276#3641을 참고하세요. @@ -158,7 +150,7 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 /// -/// note | "기술적 세부사항" +/// note | 기술적 세부사항 `from starlette.responses import HTMLResponse` 역시 사용할 수 있습니다. diff --git a/docs/ko/docs/tutorial/request-form-models.md b/docs/ko/docs/tutorial/request-form-models.md new file mode 100644 index 0000000000..3316a93d52 --- /dev/null +++ b/docs/ko/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# 폼 모델 + +FastAPI에서 **Pydantic 모델**을 이용하여 **폼 필드**를 선언할 수 있습니다. + +/// info | 정보 + +폼(Form)을 사용하려면, 먼저 `python-multipart`를 설치하세요. + +[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 아래와 같이 설치할 수 있습니다: + +```console +$ pip install python-multipart +``` + +/// + +/// note | 참고 + +이 기능은 FastAPI 버전 `0.113.0` 이후부터 지원됩니다. 🤓 + +/// + +## Pydantic 모델을 사용한 폼 + +**폼 필드**로 받고 싶은 필드를 **Pydantic 모델**로 선언한 다음, 매개변수를 `Form`으로 선언하면 됩니다: + +{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} + +**FastAPI**는 요청에서 받은 **폼 데이터**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다. + +## 문서 확인하기 + +문서 UI `/docs`에서 확인할 수 있습니다: + +
+ +
+ +## 추가 폼 필드 금지하기 + +일부 특별한 사용 사례(흔하지는 않겠지만)에서는 Pydantic 모델에서 정의한 폼 필드를 **제한**하길 원할 수도 있습니다. 그리고 **추가** 필드를 **금지**할 수도 있습니다. + +/// note | 참고 + +이 기능은 FastAPI 버전 `0.114.0` 이후부터 지원됩니다. 🤓 + +/// + +Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다: + +{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} + +클라이언트가 추가 데이터를 보내려고 하면 **오류** 응답을 받게 됩니다. + +예를 들어, 클라이언트가 폼 필드를 보내려고 하면: + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +`extra` 필드가 허용되지 않는다는 오류 응답을 받게 됩니다: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## 요약 + +Pydantic 모델을 사용하여 FastAPI에서 폼 필드를 선언할 수 있습니다. 😎 diff --git a/docs/ko/docs/tutorial/request-forms-and-files.md b/docs/ko/docs/tutorial/request-forms-and-files.md index 0867414eae..dc1bda21a8 100644 --- a/docs/ko/docs/tutorial/request-forms-and-files.md +++ b/docs/ko/docs/tutorial/request-forms-and-files.md @@ -2,7 +2,7 @@ `File` 과 `Form` 을 사용하여 파일과 폼을 함께 정의할 수 있습니다. -/// info | "정보" +/// info | 정보 파일과 폼 데이터를 함께, 또는 각각 업로드하기 위해 먼저 `python-multipart`를 설치해야합니다. @@ -12,23 +12,19 @@ ## `File` 및 `Form` 업로드 -```Python hl_lines="1" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *} ## `File` 및 `Form` 매개변수 정의 `Body` 및 `Query`와 동일한 방식으로 파일과 폼의 매개변수를 생성합니다: -```Python hl_lines="8" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *} 파일과 폼 필드는 폼 데이터 형식으로 업로드되어 파일과 폼 필드로 전달됩니다. 어떤 파일들은 `bytes`로, 또 어떤 파일들은 `UploadFile`로 선언할 수 있습니다. -/// warning | "경고" +/// warning | 경고 다수의 `File`과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json`가 아닌 `multipart/form-data`로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다. diff --git a/docs/ko/docs/tutorial/request-forms.md b/docs/ko/docs/tutorial/request-forms.md new file mode 100644 index 0000000000..5ca17b0d68 --- /dev/null +++ b/docs/ko/docs/tutorial/request-forms.md @@ -0,0 +1,74 @@ +# 폼 데이터 + +JSON 대신 폼 필드를 받아야 하는 경우 `Form`을 사용할 수 있습니다. + +/// info | 정보 + +폼을 사용하려면, 먼저 `python-multipart`를 설치하세요. + +[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 아래와 같이 설치할 수 있습니다: + +```console +$ pip install python-multipart +``` + +/// + +## `Form` 임포트하기 + +`fastapi`에서 `Form`을 임포트합니다: + +{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} + +## `Form` 매개변수 정의하기 + +`Body` 또는 `Query`와 동일한 방식으로 폼 매개변수를 만듭니다: + +{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} + +예를 들어, OAuth2 사양을 사용할 수 있는 방법 중 하나("패스워드 플로우"라고 함)로 `username`과 `password`를 폼 필드로 보내야 합니다. + +사양에서는 필드 이름이 `username` 및 `password`로 정확하게 명명되어야 하고, JSON이 아닌 폼 필드로 전송해야 합니다. + +`Form`을 사용하면 유효성 검사, 예제, 별칭(예: `username` 대신 `user-name`) 등을 포함하여 `Body`(및 `Query`, `Path`, `Cookie`)와 동일한 구성을 선언할 수 있습니다. + +/// info | 정보 + +`Form`은 `Body`에서 직접 상속되는 클래스입니다. + +/// + +/// tip | 팁 + +폼 본문을 선언할 때, 폼이 없으면 매개변수가 쿼리 매개변수나 본문(JSON) 매개변수로 해석(interpret)되기 때문에 `Form`을 명시적으로 사용해야 합니다. + +/// + +## "폼 필드"에 대해 + +HTML 폼(`
`)이 데이터를 서버로 보내는 방식은 일반적으로 해당 데이터에 대해 "특수" 인코딩을 사용하며, 이는 JSON과 다릅니다. + +**FastAPI**는 JSON 대신 올바른 위치에서 해당 데이터를 읽습니다. + +/// note | 기술 세부사항 + +폼의 데이터는 일반적으로 "미디어 유형(media type)" `application/x-www-form-urlencoded`를 사용하여 인코딩합니다. + +그러나 폼에 파일이 포함된 경우, `multipart/form-data`로 인코딩합니다. 다음 장에서 파일 처리에 대해 읽을 겁니다. + + +이러한 인코딩 및 폼 필드에 대해 더 읽고 싶다면, POST에 대한 MDN 웹 문서를 참조하세요. + +/// + +/// warning | 경고 + +*경로 작업*에서 여러 `Form` 매개변수를 선언할 수 있지만, JSON으로 수신할 것으로 예상되는 `Body` 필드와 함께 선언할 수 없습니다. 요청 본문은 `application/json` 대신에 `application/x-www-form-urlencoded`를 사용하여 인코딩되기 때문입니다. + +이는 **FastAPI**의 제한 사항이 아니며 HTTP 프로토콜의 일부입니다. + +/// + +## 요약 + +폼 데이터 입력 매개변수를 선언하려면 `Form`을 사용하세요. diff --git a/docs/ko/docs/tutorial/response-model.md b/docs/ko/docs/tutorial/response-model.md index fc74a60b3b..a71d649f90 100644 --- a/docs/ko/docs/tutorial/response-model.md +++ b/docs/ko/docs/tutorial/response-model.md @@ -8,11 +8,9 @@ * `@app.delete()` * 기타. -```Python hl_lines="17" -{!../../../docs_src/response_model/tutorial001.py!} -``` +{* ../../docs_src/response_model/tutorial001.py hl[17] *} -/// note | "참고" +/// note | 참고 `response_model`은 "데코레이터" 메소드(`get`, `post`, 등)의 매개변수입니다. 모든 매개변수들과 본문(body)처럼 *경로 작동 함수*가 아닙니다. @@ -31,7 +29,7 @@ FastAPI는 이 `response_model`를 사용하여: * 해당 모델의 출력 데이터 제한. 이것이 얼마나 중요한지 아래에서 볼 것입니다. -/// note | "기술 세부사항" +/// note | 기술 세부사항 응답 모델은 함수의 타입 어노테이션 대신 이 매개변수로 선언하는데, 경로 함수가 실제 응답 모델을 반환하지 않고 `dict`, 데이터베이스 객체나 기타 다른 모델을 `response_model`을 사용하여 필드 제한과 직렬화를 수행하고 반환할 수 있기 때문입니다 @@ -41,15 +39,11 @@ FastAPI는 이 `response_model`를 사용하여: 여기서 우리는 평문 비밀번호를 포함하는 `UserIn` 모델을 선언합니다: -```Python hl_lines="9 11" -{!../../../docs_src/response_model/tutorial002.py!} -``` +{* ../../docs_src/response_model/tutorial002.py hl[9,11] *} 그리고 이 모델을 사용하여 입력을 선언하고 같은 모델로 출력을 선언합니다: -```Python hl_lines="17-18" -{!../../../docs_src/response_model/tutorial002.py!} -``` +{* ../../docs_src/response_model/tutorial002.py hl[17:18] *} 이제 브라우저가 비밀번호로 사용자를 만들 때마다 API는 응답으로 동일한 비밀번호를 반환합니다. @@ -57,7 +51,7 @@ FastAPI는 이 `response_model`를 사용하여: 그러나 동일한 모델을 다른 *경로 작동*에서 사용할 경우, 모든 클라이언트에게 사용자의 비밀번호를 발신할 수 있습니다. -/// danger | "위험" +/// danger | 위험 절대로 사용자의 평문 비밀번호를 저장하거나 응답으로 발신하지 마십시오. @@ -67,21 +61,15 @@ FastAPI는 이 `response_model`를 사용하여: 대신 평문 비밀번호로 입력 모델을 만들고 해당 비밀번호 없이 출력 모델을 만들 수 있습니다: -```Python hl_lines="9 11 16" -{!../../../docs_src/response_model/tutorial003.py!} -``` +{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *} 여기서 *경로 작동 함수*가 비밀번호를 포함하는 동일한 입력 사용자를 반환할지라도: -```Python hl_lines="24" -{!../../../docs_src/response_model/tutorial003.py!} -``` +{* ../../docs_src/response_model/tutorial003.py hl[24] *} ...`response_model`을 `UserOut` 모델로 선언했기 때문에 비밀번호를 포함하지 않습니다: -```Python hl_lines="22" -{!../../../docs_src/response_model/tutorial003.py!} -``` +{* ../../docs_src/response_model/tutorial003.py hl[22] *} 따라서 **FastAPI**는 출력 모델에서 선언하지 않은 모든 데이터를 (Pydantic을 사용하여) 필터링합니다. @@ -99,9 +87,7 @@ FastAPI는 이 `response_model`를 사용하여: 응답 모델은 아래와 같이 기본값을 가질 수 있습니다: -```Python hl_lines="11 13-14" -{!../../../docs_src/response_model/tutorial004.py!} -``` +{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *} * `description: Optional[str] = None`은 기본값으로 `None`을 갖습니다. * `tax: float = 10.5`는 기본값으로 `10.5`를 갖습니다. @@ -115,9 +101,7 @@ FastAPI는 이 `response_model`를 사용하여: *경로 작동 데코레이터* 매개변수를 `response_model_exclude_unset=True`로 설정 할 수 있습니다: -```Python hl_lines="24" -{!../../../docs_src/response_model/tutorial004.py!} -``` +{* ../../docs_src/response_model/tutorial004.py hl[24] *} 이러한 기본값은 응답에 포함되지 않고 실제로 설정된 값만 포함됩니다. @@ -130,13 +114,13 @@ FastAPI는 이 `response_model`를 사용하여: } ``` -/// info | "정보" +/// info | 정보 FastAPI는 이를 위해 Pydantic 모델의 `.dict()`의 `exclude_unset` 매개변수를 사용합니다. /// -/// info | "정보" +/// info | 정보 아래 또한 사용할 수 있습니다: @@ -181,7 +165,7 @@ ID가 `baz`인 항목(items)처럼 기본값과 동일한 값을 갖는다면: 따라서 JSON 스키마에 포함됩니다. -/// tip | "팁" +/// tip | 팁 `None` 뿐만 아니라 다른 어떤 것도 기본값이 될 수 있습니다. @@ -197,7 +181,7 @@ ID가 `baz`인 항목(items)처럼 기본값과 동일한 값을 갖는다면: Pydantic 모델이 하나만 있고 출력에서 ​​일부 데이터를 제거하려는 경우 빠른 지름길로 사용할 수 있습니다. -/// tip | "팁" +/// tip | 팁 하지만 이러한 매개변수 대신 여러 클래스를 사용하여 위 아이디어를 사용하는 것을 추천합니다. @@ -207,11 +191,9 @@ Pydantic 모델이 하나만 있고 출력에서 ​​일부 데이터를 제 /// -```Python hl_lines="31 37" -{!../../../docs_src/response_model/tutorial005.py!} -``` +{* ../../docs_src/response_model/tutorial005.py hl[31,37] *} -/// tip | "팁" +/// tip | 팁 문법 `{"name", "description"}`은 두 값을 갖는 `set`을 만듭니다. @@ -223,9 +205,7 @@ Pydantic 모델이 하나만 있고 출력에서 ​​일부 데이터를 제 `list` 또는 `tuple` 대신 `set`을 사용하는 법을 잊었더라도, FastAPI는 `set`으로 변환하고 정상적으로 작동합니다: -```Python hl_lines="31 37" -{!../../../docs_src/response_model/tutorial006.py!} -``` +{* ../../docs_src/response_model/tutorial006.py hl[31,37] *} ## 요약 diff --git a/docs/ko/docs/tutorial/response-status-code.md b/docs/ko/docs/tutorial/response-status-code.md index 48cb49cbfe..bcaf7843b9 100644 --- a/docs/ko/docs/tutorial/response-status-code.md +++ b/docs/ko/docs/tutorial/response-status-code.md @@ -8,11 +8,9 @@ * `@app.delete()` * 기타 -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} -/// note | "참고" +/// note | 참고 `status_code` 는 "데코레이터" 메소드(`get`, `post` 등)의 매개변수입니다. 모든 매개변수들과 본문처럼 *경로 작동 함수*가 아닙니다. @@ -20,7 +18,7 @@ `status_code` 매개변수는 HTTP 상태 코드를 숫자로 입력받습니다. -/// info | "정보" +/// info | 정보 `status_code` 는 파이썬의 `http.HTTPStatus` 와 같은 `IntEnum` 을 입력받을 수도 있습니다. @@ -33,7 +31,7 @@ -/// note | "참고" +/// note | 참고 어떤 응답 코드들은 해당 응답에 본문이 없다는 것을 의미하기도 합니다 (다음 항목 참고). @@ -43,7 +41,7 @@ ## HTTP 상태 코드에 대하여 -/// note | "참고" +/// note | 참고 만약 HTTP 상태 코드에 대하여 이미 알고있다면, 다음 항목으로 넘어가십시오. @@ -66,7 +64,7 @@ HTTP는 세자리의 숫자 상태 코드를 응답의 일부로 전송합니다 * 일반적인 클라이언트 오류의 경우 `400` 을 사용할 수 있습니다. * `5xx` 상태 코드는 서버 오류에 사용됩니다. 이것들을 직접 사용할 일은 거의 없습니다. 응용 프로그램 코드나 서버의 일부에서 문제가 발생하면 자동으로 이들 상태 코드 중 하나를 반환합니다. -/// tip | "팁" +/// tip | 팁 각각의 상태 코드와 이들이 의미하는 내용에 대해 더 알고싶다면 MDN HTTP 상태 코드에 관한 문서 를 확인하십시오. @@ -76,9 +74,7 @@ HTTP는 세자리의 숫자 상태 코드를 응답의 일부로 전송합니다 상기 예시 참고: -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} `201` 은 "생성됨"를 의미하는 상태 코드입니다. @@ -86,15 +82,13 @@ HTTP는 세자리의 숫자 상태 코드를 응답의 일부로 전송합니다 `fastapi.status` 의 편의 변수를 사용할 수 있습니다. -```Python hl_lines="1 6" -{!../../../docs_src/response_status_code/tutorial002.py!} -``` +{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} 이것은 단순히 작업을 편리하게 하기 위한 것으로, HTTP 상태 코드와 동일한 번호를 갖고있지만, 이를 사용하면 편집기의 자동완성 기능을 사용할 수 있습니다: -/// note | "기술적 세부사항" +/// note | 기술적 세부사항 `from starlette import status` 역시 사용할 수 있습니다. diff --git a/docs/ko/docs/tutorial/schema-extra-example.md b/docs/ko/docs/tutorial/schema-extra-example.md index 7b5ccdd324..77e94db720 100644 --- a/docs/ko/docs/tutorial/schema-extra-example.md +++ b/docs/ko/docs/tutorial/schema-extra-example.md @@ -8,35 +8,15 @@ 생성된 JSON 스키마에 추가될 Pydantic 모델을 위한 `examples`을 선언할 수 있습니다. -//// tab | Python 3.10+ Pydantic v2 +//// tab | Pydantic v2 -```Python hl_lines="13-24" -{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} //// -//// tab | Python 3.10+ Pydantic v1 +//// tab | Pydantic v1 -```Python hl_lines="13-23" -{!> ../../../docs_src/schema_extra_example/tutorial001_py310_pv1.py!} -``` - -//// - -//// tab | Python 3.8+ Pydantic v2 - -```Python hl_lines="15-26" -{!> ../../../docs_src/schema_extra_example/tutorial001.py!} -``` - -//// - -//// tab | Python 3.8+ Pydantic v1 - -```Python hl_lines="15-25" -{!> ../../../docs_src/schema_extra_example/tutorial001_pv1.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} //// @@ -58,7 +38,7 @@ Pydantic v1에서 ../../../docs_src/schema_extra_example/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 10-13" -{!> ../../../docs_src/schema_extra_example/tutorial002.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} ## JSON Schema에서의 `examples` - OpenAPI @@ -114,57 +80,7 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 여기, `Body()`에 예상되는 예제 데이터 하나를 포함한 `examples`를 넘겼습니다: -//// tab | Python 3.10+ - -```Python hl_lines="22-29" -{!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="22-29" -{!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23-30" -{!> ../../../docs_src/schema_extra_example/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="18-25" -{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="20-27" -{!> ../../../docs_src/schema_extra_example/tutorial003.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} ### 문서 UI 예시 @@ -176,57 +92,7 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 물론 여러 `examples`를 넘길 수 있습니다: -//// tab | Python 3.10+ - -```Python hl_lines="23-38" -{!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="23-38" -{!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24-39" -{!> ../../../docs_src/schema_extra_example/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="19-34" -{!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="21-36" -{!> ../../../docs_src/schema_extra_example/tutorial004.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} 이와 같이 하면 이 예제는 그 본문 데이터를 위한 내부 **JSON 스키마**의 일부가 될 것입니다. @@ -267,57 +133,7 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 이를 다음과 같이 사용할 수 있습니다: -//// tab | Python 3.10+ - -```Python hl_lines="23-49" -{!> ../../../docs_src/schema_extra_example/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="23-49" -{!> ../../../docs_src/schema_extra_example/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24-50" -{!> ../../../docs_src/schema_extra_example/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="19-45" -{!> ../../../docs_src/schema_extra_example/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8+ Annotated가 없는 경우 - -/// tip | "팁" - -가능하다면 `Annotated`가 달린 버전을 권장합니다. - -/// - -```Python hl_lines="21-47" -{!> ../../../docs_src/schema_extra_example/tutorial005.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} ### 문서 UI에서의 OpenAPI 예시 @@ -327,7 +143,7 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 ## 기술적 세부 사항 -/// tip | "팁" +/// tip | 팁 이미 **FastAPI**의 **0.99.0 혹은 그 이상** 버전을 사용하고 있다면, 이 세부 사항을 **스킵**해도 상관 없을 것입니다. @@ -337,7 +153,7 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 /// -/// warning | "경고" +/// warning | 경고 표준 **JSON 스키마**와 **OpenAPI**에 대한 아주 기술적인 세부사항입니다. @@ -361,7 +177,7 @@ OpenAPI는 또한 `example`과 `examples` 필드를 명세서의 다른 부분 * `File()` * `Form()` -/// info | "정보" +/// info | 정보 이 예전 OpenAPI-특화 `examples` 매개변수는 이제 FastAPI `0.103.0`부터 `openapi_examples`입니다. @@ -377,7 +193,7 @@ OpenAPI는 또한 `example`과 `examples` 필드를 명세서의 다른 부분 JSON 스키마의 새로운 `examples` 필드는 예제 속 **단순한 `list`**이며, (위에서 상술한 것처럼) OpenAPI의 다른 곳에 존재하는 dict으로 된 추가적인 메타데이터가 아닙니다. -/// info | "정보" +/// info | 정보 더 쉽고 새로운 JSON 스키마와의 통합과 함께 OpenAPI 3.1.0가 배포되었지만, 잠시동안 자동 문서 생성을 제공하는 도구인 Swagger UI는 OpenAPI 3.1.0을 지원하지 않았습니다 (5.0.0 버전부터 지원합니다 🎉). diff --git a/docs/ko/docs/tutorial/security/get-current-user.md b/docs/ko/docs/tutorial/security/get-current-user.md index 9bf3d4ee1b..98ef3885e4 100644 --- a/docs/ko/docs/tutorial/security/get-current-user.md +++ b/docs/ko/docs/tutorial/security/get-current-user.md @@ -2,9 +2,7 @@ 이전 장에서 (의존성 주입 시스템을 기반으로 한)보안 시스템은 *경로 작동 함수*에서 `str`로 `token`을 제공했습니다: -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} 그러나 아직도 유용하지 않습니다. @@ -16,21 +14,7 @@ Pydantic을 사용하여 본문을 선언하는 것과 같은 방식으로 다른 곳에서 사용할 수 있습니다. -//// tab | 파이썬 3.7 이상 - -```Python hl_lines="5 12-16" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="3 10-14" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[5,12:16] *} ## `get_current_user` 의존성 생성하기 @@ -42,67 +26,25 @@ Pydantic을 사용하여 본문을 선언하는 것과 같은 방식으로 다 이전에 *경로 작동*에서 직접 수행했던 것과 동일하게 새 종속성 `get_current_user`는 하위 종속성 `oauth2_scheme`에서 `str`로 `token`을 수신합니다. -//// tab | 파이썬 3.7 이상 - -```Python hl_lines="25" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="23" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[25] *} ## 유저 가져오기 `get_current_user`는 토큰을 `str`로 취하고 Pydantic `User` 모델을 반환하는 우리가 만든 (가짜) 유틸리티 함수를 사용합니다. -//// tab | 파이썬 3.7 이상 - -```Python hl_lines="19-22 26-27" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="17-20 24-25" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *} ## 현재 유저 주입하기 이제 *경로 작동*에서 `get_current_user`와 동일한 `Depends`를 사용할 수 있습니다. -//// tab | 파이썬 3.7 이상 - -```Python hl_lines="31" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="29" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[31] *} Pydantic 모델인 `User`로 `current_user`의 타입을 선언하는 것을 알아야 합니다. 이것은 모든 완료 및 타입 검사를 통해 함수 내부에서 우리를 도울 것입니다. -/// tip | "팁" +/// tip | 팁 요청 본문도 Pydantic 모델로 선언된다는 것을 기억할 것입니다. @@ -110,7 +52,7 @@ Pydantic 모델인 `User`로 `current_user`의 타입을 선언하는 것을 알 /// -/// check | "확인" +/// check | 확인 이 의존성 시스템이 설계된 방식은 모두 `User` 모델을 반환하는 다양한 의존성(다른 "의존적인")을 가질 수 있도록 합니다. @@ -150,21 +92,7 @@ Pydantic 모델인 `User`로 `current_user`의 타입을 선언하는 것을 알 그리고 이 수천 개의 *경로 작동*은 모두 3줄 정도로 줄일 수 있습니다. -//// tab | 파이썬 3.7 이상 - -```Python hl_lines="30-32" -{!> ../../../docs_src/security/tutorial002.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="28-30" -{!> ../../../docs_src/security/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial002.py hl[30:32] *} ## 요약 diff --git a/docs/ko/docs/tutorial/security/oauth2-jwt.md b/docs/ko/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 0000000000..8d27856e84 --- /dev/null +++ b/docs/ko/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,273 @@ +# 패스워드 해싱을 이용한 OAuth2, JWT 토큰을 사용하는 Bearer 인증 + +모든 보안 흐름을 구성했으므로, 이제 JWT 토큰과 패스워드 해싱을 사용해 애플리케이션을 안전하게 만들 것입니다. + +이 코드는 실제로 애플리케이션에서 패스워드를 해싱하여 DB에 저장하는 등의 작업에 활용할 수 있습니다. + +이전 장에 이어서 시작해 봅시다. + +## JWT + +JWT 는 "JSON Web Tokens" 을 의미합니다. + +JSON 객체를 공백이 없는 긴 문자열로 인코딩하는 표준이며, 다음과 같은 형태입니다: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +JWT는 암호화되지 않아 누구든지 토큰에서 정보를 복원할 수 있습니다. + +하지만 JWT는 서명되어 있습니다. 그래서 자신이 발급한 토큰을 받았을 때, 실제로 자신이 발급한게 맞는지 검증할 수 있습니다. + +만료 기간이 일주일인 토큰을 발행했다고 가정해 봅시다. 다음 날 사용자가 토큰을 가져왔을 때, 그 사용자가 시스템에 여전히 로그인되어 있다는 것을 알 수 있습니다. + +일주일 뒤에는 토큰이 만료될 것이고, 사용자는 인가되지 않아 새 토큰을 받기 위해 다시 로그인해야 할 것입니다. 만약 사용자(또는 제3자)가 토큰을 수정하거나 만료일을 변경하면, 서명이 일치하지 않기 때문에 알아챌 수 있을 것입니다. + +만약 JWT 토큰을 다뤄보고, 작동 방식도 알아보고 싶다면 https://jwt.io 을 확인하십시오. + +## `PyJWT` 설치 + +파이썬으로 JWT 토큰을 생성하고 검증하려면 `PyJWT` 를 설치해야 합니다. + +[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 `pyjwt` 를 설치하십시오: + +
+ +```console +$ pip install pyjwt + +---> 100% +``` + +
+ +/// info | 참고 + +RSA나 ECDSA 같은 전자 서명 알고리즘을 사용하려면, `pyjwt[crypto]`라는 암호화 라이브러리 의존성을 설치해야 합니다. + +더 자세한 내용은 PyJWT 설치 에서 확인할 수 있습니다. + +/// + +## 패스워드 해싱 + +"해싱(Hashing)"은 어떤 내용(여기서는 패스워드)을 해석할 수 없는 일련의 바이트 집합(단순 문자열)으로 변환하는 것을 의미합니다. + +동일한 내용(똑같은 패스워드)을 해싱하면 동일한 문자열을 얻습니다. + +하지만 그 문자열을 다시 패스워드로 되돌릴 수는 없습니다. + +### 패스워드를 해싱하는 이유 + +데이터베이스를 탈취당하더라도, 침입자는 사용자의 평문 패스워드 대신 해시 값만 얻을 수 있습니다. + +따라서 침입자는 훔친 사용자 패스워드를 다른 시스템에서 활용할 수 없습니다. (대다수 사용자가 여러 시스템에서 동일한 패스워드를 사용하기 때문에 평문 패스워드가 유출되면 위험합니다.) + +## `passlib` 설치 + +PassLib는 패스워드 해시를 다루는 훌륭한 파이썬 패키지입니다. + +많은 안전한 해시 알고리즘과 도구들을 지원합니다. + +추천하는 알고리즘은 "Bcrypt"입니다. + +[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 PassLib와 Bcrypt를 설치하십시오: + +
+ +```console +$ pip install "passlib[bcrypt]" + +---> 100% +``` + +
+ +/// tip | 팁 + +`passlib`를 사용하여, **Django**, **Flask** 의 보안 플러그인이나 다른 도구로 생성한 패스워드를 읽을 수 있도록 설정할 수도 있습니다. + +예를 들자면, FastAPI 애플리케이션과 Django 애플리케이션이 같은 데이터베이스에서 데이터를 공유할 수 있습니다. 또는 같은 데이터베이스를 사용하여 Django 애플리케이션을 점진적으로 마이그레이션 할 수도 있습니다. + +그리고 사용자는 FastAPI 애플리케이션과 Django 애플리케이션에 동시에 로그인할 수 있습니다. + +/// + +## 패스워드의 해시와 검증 + +필요한 도구를 `passlib`에서 임포트합니다. + +PassLib "컨텍스트(context)"를 생성합니다. 이것은 패스워드를 해싱하고 검증하는데 사용합니다. + +/// tip | 팁 + +PassLib 컨텍스트는 다양한 해싱 알고리즘을 사용할 수 있는 기능을 제공하며, 더 이상 사용이 권장되지 않는 오래된 해싱 알고리즘을 검증하는 기능도 포함되어 있습니다. + +예를 들어, 다른 시스템(Django 같은)에서 생성한 패스워드를 읽고 검증할 수 있으며, 새로운 패스워드를 Bcrypt 같은 다른 알고리즘으로 해싱할 수도 있습니다. + +그리고 동시에 그런 모든 알고리즘과 호환성을 유지합니다. + +/// + +사용자로부터 받은 패스워드를 해싱하는 유틸리티 함수를 생성합니다. + +그리고 받은 패스워드가 저장된 해시와 일치하는지 검증하는 또 다른 유틸리티 함수도 생성합니다. + +그리고 사용자를 인증하고 반환하는 또 다른 함수도 생성합니다. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} + +/// note + +새로운 (가짜) 데이터베이스 `fake_users_db`를 확인하면, 해시 처리된 패스워드가 어떻게 생겼는지 볼 수 있습니다: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. + +/// + +## JWT 토큰 처리 + +설치된 모듈을 임포트 합니다. + +JWT 토큰 서명에 사용될 임의의 비밀키를 생성합니다. + +안전한 임의의 비밀키를 생성하려면 다음 명령어를 사용하십시오: + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +그리고 생성한 비밀키를 복사해 변수 `SECRET_KEY`에 대입합니다. (이 예제의 변수 값을 그대로 사용하지 마십시오.) + +JWT 토큰을 서명하는 데 사용될 알고리즘을 위한 변수 `ALGORITHM` 을 생성하고 `"HS256"` 으로 설정합니다. + +토큰 만료 기간을 위한 변수를 생성합니다. + +응답을 위한 토큰 엔드포인트에 사용될 Pydantic 모델을 정의합니다. + +새 액세스 토큰을 생성하기 위한 유틸리티 함수를 생성합니다. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} + +## 의존성 수정 + +`get_current_user` 함수를 이전과 동일한 토큰을 받도록 수정하되, 이번에는 JWT 토큰을 사용하도록 합니다. + +받은 토큰을 디코딩하여 검증한 후 현재 사용자를 반환합니다. + +토큰이 유효하지 않다면 HTTP 오류를 반환합니다. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} + +## `/token` 경로 작업 수정 + +토큰의 만료 시각을 설정하기 위해 `timedelta` 를 생성합니다. + +실제 JWT 액세스 토큰을 생성하여 반환합니다. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} + +### JWT "주체(subject)" `sub`에 대한 기술 세부 사항 + +JWT 명세에 따르면 토큰의 주체를 포함하는 `sub`라는 키가 있습니다. + +사용 여부는 선택사항이지만, 사용자의 식별 정보를 저장할 수 있으므로 여기서는 이를 사용합니다. + +JWT는 사용자를 식별하고 사용자가 API를 직접 사용할 수 있도록 허용하는 것 외에도 다른 용도로 사용될 수도 있습니다. + +예를 들어 "자동차"나 "블로그 게시물"을 식별하는 데 사용할 수 있습니다. + +그리고 "자동차를 운전하다"나 "블로그 게시물을 수정하다"처럼 해당 엔터티에 대한 권한을 추가할 수 있습니다. + +그 후 이 JWT 토큰을 사용자(또는 봇)에게 제공하면, 그들은 계정을 따로 만들 필요 없이 API가 생성한 JWT 토큰만으로 작업(자동차 운전 또는 블로그 게시물 편집)을 수행할 수 있습니다. + +이러한 개념을 활용하면 JWT는 훨씬 더 복잡한 시나리오에도 사용할 수 있습니다. + +이 경우 여러 엔터티가 동일한 ID를 가질 수 있습니다. 예를 들어 foo라는 ID를 가진 사용자, 자동차, 블로그 게시물이 있을 수 있습니다. + +그래서 ID 충돌을 방지하기 위해, 사용자의 JWT 토큰을 생성할 때 접두사로 `sub` 키를 추가할 수 있습니다. 예를 들어 `username:` 을 붙이는 방식입니다. 이 예제에서는 `sub` 값이 `username:johndoe`이 될 수 있습니다. + +가장 중요한 점은 `sub` 키는 전체 애플리케이션에서 고유한 식별자가 되어야 하며 문자열이어야 한다는 점입니다. + +## 확인해봅시다 + +서버를 실행하고 문서로 이동하십시오: http://127.0.0.1:8000/docs. + +다음과 같은 사용자 인터페이스를 볼 수 있습니다: + + + +이전과 같은 방법으로 애플리케이션에 인증하십시오. + +다음 인증 정보를 사용하십시오: + +Username: `johndoe` +Password: `secret` + +/// check + +코드 어디에도 평문 패스워드 "`secret`" 이 없다는 점에 유의하십시오. 해시된 버전만 있습니다. + +/// + + + +`/users/me/` 를 호출하면 다음과 같은 응답을 얻을 수 있습니다: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +개발자 도구를 열어보면 전송된 데이터에 토큰만 포함된 것을 확인할 수 있습니다. 패스워드는 사용자를 인증하고 액세스 토큰을 받기 위한 첫 번째 요청에만 전송되며, 이후에는 전송되지 않습니다: + + + +/// note + +`Bearer `로 시작하는 `Authorization` 헤더에 주목하십시오. + +/// + +## `scopes` 의 고급 사용법 + +OAuth2는 "스코프(scopes)" 라는 개념을 갖고 있습니다. + +이를 사용하여 JWT 토큰에 특정 권한 집합을 추가할 수 있습니다. + +그 후 이 토큰을 사용자에게 직접 제공하거나 제3자에게 제공하여, 특정 제한사항 하에있는 API와 통신하도록 할 수 있습니다. + +**FastAPI** 에서의 사용 방법과 통합 방식은 **심화 사용자 안내서** 에서 자세히 배울 수 있습니다. + +## 요약 + +지금까지 살펴본 내용을 바탕으로, OAuth2와 JWT 같은 표준을 사용하여 안전한 **FastAPI** 애플리케이션을 만들 수 있습니다. + +거의 모든 프레임워크에서 보안 처리는 상당히 복잡한 주제입니다. + +이를 단순화하는 많은 패키지는 데이터 모델, 데이터베이스, 사용 가능한 기능들에 대해 여러 제약이 있습니다. 그리고 지나치게 단순화하는 일부 패키지들은 심각한 보안 결함을 가질 수도 있습니다. + +--- + +**FastAPI** 는 어떤 데이터베이스, 데이터 모델, 도구도 강요하지 않습니다. + +프로젝트에 가장 적합한 것을 선택할 수 있는 유연성을 제공합니다. + +그리고 `passlib` 와 `PyJWT` 처럼 잘 관리되고 널리 사용되는 패키지들을 바로 사용할 수 있습니다. **FastAPI** 는 외부 패키지 통합을 위해 복잡한 메커니즘이 필요하지 않기 때문입니다. + +그러나 유연성, 견고성, 보안성을 해치지 않으면서 과정을 단순화할 수 있는 도구들을 제공합니다. + +그리고 OAuth2와 같은 표준 프로토콜을 비교적 간단한 방법으로 구현하고 사용할 수 있습니다. + +더 세분화된 권한 체계를 위해 OAuth2의 "스코프"를 사용하는 방법은 **심화 사용자 안내서**에서 더 자세히 배울 수 있습니다. OAuth2의 스코프는 제3자 애플리케이션이 사용자를 대신해 그들의 API와 상호작용하도록 권한을 부여하기 위해, Facebook, Google, GitHub, Microsoft, X (Twitter) 등의 많은 대형 인증 제공업체들이 사용하는 메커니즘입니다. diff --git a/docs/ko/docs/tutorial/security/simple-oauth2.md b/docs/ko/docs/tutorial/security/simple-oauth2.md index 9593f96f5e..f10c4f588d 100644 --- a/docs/ko/docs/tutorial/security/simple-oauth2.md +++ b/docs/ko/docs/tutorial/security/simple-oauth2.md @@ -32,7 +32,7 @@ OAuth2는 (우리가 사용하고 있는) "패스워드 플로우"을 사용할 * `instagram_basic`은 페이스북/인스타그램에서 사용합니다. * `https://www.googleapis.com/auth/drive`는 Google에서 사용합니다. -/// 정보 +/// info | 정보 OAuth2에서 "범위"는 필요한 특정 권한을 선언하는 문자열입니다. @@ -52,21 +52,7 @@ OAuth2의 경우 문자열일 뿐입니다. 먼저 `OAuth2PasswordRequestForm`을 가져와 `/token`에 대한 *경로 작동*에서 `Depends`의 의존성으로 사용합니다. -//// tab | 파이썬 3.7 이상 - -```Python hl_lines="4 76" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="2 74" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003.py hl[4,76] *} `OAuth2PasswordRequestForm`은 다음을 사용하여 폼 본문을 선언하는 클래스 의존성입니다: @@ -75,7 +61,7 @@ OAuth2의 경우 문자열일 뿐입니다. * `scope`는 선택적인 필드로 공백으로 구분된 문자열로 구성된 큰 문자열입니다. * `grant_type`(선택적으로 사용). -/// 팁 +/// tip | 팁 OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type` 필드를 *요구*하지만 `OAuth2PasswordRequestForm`은 이를 강요하지 않습니다. @@ -86,7 +72,7 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type` * `client_id`(선택적으로 사용) (예제에서는 필요하지 않습니다). * `client_secret`(선택적으로 사용) (예제에서는 필요하지 않습니다). -/// 정보 +/// info | 정보 `OAuth2PasswordRequestForm`은 `OAuth2PasswordBearer`와 같이 **FastAPI**에 대한 특수 클래스가 아닙니다. @@ -100,7 +86,7 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type` ### 폼 데이터 사용하기 -/// 팁 +/// tip | 팁 종속성 클래스 `OAuth2PasswordRequestForm`의 인스턴스에는 공백으로 구분된 긴 문자열이 있는 `scope` 속성이 없고 대신 전송된 각 범위에 대한 실제 문자열 목록이 있는 `scopes` 속성이 있습니다. @@ -114,21 +100,7 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type` 오류의 경우 `HTTPException` 예외를 사용합니다: -//// tab | 파이썬 3.7 이상 - -```Python hl_lines="3 77-79" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="1 75-77" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003.py hl[3,77:79] *} ### 패스워드 확인하기 @@ -154,21 +126,13 @@ OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type` 따라서 해커는 다른 시스템에서 동일한 암호를 사용하려고 시도할 수 없습니다(많은 사용자가 모든 곳에서 동일한 암호를 사용하므로 이는 위험할 수 있습니다). -//// tab | P파이썬 3.7 이상 +//// tab | 파이썬 3.7 이상 -```Python hl_lines="80-83" -{!> ../../../docs_src/security/tutorial003.py!} -``` +{* ../../docs_src/security/tutorial003.py hl[80:83] *} //// -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="78-81" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/security/tutorial003_py310.py hl[78:81] *} #### `**user_dict`에 대해 @@ -186,7 +150,7 @@ UserInDB( ) ``` -/// 정보 +/// info | 정보 `**user_dict`에 대한 자세한 설명은 [**추가 모델** 문서](../extra-models.md#about-user_indict){.internal-link target=_blank}를 다시 읽어봅시다. @@ -202,7 +166,7 @@ UserInDB( 이 간단한 예제에서는 완전히 안전하지 않고, 동일한 `username`을 토큰으로 반환합니다. -/// 팁 +/// tip | 팁 다음 장에서는 패스워드 해싱 및 JWT 토큰을 사용하여 실제 보안 구현을 볼 수 있습니다. @@ -210,23 +174,9 @@ UserInDB( /// -//// tab | 파이썬 3.7 이상 +{* ../../docs_src/security/tutorial003.py hl[85] *} -```Python hl_lines="85" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="83" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// - -/// 팁 +/// tip | 팁 사양에 따라 이 예제와 동일하게 `access_token` 및 `token_type`이 포함된 JSON을 반환해야 합니다. @@ -250,23 +200,9 @@ UserInDB( 따라서 엔드포인트에서는 사용자가 존재하고 올바르게 인증되었으며 활성 상태인 경우에만 사용자를 얻습니다: -//// tab | 파이썬 3.7 이상 +{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *} -```Python hl_lines="58-66 69-72 90" -{!> ../../../docs_src/security/tutorial003.py!} -``` - -//// - -//// tab | 파이썬 3.10 이상 - -```Python hl_lines="55-64 67-70 88" -{!> ../../../docs_src/security/tutorial003_py310.py!} -``` - -//// - -/// 정보 +/// info | 정보 여기서 반환하는 값이 `Bearer`인 추가 헤더 `WWW-Authenticate`도 사양의 일부입니다. diff --git a/docs/ko/docs/tutorial/sql-databases.md b/docs/ko/docs/tutorial/sql-databases.md new file mode 100644 index 0000000000..58c7017d6a --- /dev/null +++ b/docs/ko/docs/tutorial/sql-databases.md @@ -0,0 +1,360 @@ +# SQL (관계형) 데이터베이스 + +**FastAPI**에서 SQL(관계형) 데이터베이스 사용은 필수가 아닙니다. 여러분이 원하는 **어떤 데이터베이스든** 사용할 수 있습니다. + +여기서는 SQLModel을 사용하는 예제를 살펴보겠습니다. + +**SQLModel**은 SQLAlchemy와 Pydantic을 기반으로 구축되었습니다.SQLModel은 **SQL 데이터베이스**를 사용하는 FastAPI 애플리케이션에 완벽히 어울리도록 **FastAPI**의 제작자가 설계한 도구입니다. + +/// tip | 팁 + +다른 SQL 또는 NoSQL 데이터베이스 라이브러리를 사용할 수도 있습니다 (일부는 "ORM"이라고도 불립니다), FastAPI는 특정 라이브러리의 사용을 강요하지 않습니다. 😎 + +/// + +SQLModel은 SQLAlchemy를 기반으로 하므로, SQLAlchemy에서 **지원하는 모든 데이터베이스**를 손쉽게 사용할 수 있습니다(SQLModel에서도 동일하게 지원됩니다). 예를 들면: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server 등. + +이 예제에서는 **SQLite**를 사용합니다. SQLite는 단일 파일을 사용하고 파이썬에서 기본적으로 지원하기 때문입니다. 따라서 이 예제를 그대로 복사하여 실행할 수 있습니다. + +나중에 실제 프로덕션 애플리케이션에서는 **PostgreSQL**과 같은 데이터베이스 서버를 사용하는 것이 좋습니다. + +/// tip | 팁 + +**FastAPI**와 **PostgreSQL**를 포함하여 프론트엔드와 다양한 도구를 제공하는 공식 프로젝트 생성기가 있습니다: https://github.com/fastapi/full-stack-fastapi-template + +/// + +이 튜토리얼은 매우 간단하고 짧습니다. 데이터베이스 기본 개념, SQL, 또는 더 복잡한 기능에 대해 배우고 싶다면, SQLModel 문서를 참고하세요. + +## `SQLModel` 설치하기 + +먼저, [가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, `sqlmodel`을 설치하세요: + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## 단일 모델로 애플리케이션 생성하기 + +우선 단일 **SQLModel** 모델을 사용하여 애플리케이션의 가장 간단한 첫 번째 버전을 생성해보겠습니다. + +이후 **다중 모델**을 추가하여 보안과 유연성을 강화할 것입니다. 🤓 + +### 모델 생성하기 + +`SQLModel`을 가져오고 데이터베이스 모델을 생성합니다: + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +`Hero` 클래스는 Pydantic 모델과 매우 유사합니다 (실제로 내부적으로 *Pydantic 모델이기도 합니다*). + +몇 가지 차이점이 있습니다: + +* `table=True`는 SQLModel에 이 모델이 *테이블 모델*이며, 단순한 데이터 모델이 아니라 SQL 데이터베이스의 **테이블**을 나타낸다는 것을 알려줍니다. (다른 일반적인 Pydantic 클래스처럼) 단순한 *데이터 모델*이 아닙니다. + +* `Field(primary_key=True)`는 SQLModel에 `id`가 SQL 데이터베이스의 **기본 키**임을 알려줍니다 (SQL 기본 키에 대한 자세한 내용은 SQLModel 문서를 참고하세요). + + `int | None` 유형으로 설정하면, SQLModel은 해당 열이 SQL 데이터베이스에서 `INTEGER` 유형이며 `NULLABLE` 값이어야 한다는 것을 알 수 있습니다. + +* `Field(index=True)`는 SQLModel에 해당 열에 대해 **SQL 인덱스**를 생성하도록 지시합니다. 이를 통해 데이터베이스에서 이 열으로 필터링된 데이터를 읽을 때 더 빠르게 조회할 수 있습니다. + + SQLModel은 `str`으로 선언된 항목이 SQL 데이터베이스에서 `TEXT` (또는 데이터베이스에 따라 `VARCHAR`) 유형의 열로 저장된다는 것을 인식합니다. + +### 엔진 생성하기 + +SQLModel의 `engine` (내부적으로는 SQLAlchemy `engine`)은 데이터베이스에 대한 **연결을 유지**하는 역할을 합니다. + +**하나의 단일 engine 객체**를 통해 코드 전체에서 동일한 데이터베이스에 연결할 수 있습니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +`check_same_thread=False`를 사용하면 FastAPI에서 여러 스레드에서 동일한 SQLite 데이터베이스를 사용할 수 있습니다. 이는 **하나의 단일 요청**이 **여러 스레드**를 사용할 수 있기 때문에 필요합니다(예: 의존성에서 사용되는 경우). + +걱정하지 마세요. 코드가 구조화된 방식으로 인해, 이후에 **각 요청마다 단일 SQLModel *세션*을 사용**하도록 보장할 것입니다. 실제로 그것이 `check_same_thread`가 하려는 것입니다. + +### 테이블 생성하기 + +그 다음 `SQLModel.metadata.create_all(engine)`을 사용하여 모든 *테이블 모델*의 **테이블을 생성**하는 함수를 추가합니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### 세션 의존성 생성하기 + +**`Session`**은 **메모리에 객체**를 저장하고 데이터에 필요한 모든 변경 사항을 추적한 후, **`engine`을 통해** 데이터베이스와 통신합니다. + +`yield`를 사용해 FastAPI의 **의존성**을 생성하여 각 요청마다 새로운 `Session`을 제공합니다. 이는 요청당 하나의 세션만 사용되도록 보장합니다. 🤓 + +그런 다음 이 의존성을 사용하는 코드를 간소화하기 위해 `Annotated` 의존성 `SessionDep`을 생성합니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### 시작 시 데이터베이스 테이블 생성하기 + +애플리케이션 시작 시 데이터베이스 테이블을 생성합니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +여기서는 애플리케이션 시작 이벤트 시 테이블을 생성합니다. + +프로덕션 환경에서는 애플리케이션을 시작하기 전에 실행되는 마이그레이션 스크립트를 사용할 가능성이 높습니다. 🤓 + +/// tip | 팁 + +SQLModel은 Alembic을 감싸는 마이그레이션 유틸리티를 제공할 예정입니다. 하지만 현재 Alembic을 직접 사용할 수 있습니다. + +/// + +### Hero 생성하기 + +각 SQLModel 모델은 Pydantic 모델이기도 하므로, Pydantic 모델을 사용할 수 있는 **타입 어노테이**션에서 동일하게 사용할 수 있습니다. + +예를 들어, 파라미터를 `Hero` 타입으로 선언하면 **JSON 본문**에서 값을 읽어옵니다. + +마찬가지로, 함수의 **반환 타입**으로 선언하면 해당 데이터의 구조가 자동으로 생성되는 API 문서의 UI에 나타납니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + + + +여기서 `SessionDep` 의존성 (즉, `Session`)을 사용하여 새로운 `Hero`를 `Session` 인스턴스에 추가하고, 데이터베이스에 변경 사항을 커밋하고, `hero` 데이터의 최신 상태를 갱신한 다음 이를 반환합니다. + +### Heroes 조회하기 + +`select()`를 사용하여 데이터베이스에서 `Hero`를 **조회**할 수 있습니다. 결과에 페이지네이션을 적용하기 위해 `limit`와 `offset`을 포함할 수 있습니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### 단일 Hero 조회하기 + +단일 `Hero`를 **조회**할 수도 있습니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### Hero 삭제하기 + +`Hero`를 **삭제**하는 것도 가능합니다. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### 애플리케이션 실행하기 + +애플리케이션을 실행하려면 다음 명령을 사용합니다: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +그런 다음 `/docs` UI로 이동하면, **FastAPI**가 해당 **model들**을 사용하여 API **문서를 생성**하는 것으르 확인할 수 있습니다. 또한 이 모델들은 데이터를 직렬화하고 검증하는 데에도 사용됩니다. + +
+ +
+ +## 여러 모델로 애플리케이션 업데이트 + +이제 애플리케이션을 약간 **리팩토링**하여 **보안**과 **유연성**을 개선해 보겠습니다. + +이전 애플리케이션의 UI를 보면, 지금까지는 클라이언트가 생성할 `Hero`의 `id`를 직접 지정할 수 있다는 것을 알 수 있습니다. 😱 + +이는 허용되어선 안 됩니다. 클라이언트가 이미 데이터베이스에 저장된 `id`를 덮어쓸 위험이 있기 때문입니다. `id`는 **백엔드** 또는 **데이터베이스**가 결정해야 하며, **클라이언트**가 결정해서는 안 됩니다. + +또한 hero의 `secret_name`을 생성하긴 했지만, 지금까지는 이 값을 어디에서나 반환하고 있습니다. 이는 그다지 **비밀스럽지** 않습니다... 😅 + +이러한 문제를 해결하기 위해 몇 가지 **추가 모델**을 추가할 것입니다. 바로 여기서 SQLModel이 빛을 발하게 됩니다. ✨ + +### 여러 모델 생성하기 + +**SQLModel**에서 `table=True`가 설정된 모델 클래스는 **테이블 모델**입니다. + +`table=True`가 없는 모델 클래스는 **데이터 모델**로, 이는 실제로 몇 가지 추가 기능이 포함된 Pydantic 모델에 불과합니다. 🤓 + +SQLModel을 사용하면 **상속**을 통해 모든 경우에 필드를 **중복 선언하지 않아도** 됩니다. + +#### `HeroBase` - 기본 클래스 + +모든 모델에서 **공유되는 필드**를 가진 `HeroBase` 모델을 시작해 봅시다: + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` - *테이블 모델* + +다음으로 실제 *테이블 모델*인 `Hero`를 생성합니다. 이 모델은 다른 모델에는 항상 포함되는 건 아닌 **추가 필드**를 포함합니다: + +* `id` +* `secret_name` + +`Hero`는 `HeroBase`를 상속하므로 `HeroBase`에 선언된 필드도 포함합니다. 따라서 `Hero`는 다음 **필드들도** 가지게 됩니다: + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` - 공개 *데이터 모델* + +다음으로 `HeroPublic` 모델을 생성합니다. 이 모델은 API 클라이언트에 **반환**되는 모델입니다. + +`HeroPublic`은 `HeroBase`와 동일한 필드를 가지며, `secret_name`은 포함하지 않습니다. + +마침내 우리의 heroes의 정체가 보호됩니다! 🥷 + +또한 `id: int`를 다시 선언합니다. 이를 통해, API 클라이언트와 **계약**을 맺어 `id`가 항상 존재하며 항상 `int` 타입이라는 것을 보장합니다(`None`이 될 수 없습니다). + +/// tip | 팁 + +반환 모델이 값이 항상 존재하고 항상 `int`(`None`이 아님)를 보장하는 것은 API 클라이언트에게 매우 유용합니다. 이를 통해 API와 통신하는 개발자가 훨씬 더 간단한 코드를 작성할 수 있습니다. + +또한 **자동으로 생성된 클라이언트**는 더 단순한 인터페이스를 제공하므로, API와 소통하는 개발자들이 훨씬 수월하게 작업할 수 있습니다. 😎 + +/// + +`HeroPublic`의 모든 필드는 `HeroBase`와 동일하며, `id`는 `int`로 선언됩니다(`None`이 아님): + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - hero 생성용 *데이터 모델* + +이제 `HeroCreate` 모델을 생성합니다. 이 모델은 클라이언트로부터 받은 데이터를 **검증**하는 역할을 합니다. + +`HeroCreate`는 `HeroBase와` 동일한 필드를 가지며, 추가로 `secret_name을` 포함합니다. + +클라이언트가 **새 hero을 생성**할 때 `secret_name`을 보내고, 이는 데이터베이스에 저장되지만, 해당 비밀 이름은 API를 통해 클라이언트에게 반환되지 않습니다. + +/// tip | 팁 + +이 방식은 **비밀번호**를 처리하는 방법과 동일합니다. 비밀번호를 받지만, 이를 API에서 반환하지는 않습니다. + +비밀번호 값을 저장하기 전에 **해싱**하여 저장하고, **평문으로 저장하지 마십시오**. + +/// + +`HeroCreate`의 필드는 다음과 같습니다: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - hero 수정용 *데이터 모델* + +이전 애플리케이션에서는 **hero를 수정**할 방법이 없었지만, 이제 **다중 모델**을 통해 수정 기능을 추가할 수 있습니다. 🎉 + +`HeroUpdate` *데이터 모델*은 약간 특별한데, 새 hero을 생성할 때 필요한 **모든 동일한 필드**를 가지지만, 모든 필드가 **선택적**(기본값이 있음)입니다. 이렇게 하면 hero을 수정할 때 수정하려는 필드만 보낼 수 있습니다. + +모든 **필드가 변경되기** 때문에(타입이 `None`을 포함하고, 기본값이 `None`으로 설정됨), 모든 필드를 **다시 선언**해야 합니다. + +엄밀히 말하면 `HeroBase`를 상속할 필요는 없습니다. 모든 필드를 다시 선언하기 때문입니다. 일관성을 위해 상속을 유지하긴 했지만, 필수는 아닙니다. 이는 개인적인 취향의 문제입니다. 🤷 + +`HeroUpdate`의 필드는 다음과 같습니다: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### `HeroCreate`로 생성하고 `HeroPublic` 반환하기 + +이제 **다중 모델**을 사용하므로 애플리케이션의 관련 부분을 업데이트할 수 있습니다. + +요청에서 `HeroCreate` *데이터 모델*을 받아 이를 기반으로 `Hero` *테이블 모델*을 생성합니다. + +이 새 *테이블 모델* `Hero`는 클라이언트에서 보낸 필드를 가지며, 데이터베이스에서 생성된 `id`도 포함합니다. + +그런 다음 함수를 통해 동일한 *테이블 모델* `Hero`를 반환합니다. 하지만 `response_model`로 `HeroPublic` *데이터 모델*을 선언했기 때문에, **FastAPI**는 `HeroPublic`을 사용하여 데이터를 검증하고 직렬화합니다. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | 팁 + +이제 **반환 타입 주석** `-> HeroPublic` 대신 `response_model=HeroPublic`을 사용합니다. 반환하는 값이 실제로 `HeroPublic`이 *아니기* 때문입니다. + +만약 `-> HeroPublic`으로 선언했다면, 에디터와 린터에서 반환값이 `HeroPublic`이 아니라 `Hero`라고 경고했을 것입니다. 이는 적절한 경고입니다. + +`response_model`에 선언함으로써 **FastAPI**가 이를 처리하도록 하고, 타입 어노테이션과 에디터 및 다른 도구의 도움에는 영향을 미치지 않도록 설정합니다. + +/// + +### `HeroPublic`으로 Heroes 조회하기 + +이전과 동일하게 `Hero`를 **조회**할 수 있습니다. 이번에도 `response_model=list[HeroPublic]`을 사용하여 데이터가 올바르게 검증되고 직렬화되도록 보장합니다. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### `HeroPublic`으로 단일 Hero 조회하기 + +단일 hero을 **조회**할 수도 있습니다: + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### `HeroUpdate`로 Hero 수정하기 + +**hero를 수정**할 수도 있습니다. 이를 위해 HTTP `PATCH` 작업을 사용합니다. + +코드에서는 클라이언트가 보낸 데이터를 딕셔너리 형태(`dict`)로 가져옵니다. 이는 **클라이언트가 보낸 데이터만 포함**하며, 기본값으로 들어가는 값은 제외합니다. 이를 위해 `exclude_unset=True`를 사용합니다. 이것이 주요 핵심입니다. 🪄 + +그런 다음, `hero_db.sqlmodel_update(hero_data)`를 사용하여 `hero_data`의 데이터를 `hero_db`에 업데이트합니다. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### Hero 다시 삭제하기 + +hero **삭제**는 이전과 거의 동일합니다. + +이번에는 모든 것을 리팩토링하고 싶은 욕구를 만족시키지 못할 것 같습니다. 😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### 애플리케이션 다시 실행하기 + +다음 명령을 사용해 애플리케이션을 다시 실행할 수 있습니다: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +`/docs` API UI로 이동하면 업데이트된 것을 확인할 수 있습니다. 클라이언트가 영웅을 생성할 때 `id`를 제공할 필요가 없게 되는 등의 변화도 보입니다. + +
+ +
+ +## 요약 + +**SQLModel**을 사용하여 SQL 데이터베이스와 상호작용하고, *데이터 모델* 및 *테이블 모델*로 코드를 간소화할 수 있습니다. + +더 많은 내용을 배우고 싶다면, **SQLModel** 문서를 참고하세요. SQLModel을 **FastAPI**와 함께 사용하는 것에 대한 더 긴 미니 튜토리얼도 제공합니다. 🚀 diff --git a/docs/ko/docs/tutorial/static-files.md b/docs/ko/docs/tutorial/static-files.md index 360aaaa6be..4f3e3ab286 100644 --- a/docs/ko/docs/tutorial/static-files.md +++ b/docs/ko/docs/tutorial/static-files.md @@ -7,11 +7,9 @@ * `StaticFiles` 임포트합니다. * 특정 경로에 `StaticFiles()` 인스턴스를 "마운트" 합니다. -```Python hl_lines="2 6" -{!../../../docs_src/static_files/tutorial001.py!} -``` +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} -/// note | "기술적 세부사항" +/// note | 기술적 세부사항 `from starlette.staticfiles import StaticFiles` 를 사용할 수도 있습니다. @@ -40,4 +38,4 @@ ## 추가 정보 -자세한 내용과 선택 사항을 보려면 Starlette의 정적 파일에 관한 문서를 확인하십시오. +자세한 내용과 선택 사항을 보려면 Starlette의 정적 파일에 관한 문서를 확인하십시오. diff --git a/docs/ko/docs/tutorial/testing.md b/docs/ko/docs/tutorial/testing.md new file mode 100644 index 0000000000..915ff6d22d --- /dev/null +++ b/docs/ko/docs/tutorial/testing.md @@ -0,0 +1,243 @@ +# 테스팅 + +Starlette 덕분에 **FastAPI** 를 테스트하는 일은 쉽고 즐거운 일이 되었습니다. + +Starlette는 HTTPX를 기반으로 하며, 이는 Requests를 기반으로 설계되었기 때문에 매우 친숙하고 직관적입니다. + +이를 사용하면 FastAPI에서 pytest를 직접 사용할 수 있습니다. + +## `TestClient` 사용하기 + +/// info | 정보 + +`TestClient` 사용하려면, 우선 `httpx` 를 설치해야 합니다. + +[virtual environment](../virtual-environments.md){.internal-link target=_blank} 를 만들고, 활성화 시킨 뒤에 설치하세요. 예시: + +```console +$ pip install httpx +``` + +/// + +`TestClient` 를 임포트하세요. + +**FastAPI** 어플리케이션을 전달하여 `TestClient` 를 만드세요. + +이름이 `test_` 로 시작하는 함수를 만드세요(`pytest` 의 표준적인 관례입니다). + +`httpx` 를 사용하는 것과 같은 방식으로 `TestClient` 객체를 사용하세요. + +표준적인 파이썬 문법을 이용하여 확인이 필요한 곳에 간단한 `assert` 문장을 작성하세요(역시 표준적인 `pytest` 관례입니다). + +{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} + +/// tip | 팁 + +테스트를 위한 함수는 `async def` 가 아니라 `def` 로 작성됨에 주의하세요. + +그리고 클라이언트에 대한 호출도 `await` 를 사용하지 않는 일반 호출입니다. + +이렇게 하여 복잡한 과정 없이 `pytest` 를 직접적으로 사용할 수 있습니다. + +/// + +/// note | 기술 세부사항 + +`from starlette.testclient import TestClient` 역시 사용할 수 있습니다. + +**FastAPI** 는 개발자의 편의를 위해 `starlette.testclient` 를 `fastapi.testclient` 로도 제공할 뿐입니다. 이는 단지 `Starlette` 에서 직접 가져오는지의 차이일 뿐입니다. + +/// + +/// tip | 팁 + +FastAPI 애플리케이션에 요청을 보내는 것 외에도 테스트에서 `async` 함수를 호출하고 싶다면 (예: 비동기 데이터베이스 함수), 심화 튜토리얼의 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 를 참조하세요. + +/// + +## 테스트 분리하기 + +실제 애플리케이션에서는 테스트를 별도의 파일로 나누는 경우가 많습니다. + + +그리고 **FastAPI** 애플리케이션도 여러 파일이나 모듈 등으로 구성될 수 있습니다. + +### **FastAPI** app 파일 + +[Bigger Applications](bigger-applications.md){.internal-link target=_blank} 에 묘사된 파일 구조를 가지고 있는 것으로 가정해봅시다. + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +`main.py` 파일 안에 **FastAPI** app 을 만들었습니다: + +{* ../../docs_src/app_testing/main.py *} + +### 테스트 파일 + +테스트를 위해 `test_main.py` 라는 파일을 생성할 수 있습니다. 이 파일은 동일한 Python 패키지(즉, `__init__.py` 파일이 있는 동일한 디렉터리)에 위치할 수 있습니다. + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +파일들이 동일한 패키지에 위치해 있으므로, 상대 참조를 사용하여 `main` 에서 `app` 객체를 임포트 해올 수 있습니다. + +{* ../../docs_src/app_testing/test_main.py hl[3] *} + + +...그리고 이전에 작성했던 것과 같은 테스트 코드를 작성할 수 있습니다. + +## 테스트: 확장된 예시 + +이제 위의 예시를 확장하고 더 많은 세부 사항을 추가하여 다양한 부분을 어떻게 테스트하는지 살펴보겠습니다. + +### 확장된 FastAPI 애플리케이션 파일 + +이전과 같은 파일 구조를 계속 사용해 보겠습니다. + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +이제 **FastAPI** 앱이 있는 `main.py` 파일에 몇 가지 다른 **경로 작업** 이 추가된 경우를 생각해봅시다. + +단일 오류를 반환할 수 있는 `GET` 작업이 있습니다. + +여러 다른 오류를 반환할 수 있는 `POST` 작업이 있습니다. + +두 *경로 작업* 모두 `X-Token` 헤더를 요구합니다. + +//// tab | Python 3.10+ + +```Python +{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python +{!> ../../docs_src/app_testing/app_b_an/main.py!} +``` + +//// + +//// tab | Python 3.10+ non-Annotated + +/// tip | 팁 + +될 수 있으면 `Annotated` 버전 사용을 권장합나다. + +/// + +```Python +{!> ../../docs_src/app_testing/app_b_py310/main.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip | 팁 + +될 수 있으면 `Annotated` 버전 사용을 권장합나다. + +/// + +```Python +{!> ../../docs_src/app_testing/app_b/main.py!} +``` + +//// + +### 확장된 테스트 파일 + +이제는 `test_main.py` 를 확장된 테스트들로 수정할 수 있습니다: + +{* ../../docs_src/app_testing/app_b/test_main.py *} + + +클라이언트가 요청에 정보를 전달해야 하는데 방법을 모르겠다면, `httpx`에서 해당 작업을 수행하는 방법을 검색(Google)하거나, `requests`에서의 방법을 검색해보세요. HTTPX는 Requests의 디자인을 기반으로 설계되었습니다. + +그 후, 테스트에서도 동일하게 적용하면 됩니다. + +예시: + +* *경로* 혹은 *쿼리* 매개변수를 전달하려면, URL 자체에 추가한다. +* JSON 본문을 전달하려면, 파이썬 객체 (예를들면 `dict`) 를 `json` 파라미터로 전달한다. +* JSON 대신 *폼 데이터* 를 보내야한다면, `data` 파라미터를 대신 전달한다. +* *헤더* 를 전달하려면, `headers` 파라미터에 `dict` 를 전달한다. +* *쿠키* 를 전달하려면, `cookies` 파라미터에 `dict` 를 전달한다. + +백엔드로 데이터를 어떻게 보내는지 정보를 더 얻으려면 (`httpx` 혹은 `TestClient` 를 이용해서) HTTPX documentation 를 확인하세요. + +/// info | 정보 + +`TestClient` 는 Pydantic 모델이 아니라 JSON 으로 변환될 수 있는 데이터를 받습니다. + +만약 테스트중 Pydantic 모델을 어플리케이션으로에 보내고 싶다면, [JSON 호환 가능 인코더](encoder.md){.internal-link target=_blank} 에 설명되어 있는 `jsonable_encoder` 를 사용할 수 있습니다. + +/// + +## 실행하기 + +테스트 코드를 작성하고, `pytest` 를 설치해야합니다. + +[virtual environment](../virtual-environments.md){.internal-link target=_blank} 를 만들고, 활성화 시킨 뒤에 설치하세요. 예시: + +
+ +```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/ko/docs/virtual-environments.md b/docs/ko/docs/virtual-environments.md new file mode 100644 index 0000000000..0d10c3200d --- /dev/null +++ b/docs/ko/docs/virtual-environments.md @@ -0,0 +1,846 @@ +# 가상 환경 + +Python 프로젝트를 작업할 때는 **가상 환경** (또는 이와 유사한 도구)을 사용하는 것이 좋습니다. 각 프로젝트 마다 설치하는 패키지를 분리하여 관리할 수 있습니다. + +/// info | 정보 + +이미 가상 환경에 대해 잘 알고 있다면, 이 섹션은 건너 뛰어도 괜찮습니다. 🤓 + +/// + +/// tip | 팁 + +**가상 환경(Virtual Environment)** 은 **환경 변수(Environment Variable)** 와 다릅니다. + +**환경 변수**는 시스템에 존재하며, 프로그램이 사용할 수 있는 변수입니다. + +**가상 환경**은 몇몇 파일로 구성된 하나의 디렉터리입니다. + +/// + +/// info | 정보 + +이 페이지에서는 **가상 환경**의 사용 방법과 작동 방식을 설명합니다. + +만약 **모든 것을 관리해주는 도구** (Python 설치까지 포함)를 사용하고 싶다면 uv를 사용해보세요. + +/// + +## 프로젝트 생성 + +먼저, 프로젝트를 위한 디렉터리를 하나 생성합니다. + +보통 사용자 홈 디렉터리 안에 `code`라는 디렉터리를 만들고, 그 안에 프로젝트마다 하나씩 디렉터리를 만들어 관리합니다. + +
+ +```console +// 홈 디렉터리로 이동 +$ cd +// 모든 코드 프로젝트를 위한 디렉터리 생성 +$ mkdir code +// code 디렉터리로 이동 +$ cd code +// 이번 프로젝트를 위한 디렉터리 생성 +$ mkdir awesome-project +// 해당 프로젝트 디렉터리로 이동 +$ cd awesome-project +``` + +
+ +## 가상 환경 생성 + +Python 프로젝트를 **처음 시작할 때**, 가상 환경을 **프로젝트 내부**에 생성합니다. + +/// tip | 팁 + +이 작업은 **프로젝트를 처음 설정할 때 한번만** 해주면 됩니다. 이후 작업할 때 반복할 필요는 없습니다. + +/// + +//// tab | `venv` + +Python 표준 라이브러리에 포함된 venv 모듈을 사용해 가상 환경을 생성할 수 있습니다. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | 명령어 상세 설명 + +* `python`: `python` 프로그램을 실행합니다. +* `-m`: 특정 모듈을 스크립트처럼 실행합니다. 대상 모듈을 바로 뒤에 지정합니다. +* `venv`: Python 표준 라이브러리에 포함된 `venv` 모듈을 실행합니다. +* `.venv`: 가상 환경을 `.venv` 디렉터리에 생성합니다. + +/// + +//// + +//// tab | `uv` + +`uv`가 설치되어 있다면, uv를 통해 가상 환경을 생성할 수 있습니다. + +
+ +```console +$ uv venv +``` + +
+ +/// tip | 팁 + +`uv`는 기본적으로 `.venv` 디렉터리에 가상 환경을 생성합니다. + +별도로 디렉터리 이름을 추가 인자로 넘겨 주면 경로를 지정 할 수 있습니다. + +/// + +//// + +해당 명령어는 `.venv` 디렉터리에 새로운 가상 환경을 생성합니다. + +/// details | `.venv` 또는 다른 이름 + +가상 환경을 다른 디렉터리에 생성할 수도 있지만, 관례적으로 `.venv` 디렉터리 이름을 사용합니다. + +/// + +## 가상 환경 활성화 + +이후 실행하는 Python 명령어와 패키지 설치가 가상 환경을 따르도록, 가상 환경을 활성화하세요. + +/// tip | 팁 + +**터미널을 새로 열고** 프로젝트 작업을 시작할 때는, **항상 이 작업을** 해주세요. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Windows에서 Bash(예: Git Bash)를 사용하는 경우: + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip | 팁 + +가상 환경에 새로운 패키지를 설치할 때마다, 해당 환경을 다시 활성화하세요. + +이렇게 하면 해당 패키지로 설치된 **터미널(CLI) 프로그램**을 사용할 때, 전역에 설치된 다른 버전이 아니라, 가상 환경 안에 설치된 정확한 버전을 사용합니다. + +/// + +## 가상 환경이 활성화 여부 확인 + +가상 환경이 활성화되었는지 확인합니다. (이전 명령어가 제대로 작동했는지 확인합니다). + +/// tip | 팁 + +이 단계는 **선택 사항**이지만, 모든 것이 예상대로 작동하고 있는지, 그리고 의도한 가상 환경이 활성화 되었는 지 **확인**하는 좋은 방법입니다. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv/bin/python` 경로로 표시된다면 성공입니다. 🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv\bin\python` 경로로 표시된다면 성공입니다. 🎉 + +//// + +## pip 업그레이드 + +/// tip | 팁 + +`uv`를 사용한다면, `pip` 대신 `uv`로 패키지를 설치하게 되므로 `pip`을 업그레이드할 필요가 없습니다. 😎 + +/// + +`pip`을 사용하여 패키지를 설치하는 경우 (Python 표준 라이브러리에 포함되어 있습니다), **최신 버전으로 업그레이드**하는 것이 좋습니다. + +패키지 설치 중 발생하는 다양하고 특이한 에러들은 `pip` 업그레이드로 쉽게 해결되는 경우가 많습니다. + +/// tip | 팁 + +이 작업은 보통 가상 환경을 생성한 **직후 한 번만** 하면 됩니다. + +/// + +가상 환경이 활성화된 상태인지 확인한 후(앞서 설명한 명령어 사용), 아래 명령어를 실행하세요: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## `.gitignore` 추가하기 + +**Git**을 사용하고 있다면 (사용하는 것이 좋습니다), `.gitignore` 파일을 추가해서 `.venv` 디렉터리 전체를 Git에서 제외하세요. + +/// tip | 팁 + +`uv`를 사용해 가상 환경을 생성했다면, 이미 이 작업이 자동으로 처리되어 있으므로 이 단계는 건너뛰어도 됩니다. 😎 + +/// + +/// tip | 팁 + +이 작업도 마찬가지로, 가상 환경을 생성한 **직후 한 번만** 하면 됩니다. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | 명령어 상세 설명 + +* `echo "*"`: 터미널에 `*` 텍스트를 "출력"합니다 (다음 설명에서 조금 바뀝니다) +* `>`: 왼쪽 명령어의 출력 내용을 터미널에 출력하지 않고, 오른쪽에 지정된 파일로 **기록(write)** 하라는 의미입니다. +* `.gitignore`: 출력된 텍스트가 기록될 파일 이름입니다. + +그리고 Git에서 `*`는 "모든 것"을 의미합니다. 따라서 `.venv` 디렉터리 안의 모든 것을 무시하게 됩니다. + +이 명령어는 다음과 같은 내용을 가진 `.gitignore` 파일을 생성합니다: + + +```gitignore +* +``` + +/// + +## 패키지 설치 + +가상 환경을 활성화한 후, 그 안에 필요한 패키지들을 설치할 수 있습니다. + +/// tip | 팁 + +프로젝트에서 필요한 패키지를 설치하거나 업그레이드할 때는 이 작업을 **한 번만** 하면 됩니다. + +만약 특정 패키지의 버전을 업그레이드하거나, 새로운 패키지를 추가할 필요가 생기면 **다시 이 작업을 반복**하면 됩니다. + +/// + +### 패키지 직접 설치 + +급하게 작업하거나, 프로젝트에 필요한 패키지 목록을 따로 파일로 관리하고 싶지 않은 경우, 패키지를 직접 설치할 수도 있습니다. + +/// tip | 팁 + +패키지 이름과 버전 정보를 파일에 정리해두는 것(예: `requirements.txt` 또는 `pyproject.toml`)은 (매우) 좋은 생각입니다. + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +`uv`를 사용하는 경우: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### `requirements.txt`에서 설치 + +`requirements.txt` 파일이 있다면, 그 안에 명시된 패키지들을 한 번에 설치할 수 있습니다. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +`uv`를 사용하는 경우: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +다음은 몇 가지 패키지를 포함한 `requirements.txt`의 예시입니다: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## 프로그램 실행 + +가상 환경을 활성화한 후에는 프로그램을 실행할 수 있습니다. 이때 해당 가상 환경에 설치된 Python과 패키지들이 사용됩니다. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## 에디터 설정 + +에디터를 사용할 경우, 앞서 만든 가상 환경을 사용하도록 설정하는 것이 좋습니다. (대부분의 에디터는 자동으로 감지하기도 합니다.) +이렇게 하면 자동 완성 기능이나 코드 내 오류 표시 기능을 제대로 사용할 수 있습니다. + +예시: + +* VS Code +* PyCharm + +/// tip | 팁 + +이 설정은 보통 가상 환경을 **처음 만들었을 때 한 번만** 해주면 됩니다. + +/// + +## 가상 환경 비활성화 + +프로젝트 작업이 끝났다면, 가상 환경을 **비활성화**할 수 있습니다. + +
+ +```console +$ deactivate +``` + +
+ +이렇게 하면 이후에 `python` 명령어를 실행했을 때, 가상 환경의 Python이나 그 안에 설치된 패키지들을 사용하지 않게 됩니다. + +## 이제 작업할 준비가 되었습니다 + +이제 프로젝트 작업을 시작할 준비가 완료되었습니다. + + +/// tip | 팁 + +위 내용을 더 깊이 이해하고 싶으신가요? + +그렇다면 계속 읽어 주세요. 👇🤓 + +/// + +## 가상 환경을 왜 사용하는가 + +FastAPI를 사용하려면 먼저 Python을 설치해야 합니다. + +그 후에는 FastAPI와 함께 사용할 **기타 패키지들**을 **설치**해야 합니다. + +패키지를 설치할 때 보통 Python에 기본 포함된 `pip` 명령어(또는 유사한 도구)를 사용합니다. + +하지만 `pip`을 그냥 직접 사용하면, 해당 패키지들은 **전역 Python 환경**(시스템 전체에 설치된 Python)에 설치됩니다. + +### 문제점 + +그렇다면, 전역 Python 환경에 패키지를 설치하면 어떤 문제가 발생할까요? + +어느 시점이 되면, **서로 다른 패키지들**에 의존하는 여러 개의 프로그램을 작성하게 될 것입니다. 그리고 이들 중 일부는 **같은 패키지의 서로 다른 버전**을 필요로 할 수 있습니다. 😱 + +예를 들어, `마법사의 돌(philosophers-stone)` 프로젝트를 만들었다고 가정해봅시다. 이 프로그램은 `해리 포터(harry)`라는 패키지의 `v1` 버전을 **의존**합니다. 따라서 `harry`를 설치해야 합니다. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +그런데 나중에 `아즈카반의 죄수(prisoner-of-azkaban)`이라는 또 다른 프로젝트를 만들게 되었고, 이 프로젝트도 역시 `harry` 패키지를 사용합니다. 그런데 이 프로젝트는 `harry`의 `v3` 버전이 필요합니다. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +하지만 이제 문제가 생깁니다. 로컬 가상 환경 대신에 전역 환경에 패키지를 설치하게 되면, 어떤 버전의 `harry`를 설치할지를 선택해야 하기 때문입니다. + +예를 들어, `마법사의 돌(philosophers-stone)`을 실행하고 싶다면 먼저 `harry` `v1` 버전을 다음과 같이 설치 해야 합니다: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +그러면 결국 전역 Python 환경에는 `harry` `v1`버전이 설치된 상태가 됩니다. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +하지만 이제 `아즈카반의 죄수(prisoner-of-azkaban)`을 실행하고 싶다면, `harry` `v1`버전을 제거하고 `harry` `v3`버전을 설치해야 합니다. (또는 단순히 `v3`버전을 설치하는 것만으로도 기존의 `v1`버전이 자동으로 제거됩니다.) + +
+ +```console +$ pip install "harry==3" +``` + +
+ +그렇게 하면 이제 전역 Python 환경에는 `harry` `v3`버전이 설치된 상태가 됩니다. + +그리고 다시 `마법사의 돌(philosophers-stone)`을 실행하려고 하면, **작동하지** 않을 수 있습니다. 왜냐하면 이 프로그램은 `harry` `v1`버전을 필요로 하기 때문입니다. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip | 팁 + +Python 패키지들은 **새 버전**에서 **호환성 문제(breaking changes)**가 발생하지 않도록 최대한 노력하는 것이 일반적입니다. 하지만 그래도 안전하게 작업하려면, 테스트를 실행해보면서 새 버전을 의도적으로 설치하는 것이 좋습니다. + +/// + +이제, 이런 일이 여러분의 **모든 프로젝트**가 사용하는 **수많은 패키지들**에서 동시에 발생한다고 상상해보세요. 이는 매우 관리하기 어려우며, 결국 **서로 호환되지 않는 버전**의 패키지로 프로젝트를 실행하게 될 가능성이 높고, 그로 인해 어떤 문제가 왜 발생하는지 알 수 없게 될 수 있습니다. + +또한 사용하는 운영체제(Linux, Windows, macOS 등)에 따라 Python이 **미리 설치되어 있을 수도** 있습니다. 이런 경우에는 운영체제의 동작에 필요한 특정 버전의 패키지들이 함께 설치되어 있을 수 있습니다. 이 상태에서 전역 Python 환경에 임의의 패키지를 설치하면, 운영체제에 포함된 프로그램 일부가 **깨질 위험**도 있습니다. + +## 패키지들은 어디에 설치되는가 + +Python을 설치하면, 컴퓨터에 여러 디렉터리와 파일들이 생성됩니다. + +이 중 일부 디렉터리는 사용자가 설치한 패키지들을 보관하는 역할을 합니다. + +예를 들어, 아래 명령어를 실행하면: + +
+ +```console +// 지금 실행하지 않아도 됩니다, 그냥 예제일 뿐이에요 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +해당 명령어는 FastAPI 코드를 포함한 압축 파일을 다운로드합니다. 이 파일은 보통 PyPI에서 받아옵니다. + +또한 FastAPI가 의존하는 다른 패키지들도 함께 **다운로드**됩니다. + +그리고 그 모든 파일들을 **압축 해제**한 뒤, 컴퓨터의 특정 디렉터리에 저장합니다. + +기본적으로 이 파일들은 Python이 설치된 디렉터리 안, 즉 **전역 환경**에 내의 디렉터리에 저장됩니다. + +## 가상 환경이란 + +전역 환경에 모든 패키지를 설치하면서 발생하는 문제에 대한 해결책은, 작업하는 **각 프로젝트마다 가상 환경**을 사용하는 것입니다. + +가상 환경은 전역 환경과 매우 유사한 하나의 **디렉터리**이며, 그 안에 해당 프로젝트를 위한 패키지들을 설치할 수 있습니다. + +이렇게 하면 각 프로젝트는 자체적인 가상 환경(`.venv` 디렉터리)을 가지게 되며, 그 안에 해당 프로젝트 전용 패키지들을 보유하게 됩니다. + + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## 가상 환경 활성화 의미 + +가상 환경을 활성화한다는 것은, 예를 들어 다음과 같은 명령어를 실행하는 것을 의미합니다: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Windows에서 Bash(예: Git Bash)를 사용하는 경우: + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +이 명령어는 이후에 실행될 명령어에서 사용될 [환경 변수](environment-variables.md){.internal-link target=_blank} 몇 개를 생성하거나 수정합니다. + +이 변수들 중 하나가 바로 `PATH` 변수입니다. + +/// tip | 팁 + +`PATH` 환경 변수에 대해 더 알고 싶다면 [환경 변수 문서의 PATH 환경 변수 섹션](environment-variables.md#path-environment-variable){.internal-link target=_blank}을 참고하세요. + +/// + +가상 환경을 활성화하면, 가상 환경의 경로인 `.venv/bin` (Linux와 macOS) 또는 `.venv\Scripts`(Windows)를 `PATH` 환경 변수에 추가됩니다. + +예를 들어, 가상 환경을 활성화하기 전의 `PATH` 변수는 다음과 같았다고 가정해봅시다: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +시스템은 다음 경로들에서 프로그램을 찾게 됩니다: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +시스템은 다음 경로들에서 프로그램을 찾게 됩니다: + +* `C:\Windows\System32` + +//// + +가상 환경을 활성화한 후에는, `PATH` 변수는 다음과 같은 형태가 됩니다: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +그 후에 다른 디렉터리들을 탐색합니다. + +따라서 터미널에 `python`을 입력하면, 시스템은 다음 위치에 있는 Python 프로그램을 찾게 됩니다: + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +그리고 해당 Python을 사용하게 됩니다. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +그 후에 다른 디렉터리들을 탐색합니다. + +따라서 터미널에 `python`을 입력하면, 시스템은 다음 경로에 있는 Python 프로그램을 찾게 됩니다: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +그리고 해당 Python을 사용하게 됩니다. + +//// + +중요한 세부 사항 중 하나는, 가상 환경의 경로가 `PATH` 변수의 가장 **앞**에 추가된다는 점입니다. 시스템은 사용 가능한 다른 Python들보다 **먼저** 이 경로를 찾습니다. 그래서 터미널에서 `python`을 실행하면, 전역 환경의 Python이 아닌 **가상 환경에 있는** Python이 사용됩니다. (예: 전역 환경에 설치된 `python`이 있더라도 그보다 우선합니다.) + +가상 환경을 활성화하면 이 외에도 몇 가지 다른 것들이 변경되지만, 이는 그중에서도 가장 중요한 변화 중 하나입니다. + +## 가상 환경 확인하기 + +가상 환경이 활성화 되었는지 확인하려면, 아래 명령어를 사용할 수 있습니다: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +즉, 현재 사용되는 `python` 프로그램은 **가상 환경 내부에 있는 것**입니다. + +Linux와 macOS에서는 `which`, Windows PowerShell에서는 `Get-Command` 명령어를 사용합니다. + +이 명령어는 `PATH` 환경 변수에 지정된 경로들을 **순서대로 탐색**하면서 `python`이라는 이름의 프로그램을 찾습니다. +찾는 즉시, 해당 프로그램의 **경로를 출력**합니다. + +중요한 점은 터미널에서 `python`을 실행했을 때, 실제로 실행되는 "`python`"이 어떤 것인지 정확히 알 수 있다는 것입니다. + +따라서 현재 올바른 가상 환경에 있는지 확인할 수 있습니다. + +/// tip | 팁 + +하나의 가상 환경을 활성화한 뒤, 해당 Python을 가진 상태에서 **또 다른 프로젝트**로 이동하는 것은 흔히 발생합니다. + +하지만 이때 이전 프로젝트의 가상 환경에 있는 **잘못된 Python 실행 파일**을 사용하게 되어 새 프로젝트가 **정상 작동하지 않을 수 있습니다.** + +그래서 현재 어떤 `python`이 사용되고 있는지 확인할 수 있는 능력은 매우 유용합니다. 🤓 + +/// + +## 가상 환경을 비활성화하는 이유 + +예를 들어 `마법사의 돌(philosophers-stone)`이라는 프로젝트에서 작업 중이라고 해보겠습니다. 이때 해당 **가상 환경을 활성화**하고, 필요한 패키지를 설치하며 작업을 진행합니다. + +그런데 이제는 **다른 프로젝트**인 `아즈카반의 죄수(prisoner-of-azkaban)`을 작업하고 싶어졌습니다. + +그래서 그 프로젝트 디렉터리로 이동합니다: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +만약 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화하지 않았다면, 터미널에서 `python`을 실행할 때 여전히 `마법사의 돌(philosophers-stone)` 가상 환경의 Python을 사용하게 됩니다. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// sirius를 임포트하는 데 실패했습니다. 설치되어 있지 않아요 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +하지만 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화한 다음, `아즈카반의 죄수(prisoner-of-azkaban)` 프로젝트의 가상 환경을 활성화하면, 이제 `python` 명령어는 `아즈카반의 죄수(prisoner-of-azkaban)` 가상 환경의 Python을 사용하게 됩니다. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// 이전 디렉터리에 있을 필요 없이, 어디서든 가상 환경을 비활성화할 수 있습니다. 다른 프로젝트 디렉터리로 이동한 후에도 괜찮아요 😎 +$ deactivate + +// prisoner-of-azkaban/.venv 가상 환경을 활성화합니다 🚀 +$ source .venv/bin/activate + +// 이제 python을 실행하면, 이 가상 환경에 설치된 sirius 패키지를 찾게 됩니다 ✨ +$ python main.py + +못된 짓을 꾸미고 있음을 엄숙히 맹세합니다.🧙 +ImportError는 이제 없습니다. 🐺 +``` + +
+ +## 대안들 + +이 문서는 여러분이 Python 프로젝트를 시작하고, **그 내부에서** 어떻게 돌아가는지 알려주는 간단한 가이드입니다. + +가상 환경, 패키지 의존성(Requirements), 프로젝트를 관리하는 방법에는 이 외에도 다양한 **대안**들이 존재합니다. + +만약 준비가 되었다면, **프로젝트 전체**, 패키지 의존성, 가상 환경 등을 통합적으로 **관리**할 수 있는 도구를 써보는 것도 좋습니다. 그럴 때 추천하는 도구가 바로 uv입니다. + +`uv`는 다양한 기능을 지원합니다: + +* 다양한 버전의 **Python 설치** +* 각 프로젝트 별 **가상 환경 관리** +* **패키지 설치** +* 프로젝트의 **의존성과 버전** 관리 +* 설치된 패키지들과 그 버전을 **정확히 고정(lock)**해서,개발 환경과 운영 환경이 완전히 동일하게 작동할 수 있도록 보장 +* 이 외에도 다양한 기능을 지원 + +## 결론 + +여기까지 모두 읽고 이해했다면, 이제 많은 개발자들보다 가상 환경을 **훨씬 더 깊이 있게 이해**하게 되셨습니다. 🤓 + +이런 세부적인 내용을 알고 있으면, 언젠가 복잡해 보이는 문제를 디버깅할 때 분명히 큰 도움이 될 것입니다. 이제는 **이 모든 것들이 내부에서 어떻게 작동하는지** 알고 있기 때문입니다. 😎 diff --git a/docs/pl/docs/features.md b/docs/pl/docs/features.md deleted file mode 100644 index 80d3bdece8..0000000000 --- a/docs/pl/docs/features.md +++ /dev/null @@ -1,201 +0,0 @@ -# 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.8** (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. -* 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/help-fastapi.md b/docs/pl/docs/help-fastapi.md deleted file mode 100644 index 4daad5e903..0000000000 --- a/docs/pl/docs/help-fastapi.md +++ /dev/null @@ -1,269 +0,0 @@ -# Pomóż FastAPI - Uzyskaj pomoc - -Czy podoba Ci się **FastAPI**? - -Czy chciałbyś pomóc FastAPI, jego użytkownikom i autorowi? - -Może napotkałeś na trudności z **FastAPI** i potrzebujesz pomocy? - -Istnieje kilka bardzo łatwych sposobów, aby pomóc (czasami wystarczy jedno lub dwa kliknięcia). - -Istnieje również kilka sposobów uzyskania pomocy. - -## Zapisz się do newslettera - -Możesz zapisać się do rzadkiego [newslettera o **FastAPI i jego przyjaciołach**](newsletter.md){.internal-link target=_blank}, aby być na bieżąco z: - -* Aktualnościami o FastAPI i przyjaciołach 🚀 -* Przewodnikami 📝 -* Funkcjami ✨ -* Przełomowymi zmianami 🚨 -* Poradami i sztuczkami ✅ - -## Śledź FastAPI na Twitterze - -Śledź @fastapi na **Twitterze** aby być na bieżąco z najnowszymi wiadomościami o **FastAPI**. 🐦 - -## Dodaj gwiazdkę **FastAPI** na GitHubie - -Możesz "dodać gwiazdkę" FastAPI na GitHubie (klikając przycisk gwiazdki w prawym górnym rogu): https://github.com/fastapi/fastapi. ⭐️ - -Dodając gwiazdkę, inni użytkownicy będą mogli łatwiej znaleźć projekt i zobaczyć, że był już przydatny dla innych. - -## Obserwuj repozytorium GitHub w poszukiwaniu nowych wydań - -Możesz "obserwować" FastAPI na GitHubie (klikając przycisk "obserwuj" w prawym górnym rogu): https://github.com/fastapi/fastapi. 👀 - -Wybierz opcję "Tylko wydania". - -Dzięki temu będziesz otrzymywać powiadomienia (na swój adres e-mail) za każdym razem, gdy pojawi się nowe wydanie (nowa wersja) **FastAPI** z poprawkami błędów i nowymi funkcjami. - -## Skontaktuj się z autorem - -Możesz skontaktować się ze mną (Sebastián Ramírez / `tiangolo`), autorem. - -Możesz: - -* Śledzić mnie na **GitHubie**. - * Zobacz inne projekty open source, które stworzyłem, a mogą być dla Ciebie pomocne. - * Śledź mnie, aby dostać powiadomienie, gdy utworzę nowy projekt open source. -* Śledzić mnie na **Twitterze** lub na Mastodonie. - * Napisz mi, w jaki sposób korzystasz z FastAPI (uwielbiam o tym czytać). - * Dowiedz się, gdy ogłoszę coś nowego lub wypuszczę nowe narzędzia. - * Możesz także śledzić @fastapi na Twitterze (to oddzielne konto). -* Nawiąż ze mną kontakt na **Linkedinie**. - * Dowiedz się, gdy ogłoszę coś nowego lub wypuszczę nowe narzędzia (chociaż częściej korzystam z Twittera 🤷‍♂). -* Czytaj moje posty (lub śledź mnie) na **Dev.to** lub na **Medium**. - * Czytaj o innych pomysłach, artykułach i dowiedz się o narzędziach, które stworzyłem. - * Śledź mnie, by wiedzieć gdy opublikuję coś nowego. - -## Napisz tweeta o **FastAPI** - -Napisz tweeta o **FastAPI** i powiedz czemu Ci się podoba. 🎉 - -Uwielbiam czytać w jaki sposób **FastAPI** jest używane, co Ci się w nim podobało, w jakim projekcie/firmie go używasz itp. - -## Głosuj na FastAPI - -* Głosuj na **FastAPI** w Slant. -* Głosuj na **FastAPI** w AlternativeTo. -* Powiedz, że używasz **FastAPI** na StackShare. - -## Pomagaj innym, odpowiadając na ich pytania na GitHubie - -Możesz spróbować pomóc innym, odpowiadając w: - -* Dyskusjach na GitHubie -* Problemach na GitHubie - -W wielu przypadkach możesz już znać odpowiedź na te pytania. 🤓 - -Jeśli pomożesz wielu ludziom, możesz zostać oficjalnym [Ekspertem FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉 - -Pamiętaj tylko o najważniejszym: bądź życzliwy. Ludzie przychodzą sfrustrowani i w wielu przypadkach nie zadają pytań w najlepszy sposób, ale mimo to postaraj się być dla nich jak najbardziej życzliwy. 🤗 - -Chciałbym, by społeczność **FastAPI** była życzliwa i przyjazna. Nie akceptuj prześladowania ani braku szacunku wobec innych. Dbajmy o siebie nawzajem. - ---- - -Oto, jak pomóc innym z pytaniami (w dyskusjach lub problemach): - -### Zrozum pytanie - -* Upewnij się, czy rozumiesz **cel** i przypadek użycia osoby pytającej. - -* Następnie sprawdź, czy pytanie (większość to pytania) jest **jasne**. - -* W wielu przypadkach zadane pytanie dotyczy rozwiązania wymyślonego przez użytkownika, ale może istnieć **lepsze** rozwiązanie. Jeśli dokładnie zrozumiesz problem i przypadek użycia, być może będziesz mógł zaproponować lepsze **alternatywne rozwiązanie**. - -* Jeśli nie rozumiesz pytania, poproś o więcej **szczegółów**. - -### Odtwórz problem - -W większości przypadków problem wynika z **autorskiego kodu** osoby pytającej. - -Często pytający umieszczają tylko fragment kodu, niewystarczający do **odtworzenia problemu**. - -* Możesz poprosić ich o dostarczenie minimalnego, odtwarzalnego przykładu, który możesz **skopiować i wkleić** i uruchomić lokalnie, aby zobaczyć ten sam błąd lub zachowanie, które widzą, lub lepiej zrozumieć ich przypadki użycia. - -* Jeśli jesteś wyjątkowo pomocny, możesz spróbować **stworzyć taki przykład** samodzielnie, opierając się tylko na opisie problemu. Miej na uwadze, że może to zająć dużo czasu i lepiej może być najpierw poprosić ich o wyjaśnienie problemu. - -### Proponuj rozwiązania - -* Po zrozumieniu pytania możesz podać im możliwą **odpowiedź**. - -* W wielu przypadkach lepiej zrozumieć ich **podstawowy problem lub przypadek użycia**, ponieważ może istnieć lepszy sposób rozwiązania niż to, co próbują zrobić. - -### Poproś o zamknięcie - -Jeśli odpowiedzą, jest duża szansa, że rozwiązałeś ich problem, gratulacje, **jesteś bohaterem**! 🦸 - -* Jeśli Twoja odpowiedź rozwiązała problem, możesz poprosić o: - - * W Dyskusjach na GitHubie: oznaczenie komentarza jako **odpowiedź**. - * W Problemach na GitHubie: **zamknięcie** problemu. - -## Obserwuj repozytorium na GitHubie - -Możesz "obserwować" FastAPI na GitHubie (klikając przycisk "obserwuj" w prawym górnym rogu): https://github.com/fastapi/fastapi. 👀 - -Jeśli wybierzesz "Obserwuj" zamiast "Tylko wydania", otrzymasz powiadomienia, gdy ktoś utworzy nowy problem lub pytanie. Możesz również określić, że chcesz być powiadamiany tylko o nowych problemach, dyskusjach, PR-ach itp. - -Następnie możesz spróbować pomóc rozwiązać te problemy. - -## Zadawaj pytania - -Możesz utworzyć nowe pytanie w repozytorium na GitHubie, na przykład aby: - -* Zadać **pytanie** lub zapytać o **problem**. -* Zaproponować nową **funkcję**. - -**Uwaga**: jeśli to zrobisz, poproszę Cię również o pomoc innym. 😉 - -## Przeglądaj Pull Requesty - -Możesz pomóc mi w przeglądaniu pull requestów autorstwa innych osób. - -Jak wcześniej wspomniałem, postaraj się być jak najbardziej życzliwy. 🤗 - ---- - -Oto, co warto mieć na uwadze podczas oceny pull requestu: - -### Zrozum problem - -* Najpierw upewnij się, że **rozumiesz problem**, który próbuje rozwiązać pull request. Może być osadzony w większym kontekście w GitHubowej dyskusji lub problemie. - -* Jest też duża szansa, że pull request nie jest konieczny, ponieważ problem można rozwiązać w **inny sposób**. Wtedy możesz to zasugerować lub o to zapytać. - -### Nie martw się stylem - -* Nie przejmuj się zbytnio rzeczami takimi jak style wiadomości commitów, przy wcielaniu pull requesta łączę commity i modyfikuję opis sumarycznego commita ręcznie. - -* Nie przejmuj się również stylem kodu, automatyczne narzędzia w repozytorium sprawdzają to samodzielnie. - -A jeśli istnieje jakaś konkretna potrzeba dotycząca stylu lub spójności, sam poproszę o zmiany lub dodam commity z takimi zmianami. - -### Sprawdź kod - -* Przeczytaj kod, zastanów się czy ma sens, **uruchom go lokalnie** i potwierdź czy faktycznie rozwiązuje problem. - -* Następnie dodaj **komentarz** z informacją o tym, że sprawdziłeś kod, dzięki temu będę miał pewność, że faktycznie go sprawdziłeś. - -/// info - -Niestety, nie mogę ślepo ufać PR-om, nawet jeśli mają kilka zatwierdzeń. - -Kilka razy zdarzyło się, że PR-y miały 3, 5 lub więcej zatwierdzeń (prawdopodobnie dlatego, że opis obiecuje rozwiązanie ważnego problemu), ale gdy sam sprawdziłem danego PR-a, okazał się być zbugowany lub nie rozwiązywał problemu, który rzekomo miał rozwiązywać. 😅 - -Dlatego tak ważne jest, abyś faktycznie przeczytał i uruchomił kod oraz napisał w komentarzu, że to zrobiłeś. 🤓 - -/// - -* Jeśli PR można uprościć w jakiś sposób, możesz o to poprosić, ale nie ma potrzeby być zbyt wybrednym, może być wiele subiektywnych punktów widzenia (a ja też będę miał swój 🙈), więc lepiej żebyś skupił się na kluczowych rzeczach. - -### Testy - -* Pomóż mi sprawdzić, czy PR ma **testy**. - -* Sprawdź, czy testy **nie przechodzą** przed PR. 🚨 - -* Następnie sprawdź, czy testy **przechodzą** po PR. ✅ - -* Wiele PR-ów nie ma testów, możesz **przypomnieć** im o dodaniu testów, a nawet **zaproponować** samemu jakieś testy. To jedna z rzeczy, które pochłaniają najwięcej czasu i możesz w tym bardzo pomóc. - -* Następnie skomentuj również to, czego spróbowałeś, wtedy będę wiedział, że to sprawdziłeś. 🤓 - -## Utwórz Pull Request - -Możesz [wnieść wkład](contributing.md){.internal-link target=_blank} do kodu źródłowego za pomocą Pull Requestu, na przykład: - -* Naprawić literówkę, którą znalazłeś w dokumentacji. -* Podzielić się artykułem, filmem lub podcastem, który stworzyłeś lub znalazłeś na temat FastAPI, edytując ten plik. - * Upewnij się, że dodajesz swój link na początku odpowiedniej sekcji. -* Pomóc w [tłumaczeniu dokumentacji](contributing.md#translations){.internal-link target=_blank} na Twój język. - * Możesz również pomóc w weryfikacji tłumaczeń stworzonych przez innych. -* Zaproponować nowe sekcje dokumentacji. -* Naprawić istniejący problem/błąd. - * Upewnij się, że dodajesz testy. -* Dodać nową funkcję. - * Upewnij się, że dodajesz testy. - * Upewnij się, że dodajesz dokumentację, jeśli jest to istotne. - -## Pomóż w utrzymaniu FastAPI - -Pomóż mi utrzymać **FastAPI**! 🤓 - -Jest wiele pracy do zrobienia, a w większości przypadków **TY** możesz to zrobić. - -Główne zadania, które możesz wykonać teraz to: - -* [Pomóc innym z pytaniami na GitHubie](#pomagaj-innym-odpowiadajac-na-ich-pytania-na-githubie){.internal-link target=_blank} (zobacz sekcję powyżej). -* [Oceniać Pull Requesty](#przegladaj-pull-requesty){.internal-link target=_blank} (zobacz sekcję powyżej). - -Te dwie czynności **zajmują najwięcej czasu**. To główna praca związana z utrzymaniem FastAPI. - -Jeśli możesz mi w tym pomóc, **pomożesz mi utrzymać FastAPI** i zapewnisz że będzie **rozwijać się szybciej i lepiej**. 🚀 - -## Dołącz do czatu - -Dołącz do 👥 serwera czatu na Discordzie 👥 i spędzaj czas z innymi w społeczności FastAPI. - -/// tip | "Wskazówka" - -Jeśli masz pytania, zadaj je w Dyskusjach na GitHubie, jest dużo większa szansa, że otrzymasz pomoc od [Ekspertów FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. - -Używaj czatu tylko do innych ogólnych rozmów. - -/// - -### Nie zadawaj pytań na czacie - -Miej na uwadze, że ponieważ czaty pozwalają na bardziej "swobodną rozmowę", łatwo jest zadawać pytania, które są zbyt ogólne i trudniejsze do odpowiedzi, więc możesz nie otrzymać odpowiedzi. - -Na GitHubie szablon poprowadzi Cię do napisania odpowiedniego pytania, dzięki czemu łatwiej uzyskasz dobrą odpowiedź, a nawet rozwiążesz problem samodzielnie, zanim zapytasz. Ponadto na GitHubie mogę się upewnić, że zawsze odpowiadam na wszystko, nawet jeśli zajmuje to trochę czasu. Osobiście nie mogę tego zrobić z systemami czatu. 😅 - -Rozmów w systemach czatu nie można tak łatwo przeszukiwać, jak na GitHubie, więc pytania i odpowiedzi mogą zaginąć w rozmowie. A tylko te na GitHubie liczą się do zostania [Ekspertem FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, więc najprawdopodobniej otrzymasz więcej uwagi na GitHubie. - -Z drugiej strony w systemach czatu są tysiące użytkowników, więc jest duża szansa, że znajdziesz tam kogoś do rozmowy, prawie w każdej chwili. 😄 - -## Wspieraj autora - -Możesz również finansowo wesprzeć autora (mnie) poprzez sponsoring na GitHubie. - -Tam możesz postawić mi kawę ☕️ aby podziękować. 😄 - -Możesz także zostać srebrnym lub złotym sponsorem FastAPI. 🏅🎉 - -## Wspieraj narzędzia, które napędzają FastAPI - -Jak widziałeś w dokumentacji, FastAPI stoi na ramionach gigantów, Starlette i Pydantic. - -Możesz również wesprzeć: - -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) - ---- - -Dziękuję! 🚀 diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md deleted file mode 100644 index e591e1c9d2..0000000000 --- a/docs/pl/docs/index.md +++ /dev/null @@ -1,464 +0,0 @@ -# FastAPI - - - -

- FastAPI -

-

- FastAPI to szybki, prosty w nauce i gotowy do użycia w produkcji framework -

-

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

- ---- - -**Dokumentacja**: https://fastapi.tiangolo.com - -**Kod żródłowy**: https://github.com/fastapi/fastapi - ---- - -FastAPI to nowoczesny, wydajny framework webowy do budowania API z użyciem Pythona bazujący na standardowym typowaniu Pythona. - -Kluczowe cechy: - -* **Wydajność**: FastAPI jest bardzo wydajny, na równi z **NodeJS** oraz **Go** (dzięki Starlette i Pydantic). [Jeden z najszybszych dostępnych frameworków Pythonowych](#wydajnosc). -* **Szybkość kodowania**: Przyśpiesza szybkość pisania nowych funkcjonalności o około 200% do 300%. * -* **Mniejsza ilość błędów**: Zmniejsza ilość ludzkich (dewelopera) błędy o około 40%. * -* **Intuicyjność**: Wspaniałe wsparcie dla edytorów kodu. Dostępne wszędzie automatyczne uzupełnianie kodu. Krótszy czas debugowania. -* **Łatwość**: Zaprojektowany by być prosty i łatwy do nauczenia. Mniej czasu spędzonego na czytanie dokumentacji. -* **Kompaktowość**: Minimalizacja powtarzającego się kodu. Wiele funkcjonalności dla każdej deklaracji parametru. Mniej błędów. -* **Solidność**: Kod gotowy dla środowiska produkcyjnego. Wraz z automatyczną interaktywną dokumentacją. -* **Bazujący na standardach**: Oparty na (i w pełni kompatybilny z) otwartych standardach API: OpenAPI (wcześniej znane jako Swagger) oraz JSON Schema. - -* oszacowania bazowane na testach wykonanych przez wewnętrzny zespół deweloperów, budujących aplikacie używane na środowisku produkcyjnym. - -## Sponsorzy - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Inni sponsorzy - -## Opinie - -"_[...] 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 aplikacji konsolowych - - - -Jeżeli tworzysz aplikacje CLI, która ma być używana w terminalu zamiast API, sprawdź **Typer**. - -**Typer** to młodsze rodzeństwo FastAPI. Jego celem jest pozostanie **FastAPI aplikacji konsolowych** . ⌨️ 🚀 - -## Wymagania - -FastAPI oparty jest na: - -* Starlette dla części webowej. -* Pydantic dla części obsługujących dane. - -## Instalacja - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -Na serwerze produkcyjnym będziesz także potrzebował serwera ASGI, np. Uvicorn lub Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Przykład - -### Stwórz - -* Utwórz plik o nazwie `main.py` z: - -```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} -``` - -
-Albo użyj async def... - -Jeżeli twój kod korzysta z `async` / `await`, użyj `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} -``` - -**Przypis**: - -Jeżeli nie znasz, sprawdź sekcję _"In a hurry?"_ o `async` i `await` w dokumentacji. - -
- -### Uruchom - -Uruchom serwer używając: - -
- -```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. -``` - -
- -
-O komendzie uvicorn main:app --reload... -Komenda `uvicorn main:app` odnosi się do: - -* `main`: plik `main.py` ("moduł" w Pythonie). -* `app`: obiekt stworzony w `main.py` w lini `app = FastAPI()`. -* `--reload`: spraw by serwer resetował się po każdej zmianie w kodzie. Używaj tego tylko w środowisku deweloperskim. - -
- -### Wypróbuj - -Otwórz link http://127.0.0.1:8000/items/5?q=somequery w przeglądarce. - -Zobaczysz następującą odpowiedź JSON: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -Właśnie stworzyłeś API które: - -* Otrzymuje żądania HTTP w _ścieżce_ `/` i `/items/{item_id}`. -* Obie _ścieżki_ używają operacji `GET` (znane także jako _metody_ HTTP). -* _Ścieżka_ `/items/{item_id}` ma _parametr ścieżki_ `item_id` który powinien być obiektem typu `int`. -* _Ścieżka_ `/items/{item_id}` ma opcjonalny _parametr zapytania_ typu `str` o nazwie `q`. - -### Interaktywna dokumentacja API - -Otwórz teraz stronę http://127.0.0.1:8000/docs. - -Zobaczysz automatyczną interaktywną dokumentację API (dostarczoną z pomocą Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternatywna dokumentacja API - -Otwórz teraz http://127.0.0.1:8000/redoc. - -Zobaczysz alternatywną, lecz wciąż automatyczną dokumentację (wygenerowaną z pomocą ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Aktualizacja przykładu - -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 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} -``` - -Serwer powinien przeładować się automatycznie (ponieważ dodałeś `--reload` do komendy `uvicorn` powyżej). - -### Zaktualizowana interaktywna dokumentacja API - -Wejdź teraz na http://127.0.0.1:8000/docs. - -* Interaktywna dokumentacja API zaktualizuje sie automatycznie, także z nową treścią żądania (body): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Kliknij przycisk "Try it out" (wypróbuj), pozwoli Ci to wypełnić parametry i bezpośrednio użyć API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Kliknij potem przycisk "Execute" (wykonaj), interfejs użytkownika połączy się z API, wyśle parametry, otrzyma odpowiedź i wyświetli ją na ekranie: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Zaktualizowana alternatywna dokumentacja API - -Otwórz teraz http://127.0.0.1:8000/redoc. - -* Alternatywna dokumentacja również pokaże zaktualizowane parametry i treść żądania (body): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Podsumowanie - -Podsumowując, musiałeś zadeklarować typy parametrów, treści żądania (body) itp. tylko **raz**, i są one dostępne jako parametry funkcji. - -Robisz to tak samo jak ze standardowymi typami w Pythonie. - -Nie musisz sie uczyć żadnej nowej składni, metod lub klas ze specyficznych bibliotek itp. - -Po prostu standardowy **Python**. - -Na przykład, dla danych typu `int`: - -```Python -item_id: int -``` - -albo dla bardziej złożonego obiektu `Item`: - -```Python -item: Item -``` - -...i z pojedyńczą deklaracją otrzymujesz: - -* Wsparcie edytorów kodu, wliczając: - * Auto-uzupełnianie. - * Sprawdzanie typów. -* Walidacja danych: - * Automatyczne i przejrzyste błędy gdy dane są niepoprawne. - * Walidacja nawet dla głęboko zagnieżdżonych obiektów JSON. -* Konwersja danych wejściowych: przychodzących z sieci na Pythonowe typy. Pozwala na przetwarzanie danych: - * JSON. - * Parametrów ścieżki. - * Parametrów zapytania. - * Dane cookies. - * Dane nagłówków (headers). - * Formularze. - * Pliki. -* Konwersja danych wyjściowych: wychodzących z Pythona do sieci (jako JSON): - * Przetwarzanie Pythonowych typów (`str`, `int`, `float`, `bool`, `list`, itp). - * Obiekty `datetime`. - * Obiekty `UUID`. - * Modele baz danych. - * ...i wiele więcej. -* Automatyczne interaktywne dokumentacje API, wliczając 2 alternatywne interfejsy użytkownika: - * Swagger UI. - * ReDoc. - ---- - -Wracając do poprzedniego przykładu, **FastAPI** : - -* Potwierdzi, że w ścieżce jest `item_id` dla żądań `GET` i `PUT`. -* Potwierdzi, że `item_id` jest typu `int` dla żądań `GET` i `PUT`. - * Jeżeli nie jest, odbiorca zobaczy przydatną, przejrzystą wiadomość z błędem. -* Sprawdzi czy w ścieżce jest opcjonalny parametr zapytania `q` (np. `http://127.0.0.1:8000/items/foo?q=somequery`) dla żądania `GET`. - * Jako że parametr `q` jest zadeklarowany jako `= None`, jest on opcjonalny. - * Gdyby tego `None` nie było, parametr ten byłby wymagany (tak jak treść żądania w żądaniu `PUT`). -* Dla żądania `PUT` z ścieżką `/items/{item_id}`, odczyta treść żądania jako JSON: - * Sprawdzi czy posiada wymagany atrybut `name`, który powinien być typu `str`. - * Sprawdzi czy posiada wymagany atrybut `price`, który musi być typu `float`. - * Sprawdzi czy posiada opcjonalny atrybut `is_offer`, który (jeżeli obecny) powinien być typu `bool`. - * To wszystko będzie również działać dla głęboko zagnieżdżonych obiektów JSON. -* Automatycznie konwertuje z i do JSON. -* Dokumentuje wszystko w OpenAPI, które może być używane przez: - * Interaktywne systemy dokumentacji. - * Systemy automatycznego generowania kodu klienckiego, dla wielu języków. -* Dostarczy bezpośrednio 2 interaktywne dokumentacje webowe. - ---- - -To dopiero początek, ale już masz mniej-więcej pojęcie jak to wszystko działa. - -Spróbuj zmienić linijkę: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...z: - -```Python - ... "item_name": item.name ... -``` - -...na: - -```Python - ... "item_price": item.price ... -``` - -...i zobacz jak edytor kodu automatycznie uzupełni atrybuty i będzie znał ich typy: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -Dla bardziej kompletnych przykładów posiadających więcej funkcjonalności, zobacz Tutorial - User Guide. - -**Uwaga Spoiler**: tutorial - user guide zawiera: - -* Deklaracje **parametrów** z innych miejsc takich jak: **nagłówki**, **pliki cookies**, **formularze** i **pliki**. -* Jak ustawić **ograniczenia walidacyjne** takie jak `maksymalna długość` lub `regex`. -* Potężny i łatwy w użyciu system **Dependency Injection**. -* Zabezpieczenia i autentykacja, wliczając wsparcie dla **OAuth2** z **tokenami JWT** oraz autoryzacją **HTTP Basic**. -* Bardziej zaawansowane (ale równie proste) techniki deklarowania **głęboko zagnieżdżonych modeli JSON** (dzięki Pydantic). -* Wiele dodatkowych funkcji (dzięki Starlette) takie jak: - * **WebSockety** - * **GraphQL** - * bardzo proste testy bazujące na HTTPX oraz `pytest` - * **CORS** - * **Sesje cookie** - * ...i więcej. - -## Wydajność - -Niezależne benchmarki TechEmpower pokazują, że **FastAPI** (uruchomiony na serwerze Uvicorn) jest jednym z najszybszych dostępnych Pythonowych frameworków, zaraz po Starlette i Uvicorn (używanymi wewnątrznie przez FastAPI). (*) - -Aby dowiedzieć się o tym więcej, zobacz sekcję Benchmarks. - -## Opcjonalne zależności - -Używane przez Pydantic: - -* email-validator - dla walidacji adresów email. - -Używane przez Starlette: - -* 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()`. -* itsdangerous - Wymagany dla wsparcia `SessionMiddleware`. -* pyyaml - Wymagane dla wsparcia `SchemaGenerator` z Starlette (z FastAPI prawdopodobnie tego nie potrzebujesz). -* graphene - Wymagane dla wsparcia `GraphQLApp`. - -Używane przez FastAPI / Starlette: - -* uvicorn - jako serwer, który ładuje i obsługuje Twoją aplikację. -* orjson - Wymagane jeżeli chcesz używać `ORJSONResponse`. -* ujson - Wymagane jeżeli chcesz korzystać z `UJSONResponse`. - -Możesz zainstalować wszystkie te aplikacje przy pomocy `pip install fastapi[all]`. - -## Licencja - -Ten projekt jest na licencji MIT. diff --git a/docs/pl/docs/tutorial/first-steps.md b/docs/pl/docs/tutorial/first-steps.md deleted file mode 100644 index 8f1b9b922c..0000000000 --- a/docs/pl/docs/tutorial/first-steps.md +++ /dev/null @@ -1,351 +0,0 @@ -# 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.md#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 deleted file mode 100644 index 66f7c6d621..0000000000 --- a/docs/pl/docs/tutorial/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# Samouczek - -Ten samouczek pokaże Ci, krok po kroku, jak używać większości funkcji **FastAPI**. - -Każda część korzysta z poprzednich, ale jest jednocześnie osobnym tematem. Możesz przejść bezpośrednio do każdego rozdziału, jeśli szukasz rozwiązania konkretnego problemu. - -Samouczek jest tak zbudowany, żeby służył jako punkt odniesienia w przyszłości. - -Możesz wracać i sprawdzać dokładnie to czego potrzebujesz. - -## Wykonywanie kodu - -Wszystkie fragmenty kodu mogą być skopiowane bezpośrednio i użyte (są poprawnymi i przetestowanymi plikami). - -Żeby wykonać każdy przykład skopiuj kod to pliku `main.py` i uruchom `uvicorn` za pomocą: - -
- -```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. -``` - -
- -**BARDZO zalecamy** pisanie bądź kopiowanie kodu, edycję, a następnie wykonywanie go lokalnie. - -Użycie w Twoim edytorze jest tym, co pokazuje prawdziwe korzyści z FastAPI, pozwala zobaczyć jak mało kodu musisz napisać, wszystkie funkcje, takie jak kontrola typów, automatyczne uzupełnianie, itd. - ---- - -## Instalacja FastAPI - -Jako pierwszy krok zainstaluj FastAPI. - -Na potrzeby samouczka możesz zainstalować również wszystkie opcjonalne biblioteki: - -
- -```console -$ pip install "fastapi[all]" - ----> 100% -``` - -
- -...wliczając w to `uvicorn`, który będzie służył jako serwer wykonujacy Twój kod. - -/// note - -Możesz również wykonać instalację "krok po kroku". - -Prawdopodobnie zechcesz to zrobić, kiedy będziesz wdrażać swoją aplikację w środowisku produkcyjnym: - -``` -pip install fastapi -``` - -Zainstaluj też `uvicorn`, który będzie służył jako serwer: - -``` -pip install "uvicorn[standard]" -``` - -Tak samo możesz zainstalować wszystkie dodatkowe biblioteki, których chcesz użyć. - -/// - -## Zaawansowany poradnik - -Jest też **Zaawansowany poradnik**, który możesz przeczytać po lekturze tego **Samouczka**. - -**Zaawansowany poradnik** opiera się na tym samouczku, używa tych samych pojęć, żeby pokazać Ci kilka dodatkowych funkcji. - -Najpierw jednak powinieneś przeczytać **Samouczek** (czytasz go teraz). - -Ten rozdział jest zaprojektowany tak, że możesz stworzyć kompletną aplikację używając tylko informacji tutaj zawartych, a następnie rozszerzać ją na różne sposoby, w zależności od potrzeb, używając kilku dodatkowych pomysłów z **Zaawansowanego poradnika**. diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/pl/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/pt/docs/advanced/additional-responses.md b/docs/pt/docs/advanced/additional-responses.md index c27301b616..1060d18afc 100644 --- a/docs/pt/docs/advanced/additional-responses.md +++ b/docs/pt/docs/advanced/additional-responses.md @@ -1,6 +1,6 @@ # Retornos Adicionais no OpenAPI -/// warning | "Aviso" +/// warning | Aviso Este é um tema bem avançado. @@ -26,17 +26,15 @@ O **FastAPI** pegará este modelo, gerará o esquema JSON dele e incluirá no lo Por exemplo, para declarar um outro retorno com o status code `404` e um modelo do Pydantic chamado `Message`, você pode escrever: -```Python hl_lines="18 22" -{!../../../docs_src/additional_responses/tutorial001.py!} -``` +{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} -/// note | "Nota" +/// note | Nota Lembre-se que você deve retornar o `JSONResponse` diretamente. /// -/// info | "Informação" +/// info | Informação A chave `model` não é parte do OpenAPI. @@ -177,17 +175,15 @@ Você pode utilizar o mesmo parâmetro `responses` para adicionar diferentes med Por exemplo, você pode adicionar um media type adicional de `image/png`, declarando que a sua *operação de caminho* pode retornar um objeto JSON (com o media type `application/json`) ou uma imagem PNG: -```Python hl_lines="19-24 28" -{!../../../docs_src/additional_responses/tutorial002.py!} -``` +{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} -/// note | "Nota" +/// note | Nota Note que você deve retornar a imagem utilizando um `FileResponse` diretamente. /// -/// info | "Informação" +/// info | Informação A menos que você especifique um media type diferente explicitamente em seu parâmetro `responses`, o FastAPI assumirá que o retorno possui o mesmo media type contido na classe principal de retorno (padrão `application/json`). @@ -207,9 +203,7 @@ Por exemplo, você pode declarar um retorno com o código de status `404` que ut E um retorno com o código de status `200` que utiliza o seu `response_model`, porém inclui um `example` customizado: -```Python hl_lines="20-31" -{!../../../docs_src/additional_responses/tutorial003.py!} -``` +{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} Isso será combinado e incluído em seu OpenAPI, e disponibilizado na documentação da sua API: @@ -243,9 +237,7 @@ Você pode utilizar essa técnica para reutilizar alguns retornos predefinidos n Por exemplo: -```Python hl_lines="13-17 26" -{!../../../docs_src/additional_responses/tutorial004.py!} -``` +{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} ## Mais informações sobre retornos OpenAPI diff --git a/docs/pt/docs/advanced/additional-status-codes.md b/docs/pt/docs/advanced/additional-status-codes.md index a0869f3427..06d6191519 100644 --- a/docs/pt/docs/advanced/additional-status-codes.md +++ b/docs/pt/docs/advanced/additional-status-codes.md @@ -14,59 +14,9 @@ Mas você também deseja aceitar novos itens. E quando os itens não existiam, e Para conseguir isso, importe `JSONResponse` e retorne o seu conteúdo diretamente, definindo o `status_code` que você deseja: -//// tab | Python 3.10+ +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 26" -{!> ../../../docs_src/additional_status_codes/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Faça uso da versão `Annotated` quando possível. - -/// - -```Python hl_lines="2 23" -{!> ../../../docs_src/additional_status_codes/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Faça uso da versão `Annotated` quando possível. - -/// - -```Python hl_lines="4 25" -{!> ../../../docs_src/additional_status_codes/tutorial001.py!} -``` - -//// - -/// warning | "Aviso" +/// warning | Aviso Quando você retorna um `Response` diretamente, como no exemplo acima, ele será retornado diretamente. @@ -76,7 +26,7 @@ Garanta que ele tenha toda informação que você deseja, e que os valores sejam /// -/// note | "Detalhes técnicos" +/// note | Detalhes técnicos Você também pode utilizar `from starlette.responses import JSONResponse`. diff --git a/docs/pt/docs/advanced/advanced-dependencies.md b/docs/pt/docs/advanced/advanced-dependencies.md index 5a7b921b2e..f57abba618 100644 --- a/docs/pt/docs/advanced/advanced-dependencies.md +++ b/docs/pt/docs/advanced/advanced-dependencies.md @@ -18,35 +18,7 @@ Não propriamente a classe (que já é um chamável), mas a instância desta cla Para fazer isso, nós declaramos o método `__call__`: -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Prefira utilizar a versão `Annotated` se possível. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} Neste caso, o `__call__` é o que o **FastAPI** utilizará para verificar parâmetros adicionais e sub dependências, e isso é o que será chamado para passar o valor ao parâmetro na sua *função de operação de rota* posteriormente. @@ -54,35 +26,7 @@ Neste caso, o `__call__` é o que o **FastAPI** utilizará para verificar parâm E agora, nós podemos utilizar o `__init__` para declarar os parâmetros da instância que podemos utilizar para "parametrizar" a dependência: -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Prefira utilizar a versão `Annotated` se possível. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} Neste caso, o **FastAPI** nunca tocará ou se importará com o `__init__`, nós vamos utilizar diretamente em nosso código. @@ -90,35 +34,7 @@ Neste caso, o **FastAPI** nunca tocará ou se importará com o `__init__`, nós Nós poderíamos criar uma instância desta classe com: -//// tab | Python 3.9+ - -```Python hl_lines="18" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Prefira utilizar a versão `Annotated` se possível. - -/// - -```Python hl_lines="16" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} E deste modo nós podemos "parametrizar" a nossa dependência, que agora possui `"bar"` dentro dele, como o atributo `checker.fixed_content`. @@ -134,37 +50,9 @@ checker(q="somequery") ...e passar o que quer que isso retorne como valor da dependência em nossa *função de operação de rota* como o parâmetro `fixed_content_included`: -//// tab | Python 3.9+ +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} -```Python hl_lines="22" -{!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/dependencies/tutorial011_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Prefira utilizar a versão `Annotated` se possível. - -/// - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial011.py!} -``` - -//// - -/// tip | "Dica" +/// tip | Dica Tudo isso parece não ser natural. E pode não estar muito claro ou aparentar ser útil ainda. diff --git a/docs/pt/docs/advanced/async-tests.md b/docs/pt/docs/advanced/async-tests.md index ab5bfa6482..a2b79426c6 100644 --- a/docs/pt/docs/advanced/async-tests.md +++ b/docs/pt/docs/advanced/async-tests.md @@ -32,15 +32,11 @@ Para um exemplos simples, vamos considerar uma estrutura de arquivos semelhante O arquivo `main.py` teria: -```Python -{!../../../docs_src/async_tests/main.py!} -``` +{* ../../docs_src/async_tests/main.py *} O arquivo `test_main.py` teria os testes para para o arquivo `main.py`, ele poderia ficar assim: -```Python -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py *} ## Executá-lo @@ -60,11 +56,9 @@ $ pytest O marcador `@pytest.mark.anyio` informa ao pytest que esta função de teste deve ser invocada de maneira assíncrona: -```Python hl_lines="7" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[7] *} -/// tip | "Dica" +/// tip | Dica Note que a função de teste é `async def` agora, no lugar de apenas `def` como quando estávamos utilizando o `TestClient` anteriormente. @@ -72,9 +66,7 @@ Note que a função de teste é `async def` agora, no lugar de apenas `def` como Então podemos criar um `AsyncClient` com a aplicação, e enviar requisições assíncronas para ela utilizando `await`. -```Python hl_lines="9-10" -{!../../../docs_src/async_tests/test_main.py!} -``` +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} Isso é equivalente a: @@ -84,13 +76,13 @@ response = client.get('/') ...que nós utilizamos para fazer as nossas requisições utilizando o `TestClient`. -/// tip | "Dica" +/// tip | Dica Note que nós estamos utilizando async/await com o novo `AsyncClient` - a requisição é assíncrona. /// -/// warning | "Aviso" +/// warning | Aviso Se a sua aplicação depende dos eventos de vida útil (*lifespan*), o `AsyncClient` não acionará estes eventos. Para garantir que eles são acionados, utilize o `LifespanManager` do florimondmanca/asgi-lifespan. @@ -100,7 +92,7 @@ Se a sua aplicação depende dos eventos de vida útil (*lifespan*), o `AsyncCli Como a função de teste agora é assíncrona, você pode chamar (e `esperar`) outras funções `async` além de enviar requisições para a sua aplicação FastAPI em seus testes, exatamente como você as chamaria em qualquer outro lugar do seu código. -/// tip | "Dica" +/// tip | Dica Se você se deparar com um `RuntimeError: Task attached to a different loop` ao integrar funções assíncronas em seus testes (e.g. ao utilizar o MotorClient do MongoDB) Lembre-se de instanciar objetos que precisam de um loop de eventos (*event loop*) apenas em funções assíncronas, e.g. um *"callback"* `'@app.on_event("startup")`. diff --git a/docs/pt/docs/advanced/behind-a-proxy.md b/docs/pt/docs/advanced/behind-a-proxy.md index 2dfc5ca25f..6837c95426 100644 --- a/docs/pt/docs/advanced/behind-a-proxy.md +++ b/docs/pt/docs/advanced/behind-a-proxy.md @@ -18,9 +18,7 @@ Nesse caso, o caminho original `/app` seria servido em `/api/v1/app`. Embora todo o seu código esteja escrito assumindo que existe apenas `/app`. -```Python hl_lines="6" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} E o proxy estaria **"removendo"** o **prefixo do caminho** dinamicamente antes de transmitir a solicitação para o servidor da aplicação (provavelmente Uvicorn via CLI do FastAPI), mantendo sua aplicação convencida de que está sendo servida em `/app`, para que você não precise atualizar todo o seu código para incluir o prefixo `/api/v1`. @@ -43,7 +41,7 @@ browser --> proxy proxy --> server ``` -/// tip | "Dica" +/// tip | Dica O IP `0.0.0.0` é comumente usado para significar que o programa escuta em todos os IPs disponíveis naquela máquina/servidor. @@ -84,7 +82,7 @@ $ fastapi run main.py --root-path /api/v1 Se você usar Hypercorn, ele também tem a opção `--root-path`. -/// note | "Detalhes Técnicos" +/// note | Detalhes Técnicos A especificação ASGI define um `root_path` para esse caso de uso. @@ -98,9 +96,7 @@ Você pode obter o `root_path` atual usado pela sua aplicação para cada solici Aqui estamos incluindo ele na mensagem apenas para fins de demonstração. -```Python hl_lines="8" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} Então, se você iniciar o Uvicorn com: @@ -127,9 +123,7 @@ A resposta seria algo como: Alternativamente, se você não tiver uma maneira de fornecer uma opção de linha de comando como `--root-path` ou equivalente, você pode definir o parâmetro `--root-path` ao criar sua aplicação FastAPI: -```Python hl_lines="3" -{!../../../docs_src/behind_a_proxy/tutorial002.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} Passar o `root_path`h para `FastAPI` seria o equivalente a passar a opção de linha de comando `--root-path` para Uvicorn ou Hypercorn. @@ -178,7 +172,7 @@ Então, crie um arquivo `traefik.toml` com: Isso diz ao Traefik para escutar na porta 9999 e usar outro arquivo `routes.toml`. -/// tip | "Dica" +/// tip | Dica Estamos usando a porta 9999 em vez da porta padrão HTTP 80 para que você não precise executá-lo com privilégios de administrador (`sudo`). @@ -248,7 +242,7 @@ Agora, se você for ao URL com a porta para o Uvicorn: -/// tip | "Dica" +/// tip | Dica A interface de documentação interagirá com o servidor que você selecionar. @@ -358,9 +350,7 @@ A interface de documentação interagirá com o servidor que você selecionar. Se você não quiser que o **FastAPI** inclua um servidor automático usando o `root_path`, você pode usar o parâmetro `root_path_in_servers=False`: -```Python hl_lines="9" -{!../../../docs_src/behind_a_proxy/tutorial004.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} e então ele não será incluído no OpenAPI schema. diff --git a/docs/pt/docs/advanced/benchmarks.md b/docs/pt/docs/advanced/benchmarks.md deleted file mode 100644 index 72ef1e4446..0000000000 --- a/docs/pt/docs/advanced/benchmarks.md +++ /dev/null @@ -1,34 +0,0 @@ -# Benchmarks - -Benchmarks independentes da TechEmpower mostram que aplicações **FastAPI** rodando com o Uvicorn como um dos frameworks Python mais rápidos disponíveis, ficando atrás apenas do Starlette e Uvicorn (utilizado internamente pelo FastAPI). - -Porém, ao verificar benchmarks e comparações você deve prestar atenção ao seguinte: - -## Benchmarks e velocidade - -Quando você verifica os benchmarks, é comum ver diversas ferramentas de diferentes tipos comparados como se fossem equivalentes. - -Especificamente, para ver o Uvicorn, Starlette e FastAPI comparados entre si (entre diversas outras ferramentas). - -Quanto mais simples o problema resolvido pela ferramenta, melhor será a performance. E a maioria das análises não testa funcionalidades adicionais que são oferecidas pela ferramenta. - -A hierarquia é: - -* **Uvicorn**: um servidor ASGI - * **Starlette**: (utiliza Uvicorn) um microframework web - * **FastAPI**: (utiliza Starlette) um microframework para APIs com diversas funcionalidades adicionais para a construção de APIs, com validação de dados, etc. - -* **Uvicorn**: - * Terá a melhor performance, pois não possui muito código além do próprio servidor. - * Você não escreveria uma aplicação utilizando o Uvicorn diretamente. Isso significaria que o seu código teria que incluir pelo menos todo o código fornecido pelo Starlette (ou o **FastAPI**). E caso você fizesse isso, a sua aplicação final teria a mesma sobrecarga que teria se utilizasse um framework, minimizando o código e os bugs. - * Se você está comparando o Uvicorn, compare com os servidores de aplicação Daphne, Hypercorn, uWSGI, etc. -* **Starlette**: - * Terá o melhor desempenho, depois do Uvicorn. Na verdade, o Starlette utiliza o Uvicorn para rodar. Portanto, ele pode ficar mais "devagar" que o Uvicorn apenas por ter que executar mais código. - * Mas ele fornece as ferramentas para construir aplicações web simples, com roteamento baseado em caminhos, etc. - * Se você está comparando o Starlette, compare-o com o Sanic, Flask, Django, etc. Frameworks web (ou microframeworks). -* **FastAPI**: - * Da mesma forma que o Starlette utiliza o Uvicorn e não consegue ser mais rápido que ele, o **FastAPI** utiliza o Starlette, portanto, ele não consegue ser mais rápido que ele. - * O FastAPI provê mais funcionalidades em cima do Starlette. Funcionalidades que você quase sempre precisará quando estiver construindo APIs, como validação de dados e serialização. E ao utilizá-lo, você obtém documentação automática sem custo nenhum (a documentação automática sequer adiciona sobrecarga nas aplicações rodando, pois ela é gerada na inicialização). - * Caso você não utilize o FastAPI e faz uso do Starlette diretamente (ou outra ferramenta, como o Sanic, Flask, Responder, etc) você mesmo teria que implementar toda a validação de dados e serialização. Então, a sua aplicação final ainda teria a mesma sobrecarga caso estivesse usando o FastAPI. E em muitos casos, validação de dados e serialização é a maior parte do código escrito em aplicações. - * Então, ao utilizar o FastAPI, você está economizando tempo de programação, evitando bugs, linhas de código, e provavelmente terá a mesma performance (ou até melhor) do que teria caso você não o utilizasse (já que você teria que implementar tudo no seu código). - * Se você está comparando o FastAPI, compare-o com frameworks de aplicações web (ou conjunto de ferramentas) que oferecem validação de dados, serialização e documentação, como por exemplo o Flask-apispec, NestJS, Molten, etc. Frameworks que possuem validação integrada de dados, serialização e documentação. diff --git a/docs/pt/docs/advanced/custom-response.md b/docs/pt/docs/advanced/custom-response.md new file mode 100644 index 0000000000..a0bcc2b978 --- /dev/null +++ b/docs/pt/docs/advanced/custom-response.md @@ -0,0 +1,314 @@ +# Resposta Personalizada - HTML, Stream, File e outras + +Por padrão, o **FastAPI** irá retornar respostas utilizando `JSONResponse`. + +Mas você pode sobrescrever esse comportamento utilizando `Response` diretamente, como visto em [Retornando uma Resposta Diretamente](response-directly.md){.internal-link target=_blank}. + +Mas se você retornar uma `Response` diretamente (ou qualquer subclasse, como `JSONResponse`), os dados não serão convertidos automaticamente (mesmo que você declare um `response_model`), e a documentação não será gerada automaticamente (por exemplo, incluindo o "media type", no cabeçalho HTTP `Content-Type` como parte do esquema OpenAPI gerado). + +Mas você também pode declarar a `Response` que você deseja utilizar (e.g. qualquer subclasse de `Response`), em um *decorador de operação de rota* utilizando o parâmetro `response_class`. + +Os conteúdos que você retorna em sua *função de operador de rota* serão colocados dentro dessa `Response`. + +E se a `Response` tiver um media type JSON (`application/json`), como é o caso com `JSONResponse` e `UJSONResponse`, os dados que você retornar serão automaticamente convertidos (e filtrados) com qualquer `response_model` do Pydantic que for declarado em sua *função de operador de rota*. + +/// note | Nota + +Se você utilizar uma classe de Resposta sem media type, o FastAPI esperará que sua resposta não tenha conteúdo, então ele não irá documentar o formato da resposta na documentação OpenAPI gerada. + +/// + +## Utilizando `ORJSONResponse` + +Por exemplo, se você precisa bastante de performance, você pode instalar e utilizar o `orjson` e definir a resposta para ser uma `ORJSONResponse`. + +Importe a classe, ou subclasse, de `Response` que você deseja utilizar e declare ela no *decorador de operação de rota*. + +Para respostas grandes, retornar uma `Response` diretamente é muito mais rápido que retornar um dicionário. + +Isso ocorre por que, por padrão, o FastAPI irá verificar cada item dentro do dicionário e garantir que ele seja serializável para JSON, utilizando o mesmo[Codificador Compatível com JSON](../tutorial/encoder.md){.internal-link target=_blank} explicado no tutorial. Isso permite que você retorne **objetos abstratos**, como modelos do banco de dados, por exemplo. + +Mas se você tem certeza que o conteúdo que você está retornando é **serializável com JSON**, você pode passá-lo diretamente para a classe de resposta e evitar o trabalho extra que o FastAPI teria ao passar o conteúdo pelo `jsonable_encoder` antes de passar para a classe de resposta. + +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} + +/// info | Informação + +O parâmetro `response_class` também será usado para definir o "media type" da resposta. + +Neste caso, o cabeçalho HTTP `Content-Type` irá ser definido como `application/json`. + +E será documentado como tal no OpenAPI. + +/// + +/// tip | Dica + +A `ORJSONResponse` está disponível apenas no FastAPI, e não no Starlette. + +/// + +## Resposta HTML + +Para retornar uma resposta com HTML diretamente do **FastAPI**, utilize `HTMLResponse`. + +* Importe `HTMLResponse` +* Passe `HTMLResponse` como o parâmetro de `response_class` do seu *decorador de operação de rota*. + +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} + +/// info | Informação + +O parâmetro `response_class` também será usado para definir o "media type" da resposta. + +Neste caso, o cabeçalho HTTP `Content-Type` será definido como `text/html`. + +E será documentado como tal no OpenAPI. + +/// + +### Retornando uma `Response` + +Como visto em [Retornando uma Resposta Diretamente](response-directly.md){.internal-link target=_blank}, você também pode sobrescrever a resposta diretamente na sua *operação de rota*, ao retornar ela. + +O mesmo exemplo de antes, retornando uma `HTMLResponse`, poderia parecer com: + +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} + +/// warning | Aviso + +Uma `Response` retornada diretamente em sua *função de operação de rota* não será documentada no OpenAPI (por exemplo, o `Content-Type` não será documentado) e não será visível na documentação interativa automática. + +/// + +/// info | Informação + +Obviamente, o cabeçalho `Content-Type`, o código de status, etc, virão do objeto `Response` que você retornou. + +/// + +### Documentar no OpenAPI e sobrescrever `Response` + +Se você deseja sobrescrever a resposta dentro de uma função, mas ao mesmo tempo documentar o "media type" no OpenAPI, você pode utilizar o parâmetro `response_class` E retornar um objeto `Response`. + +A `response_class` será usada apenas para documentar o OpenAPI da *operação de rota*, mas sua `Response` será usada como foi definida. + +##### Retornando uma `HTMLResponse` diretamente + +Por exemplo, poderia ser algo como: + +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} + +Neste exemplo, a função `generate_html_response()` já cria e retorna uma `Response` em vez de retornar o HTML em uma `str`. + +Ao retornar o resultado chamando `generate_html_response()`, você já está retornando uma `Response` que irá sobrescrever o comportamento padrão do **FastAPI**. + +Mas se você passasse uma `HTMLResponse` em `response_class` também, o **FastAPI** saberia como documentar isso no OpenAPI e na documentação interativa como um HTML com `text/html`: + + + +## Respostas disponíveis + +Aqui estão algumas dos tipos de resposta disponíveis. + +Lembre-se que você pode utilizar `Response` para retornar qualquer outra coisa, ou até mesmo criar uma subclasse personalizada. + +/// note | Detalhes Técnicos + +Você também pode utilizar `from starlette.responses import HTMLResponse`. + +O **FastAPI** provê a mesma `starlette.responses` como `fastapi.responses` apenas como uma facilidade para você, desenvolvedor. Mas a maioria das respostas disponíveis vêm diretamente do Starlette. + +/// + +### `Response` + +A classe principal de respostas, todas as outras respostas herdam dela. + +Você pode retorná-la diretamente. + +Ela aceita os seguintes parâmetros: + +* `content` - Uma sequência de caracteres (`str`) ou `bytes`. +* `status_code` - Um código de status HTTP do tipo `int`. +* `headers` - Um dicionário `dict` de strings. +* `media_type` - Uma `str` informando o media type. E.g. `"text/html"`. + +O FastAPI (Starlette, na verdade) irá incluir o cabeçalho Content-Length automaticamente. Ele também irá incluir o cabeçalho Content-Type, baseado no `media_type` e acrescentando uma codificação para tipos textuais. + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +### `HTMLResponse` + +Usa algum texto ou sequência de bytes e retorna uma resposta HTML. Como você leu acima. + +### `PlainTextResponse` + +Usa algum texto ou sequência de bytes para retornar uma resposta de texto não formatado. + +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} + +### `JSONResponse` + +Pega alguns dados e retorna uma resposta com codificação `application/json`. + +É a resposta padrão utilizada no **FastAPI**, como você leu acima. + +### `ORJSONResponse` + +Uma alternativa mais rápida de resposta JSON utilizando o `orjson`, como você leu acima. + +/// info | Informação + +Essa resposta requer a instalação do pacote `orjson`, com o comando `pip install orjson`, por exemplo. + +/// + +### `UJSONResponse` + +Uma alternativa de resposta JSON utilizando a biblioteca `ujson`. + +/// info | Informação + +Essa resposta requer a instalação do pacote `ujson`, com o comando `pip install ujson`, por exemplo. + +/// + +/// warning | Aviso + +`ujson` é menos cauteloso que a implementação nativa do Python na forma que os casos especiais são tratados + +/// + +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} + +/// tip | Dica + +É possível que `ORJSONResponse` seja uma alternativa mais rápida. + +/// + +### `RedirectResponse` + +Retorna um redirecionamento HTTP. Utiliza o código de status 307 (Redirecionamento Temporário) por padrão. + +Você pode retornar uma `RedirectResponse` diretamente: + +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} + +--- + +Ou você pode utilizá-la no parâmetro `response_class`: + +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} + +Se você fizer isso, então você pode retornar a URL diretamente da sua *função de operação de rota* + +Neste caso, o `status_code` utilizada será o padrão de `RedirectResponse`, que é `307`. + +--- + +Você também pode utilizar o parâmetro `status_code` combinado com o parâmetro `response_class`: + +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} + +### `StreamingResponse` + +Recebe uma gerador assíncrono ou um gerador/iterador comum e retorna o corpo da requisição continuamente (stream). + +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} + +#### Utilizando `StreamingResponse` com objetos semelhantes a arquivos + +Se você tiver um objeto semelhante a um arquivo (e.g. o objeto retornado por `open()`), você pode criar uma função geradora para iterar sobre esse objeto. + +Dessa forma, você não precisa ler todo o arquivo na memória primeiro, e você pode passar essa função geradora para `StreamingResponse` e retorná-la. + +Isso inclui muitas bibliotecas que interagem com armazenamento em nuvem, processamento de vídeos, entre outras. + +```{ .python .annotate hl_lines="2 10-12 14" } +{!../../docs_src/custom_response/tutorial008.py!} +``` + +1. Essa é a função geradora. É definida como "função geradora" porque contém declarações `yield` nela. +2. Ao utilizar o bloco `with`, nós garantimos que o objeto semelhante a um arquivo é fechado após a função geradora ser finalizada. Isto é, após a resposta terminar de ser enivada. +3. Essa declaração `yield from` informa a função para iterar sobre essa coisa nomeada de `file_like`. E então, para cada parte iterada, fornece essa parte como se viesse dessa função geradora (`iterfile`). + + Então, é uma função geradora que transfere o trabalho de "geração" para alguma outra coisa interna. + + Fazendo dessa forma, podemos colocá-la em um bloco `with`, e assim garantir que o objeto semelhante a um arquivo é fechado quando a função termina. + +/// tip | Dica + +Perceba que aqui estamos utilizando o `open()` da biblioteca padrão que não suporta `async` e `await`, e declaramos a operação de rota com o `def` básico. + +/// + +### `FileResponse` + +Envia um arquivo de forma assíncrona e contínua (stream). +* +Recebe um conjunto de argumentos do construtor diferente dos outros tipos de resposta: + +* `path` - O caminho do arquivo que será transmitido +* `headers` - quaisquer cabeçalhos que serão incluídos, como um dicionário. +* `media_type` - Uma string com o media type. Se não for definida, o media type é inferido a partir do nome ou caminho do arquivo. +* `filename` - Se for definido, é incluído no cabeçalho `Content-Disposition`. + +Respostas de Arquivos incluem o tamanho do arquivo, data da última modificação e ETags apropriados, nos cabeçalhos `Content-Length`, `Last-Modified` e `ETag`, respectivamente. + +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} + +Você também pode usar o parâmetro `response_class`: + +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} + +Nesse caso, você pode retornar o caminho do arquivo diretamente da sua *função de operação de rota*. + +## Classe de resposta personalizada + +Você pode criar sua própria classe de resposta, herdando de `Response` e usando essa nova classe. + +Por exemplo, vamos supor que você queira utilizar o `orjson`, mas com algumas configurações personalizadas que não estão incluídas na classe `ORJSONResponse`. + +Vamos supor também que você queira retornar um JSON indentado e formatado, então você quer utilizar a opção `orjson.OPT_INDENT_2` do orjson. + +Você poderia criar uma classe `CustomORJSONResponse`. A principal coisa a ser feita é sobrecarregar o método render da classe Response, `Response.render(content)`, que retorna o conteúdo em bytes, para retornar o conteúdo que você deseja: + +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} + +Agora em vez de retornar: + +```json +{"message": "Hello World"} +``` + +...essa resposta retornará: + +```json +{ + "message": "Hello World" +} +``` + +Obviamente, você provavelmente vai encontrar maneiras muito melhores de se aproveitar disso do que a formatação de JSON. 😉 + +## Classe de resposta padrão + +Quando você criar uma instância da classe **FastAPI** ou um `APIRouter` você pode especificar qual classe de resposta utilizar por padrão. + +O padrão que define isso é o `default_response_class`. + +No exemplo abaixo, o **FastAPI** irá utilizar `ORJSONResponse` por padrão, em todas as *operações de rota*, em vez de `JSONResponse`. + +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} + +/// tip | Dica + +Você ainda pode substituir `response_class` em *operações de rota* como antes. + +/// + +## Documentação adicional + +Você também pode declarar o media type e muitos outros detalhes no OpenAPI utilizando `responses`: [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/pt/docs/advanced/dataclasses.md b/docs/pt/docs/advanced/dataclasses.md new file mode 100644 index 0000000000..600c8c2682 --- /dev/null +++ b/docs/pt/docs/advanced/dataclasses.md @@ -0,0 +1,97 @@ +# Usando Dataclasses + +FastAPI é construído em cima do **Pydantic**, e eu tenho mostrado como usar modelos Pydantic para declarar requisições e respostas. + +Mas o FastAPI também suporta o uso de `dataclasses` da mesma forma: + +{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} + +Isso ainda é suportado graças ao **Pydantic**, pois ele tem suporte interno para `dataclasses`. + +Então, mesmo com o código acima que não usa Pydantic explicitamente, o FastAPI está usando Pydantic para converter essas dataclasses padrão para a versão do Pydantic. + +E claro, ele suporta o mesmo: + +* validação de dados +* serialização de dados +* documentação de dados, etc. + +Isso funciona da mesma forma que com os modelos Pydantic. E na verdade é alcançado da mesma maneira por baixo dos panos, usando Pydantic. + +/// info | Informação + +Lembre-se de que dataclasses não podem fazer tudo o que os modelos Pydantic podem fazer. + +Então, você ainda pode precisar usar modelos Pydantic. + +Mas se você tem um monte de dataclasses por aí, este é um truque legal para usá-las para alimentar uma API web usando FastAPI. 🤓 + +/// + +## Dataclasses em `response_model` + +Você também pode usar `dataclasses` no parâmetro `response_model`: + +{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} + +A dataclass será automaticamente convertida para uma dataclass Pydantic. + +Dessa forma, seu esquema aparecerá na interface de documentação da API: + + + +## Dataclasses em Estruturas de Dados Aninhadas + +Você também pode combinar `dataclasses` com outras anotações de tipo para criar estruturas de dados aninhadas. + +Em alguns casos, você ainda pode ter que usar a versão do Pydantic das `dataclasses`. Por exemplo, se você tiver erros com a documentação da API gerada automaticamente. + +Nesse caso, você pode simplesmente trocar as `dataclasses` padrão por `pydantic.dataclasses`, que é um substituto direto: + +```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } +{!../../docs_src/dataclasses/tutorial003.py!} +``` + +1. Ainda importamos `field` das `dataclasses` padrão. + +2. `pydantic.dataclasses` é um substituto direto para `dataclasses`. + +3. A dataclass `Author` inclui uma lista de dataclasses `Item`. + +4. A dataclass `Author` é usada como o parâmetro `response_model`. + +5. Você pode usar outras anotações de tipo padrão com dataclasses como o corpo da requisição. + + Neste caso, é uma lista de dataclasses `Item`. + +6. Aqui estamos retornando um dicionário que contém `items`, que é uma lista de dataclasses. + + O FastAPI ainda é capaz de serializar os dados para JSON. + +7. Aqui o `response_model` está usando uma anotação de tipo de uma lista de dataclasses `Author`. + + Novamente, você pode combinar `dataclasses` com anotações de tipo padrão. + +8. Note que esta *função de operação de rota* usa `def` regular em vez de `async def`. + + Como sempre, no FastAPI você pode combinar `def` e `async def` conforme necessário. + + Se você precisar de uma atualização sobre quando usar qual, confira a seção _"Com pressa?"_ na documentação sobre [`async` e `await`](../async.md#in-a-hurry){.internal-link target=_blank}. + +9. Esta *função de operação de rota* não está retornando dataclasses (embora pudesse), mas uma lista de dicionários com dados internos. + + O FastAPI usará o parâmetro `response_model` (que inclui dataclasses) para converter a resposta. + +Você pode combinar `dataclasses` com outras anotações de tipo em muitas combinações diferentes para formar estruturas de dados complexas. + +Confira as dicas de anotação no código acima para ver mais detalhes específicos. + +## Saiba Mais + +Você também pode combinar `dataclasses` com outros modelos Pydantic, herdar deles, incluí-los em seus próprios modelos, etc. + +Para saber mais, confira a documentação do Pydantic sobre dataclasses. + +## Versão + +Isso está disponível desde a versão `0.67.0` do FastAPI. 🔖 diff --git a/docs/pt/docs/advanced/events.md b/docs/pt/docs/advanced/events.md index 5f722763ed..2d38e0899c 100644 --- a/docs/pt/docs/advanced/events.md +++ b/docs/pt/docs/advanced/events.md @@ -31,15 +31,13 @@ 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!} -``` +{* ../../docs_src/events/tutorial003.py hl[16,19] *} 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" +/// tip | Dica O `shutdown` aconteceria quando você estivesse **encerrando** a aplicação. @@ -51,9 +49,7 @@ Talvez você precise inicializar uma nova versão, ou apenas cansou de executá- 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!} -``` +{* ../../docs_src/events/tutorial003.py hl[14:19] *} A primeira parte da função, antes do `yield`, será executada **antes** da aplicação inicializar. @@ -65,9 +61,7 @@ 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!} -``` +{* ../../docs_src/events/tutorial003.py hl[1,13] *} 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: @@ -89,13 +83,11 @@ No nosso exemplo de código acima, nós não usamos ele diretamente, mas nós pa 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!} -``` +{* ../../docs_src/events/tutorial003.py hl[22] *} ## Eventos alternativos (deprecados) -/// warning | "Aviso" +/// 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. @@ -113,9 +105,7 @@ Essas funções podem ser declaradas com `async def` ou `def` normal. 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!} -``` +{* ../../docs_src/events/tutorial001.py hl[8] *} 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. @@ -127,19 +117,17 @@ E sua aplicação não irá começar a receber requisições até que todos os m 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!} -``` +{* ../../docs_src/events/tutorial002.py hl[6] *} 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" +/// 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" +/// tip | Dica Perceba que nesse caso nós estamos usando a função padrão do Python `open()` que interage com um arquivo. @@ -165,9 +153,9 @@ 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" +/// info | Informação -Você pode ler mais sobre o manipulador `lifespan` do Starlette na Documentação do Lifespan Starlette. +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. diff --git a/docs/pt/docs/advanced/generate-clients.md b/docs/pt/docs/advanced/generate-clients.md new file mode 100644 index 0000000000..dc6b29511a --- /dev/null +++ b/docs/pt/docs/advanced/generate-clients.md @@ -0,0 +1,261 @@ +# Generate Clients + +Como o **FastAPI** é baseado na especificação **OpenAPI**, você obtém compatibilidade automática com muitas ferramentas, incluindo a documentação automática da API (fornecida pelo Swagger UI). + +Uma vantagem particular que nem sempre é óbvia é que você pode **gerar clientes** (às vezes chamados de **SDKs**) para a sua API, para muitas **linguagens de programação** diferentes. + +## Geradores de Clientes OpenAPI + +Existem muitas ferramentas para gerar clientes a partir do **OpenAPI**. + +Uma ferramenta comum é o OpenAPI Generator. + +Se voce está construindo um **frontend**, uma alternativa muito interessante é o openapi-ts. + +## Geradores de Clientes e SDKs - Patrocinadores + +Existem também alguns geradores de clientes e SDKs baseados na OpenAPI (FastAPI) **patrocinados por empresas**, em alguns casos eles podem oferecer **recursos adicionais** além de SDKs/clientes gerados de alta qualidade. + +Alguns deles também ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, isso garante o **desenvolvimento** contínuo e saudável do FastAPI e seu **ecossistema**. + +E isso mostra o verdadeiro compromisso deles com o FastAPI e sua **comunidade** (você), pois eles não apenas querem fornecer um **bom serviço**, mas também querem garantir que você tenha um **framework bom e saudável**, o FastAPI. 🙇 + +Por exemplo, você pode querer experimentar: + +* Speakeasy +* Stainless +* liblab + +Existem também várias outras empresas que oferecem serviços semelhantes que você pode pesquisar e encontrar online. 🤓 + +## Gerar um Cliente Frontend TypeScript + +Vamos começar com um aplicativo **FastAPI** simples: + +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} + +Note que as *operações de rota* definem os modelos que usam para o corpo da requisição e o corpo da resposta, usando os modelos `Item` e `ResponseMessage`. + +### Documentação da API + +Se você acessar a documentação da API, verá que ela tem os **schemas** para os dados a serem enviados nas requisições e recebidos nas respostas: + + + +Você pode ver esses schemas porque eles foram declarados com os modelos no app. + +Essas informações estão disponíveis no **OpenAPI schema** do app e são mostradas na documentação da API (pelo Swagger UI). + +E essas mesmas informações dos modelos que estão incluídas no OpenAPI são o que pode ser usado para **gerar o código do cliente**. + +### Gerar um Cliente TypeScript + +Agora que temos o app com os modelos, podemos gerar o código do cliente para o frontend. + +#### Instalar o `openapi-ts` + +Você pode instalar o `openapi-ts` no seu código frontend com: + +
+ +```console +$ npm install @hey-api/openapi-ts --save-dev + +---> 100% +``` + +
+ +#### Gerar o Código do Cliente + +Para gerar o código do cliente, você pode usar a aplicação de linha de comando `openapi-ts` que agora está instalada. + +Como ela está instalada no projeto local, você provavelmente não conseguiria chamar esse comando diretamente, mas você o colocaria no seu arquivo `package.json`. + +Poderia ser assim: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "@hey-api/openapi-ts": "^0.27.38", + "typescript": "^4.6.2" + } +} +``` + +Depois de ter esse script NPM `generate-client` lá, você pode executá-lo com: + +
+ +```console +$ npm run generate-client + +frontend-app@1.0.0 generate-client /home/user/code/frontend-app +> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios +``` + +
+ +Esse comando gerará o código em `./src/client` e usará o `axios` (a biblioteca HTTP frontend) internamente. + +### Experimente o Código do Cliente + +Agora você pode importar e usar o código do cliente, ele poderia ser assim, observe que você obtém preenchimento automático para os métodos: + + + +Você também obterá preenchimento automático para o corpo a ser enviado: + + + +/// tip | Dica + +Observe o preenchimento automático para `name` e `price`, que foi definido no aplicativo FastAPI, no modelo `Item`. + +/// + +Você terá erros em linha para os dados que você envia: + + + +O objeto de resposta também terá preenchimento automático: + + + +## App FastAPI com Tags + +Em muitos casos seu app FastAPI será maior, e você provavelmente usará tags para separar diferentes grupos de *operações de rota*. + +Por exemplo, você poderia ter uma seção para **items** e outra seção para **users**, e elas poderiam ser separadas por tags: + +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} + +### Gerar um Cliente TypeScript com Tags + +Se você gerar um cliente para um app FastAPI usando tags, normalmente também separará o código do cliente com base nas tags. + +Dessa forma, você poderá ter as coisas ordenadas e agrupadas corretamente para o código do cliente: + + + +Nesse caso você tem: + +* `ItemsService` +* `UsersService` + +### Nomes dos Métodos do Cliente + +Agora os nomes dos métodos gerados como `createItemItemsPost` não parecem muito "limpos": + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...isto ocorre porque o gerador de clientes usa o **operation ID** interno do OpenAPI para cada *operação de rota*. + +O OpenAPI exige que cada operation ID seja único em todas as *operações de rota*, então o FastAPI usa o **nome da função**, o **caminho** e o **método/operacao HTTP** para gerar esse operation ID, porque dessa forma ele pode garantir que os operation IDs sejam únicos. + +Mas eu vou te mostrar como melhorar isso a seguir. 🤓 + +### IDs de Operação Personalizados e Melhores Nomes de Método + +Você pode **modificar** a maneira como esses IDs de operação são **gerados** para torná-los mais simples e ter **nomes de método mais simples** nos clientes. + +Neste caso, você terá que garantir que cada ID de operação seja **único** de alguma outra maneira. + +Por exemplo, você poderia garantir que cada *operação de rota* tenha uma tag, e então gerar o ID da operação com base na **tag** e no **nome** da *operação de rota* (o nome da função). + +### Função Personalizada para Gerar IDs de Operação Únicos + +O FastAPI usa um **ID único** para cada *operação de rota*, ele é usado para o **ID da operação** e também para os nomes de quaisquer modelos personalizados necessários, para requisições ou respostas. + +Você pode personalizar essa função. Ela recebe uma `APIRoute` e gera uma string. + +Por exemplo, aqui está usando a primeira tag (você provavelmente terá apenas uma tag) e o nome da *operação de rota* (o nome da função). + +Você pode então passar essa função personalizada para o **FastAPI** como o parâmetro `generate_unique_id_function`: + +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} + +### Gerar um Cliente TypeScript com IDs de Operação Personalizados + +Agora, se você gerar o cliente novamente, verá que ele tem os nomes dos métodos melhorados: + + + +Como você pode ver, os nomes dos métodos agora têm a tag e, em seguida, o nome da função. Agora eles não incluem informações do caminho da URL e da operação HTTP. + +### Pré-processar a Especificação OpenAPI para o Gerador de Clientes + +O código gerado ainda tem algumas **informações duplicadas**. + +Nós já sabemos que esse método está relacionado aos **items** porque essa palavra está no `ItemsService` (retirada da tag), mas ainda temos o nome da tag prefixado no nome do método também. 😕 + +Provavelmente ainda queremos mantê-lo para o OpenAPI em geral, pois isso garantirá que os IDs de operação sejam **únicos**. + +Mas para o cliente gerado, poderíamos **modificar** os IDs de operação do OpenAPI logo antes de gerar os clientes, apenas para tornar esses nomes de método mais **simples**. + +Poderíamos baixar o JSON do OpenAPI para um arquivo `openapi.json` e então poderíamos **remover essa tag prefixada** com um script como este: + +{* ../../docs_src/generate_clients/tutorial004.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +Com isso, os IDs de operação seriam renomeados de coisas como `items-get_items` para apenas `get_items`, dessa forma o gerador de clientes pode gerar nomes de métodos mais simples. + +### Gerar um Cliente TypeScript com o OpenAPI Pré-processado + +Agora, como o resultado final está em um arquivo `openapi.json`, você modificaria o `package.json` para usar esse arquivo local, por exemplo: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "@hey-api/openapi-ts": "^0.27.38", + "typescript": "^4.6.2" + } +} +``` + +Depois de gerar o novo cliente, você teria agora **nomes de métodos "limpos"**, com todo o **preenchimento automático**, **erros em linha**, etc: + + + +## Benefícios + +Ao usar os clientes gerados automaticamente, você teria **preenchimento automático** para: + +* Métodos. +* Corpo de requisições, parâmetros da query, etc. +* Corpo de respostas. + +Você também teria **erros em linha** para tudo. + +E sempre que você atualizar o código do backend, e **regenerar** o frontend, ele teria quaisquer novas *operações de rota* disponíveis como métodos, as antigas removidas, e qualquer outra alteração seria refletida no código gerado. 🤓 + +Isso também significa que se algo mudar, será **refletido** no código do cliente automaticamente. E se você **construir** o cliente, ele dará erro se houver alguma **incompatibilidade** nos dados usados. + +Então, você **detectaria vários erros** muito cedo no ciclo de desenvolvimento, em vez de ter que esperar que os erros apareçam para seus usuários finais em produção e então tentar depurar onde está o problema. ✨ diff --git a/docs/pt/docs/advanced/index.md b/docs/pt/docs/advanced/index.md index 2569fc914b..22ba2bf4a4 100644 --- a/docs/pt/docs/advanced/index.md +++ b/docs/pt/docs/advanced/index.md @@ -6,7 +6,7 @@ O [Tutorial - Guia de Usuário](../tutorial/index.md){.internal-link target=_bla Na próxima seção você verá outras opções, configurações, e recursos adicionais. -/// tip | "Dica" +/// tip | Dica As próximas seções **não são necessáriamente "avançadas"** diff --git a/docs/pt/docs/advanced/middleware.md b/docs/pt/docs/advanced/middleware.md new file mode 100644 index 0000000000..7700939f06 --- /dev/null +++ b/docs/pt/docs/advanced/middleware.md @@ -0,0 +1,96 @@ +# Middleware Avançado + +No tutorial principal você leu como adicionar [Middleware Personalizado](../tutorial/middleware.md){.internal-link target=_blank} à sua aplicação. + +E então você também leu como lidar com [CORS com o `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. + +Nesta seção, veremos como usar outros middlewares. + +## Adicionando middlewares ASGI + +Como o **FastAPI** é baseado no Starlette e implementa a especificação ASGI, você pode usar qualquer middleware ASGI. + +O middleware não precisa ser feito para o FastAPI ou Starlette para funcionar, desde que siga a especificação ASGI. + +No geral, os middlewares ASGI são classes que esperam receber um aplicativo ASGI como o primeiro argumento. + +Então, na documentação de middlewares ASGI de terceiros, eles provavelmente dirão para você fazer algo como: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +Mas, o FastAPI (na verdade, o Starlette) fornece uma maneira mais simples de fazer isso que garante que os middlewares internos lidem com erros do servidor e que os manipuladores de exceções personalizados funcionem corretamente. + +Para isso, você usa `app.add_middleware()` (como no exemplo para CORS). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` recebe uma classe de middleware como o primeiro argumento e quaisquer argumentos adicionais a serem passados para o middleware. + +## Middlewares Integrados + +**FastAPI** inclui vários middlewares para casos de uso comuns, veremos a seguir como usá-los. + +/// note | Detalhes Técnicos + +Para o próximo exemplo, você também poderia usar `from starlette.middleware.something import SomethingMiddleware`. + +**FastAPI** fornece vários middlewares em `fastapi.middleware` apenas como uma conveniência para você, o desenvolvedor. Mas a maioria dos middlewares disponíveis vem diretamente do Starlette. + +/// + +## `HTTPSRedirectMiddleware` + +Garante que todas as requisições devem ser `https` ou `wss`. + +Qualquer requisição para `http` ou `ws` será redirecionada para o esquema seguro. + +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} + +## `TrustedHostMiddleware` + +Garante que todas as requisições recebidas tenham um cabeçalho `Host` corretamente configurado, a fim de proteger contra ataques de cabeçalho de host HTTP. + +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} + +Os seguintes argumentos são suportados: + +* `allowed_hosts` - Uma lista de nomes de domínio que são permitidos como nomes de host. Domínios com coringa, como `*.example.com`, são suportados para corresponder a subdomínios. Para permitir qualquer nome de host, use `allowed_hosts=["*"]` ou omita o middleware. + +Se uma requisição recebida não for validada corretamente, uma resposta `400` será enviada. + +## `GZipMiddleware` + +Gerencia respostas GZip para qualquer requisição que inclua `"gzip"` no cabeçalho `Accept-Encoding`. + +O middleware lidará com respostas padrão e de streaming. + +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} + +Os seguintes argumentos são suportados: + +* `minimum_size` - Não comprima respostas menores que este tamanho mínimo em bytes. O padrão é `500`. +* `compresslevel` - Usado durante a compressão GZip. É um inteiro variando de 1 a 9. O padrão é `9`. Um valor menor resulta em uma compressão mais rápida, mas em arquivos maiores, enquanto um valor maior resulta em uma compressão mais lenta, mas em arquivos menores. + +## Outros middlewares + +Há muitos outros middlewares ASGI. + +Por exemplo: + +* Uvicorn's `ProxyHeadersMiddleware` +* MessagePack + +Para checar outros middlewares disponíveis, confira Documentação de Middlewares do Starlette e a Lista Incrível do ASGI. diff --git a/docs/pt/docs/advanced/openapi-callbacks.md b/docs/pt/docs/advanced/openapi-callbacks.md new file mode 100644 index 0000000000..b0659d3d6a --- /dev/null +++ b/docs/pt/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# Callbacks na OpenAPI + +Você poderia criar uma API com uma *operação de rota* que poderia acionar uma solicitação a uma *API externa* criada por outra pessoa (provavelmente o mesmo desenvolvedor que estaria *usando* sua API). + +O processo que acontece quando seu aplicativo de API chama a *API externa* é chamado de "callback". Porque o software que o desenvolvedor externo escreveu envia uma solicitação para sua API e então sua API *chama de volta*, enviando uma solicitação para uma *API externa* (que provavelmente foi criada pelo mesmo desenvolvedor). + +Nesse caso, você poderia querer documentar como essa API externa *deveria* ser. Que *operação de rota* ela deveria ter, que corpo ela deveria esperar, que resposta ela deveria retornar, etc. + +## Um aplicativo com callbacks + +Vamos ver tudo isso com um exemplo. + +Imagine que você tem um aplicativo que permite criar faturas. + +Essas faturas terão um `id`, `title` (opcional), `customer` e `total`. + +O usuário da sua API (um desenvolvedor externo) criará uma fatura em sua API com uma solicitação POST. + +Então sua API irá (vamos imaginar): + +* Enviar uma solicitação de pagamento para o desenvolvedor externo. +* Coletar o dinheiro. +* Enviar a notificação de volta para o usuário da API (o desenvolvedor externo). +* Isso será feito enviando uma solicitação POST (de *sua API*) para alguma *API externa* fornecida por esse desenvolvedor externo (este é o "callback"). + +## O aplicativo **FastAPI** normal + +Vamos primeiro ver como o aplicativo da API normal se pareceria antes de adicionar o callback. + +Ele terá uma *operação de rota* que receberá um corpo `Invoice`, e um parâmetro de consulta `callback_url` que conterá a URL para o callback. + +Essa parte é bastante normal, a maior parte do código provavelmente já é familiar para você: + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} + +/// tip | Dica + +O parâmetro de consulta `callback_url` usa um tipo Pydantic Url. + +/// + +A única coisa nova é o argumento `callbacks=invoices_callback_router.routes` no decorador da *operação de rota*. Veremos o que é isso a seguir. + +## Documentando o callback + +O código real do callback dependerá muito do seu próprio aplicativo de API. + +E provavelmente variará muito de um aplicativo para o outro. + +Poderia ser apenas uma ou duas linhas de código, como: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +Mas possivelmente a parte mais importante do callback é garantir que o usuário da sua API (o desenvolvedor externo) implemente a *API externa* corretamente, de acordo com os dados que *sua API* vai enviar no corpo da solicitação do callback, etc. + +Então, o que faremos a seguir é adicionar o código para documentar como essa *API externa* deve ser para receber o callback de *sua API*. + +A documentação aparecerá na interface do Swagger em `/docs` em sua API, e permitirá que os desenvolvedores externos saibam como construir a *API externa*. + +Esse exemplo não implementa o callback em si (que poderia ser apenas uma linha de código), apenas a parte da documentação. + +/// tip | Dica + +O callback real é apenas uma solicitação HTTP. + +Quando implementando o callback por você mesmo, você pode usar algo como HTTPX ou Requisições. + +/// + +## Escrevendo o código de documentação do callback + +Esse código não será executado em seu aplicativo, nós só precisamos dele para *documentar* como essa *API externa* deveria ser. + +Mas, você já sabe como criar facilmente documentação automática para uma API com o **FastAPI**. + +Então vamos usar esse mesmo conhecimento para documentar como a *API externa* deveria ser... criando as *operações de rota* que a *API externa* deveria implementar (as que sua API irá chamar). + +/// tip | Dica + +Quando escrever o código para documentar um callback, pode ser útil imaginar que você é aquele *desenvolvedor externo*. E que você está atualmente implementando a *API externa*, não *sua API*. + +Adotar temporariamente esse ponto de vista (do *desenvolvedor externo*) pode ajudar a sentir que é mais óbvio onde colocar os parâmetros, o modelo Pydantic para o corpo, para a resposta, etc. para essa *API externa*. + +/// + +### Criar um `APIRouter` para o callback + +Primeiramente crie um novo `APIRouter` que conterá um ou mais callbacks. + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} + +### Crie a *operação de rota* do callback + +Para criar a *operação de rota* do callback, use o mesmo `APIRouter` que você criou acima. + +Ele deve parecer exatamente como uma *operação de rota* normal do FastAPI: + +* Ele provavelmente deveria ter uma declaração do corpo que deveria receber, por exemplo. `body: InvoiceEvent`. +* E também deveria ter uma declaração de um código de status de resposta, por exemplo. `response_model=InvoiceEventReceived`. + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} + +Há 2 diferenças principais de uma *operação de rota* normal: + +* Ela não necessita ter nenhum código real, porque seu aplicativo nunca chamará esse código. Ele é usado apenas para documentar a *API externa*. Então, a função poderia ter apenas `pass`. +* A *rota* pode conter uma expressão OpenAPI 3 (veja mais abaixo) onde pode usar variáveis com parâmetros e partes da solicitação original enviada para *sua API*. + +### A expressão do caminho do callback + +A *rota* do callback pode ter uma expressão OpenAPI 3 que pode conter partes da solicitação original enviada para *sua API*. + +Nesse caso, é a `str`: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +Então, se o usuário da sua API (o desenvolvedor externo) enviar uma solicitação para *sua API* para: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +com um corpo JSON de: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +então *sua API* processará a fatura e, em algum momento posterior, enviará uma solicitação de callback para o `callback_url` (a *API externa*): + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +com um corpo JSON contendo algo como: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +e esperaria uma resposta daquela *API externa* com um corpo JSON como: + +```JSON +{ + "ok": true +} +``` + +/// tip | Dica + +Perceba como a URL de callback usada contém a URL recebida como um parâmetro de consulta em `callback_url` (`https://www.external.org/events`) e também o `id` da fatura de dentro do corpo JSON (`2expen51ve`). + +/// + +### Adicionar o roteador de callback + +Nesse ponto você tem a(s) *operação de rota de callback* necessária(s) (a(s) que o *desenvolvedor externo* deveria implementar na *API externa*) no roteador de callback que você criou acima. + +Agora use o parâmetro `callbacks` no decorador da *operação de rota de sua API* para passar o atributo `.routes` (que é na verdade apenas uma `list` de rotas/*operações de rota*) do roteador de callback que você criou acima: + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} + +/// tip | Dica + +Perceba que você não está passando o roteador em si (`invoices_callback_router`) para `callback=`, mas o atributo `.routes`, como em `invoices_callback_router.routes`. + +/// + +### Verifique a documentação + +Agora você pode iniciar seu aplicativo e ir para http://127.0.0.1:8000/docs. + +Você verá sua documentação incluindo uma seção "Callbacks" para sua *operação de rota* que mostra como a *API externa* deveria ser: + + diff --git a/docs/pt/docs/advanced/openapi-webhooks.md b/docs/pt/docs/advanced/openapi-webhooks.md index 2ccd0e8196..f35922234f 100644 --- a/docs/pt/docs/advanced/openapi-webhooks.md +++ b/docs/pt/docs/advanced/openapi-webhooks.md @@ -22,7 +22,7 @@ Com o **FastAPI**, utilizando o OpenAPI, você pode definir os nomes destes webh Isto pode facilitar bastante para os seus usuários **implementarem as APIs deles** para receber as requisições dos seus **webhooks**, eles podem inclusive ser capazes de gerar parte do código da API deles. -/// info | "Informação" +/// info | Informação Webhooks estão disponíveis a partir do OpenAPI 3.1.0, e possui suporte do FastAPI a partir da versão `0.99.0`. @@ -32,13 +32,11 @@ Webhooks estão disponíveis a partir do OpenAPI 3.1.0, e possui suporte do Fast Quando você cria uma aplicação com o **FastAPI**, existe um atributo chamado `webhooks`, que você utilizar para defini-los da mesma maneira que você definiria as suas **operações de rotas**, utilizando por exemplo `@app.webhooks.post()`. -```Python hl_lines="9-13 36-53" -{!../../../docs_src/openapi_webhooks/tutorial001.py!} -``` +{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} Os webhooks que você define aparecerão no esquema do **OpenAPI** e na **página de documentação** gerada automaticamente. -/// info | "Informação" +/// info | Informação O objeto `app.webhooks` é na verdade apenas um `APIRouter`, o mesmo tipo que você utilizaria ao estruturar a sua aplicação com diversos arquivos. diff --git a/docs/pt/docs/advanced/path-operation-advanced-configuration.md b/docs/pt/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 0000000000..411d0f9a7a --- /dev/null +++ b/docs/pt/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,204 @@ +# Configuração Avançada da Operação de Rota + +## operationId do OpenAPI + +/// warning | Aviso + +Se você não é um "especialista" no OpenAPI, você provavelmente não precisa disso. + +/// + +Você pode definir o `operationId` do OpenAPI que será utilizado na sua *operação de rota* com o parâmetro `operation_id`. + +Você precisa ter certeza que ele é único para cada operação. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} + +### Utilizando o nome da *função de operação de rota* como o operationId + +Se você quiser utilizar o nome das funções da sua API como `operationId`s, você pode iterar sobre todos esses nomes e sobrescrever o `operationId` em cada *operação de rota* utilizando o `APIRoute.name` dela. + +Você deve fazer isso depois de adicionar todas as suas *operações de rota*. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *} + +/// tip | Dica + +Se você chamar `app.openapi()` manualmente, os `operationId`s devem ser atualizados antes dessa chamada. + +/// + +/// warning | Aviso + +Se você fizer isso, você tem que ter certeza de que cada uma das suas *funções de operação de rota* tem um nome único. + +Mesmo que elas estejam em módulos (arquivos Python) diferentes. + +/// + +## Excluir do OpenAPI + +Para excluir uma *operação de rota* do esquema OpenAPI gerado (e por consequência, dos sistemas de documentação automáticos), utilize o parâmetro `include_in_schema` e defina ele como `False`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} + +## Descrição avançada a partir de docstring + +Você pode limitar as linhas utilizadas a partir de uma docstring de uma *função de operação de rota* para o OpenAPI. + +Adicionar um `\f` (um caractere de escape para alimentação de formulário) faz com que o **FastAPI** restrinja a saída utilizada pelo OpenAPI até esse ponto. + +Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto do texto. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} + +## Respostas Adicionais + +Você provavelmente já viu como declarar o `response_model` e `status_code` para uma *operação de rota*. + +Isso define os metadados sobre a resposta principal da *operação de rota*. + +Você também pode declarar respostas adicionais, com seus modelos, códigos de status, etc. + +Existe um capítulo inteiro da nossa documentação sobre isso, você pode ler em [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}. + +## Extras do OpenAPI + +Quando você declara uma *operação de rota* na sua aplicação, o **FastAPI** irá gerar os metadados relevantes da *operação de rota* automaticamente para serem incluídos no esquema do OpenAPI. + +/// note | Nota + +Na especificação do OpenAPI, isso é chamado de um Objeto de Operação. + +/// + +Ele possui toda a informação sobre a *operação de rota* e é usado para gerar a documentação automaticamente. + +Ele inclui os atributos `tags`, `parameters`, `requestBody`, `responses`, etc. + +Esse esquema específico para uma *operação de rota* normalmente é gerado automaticamente pelo **FastAPI**, mas você também pode estender ele. + +/// tip | Dica + +Esse é um ponto de extensão de baixo nível. + +Caso você só precise declarar respostas adicionais, uma forma conveniente de fazer isso é com [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}. + +/// + +Você pode estender o esquema do OpenAPI para uma *operação de rota* utilizando o parâmetro `openapi_extra`. + +### Extensões do OpenAPI + +Esse parâmetro `openapi_extra` pode ser útil, por exemplo, para declarar [Extensões do OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): + +{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} + +Se você abrir os documentos criados automaticamente para a API, sua extensão aparecerá no final da *operação de rota* específica. + + + +E se você olhar o esquema OpenAPI resultante (na rota `/openapi.json` da sua API), você verá que a sua extensão também faz parte da *operação de rota* específica: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### Esquema de *operação de rota* do OpenAPI personalizado + +O dicionário em `openapi_extra` vai ter todos os seus níveis mesclados dentro do esquema OpenAPI gerado automaticamente para a *operação de rota*. + +Então, você pode adicionar dados extras para o esquema gerado automaticamente. + +Por exemplo, você poderia optar por ler e validar a requisição com seu próprio código, sem utilizar funcionalidades automatizadas do FastAPI com o Pydantic, mas você ainda pode quere definir a requisição no esquema OpenAPI. + +Você pode fazer isso com `openapi_extra`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36,39:40] *} + +Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo analisado como JSON, ele é lido diretamente como `bytes` e a função `magic_data_reader()` seria a responsável por analisar ele de alguma forma. + +De toda forma, nós podemos declarar o esquema esperado para o corpo da requisição. + +### Tipo de conteúdo do OpenAPI personalizado + +Utilizando esse mesmo truque, você pode utilizar um modelo Pydantic para definir o esquema JSON que é então incluído na seção do esquema personalizado do OpenAPI na *operação de rota*. + +E você pode fazer isso até mesmo quando os dados da requisição não seguem o formato JSON. + +Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o esquema JSON dos modelos Pydantic nem a validação automática do JSON. Na verdade, estamos declarando o tipo do conteúdo da requisição como YAML, em vez de JSON: + +//// tab | Pydantic v2 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22,24] *} + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22,24] *} + +//// + +/// info | Informação + +Na versão 1 do Pydantic, o método para obter o esquema JSON de um modelo é `Item.schema()`, na versão 2 do Pydantic, o método é `Item.model_json_schema()` + +/// + +Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um esquema JSON manualmente para os dados que queremos receber no formato YAML. + +Então utilizamos a requisição diretamente, e extraímos o corpo como `bytes`. Isso significa que o FastAPI não vai sequer tentar analisar o corpo da requisição como JSON. + +E então no nosso código, nós analisamos o conteúdo YAML diretamente, e estamos utilizando o mesmo modelo Pydantic para validar o conteúdo YAML: + +//// tab | Pydantic v2 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} + +//// + +/// info | Informação + +Na versão 1 do Pydantic, o método para analisar e validar um objeto era `Item.parse_obj()`, na versão 2 do Pydantic, o método é chamado de `Item.model_validate()`. + +/// + +///tip | Dica + +Aqui reutilizamos o mesmo modelo do Pydantic. + +Mas da mesma forma, nós poderíamos ter validado de alguma outra forma. + +/// diff --git a/docs/pt/docs/advanced/response-change-status-code.md b/docs/pt/docs/advanced/response-change-status-code.md index 99695c8317..358c12d549 100644 --- a/docs/pt/docs/advanced/response-change-status-code.md +++ b/docs/pt/docs/advanced/response-change-status-code.md @@ -20,9 +20,7 @@ Você pode declarar um parâmetro do tipo `Response` em sua *função de operaç E então você pode definir o `status_code` neste objeto de retorno temporal. -```Python hl_lines="1 9 12" -{!../../../docs_src/response_change_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} E então você pode retornar qualquer objeto que você precise, como você faria normalmente (um `dict`, um modelo de banco de dados, etc.). diff --git a/docs/pt/docs/advanced/response-cookies.md b/docs/pt/docs/advanced/response-cookies.md new file mode 100644 index 0000000000..f005f0b9b9 --- /dev/null +++ b/docs/pt/docs/advanced/response-cookies.md @@ -0,0 +1,51 @@ +# Cookies de Resposta + +## Usando um parâmetro `Response` + +Você pode declarar um parâmetro do tipo `Response` na sua *função de operação de rota*. + +E então você pode definir cookies nesse objeto de resposta *temporário*. + +{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *} + +Em seguida, você pode retornar qualquer objeto que precise, como normalmente faria (um `dict`, um modelo de banco de dados, etc). + +E se você declarou um `response_model`, ele ainda será usado para filtrar e converter o objeto que você retornou. + +**FastAPI** usará essa resposta *temporária* para extrair os cookies (também os cabeçalhos e código de status) e os colocará na resposta final que contém o valor que você retornou, filtrado por qualquer `response_model`. + +Você também pode declarar o parâmetro `Response` em dependências e definir cookies (e cabeçalhos) nelas. + +## Retornando uma `Response` diretamente + +Você também pode criar cookies ao retornar uma `Response` diretamente no seu código. + +Para fazer isso, você pode criar uma resposta como descrito em [Retornando uma Resposta Diretamente](response-directly.md){.internal-link target=_blank}. + +Então, defina os cookies nela e a retorne: + +{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} + +/// tip | Dica + +Lembre-se de que se você retornar uma resposta diretamente em vez de usar o parâmetro `Response`, FastAPI a retornará diretamente. + +Portanto, você terá que garantir que seus dados sejam do tipo correto. E.g. será compatível com JSON se você estiver retornando um `JSONResponse`. + +E também que você não esteja enviando nenhum dado que deveria ter sido filtrado por um `response_model`. + +/// + +### Mais informações + +/// note | Detalhes Técnicos + +Você também poderia usar `from starlette.responses import Response` ou `from starlette.responses import JSONResponse`. + +**FastAPI** fornece as mesmas `starlette.responses` em `fastapi.responses` apenas como uma conveniência para você, o desenvolvedor. Mas a maioria das respostas disponíveis vem diretamente do Starlette. + +E como o `Response` pode ser usado frequentemente para definir cabeçalhos e cookies, o **FastAPI** também o fornece em `fastapi.Response`. + +/// + +Para ver todos os parâmetros e opções disponíveis, verifique a documentação no Starlette. diff --git a/docs/pt/docs/advanced/response-directly.md b/docs/pt/docs/advanced/response-directly.md new file mode 100644 index 0000000000..ea717be008 --- /dev/null +++ b/docs/pt/docs/advanced/response-directly.md @@ -0,0 +1,66 @@ +# Retornando uma Resposta Diretamente + +Quando você cria uma *operação de rota* no **FastAPI** você pode retornar qualquer dado nela: um dicionário (`dict`), uma lista (`list`), um modelo do Pydantic ou do seu banco de dados, etc. + +Por padrão, o **FastAPI** irá converter automaticamente o valor do retorno para JSON utilizando o `jsonable_encoder` explicado em [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. + +Então, por baixo dos panos, ele incluiria esses dados compatíveis com JSON (e.g. um `dict`) dentro de uma `JSONResponse` que é utilizada para enviar uma resposta para o cliente. + +Mas você pode retornar a `JSONResponse` diretamente nas suas *operações de rota*. + +Pode ser útil para retornar cabeçalhos e cookies personalizados, por exemplo. + +## Retornando uma `Response` + +Na verdade, você pode retornar qualquer `Response` ou subclasse dela. + +/// tip | Dica + +A própria `JSONResponse` é uma subclasse de `Response`. + +/// + +E quando você retorna uma `Response`, o **FastAPI** vai repassá-la diretamente. + +Ele não vai fazer conversões de dados com modelos do Pydantic, não irá converter a tipagem de nenhum conteúdo, etc. + +Isso te dá bastante flexibilidade. Você pode retornar qualquer tipo de dado, sobrescrever qualquer declaração e validação nos dados, etc. + +## Utilizando o `jsonable_encoder` em uma `Response` + +Como o **FastAPI** não realiza nenhuma mudança na `Response` que você retorna, você precisa garantir que o conteúdo dela está pronto para uso. + +Por exemplo, você não pode colocar um modelo do Pydantic em uma `JSONResponse` sem antes convertê-lo em um `dict` com todos os tipos de dados (como `datetime`, `UUID`, etc) convertidos para tipos compatíveis com JSON. + +Para esses casos, você pode usar o `jsonable_encoder` para converter seus dados antes de repassá-los para a resposta: + +{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} + +/// note | Detalhes Técnicos + +Você também pode utilizar `from starlette.responses import JSONResponse`. + +**FastAPI** utiliza a mesma `starlette.responses` como `fastapi.responses` apenas como uma conveniência para você, desenvolvedor. Mas maior parte das respostas disponíveis vem diretamente do Starlette. + +/// + +## Retornando uma `Response` + +O exemplo acima mostra todas as partes que você precisa, mas ainda não é muito útil, já que você poderia ter retornado o `item` diretamente, e o **FastAPI** colocaria em uma `JSONResponse` para você, convertendo em um `dict`, etc. Tudo isso por padrão. + +Agora, vamos ver como você pode usar isso para retornar uma resposta personalizada. + +Vamos dizer quer retornar uma resposta XML. + +Você pode colocar o seu conteúdo XML em uma string, colocar em uma `Response`, e retorná-lo: + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +## Notas + +Quando você retorna uma `Response` diretamente os dados não são validados, convertidos (serializados) ou documentados automaticamente. + +Mas você ainda pode documentar como descrito em [Retornos Adicionais no OpenAPI +](additional-responses.md){.internal-link target=_blank}. + +Você pode ver nas próximas seções como usar/declarar essas `Responses` customizadas enquanto mantém a conversão e documentação automática dos dados. diff --git a/docs/pt/docs/advanced/response-headers.md b/docs/pt/docs/advanced/response-headers.md new file mode 100644 index 0000000000..a1fc84cc0e --- /dev/null +++ b/docs/pt/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# Cabeçalhos de resposta + +## Usando um parâmetro `Response` + +Você pode declarar um parâmetro do tipo `Response` na sua *função de operação de rota* (assim como você pode fazer para cookies). + +Então você pode definir os cabeçalhos nesse objeto de resposta *temporário*. + +{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *} + +Em seguida você pode retornar qualquer objeto que precisar, da maneira que faria normalmente (um `dict`, um modelo de banco de dados, etc.). + +Se você declarou um `response_model`, ele ainda será utilizado para filtrar e converter o objeto que você retornou. + +**FastAPI** usará essa resposta *temporária* para extrair os cabeçalhos (cookies e código de status também) e os colocará na resposta final que contém o valor que você retornou, filtrado por qualquer `response_model`. + +Você também pode declarar o parâmetro `Response` em dependências e definir cabeçalhos (e cookies) nelas. + +## Retornar uma `Response` diretamente + +Você também pode adicionar cabeçalhos quando retornar uma `Response` diretamente. + +Crie uma resposta conforme descrito em [Retornar uma resposta diretamente](response-directly.md){.internal-link target=_blank} e passe os cabeçalhos como um parâmetro adicional: + +{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} + +/// note | Detalhes técnicos + +Você também pode usar `from starlette.responses import Response` ou `from starlette.responses import JSONResponse`. + +**FastAPI** fornece as mesmas `starlette.responses` como `fastapi.responses` apenas como uma conveniência para você, desenvolvedor. Mas a maioria das respostas disponíveis vem diretamente do Starlette. + +E como a `Response` pode ser usada frequentemente para definir cabeçalhos e cookies, **FastAPI** também a fornece em `fastapi.Response`. + +/// + +## Cabeçalhos personalizados + +Tenha em mente que cabeçalhos personalizados proprietários podem ser adicionados usando o prefixo 'X-'. + +Porém, se voce tiver cabeçalhos personalizados que deseja que um cliente no navegador possa ver, você precisa adicioná-los às suas configurações de CORS (saiba mais em [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), usando o parâmetro `expose_headers` descrito na documentação de CORS do Starlette. diff --git a/docs/pt/docs/advanced/security/http-basic-auth.md b/docs/pt/docs/advanced/security/http-basic-auth.md new file mode 100644 index 0000000000..3315139273 --- /dev/null +++ b/docs/pt/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,108 @@ +# HTTP Basic Auth + +Para os casos mais simples, você pode utilizar o HTTP Basic Auth. + +No HTTP Basic Auth, a aplicação espera um cabeçalho que contém um usuário e uma senha. + +Caso ela não receba, ela retorna um erro HTTP 401 "Unauthorized" (*Não Autorizado*). + +E retorna um cabeçalho `WWW-Authenticate` com o valor `Basic`, e um parâmetro opcional `realm`. + +Isso sinaliza ao navegador para mostrar o prompt integrado para um usuário e senha. + +Então, quando você digitar o usuário e senha, o navegador os envia automaticamente no cabeçalho. + +## HTTP Basic Auth Simples + +* Importe `HTTPBasic` e `HTTPBasicCredentials`. +* Crie um "esquema `security`" utilizando `HTTPBasic`. +* Utilize o `security` com uma dependência em sua *operação de rota*. +* Isso retorna um objeto do tipo `HTTPBasicCredentials`: + * Isto contém o `username` e o `password` enviado. + +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} + +Quando você tentar abrir a URL pela primeira vez (ou clicar no botão "Executar" nos documentos) o navegador vai pedir pelo seu usuário e senha: + + + +## Verifique o usuário + +Aqui está um exemplo mais completo. + +Utilize uma dependência para verificar se o usuário e a senha estão corretos. + +Para isso, utilize o módulo padrão do Python `secrets` para verificar o usuário e senha. + +O `secrets.compare_digest()` necessita receber `bytes` ou `str` que possuem apenas caracteres ASCII (os em inglês). Isso significa que não funcionaria com caracteres como o `á`, como em `Sebastián`. + +Para lidar com isso, primeiramente nós convertemos o `username` e o `password` para `bytes`, codificando-os com UTF-8. + +Então nós podemos utilizar o `secrets.compare_digest()` para garantir que o `credentials.username` é `"stanleyjobson"`, e que o `credentials.password` é `"swordfish"`. + +{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} + +Isso seria parecido com: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +Porém, ao utilizar o `secrets.compare_digest()`, isso estará seguro contra um tipo de ataque chamado "timing attacks" (ataques de temporização). + +### Ataques de Temporização + +Mas o que é um "timing attack" (ataque de temporização)? + +Vamos imaginar que alguns invasores estão tentando adivinhar o usuário e a senha. + +E eles enviam uma requisição com um usuário `johndoe` e uma senha `love123`. + +Então o código Python em sua aplicação seria equivalente a algo como: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Mas no exato momento que o Python compara o primeiro `j` em `johndoe` contra o primeiro `s` em `stanleyjobson`, ele retornará `False`, porque ele já sabe que aquelas duas strings não são a mesma, pensando que "não existe a necessidade de desperdiçar mais poder computacional comparando o resto das letras". E a sua aplicação dirá "Usuário ou senha incorretos". + +Então os invasores vão tentar com o usuário `stanleyjobsox` e a senha `love123`. + +E a sua aplicação faz algo como: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +O Python terá que comparar todo o `stanleyjobso` tanto em `stanleyjobsox` como em `stanleyjobson` antes de perceber que as strings não são a mesma. Então isso levará alguns microssegundos a mais para retornar "Usuário ou senha incorretos". + +#### O tempo para responder ajuda os invasores + +Neste ponto, ao perceber que o servidor demorou alguns microssegundos a mais para enviar o retorno "Usuário ou senha incorretos", os invasores irão saber que eles acertaram _alguma coisa_, algumas das letras iniciais estavam certas. + +E eles podem tentar de novo sabendo que provavelmente é algo mais parecido com `stanleyjobsox` do que com `johndoe`. + +#### Um ataque "profissional" + +Claro, os invasores não tentariam tudo isso de forma manual, eles escreveriam um programa para fazer isso, possivelmente com milhares ou milhões de testes por segundo. E obteriam apenas uma letra a mais por vez. + +Mas fazendo isso, em alguns minutos ou horas os invasores teriam adivinhado o usuário e senha corretos, com a "ajuda" da nossa aplicação, apenas usando o tempo levado para responder. + +#### Corrija com o `secrets.compare_digest()` + +Mas em nosso código já estamos utilizando o `secrets.compare_digest()`. + +Resumindo, levará o mesmo tempo para comparar `stanleyjobsox` com `stanleyjobson` do que comparar `johndoe` com `stanleyjobson`. E o mesmo para a senha. + +Deste modo, ao utilizar `secrets.compare_digest()` no código de sua aplicação, ela estará a salvo contra toda essa gama de ataques de segurança. + + +### Retorne o erro + +Após detectar que as credenciais estão incorretas, retorne um `HTTPException` com o status 401 (o mesmo retornado quando nenhuma credencial foi informada) e adicione o cabeçalho `WWW-Authenticate` para fazer com que o navegador mostre o prompt de login novamente: + +{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/pt/docs/advanced/security/index.md b/docs/pt/docs/advanced/security/index.md new file mode 100644 index 0000000000..6c7becb67a --- /dev/null +++ b/docs/pt/docs/advanced/security/index.md @@ -0,0 +1,19 @@ +# Segurança Avançada + +## Funcionalidades Adicionais + +Existem algumas funcionalidades adicionais para lidar com segurança além das cobertas em [Tutorial - Guia de Usuário: Segurança](../../tutorial/security/index.md){.internal-link target=_blank}. + +/// tip | Dica + +As próximas seções **não são necessariamente "avançadas"**. + +E é possível que para o seu caso de uso, a solução está em uma delas. + +/// + +## Leia o Tutorial primeiro + +As próximas seções pressupõem que você já leu o principal [Tutorial - Guia de Usuário: Segurança](../../tutorial/security/index.md){.internal-link target=_blank}. + +Todas elas são baseadas nos mesmos conceitos, mas permitem algumas funcionalidades extras. diff --git a/docs/pt/docs/advanced/security/oauth2-scopes.md b/docs/pt/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 0000000000..07b3459452 --- /dev/null +++ b/docs/pt/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# Escopos OAuth2 + +Você pode utilizar escopos do OAuth2 diretamente com o **FastAPI**, eles são integrados para funcionar perfeitamente. + +Isso permitiria que você tivesse um sistema de permissionamento mais refinado, seguindo o padrão do OAuth2 integrado na sua aplicação OpenAPI (e as documentações da API). + +OAuth2 com escopos é o mecanismo utilizado por muitos provedores de autenticação, como o Facebook, Google, GitHub, Microsoft, X (Twitter), etc. Eles utilizam isso para prover permissões específicas para os usuários e aplicações. + +Toda vez que você "se autentica com" Facebook, Google, GitHub, Microsoft, X (Twitter), aquela aplicação está utilizando o OAuth2 com escopos. + +Nesta seção, você verá como gerenciar a autenticação e autorização com os mesmos escopos do OAuth2 em sua aplicação **FastAPI**. + +/// warning | Aviso + +Isso é uma seção mais ou menos avançada. Se você está apenas começando, você pode pular. + +Você não necessariamente precisa de escopos do OAuth2, e você pode lidar com autenticação e autorização da maneira que você achar melhor. + +Mas o OAuth2 com escopos pode ser integrado de maneira fácil em sua API (com OpenAPI) e a sua documentação de API. + +No entando, você ainda aplica estes escopos, ou qualquer outro requisito de segurança/autorização, conforme necessário, em seu código. + +Em muitos casos, OAuth2 com escopos pode ser um exagero. + +Mas se você sabe que precisa, ou está curioso, continue lendo. + +/// + +## Escopos OAuth2 e OpenAPI + +A especificação OAuth2 define "escopos" como uma lista de strings separadas por espaços. + +O conteúdo de cada uma dessas strings pode ter qualquer formato, mas não devem possuir espaços. + +Estes escopos representam "permissões". + +No OpenAPI (e.g. os documentos da API), você pode definir "esquemas de segurança". + +Quando um desses esquemas de segurança utiliza OAuth2, você pode também declarar e utilizar escopos. + +Cada "escopo" é apenas uma string (sem espaços). + +Eles são normalmente utilizados para declarar permissões de segurança específicas, como por exemplo: + +* `users:read` or `users:write` são exemplos comuns. +* `instagram_basic` é utilizado pelo Facebook / Instagram. +* `https://www.googleapis.com/auth/drive` é utilizado pelo Google. + +/// info | Informação + +No OAuth2, um "escopo" é apenas uma string que declara uma permissão específica necessária. + +Não importa se ela contém outros caracteres como `:` ou se ela é uma URL. + +Estes detalhes são específicos da implementação. + +Para o OAuth2, eles são apenas strings. + +/// + +## Visão global + +Primeiro, vamos olhar rapidamente as partes que mudam dos exemplos do **Tutorial - Guia de Usuário** para [OAuth2 com Senha (e hash), Bearer com tokens JWT](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Agora utilizando escopos OAuth2: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *} + +Agora vamos revisar essas mudanças passo a passo. + +## Esquema de segurança OAuth2 + +A primeira mudança é que agora nós estamos declarando o esquema de segurança OAuth2 com dois escopos disponíveis, `me` e `items`. + +O parâmetro `scopes` recebe um `dict` contendo cada escopo como chave e a descrição como valor: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +Pelo motivo de estarmos declarando estes escopos, eles aparecerão nos documentos da API quando você se autenticar/autorizar. + +E você poderá selecionar quais escopos você deseja dar acesso: `me` e `items`. + +Este é o mesmo mecanismo utilizado quando você adiciona permissões enquanto se autentica com o Facebook, Google, GitHub, etc: + + + +## Token JWT com escopos + +Agora, modifique o *caminho de rota* para retornar os escopos solicitados. + +Nós ainda estamos utilizando o mesmo `OAuth2PasswordRequestForm`. Ele inclui a propriedade `scopes` com uma `list` de `str`, com cada escopo que ele recebeu na requisição. + +E nós retornamos os escopos como parte do token JWT. + +/// danger | Cuidado + +Para manter as coisas simples, aqui nós estamos apenas adicionando os escopos recebidos diretamente ao token. + +Porém em sua aplicação, por segurança, você deve garantir que você apenas adiciona os escopos que o usuário possui permissão de fato, ou aqueles que você predefiniu. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *} + +## Declare escopos em *operações de rota* e dependências + +Agora nós declaramos que a *operação de rota* para `/users/me/items/` exige o escopo `items`. + +Para isso, nós importamos e utilizamos `Security` de `fastapi`. + +Você pode utilizar `Security` para declarar dependências (assim como `Depends`), porém o `Security` também recebe o parâmetros `scopes` com uma lista de escopos (strings). + +Neste caso, nós passamos a função `get_current_active_user` como dependência para `Security` (da mesma forma que nós faríamos com `Depends`). + +Mas nós também passamos uma `list` de escopos, neste caso com apenas um escopo: `items` (poderia ter mais). + +E a função de dependência `get_current_active_user` também pode declarar subdependências, não apenas com `Depends`, mas também com `Security`. Ao declarar sua própria função de subdependência (`get_current_user`), e mais requisitos de escopo. + +Neste caso, ele requer o escopo `me` (poderia requerer mais de um escopo). + +/// note | Nota + +Você não necessariamente precisa adicionar diferentes escopos em diferentes lugares. + +Nós estamos fazendo isso aqui para demonstrar como o **FastAPI** lida com escopos declarados em diferentes níveis. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *} + +/// info | Informações Técnicas + +`Security` é na verdade uma subclasse de `Depends`, e ele possui apenas um parâmetro extra que veremos depois. + +Porém ao utilizar `Security` no lugar de `Depends`, o **FastAPI** saberá que ele pode declarar escopos de segurança, utilizá-los internamente, e documentar a API com o OpenAPI. + +Mas quando você importa `Query`, `Path`, `Depends`, `Security` entre outros de `fastapi`, eles são na verdade funções que retornam classes especiais. + +/// + +## Utilize `SecurityScopes` + +Agora atualize a dependência `get_current_user`. + +Este é o usado pelas dependências acima. + +Aqui é onde estamos utilizando o mesmo esquema OAuth2 que nós declaramos antes, declarando-o como uma dependência: `oauth2_scheme`. + +Porque esta função de dependência não possui nenhum requerimento de escopo, nós podemos utilizar `Depends` com o `oauth2_scheme`. Nós não precisamos utilizar `Security` quando nós não precisamos especificar escopos de segurança. + +Nós também declaramos um parâmetro especial do tipo `SecurityScopes`, importado de `fastapi.security`. + +A classe `SecurityScopes` é semelhante à classe `Request` (`Request` foi utilizada para obter o objeto da requisição diretamente). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## Utilize os `scopes` + +O parâmetro `security_scopes` será do tipo `SecurityScopes`. + +Ele terá a propriedade `scopes` com uma lista contendo todos os escopos requeridos por ele e todas as dependências que utilizam ele como uma subdependência. Isso significa, todos os "dependentes"... pode soar meio confuso, e isso será explicado novamente mais adiante. + +O objeto `security_scopes` (da classe `SecurityScopes`) também oferece um atributo `scope_str` com uma única string, contendo os escopos separados por espaços (nós vamos utilizar isso). + +Nós criamos uma `HTTPException` que nós podemos reutilizar (`raise`) mais tarde em diversos lugares. + +Nesta exceção, nós incluímos os escopos necessários (se houver algum) como uma string separada por espaços (utilizando `scope_str`). Nós colocamos esta string contendo os escopos no cabeçalho `WWW-Authenticate` (isso é parte da especificação). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## Verifique o `username` e o formato dos dados + +Nós verificamos que nós obtemos um `username`, e extraímos os escopos. + +E depois nós validamos esse dado com o modelo Pydantic (capturando a exceção `ValidationError`), e se nós obtemos um erro ao ler o token JWT ou validando os dados com o Pydantic, nós levantamos a exceção `HTTPException` que criamos anteriormente. + +Para isso, nós atualizamos o modelo Pydantic `TokenData` com a nova propriedade `scopes`. + +Ao validar os dados com o Pydantic nós podemos garantir que temos, por exemplo, exatamente uma `list` de `str` com os escopos e uma `str` com o `username`. + +No lugar de, por exemplo, um `dict`, ou alguma outra coisa, que poderia quebrar a aplicação em algum lugar mais tarde, tornando isso um risco de segurança. + +Nós também verificamos que nós temos um usuário com o "*username*", e caso contrário, nós levantamos a mesma exceção que criamos anteriormente. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *} + +## Verifique os `scopes` + +Nós verificamos agora que todos os escopos necessários, por essa dependência e todos os dependentes (incluindo as *operações de rota*) estão incluídas nos escopos fornecidos pelo token recebido, caso contrário, levantamos uma `HTTPException`. + +Para isso, nós utilizamos `security_scopes.scopes`, que contém uma `list` com todos esses escopos como uma `str`. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *} + +## Árvore de dependência e escopos + +Vamos rever novamente essa árvore de dependência e os escopos. + +Como a dependência `get_current_active_user` possui uma subdependência em `get_current_user`, o escopo `"me"` declarado em `get_current_active_user` será incluído na lista de escopos necessários em `security_scopes.scopes` passado para `get_current_user`. + +A própria *operação de rota* também declara o escopo, `"items"`, então ele também estará na lista de `security_scopes.scopes` passado para o `get_current_user`. + +Aqui está como a hierarquia de dependências e escopos parecem: + +* A *operação de rota* `read_own_items` possui: + * Escopos necessários `["items"]` com a dependência: + * `get_current_active_user`: + * A função de dependência `get_current_active_user` possui: + * Escopos necessários `["me"]` com a dependência: + * `get_current_user`: + * A função de dependência `get_current_user` possui: + * Nenhum escopo necessário. + * Uma dependência utilizando `oauth2_scheme`. + * Um parâmetro `security_scopes` do tipo `SecurityScopes`: + * Este parâmetro `security_scopes` possui uma propriedade `scopes` com uma `list` contendo todos estes escopos declarados acima, então: + * `security_scopes.scopes` terá `["me", "items"]` para a *operação de rota* `read_own_items`. + * `security_scopes.scopes` terá `["me"]` para a *operação de rota* `read_users_me`, porque ela declarou na dependência `get_current_active_user`. + * `security_scopes.scopes` terá `[]` (nada) para a *operação de rota* `read_system_status`, porque ele não declarou nenhum `Security` com `scopes`, e sua dependência, `get_current_user`, não declara nenhum `scopes` também. + +/// tip | Dica + +A coisa importante e "mágica" aqui é que `get_current_user` terá diferentes listas de `scopes` para validar para cada *operação de rota*. + +Tudo depende dos `scopes` declarados em cada *operação de rota* e cada dependência da árvore de dependências para aquela *operação de rota* específica. + +/// + +## Mais detalhes sobre `SecurityScopes` + +Você pode utilizar `SecurityScopes` em qualquer lugar, e em diversos lugares. Ele não precisa estar na dependência "raiz". + +Ele sempre terá os escopos de segurança declarados nas dependências atuais de `Security` e todos os dependentes para **aquela** *operação de rota* **específica** e **aquela** árvore de dependência **específica**. + +Porque o `SecurityScopes` terá todos os escopos declarados por dependentes, você pode utilizá-lo para verificar se o token possui os escopos necessários em uma função de dependência central, e depois declarar diferentes requisitos de escopo em diferentes *operações de rota*. + +Todos eles serão validados independentemente para cada *operação de rota*. + +## Verifique + +Se você abrir os documentos da API, você pode antenticar e especificar quais escopos você quer autorizar. + + + +Se você não selecionar nenhum escopo, você terá "autenticado", mas quando você tentar acessar `/users/me/` ou `/users/me/items/`, você vai obter um erro dizendo que você não possui as permissões necessárias. Você ainda poderá acessar `/status/`. + +E se você selecionar o escopo `me`, mas não o escopo `items`, você poderá acessar `/users/me/`, mas não `/users/me/items/`. + +Isso é o que aconteceria se uma aplicação terceira que tentou acessar uma dessas *operações de rota* com um token fornecido por um usuário, dependendo de quantas permissões o usuário forneceu para a aplicação. + +## Sobre integrações de terceiros + +Neste exemplos nós estamos utilizando o fluxo de senha do OAuth2. + +Isso é apropriado quando nós estamos autenticando em nossa própria aplicação, provavelmente com o nosso próprio "*frontend*". + +Porque nós podemos confiar nele para receber o `username` e o `password`, pois nós controlamos isso. + +Mas se nós estamos construindo uma aplicação OAuth2 que outros poderiam conectar (i.e., se você está construindo um provedor de autenticação equivalente ao Facebook, Google, GitHub, etc.) você deveria utilizar um dos outros fluxos. + +O mais comum é o fluxo implícito. + +O mais seguro é o fluxo de código, mas ele é mais complexo para implementar, pois ele necessita mais passos. Como ele é mais complexo, muitos provedores terminam sugerindo o fluxo implícito. + +/// note | Nota + +É comum que cada provedor de autenticação nomeie os seus fluxos de forma diferente, para torná-lo parte de sua marca. + +Mas no final, eles estão implementando o mesmo padrão OAuth2. + +/// + +O **FastAPI** inclui utilitários para todos esses fluxos de autenticação OAuth2 em `fastapi.security.oauth2`. + +## `Security` em docoradores de `dependências` + +Da mesma forma que você pode definir uma `list` de `Depends` no parâmetro de `dependencias` do decorador (como explicado em [Dependências em decoradores de operações de rota](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), você também pode utilizar `Security` com escopos lá. diff --git a/docs/pt/docs/advanced/settings.md b/docs/pt/docs/advanced/settings.md index db2b4c9cc4..cdc6400adf 100644 --- a/docs/pt/docs/advanced/settings.md +++ b/docs/pt/docs/advanced/settings.md @@ -8,7 +8,7 @@ Por isso é comum prover essas configurações como variáveis de ambiente que s ## Variáveis de Ambiente -/// dica +/// tip | Dica Se você já sabe o que são variáveis de ambiente e como utilizá-las, sinta-se livre para avançar para o próximo tópico. @@ -67,7 +67,7 @@ name = os.getenv("MY_NAME", "World") print(f"Hello {name} from Python") ``` -/// dica +/// tip | Dica O segundo parâmetro em `os.getenv()` é o valor padrão para o retorno. @@ -124,7 +124,7 @@ Hello World from Python -/// dica +/// tip | Dica Você pode ler mais sobre isso em: The Twelve-Factor App: Configurações. @@ -180,9 +180,7 @@ Você pode utilizar todas as ferramentas e funcionalidades de validação que s //// tab | Pydantic v2 -```Python hl_lines="2 5-8 11" -{!> ../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} //// @@ -194,13 +192,11 @@ Na versão 1 do Pydantic você importaria `BaseSettings` diretamente do módulo /// -```Python hl_lines="2 5-8 11" -{!> ../../../docs_src/settings/tutorial001_pv1.py!} -``` +{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} //// -/// dica +/// tip | Dica Se você quiser algo pronto para copiar e colar na sua aplicação, não use esse exemplo, mas sim o exemplo abaixo. @@ -214,9 +210,7 @@ Depois ele irá converter e validar os dados. Assim, quando você utilizar aquel Depois, Você pode utilizar o novo objeto `settings` na sua aplicação: -```Python hl_lines="18-20" -{!../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[18:20] *} ### Executando o servidor @@ -232,7 +226,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p -/// dica +/// tip | Dica Para definir múltiplas variáveis de ambiente para um único comando basta separá-las utilizando espaços, e incluir todas elas antes do comando. @@ -250,17 +244,13 @@ Você também pode incluir essas configurações em um arquivo de um módulo sep Por exemplo, você pode adicionar um arquivo `config.py` com: -```Python -{!../../../docs_src/settings/app01/config.py!} -``` +{* ../../docs_src/settings/app01/config.py *} E utilizar essa configuração em `main.py`: -```Python hl_lines="3 11-13" -{!../../../docs_src/settings/app01/main.py!} -``` +{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} -/// dica +/// tip | Dica Você também precisa incluir um arquivo `__init__.py` como visto em [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=\_blank}. @@ -276,9 +266,7 @@ Isso é especialmente útil durante os testes, já que é bastante simples sobre Baseando-se no exemplo anterior, seu arquivo `config.py` seria parecido com isso: -```Python hl_lines="10" -{!../../../docs_src/settings/app02/config.py!} -``` +{* ../../docs_src/settings/app02/config.py hl[10] *} Perceba que dessa vez não criamos uma instância padrão `settings = Settings()`. @@ -286,37 +274,9 @@ Perceba que dessa vez não criamos uma instância padrão `settings = Settings() Agora criamos a dependência que retorna um novo objeto `config.Settings()`. -//// tab | Python 3.9+ +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="5 11-12" -{!> ../../../docs_src/settings/app02/main.py!} -``` - -//// - -/// dica +/// tip | Dica Vamos discutir sobre `@lru_cache` logo mais. @@ -326,43 +286,13 @@ Por enquanto, você pode considerar `get_settings()` como uma função normal. E então podemos declarar essas configurações como uma dependência na função de operação da rota e utilizar onde for necessário. -//// tab | Python 3.9+ - -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="16 18-20" -{!> ../../../docs_src/settings/app02/main.py!} -``` - -//// +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} ### Configurações e testes Então seria muito fácil fornecer uma configuração diferente durante a execução dos testes sobrescrevendo a dependência de `get_settings`: -```Python hl_lines="9-10 13 21" -{!../../../docs_src/settings/app02/test_main.py!} -``` +{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} Na sobrescrita da dependência, definimos um novo valor para `admin_email` quando instanciamos um novo objeto `Settings`, e então retornamos esse novo objeto. @@ -374,7 +304,7 @@ Se você tiver muitas configurações que variem bastante, talvez em ambientes d Essa prática é tão comum que possui um nome, essas variáveis de ambiente normalmente são colocadas em um arquivo `.env`, e esse arquivo é chamado de "dotenv". -/// dica +/// tip | Dica Um arquivo iniciando com um ponto final (`.`) é um arquivo oculto em sistemas baseados em Unix, como Linux e MacOS. @@ -384,7 +314,7 @@ Mas um arquivo dotenv não precisa ter esse nome exato. Pydantic suporta a leitura desses tipos de arquivos utilizando uma biblioteca externa. Você pode ler mais em Pydantic Settings: Dotenv (.env) support. -/// dica +/// tip | Dica Para que isso funcione você precisa executar `pip install python-dotenv`. @@ -405,11 +335,9 @@ E então adicionar o seguinte código em `config.py`: //// tab | Pydantic v2 -```Python hl_lines="9" -{!> ../../../docs_src/settings/app03_an/config.py!} -``` +{* ../../docs_src/settings/app03_an/config.py hl[9] *} -/// dica +/// tip | Dica O atributo `model_config` é usado apenas para configuração do Pydantic. Você pode ler mais em Pydantic Model Config. @@ -419,11 +347,9 @@ O atributo `model_config` é usado apenas para configuração do Pydantic. Você //// tab | Pydantic v1 -```Python hl_lines="9-10" -{!> ../../../docs_src/settings/app03_an/config_pv1.py!} -``` +{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} -/// dica +/// tip | Dica A classe `Config` é usada apenas para configuração do Pydantic. Você pode ler mais em Pydantic Model Config. @@ -462,35 +388,7 @@ Iriamos criar um novo objeto a cada requisição, e estaríamos lendo o arquivo Mas como estamos utilizando o decorador `@lru_cache` acima, o objeto `Settings` é criado apenas uma vez, na primeira vez que a função é chamada. ✔️ -//// tab | Python 3.9+ - -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="1 10" -{!> ../../../docs_src/settings/app03/main.py!} -``` - -//// +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} Dessa forma, todas as chamadas da função `get_settings()` nas dependências das próximas requisições, em vez de executar o código interno de `get_settings()` e instanciar um novo objeto `Settings`, irão retornar o mesmo objeto que foi retornado na primeira chamada, de novo e de novo. diff --git a/docs/pt/docs/advanced/sub-applications.md b/docs/pt/docs/advanced/sub-applications.md index 8149edc5aa..efc6bef647 100644 --- a/docs/pt/docs/advanced/sub-applications.md +++ b/docs/pt/docs/advanced/sub-applications.md @@ -10,9 +10,7 @@ Se você precisar ter duas aplicações FastAPI independentes, cada uma com seu Primeiro, crie a aplicação principal, de nível superior, **FastAPI**, e suas *operações de rota*: -```Python hl_lines="3 6-8" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *} ### Sub-aplicação @@ -20,9 +18,7 @@ Em seguida, crie sua sub-aplicação e suas *operações de rota*. Essa sub-aplicação é apenas outra aplicação FastAPI padrão, mas esta é a que será "montada": -```Python hl_lines="11 14-16" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *} ### Monte a sub-aplicação @@ -30,9 +26,7 @@ Na sua aplicação de nível superior, `app`, monte a sub-aplicação, `subapi`. Neste caso, ela será montada no caminho `/subapi`: -```Python hl_lines="11 19" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *} ### Verifique a documentação automática da API diff --git a/docs/pt/docs/advanced/templates.md b/docs/pt/docs/advanced/templates.md index 6d231b3c25..65ff89faee 100644 --- a/docs/pt/docs/advanced/templates.md +++ b/docs/pt/docs/advanced/templates.md @@ -25,9 +25,7 @@ $ pip install jinja2 * Declare um parâmetro `Request` no *path operation* que retornará um template. * Use o `template` que você criou para renderizar e retornar uma `TemplateResponse`, passe o nome do template, o request object, e um "context" dict com pares chave-valor a serem usados dentro do template do Jinja2. -```Python hl_lines="4 11 15-18" -{!../../../docs_src/templates/tutorial001.py!} -``` +{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} /// note @@ -37,13 +35,13 @@ Além disso, em versões anteriores, o objeto `request` era passado como parte d /// -/// tip | "Dica" +/// tip | Dica Ao declarar `response_class=HTMLResponse`, a documentação entenderá que a resposta será HTML. /// -/// note | "Detalhes Técnicos" +/// note | Detalhes Técnicos Você também poderia usar `from starlette.templating import Jinja2Templates`. @@ -56,7 +54,7 @@ Você também poderia usar `from starlette.templating import Jinja2Templates`. Então você pode escrever um template em `templates/item.html`, por exemplo: ```jinja hl_lines="7" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` ### Interpolação de Valores no Template @@ -110,17 +108,17 @@ Por exemplo, com um ID de `42`, isso renderizará: Você também pode usar `url_for()` dentro do template e usá-lo, por examplo, com o `StaticFiles` que você montou com o `name="static"`. ```jinja hl_lines="4" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` Neste exemplo, ele seria vinculado a um arquivo CSS em `static/styles.css` com: ```CSS hl_lines="4" -{!../../../docs_src/templates/static/styles.css!} +{!../../docs_src/templates/static/styles.css!} ``` E como você está usando `StaticFiles`, este arquivo CSS será automaticamente servido pela sua aplicação FastAPI na URL `/static/styles.css`. ## Mais detalhes -Para obter mais detalhes, incluindo como testar templates, consulte a documentação da Starlette sobre templates. +Para obter mais detalhes, incluindo como testar templates, consulte a documentação da Starlette sobre templates. diff --git a/docs/pt/docs/advanced/testing-dependencies.md b/docs/pt/docs/advanced/testing-dependencies.md index 747dd7d06f..3ede4741d9 100644 --- a/docs/pt/docs/advanced/testing-dependencies.md +++ b/docs/pt/docs/advanced/testing-dependencies.md @@ -28,59 +28,9 @@ Para sobrepor a dependência para os testes, você coloca como chave a dependên E então o **FastAPI** chamará a sobreposição no lugar da dependência original. -//// tab | Python 3.10+ +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} -```Python hl_lines="26-27 30" -{!> ../../../docs_src/dependency_testing/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="28-29 32" -{!> ../../../docs_src/dependency_testing/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="29-30 33" -{!> ../../../docs_src/dependency_testing/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Prefira utilizar a versão `Annotated` se possível. - -/// - -```Python hl_lines="24-25 28" -{!> ../../../docs_src/dependency_testing/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Prefira utilizar a versão `Annotated` se possível. - -/// - -```Python hl_lines="28-29 32" -{!> ../../../docs_src/dependency_testing/tutorial001.py!} -``` - -//// - -/// tip | "Dica" +/// tip | Dica Você pode definir uma sobreposição de dependência para uma dependência que é utilizada em qualquer lugar da sua aplicação **FastAPI**. @@ -96,7 +46,7 @@ E então você pode redefinir as suas sobreposições (removê-las) definindo o app.dependency_overrides = {} ``` -/// tip | "Dica" +/// tip | Dica Se você quer sobrepor uma dependência apenas para alguns testes, você pode definir a sobreposição no início do testes (dentro da função de teste) e reiniciá-la ao final (no final da função de teste). diff --git a/docs/pt/docs/advanced/testing-events.md b/docs/pt/docs/advanced/testing-events.md new file mode 100644 index 0000000000..6113c99136 --- /dev/null +++ b/docs/pt/docs/advanced/testing-events.md @@ -0,0 +1,5 @@ +# Testando Eventos: inicialização - encerramento + +Quando você precisa que os seus manipuladores de eventos (`startup` e `shutdown`) sejam executados em seus testes, você pode utilizar o `TestClient` usando a instrução `with`: + +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/pt/docs/advanced/testing-websockets.md b/docs/pt/docs/advanced/testing-websockets.md index f458a05d4e..9b81936552 100644 --- a/docs/pt/docs/advanced/testing-websockets.md +++ b/docs/pt/docs/advanced/testing-websockets.md @@ -4,12 +4,10 @@ Você pode usar o mesmo `TestClient` para testar WebSockets. Para isso, você utiliza o `TestClient` dentro de uma instrução `with`, conectando com o WebSocket: -```Python hl_lines="27-31" -{!../../../docs_src/app_testing/tutorial002.py!} -``` +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} -/// note | "Nota" +/// note | Nota -Para mais detalhes, confira a documentação do Starlette para testar WebSockets. +Para mais detalhes, confira a documentação do Starlette para testar WebSockets. /// diff --git a/docs/pt/docs/advanced/using-request-directly.md b/docs/pt/docs/advanced/using-request-directly.md index 3dd0a8aefe..f4fb0ed8f3 100644 --- a/docs/pt/docs/advanced/using-request-directly.md +++ b/docs/pt/docs/advanced/using-request-directly.md @@ -15,7 +15,7 @@ Porém há situações em que você possa precisar acessar o objeto `Request` di ## Detalhes sobre o objeto `Request` -Como o **FastAPI** é na verdade o **Starlette** por baixo, com camadas de diversas funcionalidades por cima, você pode utilizar o objeto `Request` do Starlette diretamente quando precisar. +Como o **FastAPI** é na verdade o **Starlette** por baixo, com camadas de diversas funcionalidades por cima, você pode utilizar o objeto `Request` do Starlette diretamente quando precisar. Isso significaria também que se você obtiver informações do objeto `Request` diretamente (ler o corpo da requisição por exemplo), as informações não serão validadas, convertidas ou documentadas (com o OpenAPI, para a interface de usuário automática da API) pelo FastAPI. @@ -29,13 +29,11 @@ Vamos imaginar que você deseja obter o endereço de IP/host do cliente dentro d Para isso você precisa acessar a requisição diretamente. -```Python hl_lines="1 7-8" -{!../../../docs_src/using_request_directly/tutorial001.py!} -``` +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} Ao declarar o parâmetro com o tipo sendo um `Request` em sua *função de operação de rota*, o **FastAPI** saberá como passar o `Request` neste parâmetro. -/// tip | "Dica" +/// tip | Dica Note que neste caso, nós estamos declarando o parâmetro da rota ao lado do parâmetro da requisição. @@ -47,9 +45,9 @@ Do mesmo jeito, você pode declarar qualquer outro parâmetro normalmente, e al ## Documentação do `Request` -Você pode ler mais sobre os detalhes do objeto `Request` no site da documentação oficial do Starlette.. +Você pode ler mais sobre os detalhes do objeto `Request` no site da documentação oficial do Starlette.. -/// note | "Detalhes Técnicos" +/// note | Detalhes Técnicos Você também pode utilizar `from starlette.requests import Request`. diff --git a/docs/pt/docs/advanced/websockets.md b/docs/pt/docs/advanced/websockets.md new file mode 100644 index 0000000000..721c0b403a --- /dev/null +++ b/docs/pt/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSockets + +Você pode usar WebSockets com **FastAPI**. + +## Instalando `WebSockets` + +Garanta que você criou um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, o ativou e instalou o `websockets`: + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## Cliente WebSockets + +### Em produção + +Em seu sistema de produção, você provavelmente tem um frontend criado com um framework moderno como React, Vue.js ou Angular. + +E para comunicar usando WebSockets com seu backend, você provavelmente usaria as utilidades do seu frontend. + +Ou você pode ter um aplicativo móvel nativo que se comunica diretamente com seu backend WebSocket, em código nativo. + +Ou você pode ter qualquer outra forma de comunicar com o endpoint WebSocket. + +--- + +Mas para este exemplo, usaremos um documento HTML muito simples com algum JavaScript, tudo dentro de uma string longa. + +Esse, é claro, não é o ideal e você não o usaria para produção. + +Na produção, você teria uma das opções acima. + +Mas é a maneira mais simples de focar no lado do servidor de WebSockets e ter um exemplo funcional: + +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} + +## Criando um `websocket` + +Em sua aplicação **FastAPI**, crie um `websocket`: + +{*../../docs_src/websockets/tutorial001.py hl[46:47]*} + +/// note | Detalhes Técnicos + +Você também poderia usar `from starlette.websockets import WebSocket`. + +A **FastAPI** fornece o mesmo `WebSocket` diretamente apenas como uma conveniência para você, o desenvolvedor. Mas ele vem diretamente do Starlette. + +/// + +## Aguardar por mensagens e enviar mensagens + +Em sua rota WebSocket você pode esperar (`await`) por mensagens e enviar mensagens. + +{*../../docs_src/websockets/tutorial001.py hl[48:52]*} + +Você pode receber e enviar dados binários, de texto e JSON. + +## Tente você mesmo + +Se seu arquivo for nomeado `main.py`, execute sua aplicação com: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Abra seu navegador em: http://127.0.0.1:8000. + +Você verá uma página simples como: + + + +Você pode digitar mensagens na caixa de entrada e enviá-las: + + + +E sua aplicação **FastAPI** com WebSockets responderá de volta: + + + +Você pode enviar (e receber) muitas mensagens: + + + +E todas elas usarão a mesma conexão WebSocket. + +## Usando `Depends` e outros + +Nos endpoints WebSocket você pode importar do `fastapi` e usar: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +Eles funcionam da mesma forma que para outros endpoints FastAPI/*operações de rota*: + +{*../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82]*} + +/// info | Informação + +Como isso é um WebSocket, não faz muito sentido levantar uma `HTTPException`, em vez disso levantamos uma `WebSocketException`. + +Você pode usar um código de fechamento dos códigos válidos definidos na especificação. + +/// + +### Tente os WebSockets com dependências + +Se seu arquivo for nomeado `main.py`, execute sua aplicação com: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Abrar seu browser em: http://127.0.0.1:8000. + +Lá você pode definir: + +* O "Item ID", usado na rota. +* O "Token" usado como um parâmetro de consulta. + +/// tip | Dica + +Perceba que a consulta `token` será manipulada por uma dependência. + +/// + +Com isso você pode conectar o WebSocket e então enviar e receber mensagens: + + + +## Lidando com desconexões e múltiplos clientes + +Quando uma conexão WebSocket é fechada, o `await websocket.receive_text()` levantará uma exceção `WebSocketDisconnect`, que você pode então capturar e lidar como neste exemplo. + +{*../../docs_src/websockets/tutorial003_py39.py hl[79:81]*} + +Para testar: + +* Abrar o aplicativo com várias abas do navegador. +* Escreva mensagens a partir delas. +* Então feche uma das abas. + +Isso levantará a exceção `WebSocketDisconnect`, e todos os outros clientes receberão uma mensagem como: + +``` +Client #1596980209979 left the chat +``` + +/// tip | Dica + +O app acima é um exemplo mínimo e simples para demonstrar como lidar e transmitir mensagens para várias conexões WebSocket. + +Mas tenha em mente que, como tudo é manipulado na memória, em uma única lista, ele só funcionará enquanto o processo estiver em execução e só funcionará com um único processo. + +Se você precisa de algo fácil de integrar com o FastAPI, mas que seja mais robusto, suportado por Redis, PostgreSQL ou outros, verifique o encode/broadcaster. + +/// + +## Mais informações + +Para aprender mais sobre as opções, verifique a documentação do Starlette para: + +* A classe `WebSocket`. +* Manipulação de WebSockets baseada em classes. diff --git a/docs/pt/docs/advanced/wsgi.md b/docs/pt/docs/advanced/wsgi.md index 2c7ac1ffed..a36261e5ef 100644 --- a/docs/pt/docs/advanced/wsgi.md +++ b/docs/pt/docs/advanced/wsgi.md @@ -12,9 +12,7 @@ Em seguinda, encapsular a aplicação WSGI (e.g. Flask) com o middleware. E então **"montar"** em um caminho de rota. -```Python hl_lines="2-3 23" -{!../../../docs_src/wsgi/tutorial001.py!} -``` +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,23] *} ## Conferindo diff --git a/docs/pt/docs/alternatives.md b/docs/pt/docs/alternatives.md index d3a304fa78..66cf3fe127 100644 --- a/docs/pt/docs/alternatives.md +++ b/docs/pt/docs/alternatives.md @@ -30,13 +30,13 @@ Ele é utilizado por muitas companhias incluindo Mozilla, Red Hat e Eventbrite. Ele foi um dos primeiros exemplos de **documentação automática de API**, e essa foi especificamente uma das primeiras idéias que inspirou "a busca por" **FastAPI**. -/// note | "Nota" +/// note | Nota Django REST Framework foi criado por Tom Christie. O mesmo criador de Starlette e Uvicorn, nos quais **FastAPI** é baseado. /// -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Ter uma documentação automática da API em interface web. @@ -56,7 +56,7 @@ Esse desacoplamento de partes, e sendo um "microframework" que pode ser extendid Dada a simplicidade do Flask, parecia uma ótima opção para construção de APIs. A próxima coisa a procurar era um "Django REST Framework" para Flask. -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Ser um microframework. Fazer ele fácil para misturar e combinar com ferramentas e partes necessárias. @@ -98,7 +98,7 @@ def read_url(): Veja as similaridades em `requests.get(...)` e `@app.get(...)`. -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para * Ter uma API simples e intuitiva. * Utilizar nomes de métodos HTTP (operações) diretamente, de um jeito direto e intuitivo. @@ -118,7 +118,7 @@ Em algum ponto, Swagger foi dado para a Fundação Linux, e foi renomeado OpenAP Isso acontece porquê quando alguém fala sobre a versão 2.0 é comum dizer "Swagger", e para a versão 3+, "OpenAPI". -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Adotar e usar um padrão aberto para especificações API, ao invés de algum esquema customizado. @@ -147,7 +147,7 @@ Esses recursos são o que Marshmallow foi construído para fornecer. Ele é uma Mas ele foi criado antes da existência do _type hints_ do Python. Então, para definir todo o _schema_ você precisa utilizar específicas ferramentas e classes fornecidas pelo Marshmallow. -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Usar código para definir "schemas" que forneçam, automaticamente, tipos de dados e validação. @@ -169,7 +169,7 @@ Webargs foi criado pelos mesmos desenvolvedores do Marshmallow. /// -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Ter validação automática de dados vindos de requisições. @@ -199,7 +199,7 @@ APISpec foi criado pelos mesmos desenvolvedores do Marshmallow. /// -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Dar suporte a padrões abertos para APIs, OpenAPI. @@ -231,7 +231,7 @@ Flask-apispec foi criado pelos mesmos desenvolvedores do Marshmallow. /// -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Gerar _schema_ OpenAPI automaticamente, a partir do mesmo código que define serialização e validação. @@ -251,7 +251,7 @@ Mas como os dados TypeScript não são preservados após a compilação para o J Ele também não controla modelos aninhados muito bem. Então, se o corpo JSON na requisição for um objeto JSON que contém campos internos que contém objetos JSON aninhados, ele não consegue ser validado e documentado apropriadamente. -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Usar tipos Python para ter um ótimo suporte do editor. @@ -263,7 +263,7 @@ Ter um sistema de injeção de dependência poderoso. Achar um jeito de minimiza Ele foi um dos primeiros frameworks Python extremamente rápido baseado em `asyncio`. Ele foi feito para ser muito similar ao Flask. -/// note | "Detalhes técnicos" +/// note | Detalhes técnicos Ele utiliza `uvloop` ao invés do '_loop_' `asyncio` padrão do Python. É isso que deixa ele tão rápido. @@ -271,7 +271,7 @@ Ele claramente inspirou Uvicorn e Starlette, que são atualmente mais rápidos q /// -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Achar um jeito de ter uma performance insana. @@ -289,7 +289,7 @@ Ele é projetado para ter funções que recebem dois parâmetros, uma "requisiç Então, validação de dados, serialização e documentação tem que ser feitos no código, não automaticamente. Ou eles terão que ser implementados como um framework acima do Falcon, como o Hug. Essa mesma distinção acontece em outros frameworks que são inspirados pelo design do Falcon, tendo um objeto de requisição e um objeto de resposta como parâmetros. -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Achar jeitos de conseguir melhor performance. @@ -315,7 +315,7 @@ O sistema de injeção de dependência exige pré-registro das dependências e a Rotas são declaradas em um único lugar, usando funções declaradas em outros lugares (ao invés de usar decoradores que possam ser colocados diretamente acima da função que controla o _endpoint_). Isso é mais perto de como o Django faz isso do que como Flask (e Starlette) faz. Ele separa no código coisas que são relativamente amarradas. -/// check | "**FastAPI** inspirado para" +/// check | **FastAPI** inspirado para Definir validações extras para tipos de dados usando valores "padrão" de atributos dos modelos. Isso melhora o suporte do editor, e não estava disponível no Pydantic antes. @@ -323,7 +323,7 @@ Isso na verdade inspirou a atualização de partes do Pydantic, para dar suporte /// -### Hug +### Hug Hug foi um dos primeiros frameworks a implementar a declaração de tipos de parâmetros usando Python _type hints_. Isso foi uma ótima idéia que inspirou outras ferramentas a fazer o mesmo. @@ -343,7 +343,7 @@ Hug foi criado por Timothy Crosley, o mesmo criador do Starlette +### Starlette Starlette é um framework/caixa de ferramentas ASGI peso leve, o que é ideal para construir serviços assíncronos de alta performance. @@ -447,7 +447,7 @@ Mas ele não fornece validação de dados automática, serialização e document Essa é uma das principais coisas que **FastAPI** adiciona no topo, tudo baseado em Python _type hints_ (usando Pydantic). Isso, mais o sistema de injeção de dependência, utilidades de segurança, geração de _schema_ OpenAPI, etc. -/// note | "Detalhes Técnicos" +/// note | Detalhes Técnicos ASGI é um novo "padrão" sendo desenvolvido pelos membros do time central do Django. Ele ainda não está como "Padrão Python" (PEP), embora eles estejam em processo de fazer isso. @@ -455,7 +455,7 @@ No entanto, ele já está sendo utilizado como "padrão" por diversas ferramenta /// -/// check | "**FastAPI** usa isso para" +/// check | **FastAPI** usa isso para Controlar todas as partes web centrais. Adiciona recursos no topo. @@ -465,7 +465,7 @@ Então, qualquer coisa que você faz com Starlette, você pode fazer diretamente /// -### Uvicorn +### Uvicorn Uvicorn é um servidor ASGI peso leve, construído com uvloop e httptools. @@ -473,7 +473,7 @@ Ele não é um framework web, mas sim um servidor. Por exemplo, ele não fornece Ele é o servidor recomendado para Starlette e **FastAPI**. -/// check | "**FastAPI** recomenda isso para" +/// check | **FastAPI** recomenda isso para O principal servidor web para rodar aplicações **FastAPI**. diff --git a/docs/pt/docs/async.md b/docs/pt/docs/async.md index 0d6bdbf0e5..c70924ea52 100644 --- a/docs/pt/docs/async.md +++ b/docs/pt/docs/async.md @@ -40,7 +40,7 @@ def results(): --- -Se sua aplicação (de alguma forma) não tem que se comunicar com nada mais e tem que esperar que o respondam, use `async def`. +Se sua aplicação (de alguma forma) não tem que se comunicar com nada mais e esperar que o respondam, use `async def`. --- @@ -52,7 +52,7 @@ Se você simplesmente não sabe, use apenas `def`. De qualquer forma, em ambos os casos acima, FastAPI irá trabalhar assincronamente e ser extremamente rápido. -Seguindo os passos acima, ele será capaz de fazer algumas otimizações de performance. +Mas, seguindo os passos acima, ele será capaz de fazer algumas otimizações de performance. ## Detalhes Técnicos @@ -66,36 +66,36 @@ Vamos ver aquela frase por partes na seção abaixo: ## Código assíncrono -Código assíncrono apenas significa que a linguagem 💬 tem um jeito de dizer para o computador / programa 🤖 que em certo ponto, ele 🤖 terá que esperar por *algo* para finalizar em outro lugar. Vamos dizer que esse *algo* seja chamado "arquivo lento" 📝. +Código assíncrono apenas significa que a linguagem 💬 tem um jeito de dizer para o computador / programa 🤖 que em certo ponto do código, ele 🤖 terá que esperar *algo* finalizar em outro lugar. Vamos dizer que esse *algo* seja chamado "arquivo lento" 📝. -Então, durante esse tempo, o computador pode ir e fazer outro trabalho, enquanto o "arquivo lento" 📝 termine. +Então, durante esse tempo, o computador pode ir e fazer outro trabalho, enquanto o "arquivo lento" 📝 termina. -Então o computador / programa 🤖 irá voltar toda hora que tiver uma chance porquê ele ainda está esperando o "arquivo lento", ou ele 🤖 nunca irá terminar todo o trabalho que tem até esse ponto. E ele 🤖 irá ver se alguma das tarefas que estava esperando já terminaram, fazendo o que quer que tinham que fazer. +Então o computador / programa 🤖 irá voltar sempre que tiver uma chance, seja porque ele está esperando novamente, ou quando ele 🤖 terminar todo o trabalho que tem até esse ponto. E ele 🤖 irá ver se alguma das tarefas que estava esperando já terminaram de fazer o que quer que tinham que fazer. -Depois, ele 🤖 pega a primeira tarefa para finalizar (vamos dizer, nosso "arquivo lento" 📝) e continua o que ele tem que fazer com isso. +Depois, ele 🤖 pega a primeira tarefa para finalizar (vamos dizer, nosso "arquivo lento" 📝) e continua o que tem que fazer com ela. -Esse "esperar por algo" normalmente se refere a operações I/O que são relativamente "lentas" (comparadas a velocidade do processador e da memória RAM), como esperar por: +Esse "esperar por algo" normalmente se refere a operações I/O que são relativamente "lentas" (comparadas à velocidade do processador e da memória RAM), como esperar por: * dados do cliente para serem enviados através da rede -* dados enviados pelo seu programa para serem recebidos pelo clente através da rede -* conteúdo de um arquivo no disco pra ser lido pelo sistema e entregar ao seu programa +* dados enviados pelo seu programa serem recebidos pelo clente através da rede +* conteúdo de um arquivo no disco ser lido pelo sistema e entregue ao seu programa * conteúdo que seu programa deu ao sistema para ser escrito no disco -* uma operação remota API -* uma operação no banco de dados para finalizar -* uma solicitação no banco de dados esperando o retorno do resultado +* uma operação em uma API remota +* uma operação no banco de dados finalizar +* uma solicitação no banco de dados retornar o resultado * etc. -Enquanto o tempo de execução é consumido mais pela espera das operações I/O, essas operações são chamadas de operações "limitadas por I/O". +Quanto o tempo de execução é consumido majoritariamente pela espera de operações I/O, essas operações são chamadas operações "limitadas por I/O". -Isso é chamado de "assíncrono" porquê o computador / programa não tem que ser "sincronizado" com a tarefa lenta, esperando pelo exato momento que a tarefa finalize, enquanto não faz nada, para ser capaz de pegar o resultado da tarefa e dar continuidade ao trabalho. +Isso é chamado de "assíncrono" porque o computador / programa não tem que ser "sincronizado" com a tarefa lenta, esperando pelo momento exato em que a tarefa finaliza, enquanto não faz nada, para ser capaz de pegar o resultado da tarefa e dar continuidade ao trabalho. -Ao invés disso, sendo um sistema "assíncrono", uma vez finalizada, a tarefa pode esperar um pouco (alguns microssegundos) para que o computador / programa finalize o que quer que esteja fazendo,e então volte para pegar o resultado e continue trabalhando com ele. +Ao invés disso, sendo um sistema "assíncrono", uma vez finalizada, a tarefa pode esperar na fila um pouco (alguns microssegundos) para que o computador / programa finalize o que quer que esteja fazendo, e então volte para pegar o resultado e continue trabalhando com ele. -Para "síncrono" (contrário de "assíncrono") também é utilizado o termo "sequencial", porquê o computador / programa segue todos os passos, na sequência, antes de trocar para uma tarefa diferente, mesmo se alguns passos envolvam esperar. +Para "síncrono" (contrário de "assíncrono") também é utilizado o termo "sequencial", porquê o computador / programa segue todos os passos, em sequência, antes de trocar para uma tarefa diferente, mesmo se alguns passos envolvam esperar. ### Concorrência e hambúrgueres -Essa idéia de código **assíncrono** descrito acima é algo às vezes chamado de **"concorrência"**. E é diferente de **"paralelismo"**. +Essa idéia de código **assíncrono** descrita acima é às vezes chamado de **"concorrência"**. Isso é diferente de **"paralelismo"**. **Concorrência** e **paralelismo** ambos são relacionados a "diferentes coisas acontecendo mais ou menos ao mesmo tempo". @@ -105,117 +105,115 @@ Para ver essa diferença, imagine a seguinte história sobre hambúrgueres: ### Hambúrgueres concorrentes -Você vai com seu _crush_ :heart_eyes: na lanchonete, fica na fila enquanto o caixa pega os pedidos das pessoas na sua frente. +Você vai com seu _crush_ na lanchonete, e fica na fila enquanto o caixa pega os pedidos das pessoas na sua frente. 😍 -Então chega a sua vez, você pede dois saborosos hambúrgueres para você e seu _crush_ :heart_eyes:. +Então chega a sua vez, você pede dois saborosos hambúrgueres para você e seu _crush_. 🍔🍔 -Você paga. +O caixa diz alguma coisa para o cozinheiro na cozinha para que eles saivam que têm que preparar seus hambúrgueres (mesmo que ele esteja atualmente preparando os lanches dos outros clientes). -O caixa diz alguma coisa para o cara na cozinha para que ele tenha que preparar seus hambúrgueres (mesmo embora ele esteja preparando os lanches dos outros clientes). +Você paga. 💸 O caixa te entrega seu número de chamada. -Enquanto você espera, você vai com seu _crush_ :heart_eyes: e pega uma mesa, senta e conversa com seu _crush_ :heart_eyes: por um bom tempo (como seus hambúrgueres são muito saborosos, leva um tempo para serem preparados). +Enquanto você espera, você vai com seu _crush_ e pega uma mesa, senta e conversa com seu _crush_ por um bom tempo (já que seus hambúrgueres são muito saborosos, e leva um tempo para serem preparados). -Enquanto você está sentado na mesa com seu _crush_ :heart_eyes:, esperando os hambúrgueres, você pode gastar o tempo admirando como lindo, maravilhoso e esperto é seu _crush_ :heart_eyes:. +Já que você está sentado na mesa com seu _crush_, esperando os hambúrgueres, você pode passar esse tempo admirando o quão lindo, maravilhoso e esperto é seu _crush_ ✨😍✨. -Enquanto espera e conversa com seu _crush_ :heart_eyes:, de tempos em tempos, você verifica o número de chamada exibido no balcão para ver se já é sua vez. +Enquanto espera e conversa com seu _crush_, de tempos em tempos, você verifica o número da chamada exibido no balcão para ver se já é sua vez. -Então a certo ponto, é finalmente sua vez. Você vai no balcão, pega seus hambúrgueres e volta para a mesa. +Então em algum momento, é finalmente sua vez. Você vai ao balcão, pega seus hambúrgueres e volta para a mesa. -Você e seu _crush_ :heart_eyes: comem os hambúrgueres e aproveitam o tempo. +Você e seu _crush_ comem os hambúrgueres e aproveitam o tempo. ✨ --- -Imagine que você seja o computador / programa nessa história. +Imagine que você seja o computador / programa nessa história. -Enquanto você está na fila, tranquilo, esperando por sua vez, não está fazendo nada "produtivo". Mas a fila é rápida porquê o caixa só está pegando os pedidos, então está tudo bem. +Enquanto você está na fila, você está somente ocioso 😴, esperando por sua vez, sem fazer nada muito "produtivo". Mas a fila é rápida porque o caixa só está pegando os pedidos (não os preparando), então está tudo bem. -Então, quando é sua vez, você faz o trabalho "produtivo" de verdade, você processa o menu, decide o que quer, pega a escolha de seu _crush_ :heart_eyes:, paga, verifica se entregou o valor correto em dinheiro ou cartão de crédito, verifica se foi cobrado corretamente, verifica se seu pedido está correto etc. +Então, quando é sua vez, você faz trabalho realmente "produtivo", você processa o menu, decide o que quer, pega a escolha de seu _crush_, paga, verifica se entregou o cartão ou a cédula correta, verifica se foi cobrado corretamente, verifica se seu pedido está correto etc. -Mas então, embora você ainda não tenha os hambúrgueres, seu trabalho no caixa está "pausado", porquê você tem que esperar seus hambúrgueres estarem prontos. +Mas então, embora você ainda não tenha os hambúrgueres, seu trabalho no caixa está "pausado" ⏸, porque você tem que esperar 🕙 seus hambúrgueres ficarem prontos. -Mas enquanto você se afasta do balcão e senta na mesa com o número da sua chamada, você pode trocar sua atenção para seu _crush_ :heart_eyes:, e "trabalhar" nisso. Então você está novamente fazendo algo muito "produtivo", como flertar com seu _crush_ :heart_eyes:. +Contudo, à medida que você se afasta do balcão e senta na mesa, com um número para sua chamada, você pode trocar 🔀 sua atenção para seu _crush_, e "trabalhar" ⏯ 🤓 nisso. Então você está novamente fazendo algo muito "produtivo", como flertar com seu _crush_ 😍. -Então o caixa diz que "seus hambúrgueres estão prontos" colocando seu número no balcão, mas você não corre que nem um maluco imediatamente quando o número exibido é o seu. Você sabe que ninguém irá roubar seus hambúrgueres porquê você tem o número de chamada, e os outros tem os números deles. +Então o caixa 💁 diz que "seus hambúrgueres estão prontos" colocando seu número no balcão, mas você não corre que nem um maluco imediatamente quando o número exibido é o seu. Você sabe que ninguém irá roubar seus hambúrgueres porque você tem o seu número da chamada, e os outros têm os deles. -Então você espera que seu _crush_ :heart_eyes: termine a história que estava contando (terminar o trabalho atual / tarefa sendo processada), sorri gentilmente e diz que você está indo buscar os hambúrgueres. +Então você espera seu _crush_ terminar a história que estava contando (terminar o trabalho atual ⏯ / tarefa sendo processada 🤓), sorri gentilmente e diz que você está indo buscar os hambúrgueres. -Então você vai no balcão, para a tarefa inicial que agora está finalizada, pega os hambúrgueres, e leva para a mesa. Isso finaliza esse passo / tarefa da interação com o balcão. Agora é criada uma nova tarefa, "comer hambúrgueres", mas a tarefa anterior, "pegar os hambúrgueres" já está finalizada. +Então você vai ao balcão 🔀, para a tarefa inicial que agora está finalizada⏯, pega os hambúrgueres, agradece, e leva-os para a mesa. Isso finaliza esse passo / tarefa da interação com o balcão ⏹. Isso, por sua vez, cria uma nova tarefa, a de "comer hambúrgueres" 🔀 ⏯, mas a tarefa anterior de "pegar os hambúrgueres" já está finalizada ⏹. ### Hambúrgueres paralelos -Você vai com seu _crush_ :heart_eyes: em uma lanchonete paralela. +Agora vamos imaginar que esses não são "Hambúrgueres Concorrentes", e sim "Hambúrgueres Paralelos" -Você fica na fila enquanto alguns (vamos dizer 8) caixas pegam os pedidos das pessoas na sua frente. +Você vai com seu _crush_ na lanchonete paralela. -Todo mundo antes de você está esperando pelos hambúrgueres estarem prontos antes de deixar o caixa porquê cada um dos 8 caixas vai e prepara o hambúrguer antes de pegar o próximo pedido. +Você fica na fila enquanto vários (vamos dizer 8) caixas que também são cozinheiros pegam os pedidos das pessoas na sua frente. -Então é finalmente sua vez, e pede 2 hambúrgueres muito saborosos para você e seu _crush_ :heart_eyes:. +Todo mundo na sua frente está esperando seus hambúrgueres ficarem prontos antes de deixar o caixa porque cada um dos 8 caixas vai e prepara o hambúrguer logo após receber o pedido, antes de pegar o próximo pedido. -Você paga. +Então é finalmente sua vez, você pede 2 hambúrgueres muito saborosos para você e seu _crush_. + +Você paga 💸. O caixa vai para a cozinha. -Você espera, na frente do balcão, para que ninguém pegue seus hambúrgueres antes de você, já que não tem números de chamadas. +Você espera, na frente do balcão 🕙, para que ninguém pegue seus hambúrgueres antes de você, já que não tem números de chamadas. -Enquanto você e seu _crush_ :heart_eyes: estão ocupados não permitindo que ninguém passe a frente e pegue seus hambúrgueres assim que estiverem prontos, você não pode dar atenção ao seu _crush_ :heart_eyes:. +Como você e seu _crush_ estão ocupados não permitindo que ninguém passe na frente e pegue seus hambúrgueres assim que estiverem prontos, você não pode dar atenção ao seu _crush_. 😞 -Isso é trabalho "síncrono", você está "sincronizado" com o caixa / cozinheiro. Você tem que esperar e estar lá no exato momento que o caixa / cozinheiro terminar os hambúrgueres e dá-los a você, ou então, outro alguém pode pegá-los. +Isso é trabalho "síncrono", você está "sincronizado" com o caixa / cozinheiro👨‍🍳. Você tem que esperar 🕙 e estar lá no exato momento que o caixa / cozinheiro 👨‍🍳 terminar os hambúrgueres e os der a você, ou então, outro alguém pode pegá-los. -Então seu caixa / cozinheiro finalmente volta com seus hambúrgueres, depois de um longo tempo esperando por eles em frente ao balcão. +Então seu caixa / cozinheiro 👨‍🍳 finalmente volta com seus hambúrgueres, depois de um longo tempo esperando 🕙 por eles em frente ao balcão. -Você pega seus hambúrgueres e vai para a mesa com seu _crush_ :heart_eyes:. +Você pega seus hambúrgueres e vai para a mesa com seu _crush_. -Vocês comem os hambúrgueres, e o trabalho está terminado. +Vocês comem os hambúrgueres, e o trabalho está terminado. ⏹ -Não houve muita conversa ou flerte já que a maior parte do tempo foi gasto esperando os lanches na frente do balcão. +Não houve muita conversa ou flerte já que a maior parte do tempo foi gasto esperando 🕙 na frente do balcão. 😞 --- -Nesse cenário dos hambúrgueres paralelos, você é um computador / programa com dois processadores (você e seu _crush_ :heart_eyes:), ambos esperando e dedicando a atenção de estar "esperando no balcão" por um bom tempo. +Nesse cenário dos hambúrgueres paralelos, você é um computador / programa com dois processadores (você e seu _crush_), ambos esperando 🕙 e dedicando sua atenção ⏯ "esperando no balcão" 🕙 por um bom tempo. -A lanchonete paralela tem 8 processadores (caixas / cozinheiros). Enquanto a lanchonete dos hambúrgueres concorrentes tinham apenas 2 (um caixa e um cozinheiro). +A lanchonete paralela tem 8 processadores (caixas / cozinheiros), enquanto a lanchonete dos hambúrgueres concorrentes tinha apenas 2 (um caixa e um cozinheiro). -Ainda assim, a última experiência não foi a melhor. +Ainda assim, a experiência final não foi a melhor. 😞 --- -Essa poderia ser a história paralela equivalente aos hambúrgueres. +Essa seria o equivalente paralelo à histório dos hambúrgueres. 🍔 Para um exemplo "mais real", imagine um banco. -Até recentemente, a maioria dos bancos tinha muitos caixas e uma grande fila. +Até recentemente, a maioria dos bancos tinham muitos caixas 👨‍💼👨‍💼👨‍💼👨‍💼 e uma grande fila 🕙🕙🕙🕙🕙🕙🕙🕙. -Todos os caixas fazendo todo o trabalho, um cliente após o outro. +Todos os caixas fazendo todo o trabalho, um cliente após o outro 👨‍💼⏯. -E você tinha que esperar na fila por um longo tempo ou poderia perder a vez. +E você tinha que esperar 🕙 na fila por um longo tempo ou poderia perder a vez. -Você provavelmente não gostaria de levar seu _crush_ :heart_eyes: com você para um rolezinho no banco. +Você provavelmente não gostaria de levar seu _crush_ 😍 com você para um rolezinho no banco 🏦. ### Conclusão dos hambúrgueres -Nesse cenário dos "hambúrgueres com seu _crush_ :heart_eyes:", como tem muita espera, faz mais sentido ter um sistema concorrente. +Nesse cenário dos "hambúrgueres com seu _crush_", como tem muita espera, faz mais sentido ter um sistema concorrente ⏸🔀⏯. Esse é o caso da maioria das aplicações web. -Geralmente são muitos usuários, e seu servidor está esperando pelas suas conexões não tão boas para enviar as requisições. +Muitos, muitos usuários, mas seu servidor está esperando 🕙 pela sua conexão não tão boa enviar suas requisições. -E então esperando novamente pelas respostas voltarem. +E então esperando 🕙 novamente as respostas voltarem. -Essa "espera" é medida em microssegundos, e ainda assim, somando tudo, é um monte de espera no final. +Essa "espera" 🕙 é medida em microssegundos, mas ainda assim, somando tudo, é um monte de espera no final. -Por isso que faz muito mais sentido utilizar código assíncrono para APIs web. +Por isso que faz bastante sentido utilizar código assíncrono ⏸🔀⏯ para APIs web. -A maioria dos frameworks Python existentes mais populares (incluindo Flask e Django) foram criados antes que os novos recursos assíncronos existissem em Python. Então, os meios que eles podem ser colocados em produção para suportar execução paralela mais a forma antiga de execução assíncrona não são tão poderosos quanto as novas capacidades. - -Mesmo embora a especificação principal para web assíncrono em Python (ASGI) foi desenvolvida no Django, para adicionar suporte para WebSockets. - -Esse tipo de assincronicidade é o que fez NodeJS popular (embora NodeJS não seja paralelo) e que essa seja a força do Go como uma linguagem de programa. +Esse tipo de assincronicidade é o que fez NodeJS popular (embora NodeJS não seja paralelo) e essa é a força do Go como uma linguagem de programação. E esse é o mesmo nível de performance que você tem com o **FastAPI**. -E como você pode ter paralelismo e sincronicidade ao mesmo tempo, você tem uma maior performance do que a maioria dos frameworks NodeJS testados e lado a lado com Go, que é uma linguagem compilada próxima ao C (tudo graças ao Starlette). +E como você pode ter paralelismo e assincronicidade ao mesmo tempo, você tem uma maior performance do que a maioria dos frameworks NodeJS testados e lado a lado com Go, que é uma linguagem compilada, mais próxima ao C (tudo graças ao Starlette). ### Concorrência é melhor que paralelismo? @@ -225,64 +223,64 @@ Concorrência é diferente de paralelismo. E é melhor em cenários **específic Então, para equilibrar tudo, imagine a seguinte historinha: -> Você tem que limpar uma grande casa suja. +> Você tem que limpar uma casa grande e suja. *Sim, essa é toda a história*. --- -Não há espera em lugar algum, apenas um monte de trabalho para ser feito, em múltiplos cômodos da casa. +Não há espera 🕙 em lugar algum, apenas um monte de trabalho para ser feito, em múltiplos cômodos da casa. -Você poderia ter chamadas como no exemplo dos hambúrgueres, primeiro a sala de estar, então a cozinha, mas você não está esperando por nada, apenas limpar e limpar, as chamadas não afetariam em nada. +Você poderia ter turnos como no exemplo dos hambúrgueres, primeiro a sala de estar, então a cozinha, mas como você não está esperando por nada, apenas limpando e limpando, as chamadas não afetariam em nada. -Levaria o mesmo tempo para finalizar com ou sem chamadas (concorrência) e você teria feito o mesmo tanto de trabalho. +Levaria o mesmo tempo para finalizar com ou sem turnos (concorrência) e você teria feito o mesmo tanto de trabalho. Mas nesse caso, se você trouxesse os 8 ex-caixas / cozinheiros / agora-faxineiros, e cada um deles (mais você) pudessem dividir a casa para limpá-la, vocês fariam toda a limpeza em **paralelo**, com a ajuda extra, e terminariam muito mais cedo. Nesse cenário, cada um dos faxineiros (incluindo você) poderia ser um processador, fazendo a sua parte do trabalho. -E a maior parte do tempo de execução é tomada por trabalho (ao invés de ficar esperando), e o trabalho em um computador é feito pela CPU, que podem gerar problemas que são chamados de "limite de CPU". +E a maior parte do tempo de execução é tomada por trabalho real (ao invés de ficar esperando), e o trabalho em um computador é feito pela CPU. Eles chamam esses problemas de "limitados por CPU". --- -Exemplos comuns de limite de CPU são coisas que exigem processamento matemático complexo. +Exemplos comuns de operações limitadas por CPU são coisas que exigem processamento matemático complexo. Por exemplo: * **Processamento de áudio** ou **imagem** -* **Visão do Computador**: uma imagem é composta por milhões de pixels, cada pixel tem 3 valores (cores, processamento que normalmente exige alguma computação em todos esses pixels ao mesmo tempo) +* **Visão Computacional**: uma imagem é composta por milhões de pixels, cada pixel tem 3 valores / cores, processar isso normalmente exige alguma computação em todos esses pixels ao mesmo tempo -* **Machine Learning**: Normalmente exige muita multiplicação de matrizes e vetores. Pense numa grande folha de papel com números e multiplicando todos eles juntos e ao mesmo tempo. +* **Machine Learning**: Normalmente exige muita multiplicação de matrizes e vetores. Pense numa grande planilha com números e em multiplicar todos eles juntos e ao mesmo tempo. -* **Deep Learning**: Esse é um subcampo do Machine Learning, então o mesmo se aplica. A diferença é que não há apenas uma grande folha de papel com números para multiplicar, mas um grande conjunto de folhas de papel, e em muitos casos, você utiliza um processador especial para construir e/ou usar modelos. +* **Deep Learning**: Esse é um subcampo do Machine Learning, então, o mesmo se aplica. A diferença é que não há apenas uma grande planilha com números para multiplicar, mas um grande conjunto delas, e em muitos casos, você utiliza um processador especial para construir e/ou usar esses modelos. ### Concorrência + Paralelismo: Web + Machine learning Com **FastAPI** você pode levar a vantagem da concorrência que é muito comum para desenvolvimento web (o mesmo atrativo de NodeJS). -Mas você também pode explorar os benefícios do paralelismo e multiprocessamento (tendo múltiplos processadores rodando em paralelo) para trabalhos pesados que geram **limite de CPU** como aqueles em sistemas de Machine Learning. +Mas você também pode explorar os benefícios do paralelismo e multiprocessamento (tendo múltiplos processadores rodando em paralelo) para trabalhos **limitados por CPU** como aqueles em sistemas de Machine Learning. -Isso, mais o simples fato que Python é a principal linguagem para **Data Science**, Machine Learning e especialmente Deep Learning, faz do FastAPI uma ótima escolha para APIs web e aplicações com Data Science / Machine Learning (entre muitas outras). +Isso, somado ao simples fato que Python é a principal linguagem para **Data Science**, Machine Learning e especialmente Deep Learning, faz do FastAPI uma ótima escolha para APIs web e aplicações com Data Science / Machine Learning (entre muitas outras). Para ver como alcançar esse paralelismo em produção veja a seção sobre [Deployment](deployment/index.md){.internal-link target=_blank}. ## `async` e `await` -Versões modernas do Python tem um modo muito intuitivo para definir código assíncrono. Isso faz parecer normal o código "sequencial" e fazer o "esperar" para você nos momentos certos. +Versões modernas do Python têm um modo muito intuitivo para definir código assíncrono. Isso faz parecer do mesmo jeito do código normal "sequencial" e fazer a "espera" para você nos momentos certos. -Quando tem uma operação que exigirá espera antes de dar os resultados e tem suporte para esses recursos Python, você pode escrever assim: +Quando tem uma operação que exigirá espera antes de dar os resultados e tem suporte para esses novos recursos do Python, você pode escrever assim: ```Python burgers = await get_burgers(2) ``` -A chave aqui é o `await`. Ele diz ao Python que ele tem que esperar por `get_burgers(2)` para finalizar suas coisas antes de armazenar os resultados em `burgers`. Com isso, o Python saberá que ele pode ir e fazer outras coisas nesse meio tempo (como receber outra requisição). +A chave aqui é o `await`. Ele diz ao Python que ele tem que esperar por `get_burgers(2)` finalizar suas coisas 🕙 antes de armazenar os resultados em `burgers`. Com isso, o Python saberá que ele pode ir e fazer outras coisas 🔀 ⏯ nesse meio tempo (como receber outra requisição). Para o `await` funcionar, tem que estar dentro de uma função que suporte essa assincronicidade. Para fazer isso, apenas declare a função com `async def`: ```Python hl_lines="1" async def get_burgers(number: int): - # Fazer alguma coisa assíncrona para criar os hambúrgueres + # Faz alguma coisa assíncrona para criar os hambúrgueres return burgers ``` @@ -295,9 +293,9 @@ def get_sequential_burgers(number: int): return burgers ``` -Com `async def`, o Python sabe que, dentro dessa função, tem que estar ciente das expressões `await`, e que isso pode "pausar" a execução dessa função, e poderá fazer outra coisa antes de voltar. +Com `async def`, o Python sabe que, dentro dessa função, ele deve estar ciente das expressões `await`, e que isso poderá "pausar" ⏸ a execução dessa função, e ir fazer outra coisa 🔀 antes de voltar. -Quando você quiser chamar uma função `async def`, você tem que "esperar". Então, isso não funcionará: +Quando você quiser chamar uma função `async def`, você tem que "esperar" ela. Então, isso não funcionará: ```Python # Isso não irá funcionar, porquê get_burgers foi definido com: async def @@ -319,13 +317,24 @@ async def read_burgers(): Você deve ter observado que `await` pode ser usado somente dentro de funções definidas com `async def`. -Mas ao mesmo tempo, funções definidas com `async def` tem que ser aguardadas. Então, funções com `async def` pdem ser chamadas somente dentro de funções definidas com `async def` também. +Mas ao mesmo tempo, funções definidas com `async def` têm que ser "aguardadas". Então, funções com `async def` pdem ser chamadas somente dentro de funções definidas com `async def` também. Então, sobre o ovo e a galinha, como você chama a primeira função async? Se você estivar trabalhando com **FastAPI** não terá que se preocupar com isso, porquê essa "primeira" função será a sua *função de operação de rota*, e o FastAPI saberá como fazer a coisa certa. -Mas se você quiser usar `async` / `await` sem FastAPI, verifique a documentação oficial Python. +Mas se você quiser usar `async` / `await` sem FastAPI, você também pode fazê-lo. + +### Escreva seu próprio código assíncrono + +Starlette (e **FastAPI**) são baseados no AnyIO, o que o torna compatível com ambos o asyncio da biblioteca padrão do Python, e o Trio. + +Em particular, você pode usar diretamente o AnyIO para seus casos de uso avançados de concorrência que requerem padrões mais avançados no seu próprio código. + +E até se você não estiver utilizando FastAPI, você também pode escrever suas próprias aplicações assíncronas com o AnyIO por ser altamente compatível e ganhar seus benefícios (e.g. *concorrência estruturada*). + +Eu criei outra biblioteca em cima do AnyIO, como uma fina camada acima, para melhorar um pouco as anotações de tipo e obter melhor **autocompletar**, **erros de linha**, etc. Ela também possui uma introdução amigável e um tutorial para ajudar você a **entender** e escrever **seu próprio código async**: Asyncer. Seria particularmente útil se você precisar **combinar código async com código regular** (bloqueador/síncrono). + ### Outras formas de código assíncrono @@ -337,25 +346,25 @@ Essa mesma sintaxe (ou quase a mesma) foi também incluída recentemente em vers Mas antes disso, controlar código assíncrono era bem mais complexo e difícil. -Nas versões anteriores do Python, você poderia utilizar threads ou Gevent. Mas o código é um pouco mais complexo de entender, debugar, e pensar sobre. +Nas versões anteriores do Python, você poderia utilizar threads ou Gevent. Mas o código é bem mais complexo de entender, debugar, e pensar sobre. -Nas versões anteriores do NodeJS / Navegador JavaScript, você poderia utilizar "callbacks". O que leva ao inferno do callback. +Nas versões anteriores do NodeJS / Navegador JavaScript, você utilizaria "callbacks". O que leva ao "inferno do callback". ## Corrotinas -**Corrotina** é apenas um jeito bonitinho para a coisa que é retornada de uma função `async def`. O Python sabe que é uma função que pode começar e terminar em algum ponto, mas que pode ser pausada internamente também, sempre que tiver um `await` dentro dela. +**Corrotina** é apenas um jeito bonitinho para a coisa que é retornada de uma função `async def`. O Python sabe que é algo como uma função, que pode começar e que vai terminar em algum ponto, mas que pode ser pausada ⏸ internamente também, sempre que tiver um `await` dentro dela. -Mas toda essa funcionalidade de código assíncrono com `async` e `await` é muitas vezes resumida como "corrotina". É comparável ao principal recurso chave do Go, a "Gorotina". +Mas toda essa funcionalidade de código assíncrono com `async` e `await` é muitas vezes resumida como usando "corrotinas". É comparável ao principal recurso chave do Go, a "Gorrotina". ## Conclusão -Vamos ver a mesma frase com o conteúdo cima: +Vamos ver a mesma frase de cima: -> Versões modernas do Python tem suporte para **"código assíncrono"** usando algo chamado **"corrotinas"**, com sintaxe **`async` e `await`**. +> Versões modernas do Python têm suporte para **"código assíncrono"** usando algo chamado **"corrotinas"**, com sintaxe **`async` e `await`**. -Isso pode fazer mais sentido agora. +Isso pode fazer mais sentido agora. ✨ -Tudo isso é o que deixa o FastAPI poderoso (através do Starlette) e que o faz ter uma performance impressionante. +Tudo isso é o que empodera o FastAPI (através do Starlette) e que o faz ter uma performance tão impressionante. ## Detalhes muito técnicos @@ -365,25 +374,25 @@ Você pode provavelmente pular isso. Esses são detalhes muito técnicos de como **FastAPI** funciona por baixo do capô. -Se você tem algum conhecimento técnico (corrotinas, threads, blocking etc) e está curioso sobre como o FastAPI controla o `async def` vs normal `def`, vá em frente. +Se você tem certo conhecimento técnico (corrotinas, threads, blocking etc) e está curioso sobre como o FastAPI controla o `async def` vs normal `def`, vá em frente. /// ### Funções de operação de rota -Quando você declara uma *função de operação de rota* com `def` normal ao invés de `async def`, ela é rodada em uma threadpool externa que então é aguardada, ao invés de ser chamada diretamente (ela poderia bloquear o servidor). +Quando você declara uma *função de operação de rota* com `def` normal ao invés de `async def`, ela é rodada em uma threadpool externa que é então aguardada, ao invés de ser chamada diretamente (já que ela bloquearia o servidor). -Se você está chegando de outro framework assíncrono que não faz o trabalho descrito acima e você está acostumado a definir triviais *funções de operação de rota* com simples `def` para ter um mínimo ganho de performance (cerca de 100 nanosegundos), por favor observe que no **FastAPI** o efeito pode ser bem o oposto. Nesses casos, é melhor usar `async def` a menos que suas *funções de operação de rota* utilizem código que performem bloqueamento IO. +Se você está chegando de outro framework assíncrono que não funciona como descrito acima e você está acostumado a definir *funções de operação de rota* triviais somente de computação com simples `def` para ter um mínimo ganho de performance (cerca de 100 nanosegundos), por favor observe que no **FastAPI** o efeito pode ser bem o oposto. Nesses casos, é melhor usar `async def` a menos que suas *funções de operação de rota* utilizem código que performe bloqueamento IO. -Ainda, em ambas as situações, as chances são que o **FastAPI** será [ainda mais rápido](index.md#performance){.internal-link target=_blank} do que (ou ao menos comparável a) seus frameworks antecessores. +Ainda, em ambas as situações, as chances são que o **FastAPI** [ainda será mais rápido](index.md#performance){.internal-link target=_blank} do que (ou ao menos comparável a) seu framework anterior. ### Dependências -O mesmo se aplica para as dependências. Se uma dependência tem as funções com padrão `def` ao invés de `async def`, ela é rodada no threadpool externo. +O mesmo se aplica para as [dependências](tutorial/dependencies/index.md){.internal-link target=_blank}. Se uma dependência tem as funções com padrão `def` ao invés de `async def`, ela é rodada no threadpool externo. ### Sub-dependências -Você pode ter múltiplas dependências e sub-dependências exigindo uma a outra (como parâmetros de definições de funções), algumas delas podem ser criadas com `async def` e algumas com `def` normal. Isso ainda poderia funcionar, e aquelas criadas com `def` podem ser chamadas em uma thread externa ao invés de serem "aguardadas". +Você pode ter múltiplas dependências e [sub-dependências](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} requisitando uma à outra (como parâmetros de definições de funções), algumas delas podem ser criadas com `async def` e algumas com `def` normal. Isso ainda funcionaria, e aquelas criadas com `def` normal seriam chamadas em uma thread externa (do threadpool) ao invés de serem "aguardadas". ### Outras funções de utilidade @@ -395,6 +404,6 @@ Se sua função de utilidade é uma função normal com `def`, ela será chamada --- -Novamente, esses são detalhes muito técnicos que provavelmente possam ser úteis caso você esteja procurando por eles. +Novamente, esses são detalhes muito técnicos que provavelmente seriam úteis caso você esteja procurando por eles. Caso contrário, você deve ficar bem com as dicas da seção acima: Com pressa?. diff --git a/docs/pt/docs/contributing.md b/docs/pt/docs/contributing.md deleted file mode 100644 index bb518a2fa0..0000000000 --- a/docs/pt/docs/contributing.md +++ /dev/null @@ -1,507 +0,0 @@ -# Desenvolvimento - Contribuindo - -Primeiramente, você deveria ver os meios básicos para [ajudar FastAPI e pedir ajuda](help-fastapi.md){.internal-link target=_blank}. - -## Desenvolvendo - -Se você já clonou o repositório e precisa mergulhar no código, aqui estão algumas orientações para configurar seu ambiente. - -### Ambiente virtual com `venv` - -Você pode criar um ambiente virtual em um diretório utilizando o módulo `venv` do Python: - -
- -```console -$ python -m venv env -``` - -
- -Isso criará o diretório `./env/` com os binários Python e então você será capaz de instalar pacotes nesse ambiente isolado. - -### Ativar o ambiente - -Ative o novo ambiente com: - -//// tab | Linux, macOS - -
- -```console -$ source ./env/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .\env\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Ou se você usa Bash para Windows (por exemplo Git Bash): - -
- -```console -$ source ./env/Scripts/activate -``` - -
- -//// - -Para verificar se funcionou, use: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ Get-Command pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -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, você utilizará aquele de seu ambiente local e não outro que possa estar instalado globalmente. - -/// - -### pip - -Após ativar o ambiente como descrito acima: - -
- -```console -$ pip install -r requirements.txt - ----> 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 `-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. - -### Formato - -Tem um arquivo que você pode rodar que irá formatar e limpar todo o seu código: - -
- -```console -$ 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 `-e`. - -### Formato dos imports - -Tem outro _script_ que formata todos os imports e garante que você não tenha imports não utilizados: - -
- -```console -$ bash scripts/format-imports.sh -``` - -
- -Como ele roda um comando após o outro, modificando e revertendo muitos arquivos, ele demora um pouco para concluir, então pode ser um pouco mais fácil utilizar `scripts/format.sh` frequentemente e `scripts/format-imports.sh` somente após "commitar uma branch". - -## Documentação - -Primeiro, tenha certeza de configurar seu ambiente como descrito acima, isso irá instalar todas as requisições. - -A documentação usa MkDocs. - -E existem ferramentas/_scripts_ extras para controlar as traduções em `./scripts/docs.py`. - -/// tip - -Você não precisa ver o código em `./scripts/docs.py`, você apenas o utiliza na linha de comando. - -/// - -Toda a documentação está no formato Markdown no diretório `./docs/pt/`. - -Muitos dos tutoriais tem blocos de código. - -Na maioria dos casos, esse blocos de código são aplicações completas que podem ser rodadas do jeito que estão apresentados. - -De fato, esses blocos de código não estão escritos dentro do Markdown, eles são arquivos Python dentro do diretório `./docs_src/`. - -E esses arquivos Python são incluídos/injetados na documentação quando se gera o site. - -### Testes para Documentação - -A maioria dos testes na verdade rodam encima dos arquivos fonte na documentação. - -Isso ajuda a garantir: - -* Que a documentação esteja atualizada. -* Que os exemplos da documentação possam ser rodadas do jeito que estão apresentadas. -* A maior parte dos recursos é coberta pela documentação, garantida por cobertura de testes. - -Durante o desenvolvimento local, existe um _script_ que constrói o site e procura por quaisquer mudanças, carregando na hora: - -
- -```console -$ python ./scripts/docs.py live - -[INFO] Serving on http://127.0.0.1:8008 -[INFO] Start watching changes -[INFO] Start detecting changes -``` - -
- -Isso irá servir a documentação em `http://127.0.0.1:8008`. - -Desse jeito, você poderá editar a documentação/arquivos fonte e ver as mudanças na hora. - -#### Typer CLI (opcional) - -As instruções aqui mostram como utilizar _scripts_ em `./scripts/docs.py` com o programa `python` diretamente. - -Mas você pode usar também Typer CLI, e você terá auto-completação para comandos no seu terminal após instalar o _completion_. - -Se você instalou Typer CLI, você pode instalar _completion_ com: - -
- -```console -$ typer --install-completion - -zsh completion installed in /home/user/.bashrc. -Completion will take effect once you restart the terminal. -``` - -
- -### Aplicações e documentação ao mesmo tempo - -Se você rodar os exemplos com, por exemplo: - -
- -```console -$ uvicorn tutorial001:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -como Uvicorn utiliza por padrão a porta `8000`, a documentação na porta `8008` não dará conflito. - -### Traduções - -Ajuda com traduções É MUITO apreciada! E essa tarefa não pode ser concluída sem a ajuda da comunidade. 🌎 🚀 - -Aqui estão os passos para ajudar com as traduções. - -#### Dicas e orientações - -* Verifique sempre os _pull requests_ existentes para a sua linguagem e faça revisões das alterações e aprove elas. - -/// tip - -Você pode adicionar comentários com sugestões de alterações para _pull requests_ existentes. - -Verifique as documentações sobre adicionar revisão ao _pull request_ para aprovação ou solicitação de alterações. - -/// - -* Verifique em _issues_ para ver se existe alguém coordenando traduções para a sua linguagem. - -* Adicione um único _pull request_ por página traduzida. Isso tornará muito mais fácil a revisão para as outras pessoas. - -Para as linguagens que eu não falo, vou esperar por várias pessoas revisarem a tradução antes de _mergear_. - -* Você pode verificar também se há traduções para sua linguagem e adicionar revisão para elas, isso irá me ajudar a saber que a tradução está correta e eu possa _mergear_. - -* Utilize os mesmos exemplos Python e somente traduza o texto na documentação. Você não tem que alterar nada no código para que funcione. - -* Utilize as mesmas imagens, nomes de arquivo e links. Você não tem que alterar nada disso para que funcione. - -* Para verificar o código de duas letras para a linguagem que você quer traduzir, você pode usar a Lista de códigos ISO 639-1. - -#### Linguagem existente - -Vamos dizer que você queira traduzir uma página para uma linguagem que já tenha traduções para algumas páginas, como o Espanhol. - -No caso do Espanhol, o código de duas letras é `es`. Então, o diretório para traduções em Espanhol está localizada em `docs/es/`. - -/// tip - -A principal ("oficial") linguagem é o Inglês, localizado em `docs/en/`. - -/// - -Agora rode o _servidor ao vivo_ para as documentações em Espanhol: - -
- -```console -// Use o comando "live" e passe o código da linguagem como um argumento de linha de comando -$ python ./scripts/docs.py live es - -[INFO] Serving on http://127.0.0.1:8008 -[INFO] Start watching changes -[INFO] Start detecting changes -``` - -
- -Agora você pode ir em http://127.0.0.1:8008 e ver suas mudanças ao vivo. - -Se você procurar no site da documentação do FastAPI, você verá que toda linguagem tem todas as páginas. Mas algumas páginas não estão traduzidas e tem notificação sobre a falta da tradução. - -Mas quando você rodar localmente como descrito acima, você somente verá as páginas que já estão traduzidas. - -Agora vamos dizer que você queira adicionar uma tradução para a seção [Recursos](features.md){.internal-link target=_blank}. - -* Copie o arquivo em: - -``` -docs/en/docs/features.md -``` - -* Cole ele exatamente no mesmo local mas para a linguagem que você quer traduzir, por exemplo: - -``` -docs/es/docs/features.md -``` - -/// tip - -Observe que a única mudança na rota é o código da linguagem, de `en` para `es`. - -/// - -* Agora abra o arquivo de configuração MkDocs para Inglês em: - -``` -docs/en/docs/mkdocs.yml -``` - -* Procure o lugar onde `docs/features.md` está localizado no arquivo de configuração. Algum lugar como: - -```YAML hl_lines="8" -site_name: FastAPI -# Mais coisas -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -* Abra o arquivo de configuração MkDocs para a linguagem que você está editando, por exemplo: - -``` -docs/es/docs/mkdocs.yml -``` - -* Adicione no mesmo local que está no arquivo em Inglês, por exemplo: - -```YAML hl_lines="8" -site_name: FastAPI -# Mais coisas -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -Tenha certeza que se existem outras entradas, a nova entrada com sua tradução esteja exatamente na mesma ordem como na versão em Inglês. - -Se você for no seu navegador verá que agora a documentação mostra sua nova seção. 🎉 - -Agora você poderá traduzir tudo e ver como está toda vez que salva o arquivo. - -#### Nova linguagem - -Vamos dizer que você queira adicionar traduções para uma linguagem que ainda não foi traduzida, nem sequer uma página. - -Vamos dizer que você queira adicionar tradução para Haitiano, e ainda não tenha na documentação. - -Verificando o link acima, o código para "Haitiano" é `ht`. - -O próximo passo é rodar o _script_ para gerar um novo diretório de tradução: - -
- -```console -// Use o comando new-lang, passe o código da linguagem como um argumento de linha de comando -$ python ./scripts/docs.py new-lang ht - -Successfully initialized: docs/ht -Updating ht -Updating en -``` - -
- -Agora você pode verificar em seu editor de código o mais novo diretório criado `docs/ht/`. - -/// tip - -Crie um primeiro _pull request_ com apenas isso, para iniciar a configuração da nova linguagem, antes de adicionar traduções. - -Desse modo outros poderão ajudar com outras páginas enquanto você trabalha na primeira. 🚀 - -/// - -Inicie traduzindo a página principal, `docs/ht/index.md`. - -Então você pode continuar com as instruções anteriores, para uma "Linguagem Existente". - -##### Nova linguagem não suportada - -Se quando rodar o _script_ do _servidor ao vivo_ você pega um erro sobre linguagem não suportada, alguma coisa como: - -``` - raise TemplateNotFound(template) -jinja2.exceptions.TemplateNotFound: partials/language/xx.html -``` - -Isso significa que o tema não suporta essa linguagem (nesse caso, com um código falso de duas letras `xx`). - -Mas não se preocupe, você pode configurar o tema de linguagem para Inglês e então traduzir o conteúdo da documentação. - -Se você precisar fazer isso, edite o `mkdocs.yml` para sua nova linguagem, teremos algo como: - -```YAML hl_lines="5" -site_name: FastAPI -# Mais coisas -theme: - # Mais coisas - language: xx -``` - -Altere essa linguagem de `xx` (do seu código de linguagem) para `en`. - -Então você poderá iniciar novamente o _servidor ao vivo_. - -#### Pré-visualize o resultado - -Quando você usa o _script_ em `./scripts/docs.py` com o comando `live` ele somente exibe os arquivos e traduções disponíveis para a linguagem atual. - -Mas uma vez que você tenha concluído, você poderá testar tudo como se parecesse _online_. - -Para fazer isso, primeiro construa toda a documentação: - -
- -```console -// Use o comando "build-all", isso leverá um tempinho -$ 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 -``` - -
- -Isso gera toda a documentação em `./docs_build/` para cada linguagem. Isso inclui a adição de quaisquer arquivos com tradução faltando, com uma nota dizendo que "esse arquivo ainda não tem tradução". Mas você não tem que fazer nada com esse diretório. - -Então ele constrói todos aqueles _sites_ independentes MkDocs para cada linguagem, combina eles, e gera a saída final em `./site/`. - -Então você poderá "servir" eles com o comando `serve`: - -
- -```console -// Use o comando "serve" após rodar "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 -``` - -
- -## Testes - -Tem um _script_ que você pode rodar localmente para testar todo o código e gerar relatórios de cobertura em HTML: - -
- -```console -$ bash scripts/test-cov-html.sh -``` - -
- -Esse comando gera um diretório `./htmlcov/`, se você abrir o arquivo `./htmlcov/index.html` no seu navegador, poderá explorar interativamente as regiões de código que estão cobertas pelos testes, e observar se existe alguma região faltando. - -### Testes no seu editor - -Se você quer usar os testes integrados em seu editor adicione `./docs_src` na sua variável `PYTHONPATH`. - -Por exemplo, no VS Code você pode criar um arquivo `.env` com: - -```env -PYTHONPATH=./docs_src -``` diff --git a/docs/pt/docs/deployment.md b/docs/pt/docs/deployment.md deleted file mode 100644 index 6874a2529c..0000000000 --- a/docs/pt/docs/deployment.md +++ /dev/null @@ -1,414 +0,0 @@ -# Implantação - -Implantar uma aplicação **FastAPI** é relativamente fácil. - -Existem vários modos de realizar o _deploy_ dependendo de seu caso de uso específico e as ferramentas que você utiliza. - -Você verá mais sobre alguns modos de fazer o _deploy_ nas próximas seções. - -## Versões do FastAPI - -**FastAPI** já está sendo utilizado em produção em muitas aplicações e sistemas. E a cobertura de teste é mantida a 100%. Mas seu desenvolvimento continua andando rapidamente. - -Novos recursos são adicionados frequentemente, _bugs_ são corrigidos regularmente, e o código está continuamente melhorando. - -É por isso que as versões atuais estão ainda no `0.x.x`, isso reflete que cada versão poderia ter potencialmente alterações que podem quebrar. Isso segue as convenções de Versionamento Semântico. - -Você pode criar aplicações para produção com **FastAPI** bem agora (e você provavelmente já faça isso por um tempo), você tem que ter certeza de utilizar uma versão que funcione corretamente com o resto do seu código. - -### Anote sua versão `fastapi` - -A primeira coisa que você deve fazer é "fixar" a versão do **FastAPI** que está utilizando para a última versão específica que você sabe que funciona corretamente para a sua aplicação. - -Por exemplo, vamos dizer que você esteja utilizando a versão `0.45.0` no seu _app_. - -Se você usa um arquivo `requirements.txt`, dá para especificar a versão assim: - -```txt -fastapi==0.45.0 -``` - -isso significa que você pode usar exatamente a versão `0.45.0`. - -Ou você poderia fixar assim: - -```txt -fastapi>=0.45.0,<0.46.0 -``` - -o que significa que você pode usar as versões `0.45.0` ou acima, mas menor que `0.46.0`. Por exemplo, a versão `0.45.2` poderia ser aceita. - -Se você usa qualquer outra ferramenta para gerenciar suas instalações, como Poetry, Pipenv ou outro, todos terão um modo que você possa usar para definir versões específicas para seus pacotes. - -### Versões disponíveis - -Você pode ver as versões disponíveis (por exemplo, para verificar qual é a versão atual) nas [Notas de Lançamento](release-notes.md){.internal-link target=_blank}. - -### Sobre as versões - -Seguindo as convenções do Versionamento Semântico, qualquer versão abaixo de `1.0.0` pode potencialmente adicionar mudanças que quebrem. - -FastAPI também segue a convenção que qualquer versão de _"PATCH"_ seja para ajustes de _bugs_ e mudanças que não quebrem a aplicação. - -/// tip - -O _"PATCH"_ é o último número, por exemplo, em `0.2.3`, a versão do _PATCH_ é `3`. - -/// - -Então, você poderia ser capaz de fixar para uma versão como: - -```txt -fastapi>=0.45.0,<0.46.0 -``` - -Mudanças que quebram e novos recursos são adicionados em versões _"MINOR"_. - -/// tip - -O _"MINOR"_ é o número do meio, por exemplo, em `0.2.3`, a versão _MINOR_ é `2`. - -/// - -### Atualizando as versões FastAPI - -Você pode adicionar testes em sua aplicação. - -Com o **FastAPI** é muito fácil (graças ao Starlette), verifique a documentação: [Testando](tutorial/testing.md){.internal-link target=_blank} - -Após você ter os testes, então você pode fazer o _upgrade_ da versão **FastAPI** para uma mais recente, e ter certeza que todo seu código esteja funcionando corretamente rodando seus testes. - -Se tudo estiver funcionando, ou após você fazer as alterações necessárias, e todos seus testes estiverem passando, então você poderá fixar o `fastapi` para a versão mais recente. - -### Sobre Starlette - -Você não deve fixar a versão do `starlette`. - -Versões diferentes do **FastAPI** irão utilizar uma versão mais nova específica do Starlette. - -Então, você pode deixar que o **FastAPI** use a versão correta do Starlette. - -### Sobre Pydantic - -Pydantic inclui os testes para **FastAPI** em seus próprios testes, então novas versões do Pydantic (acima de `1.0.0`) são sempre compatíveis com FastAPI. - -Você pode fixar o Pydantic para qualquer versão acima de `1.0.0` e abaixo de `2.0.0` que funcionará. - -Por exemplo: - -```txt -pydantic>=1.2.0,<2.0.0 -``` - -## Docker - -Nessa seção você verá instruções e _links_ para guias de saber como: - -* Fazer uma imagem/container da sua aplicação **FastAPI** com máxima performance. Em aproximadamente **5 min**. -* (Opcionalmente) entender o que você, como desenvolvedor, precisa saber sobre HTTPS. -* Inicializar um _cluster_ Docker Swarm Mode com HTTPS automático, mesmo em um simples servidor de $5 dólares/mês. Em aproximadamente **20 min**. -* Gere e implante uma aplicação **FastAPI** completa, usando seu _cluster_ Docker Swarm, com HTTPS etc. Em aproxiamadamente **10 min**. - -Você pode usar **Docker** para implantação. Ele tem várias vantagens como segurança, replicabilidade, desenvolvimento simplificado etc. - -Se você está usando Docker, você pode utilizar a imagem Docker oficial: - -### tiangolo/uvicorn-gunicorn-fastapi - -Essa imagem tem um mecanismo incluído de "auto-ajuste", para que você possa apenas adicionar seu código e ter uma alta performance automaticamente. E sem fazer sacrifícios. - -Mas você pode ainda mudar e atualizar todas as configurações com variáveis de ambiente ou arquivos de configuração. - -/// tip - -Para ver todas as configurações e opções, vá para a página da imagem do Docker: tiangolo/uvicorn-gunicorn-fastapi. - -/// - -### Crie um `Dockerfile` - -* Vá para o diretório de seu projeto. -* Crie um `Dockerfile` com: - -```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 - -COPY ./app /app -``` - -#### Grandes aplicações - -Se você seguiu a seção sobre criação de [Grandes Aplicações com Múltiplos Arquivos](tutorial/bigger-applications.md){.internal-link target=_blank}, seu `Dockerfile` poderia parecer como: - -```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 - -COPY ./app /app/app -``` - -#### Raspberry Pi e outras arquiteturas - -Se você estiver rodando Docker em um Raspberry Pi (que possui um processador ARM) ou qualquer outra arquitetura, você pode criar um `Dockerfile` do zero, baseado em uma imagem base Python (que é multi-arquitetural) e utilizar Uvicorn sozinho. - -Nesse caso, seu `Dockerfile` poderia parecer assim: - -```Dockerfile -FROM python:3.7 - -RUN pip install fastapi uvicorn - -EXPOSE 80 - -COPY ./app /app - -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] -``` - -### Crie o código **FastAPI** - -* Crie um diretório `app` e entre nele. -* Crie um arquivo `main.py` com: - -```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, q: str = None): - return {"item_id": item_id, "q": q} -``` - -* Você deve ter uma estrutura de diretórios assim: - -``` -. -├── app -│ └── main.py -└── Dockerfile -``` - -### Construa a imagem Docker - -* Vá para o diretório do projeto (onde seu `Dockerfile` está, contendo seu diretório `app`. -* Construa sua imagem FastAPI: - -
- -```console -$ docker build -t myimage . - ----> 100% -``` - -
- -### Inicie o container Docker - -* Rode um container baseado em sua imagem: - -
- -```console -$ docker run -d --name mycontainer -p 80:80 myimage -``` - -
- -Agora você tem um servidor FastAPI otimizado em um container Docker. Auto-ajustado para seu servidor atual (e número de núcleos de CPU). - -### Verifique - -Você deve ser capaz de verificar na URL de seu container 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"} -``` - -### API interativa de documetação - -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 API interativa de documentação (fornecida por Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### APIs alternativas de documentação - -E você pode também 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 automática alternativa (fornecida por ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## HTTPS - -### Sobre HTTPS - -É fácil assumir que HTTPS seja algo que esteja apenas "habilitado" ou não. - -Mas ele é um pouquinho mais complexo do que isso. - -/// tip - -Se você está com pressa ou não se importa, continue na próxima seção com instruções passo a passo para configurar tudo. - -/// - -Para aprender o básico de HTTPS, pela perspectiva de um consumidor, verifique https://howhttps.works/. - -Agora, pela perspectiva de um desenvolvedor, aqui estão algumas coisas para se ter em mente enquanto se pensa sobre HTTPS: - -* Para HTTPS, o servidor precisa ter "certificados" gerados por terceiros. - * Esses certificados são na verdade adquiridos por terceiros, não "gerados". -* Certificados tem um prazo de uso. - * Eles expiram. - * E então eles precisam ser renovados, adquiridos novamente por terceiros. -* A encriptação da conexão acontece no nível TCP. - * TCP é uma camada abaixo do HTTP. - * Então, o controle de certificado e encriptação é feito antes do HTTP. -* TCP não conhece nada sobre "domínios". Somente sobre endereços IP. - * A informação sobre o domínio requisitado vai nos dados HTTP. -* Os certificados HTTPS "certificam" um certo domínio, mas o protocolo e a encriptação acontecem no nível TCP, antes de saber qual domínio está sendo lidado. -* Por padrão, isso significa que você pode ter somente um certificado HTTPS por endereço IP. - * Não importa quão grande é seu servidor ou quão pequena cada aplicação que você tenha possar ser. - * No entanto, existe uma solução para isso. -* Existe uma extensão para o protocolo TLS (o que controla a encriptação no nível TCP, antes do HTTP) chamada SNI. - * Essa extensão SNI permite um único servidor (com um único endereço IP) a ter vários certificados HTTPS e servir múltiplas aplicações/domínios HTTPS. - * Para que isso funcione, um único componente (programa) rodando no servidor, ouvindo no endereço IP público, deve ter todos os certificados HTTPS no servidor. -* Após obter uma conexão segura, o protocolo de comunicação ainda é HTTP. - * O conteúdo está encriptado, mesmo embora ele esteja sendo enviado com o protocolo HTTP. - -É uma prática comum ter um servidor HTTP/programa rodando no servidor (a máquina, _host_ etc.) e gerenciar todas as partes HTTP: enviar as requisições HTTP decriptadas para a aplicação HTTP rodando no mesmo servidor (a aplicação **FastAPI**, nesse caso), pega a resposta HTTP da aplicação, encripta utilizando o certificado apropriado e enviando de volta para o cliente usando HTTPS. Esse servidor é frequentemente chamado TLS _Termination Proxy_. - -### Vamos encriptar - -Antes de encriptar, esses certificados HTTPS foram vendidos por terceiros de confiança. - -O processo para adquirir um desses certificados costumava ser chato, exigia muita papelada e eram bem caros. - -Mas então _Let's Encrypt_ foi criado. - -É um projeto da Fundação Linux.Ele fornece certificados HTTPS de graça. De um jeito automatizado. Esses certificados utilizam todos os padrões de segurança criptográfica, e tem vida curta (cerca de 3 meses), para que a segurança seja melhor devido ao seu curto período de vida. - -Os domínios são seguramente verificados e os certificados são gerados automaticamente. Isso também permite automatizar a renovação desses certificados. - -A idéia é automatizar a aquisição e renovação desses certificados, para que você possa ter um HTTPS seguro, grátis, para sempre. - -### Traefik - -Traefik é um _proxy_ reverso / _load balancer_ de alta performance. Ele pode fazer o trabalho do _"TLS Termination Proxy"_ (à parte de outros recursos). - -Ele tem integração com _Let's Encrypt_. Assim, ele pode controlar todas as partes HTTPS, incluindo a aquisição e renovação de certificados. - -Ele também tem integrações com Docker. Assim, você pode declarar seus domínios em cada configuração de aplicação e leitura dessas configurações, gerando os certificados HTTPS e servindo o HTTPS para sua aplicação automaticamente, sem exigir qualquer mudança em sua configuração. - ---- - -Com essas ferramentas e informações, continue com a próxima seção para combinar tudo. - -## _Cluster_ de Docker Swarm Mode com Traefik e HTTPS - -Você pode ter um _cluster_ de Docker Swarm Mode configurado em minutos (cerca de 20) com o Traefik controlando HTTPS (incluindo aquisição e renovação de certificados). - -Utilizando o Docker Swarm Mode, você pode iniciar com um _"cluster"_ de apenas uma máquina (que pode até ser um servidor por 5 dólares / mês) e então você pode aumentar conforme a necessidade adicionando mais servidores. - -Para configurar um _cluster_ Docker Swarm Mode com Traefik controlando HTTPS, siga essa orientação: - -### Docker Swarm Mode and Traefik for an HTTPS cluster - -### Faça o _deploy_ de uma aplicação FastAPI - -O jeito mais fácil de configurar tudo pode ser utilizando o [Gerador de Projetos **FastAPI**](project-generation.md){.internal-link target=_blank}. - -Ele é designado para ser integrado com esse _cluster_ Docker Swarm com Traefik e HTTPS descrito acima. - -Você pode gerar um projeto em cerca de 2 minutos. - -O projeto gerado tem instruções para fazer o _deploy_, fazendo isso leva outros 2 minutos. - -## Alternativamente, faça o _deploy_ **FastAPI** sem Docker - -Você pode fazer o _deploy_ do **FastAPI** diretamente sem o Docker também. - -Você apenas precisa instalar um servidor ASGI compatível como: - -//// tab | Uvicorn - -* Uvicorn, um servidor ASGI peso leve, construído sobre uvloop e httptools. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -//// - -//// tab | Hypercorn - -* Hypercorn, um servidor ASGI também compatível com HTTP/2. - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...ou qualquer outro servidor ASGI. - -//// - -E rode sua applicação do mesmo modo que você tem feito nos tutoriais, mas sem a opção `--reload`, por exemplo: - -//// tab | 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) -``` - -
- -//// - -//// tab | Hypercorn - -
- -```console -$ hypercorn main:app --bind 0.0.0.0:80 - -Running on 0.0.0.0:8080 over http (CTRL + C to quit) -``` - -
- -//// - -Você deve querer configurar mais algumas ferramentas para ter certeza que ele seja reinicializado automaticamante se ele parar. - -Você também deve querer instalar Gunicorn e utilizar ele como um gerenciador para o Uvicorn, ou usar Hypercorn com múltiplos _workers_. - -Tenha certeza de ajustar o número de _workers_ etc. - -Mas se você estiver fazendo tudo isso, você pode apenas usar uma imagem Docker que fará isso automaticamente. diff --git a/docs/pt/docs/deployment/cloud.md b/docs/pt/docs/deployment/cloud.md new file mode 100644 index 0000000000..fc490db4dc --- /dev/null +++ b/docs/pt/docs/deployment/cloud.md @@ -0,0 +1,13 @@ +# Implantar FastAPI em provedores de nuvem + +Você pode usar praticamente **qualquer provedor de nuvem** para implantar seu aplicativo FastAPI. + +Na maioria dos casos, os principais provedores de nuvem têm guias para implantar o FastAPI com eles. + +## Provedores de Nuvem - Patrocinadores + +Alguns provedores de nuvem ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, o que garante o **desenvolvimento** contínuo e saudável do FastAPI e seu **ecossistema**. + +E isso mostra seu verdadeiro comprometimento com o FastAPI e sua **comunidade** (você), pois eles não querem apenas fornecer a você um **bom serviço**, mas também querem ter certeza de que você tenha uma **estrutura boa e saudável**, o FastAPI. 🙇 + +Talvez você queira experimentar os serviços deles e seguir os guias. diff --git a/docs/pt/docs/deployment/concepts.md b/docs/pt/docs/deployment/concepts.md new file mode 100644 index 0000000000..014ca3797a --- /dev/null +++ b/docs/pt/docs/deployment/concepts.md @@ -0,0 +1,321 @@ +# Conceitos de Implantações + +Ao implantar um aplicativo **FastAPI**, ou na verdade, qualquer tipo de API da web, há vários conceitos com os quais você provavelmente se importa e, usando-os, você pode encontrar a maneira **mais apropriada** de **implantar seu aplicativo**. + +Alguns dos conceitos importantes são: + +* Segurança - HTTPS +* Executando na inicialização +* Reinicializações +* Replicação (o número de processos em execução) +* Memória +* Etapas anteriores antes de iniciar + +Veremos como eles afetariam as **implantações**. + +No final, o principal objetivo é ser capaz de **atender seus clientes de API** de uma forma **segura**, **evitar interrupções** e usar os **recursos de computação** (por exemplo, servidores remotos/máquinas virtuais) da forma mais eficiente possível. 🚀 + +Vou lhe contar um pouco mais sobre esses **conceitos** aqui, e espero que isso lhe dê a **intuição** necessária para decidir como implantar sua API em ambientes muito diferentes, possivelmente até mesmo em **futuros** ambientes que ainda não existem. + +Ao considerar esses conceitos, você será capaz de **avaliar e projetar** a melhor maneira de implantar **suas próprias APIs**. + +Nos próximos capítulos, darei a você mais **receitas concretas** para implantar aplicativos FastAPI. + +Mas por enquanto, vamos verificar essas importantes **ideias conceituais**. Esses conceitos também se aplicam a qualquer outro tipo de API da web. 💡 + +## Segurança - HTTPS + +No [capítulo anterior sobre HTTPS](https.md){.internal-link target=_blank} aprendemos como o HTTPS fornece criptografia para sua API. + +Também vimos que o HTTPS normalmente é fornecido por um componente **externo** ao seu servidor de aplicativos, um **Proxy de terminação TLS**. + +E tem que haver algo responsável por **renovar os certificados HTTPS**, pode ser o mesmo componente ou pode ser algo diferente. + +### Ferramentas de exemplo para HTTPS + +Algumas das ferramentas que você pode usar como um proxy de terminação TLS são: + +* Traefik + * Lida automaticamente com renovações de certificados ✨ +* Caddy + * Lida automaticamente com renovações de certificados ✨ +* Nginx + * Com um componente externo como o Certbot para renovações de certificados +* HAProxy + * Com um componente externo como o Certbot para renovações de certificados +* Kubernetes com um controlador Ingress como o Nginx + * Com um componente externo como cert-manager para renovações de certificados +* Gerenciado internamente por um provedor de nuvem como parte de seus serviços (leia abaixo 👇) + +Outra opção é que você poderia usar um **serviço de nuvem** que faz mais do trabalho, incluindo a configuração de HTTPS. Ele pode ter algumas restrições ou cobrar mais, etc. Mas, nesse caso, você não teria que configurar um Proxy de terminação TLS sozinho. + +Mostrarei alguns exemplos concretos nos próximos capítulos. + +--- + +Os próximos conceitos a serem considerados são todos sobre o programa que executa sua API real (por exemplo, Uvicorn). + +## Programa e Processo + +Falaremos muito sobre o "**processo**" em execução, então é útil ter clareza sobre o que ele significa e qual é a diferença com a palavra "**programa**". + +### O que é um Programa + +A palavra **programa** é comumente usada para descrever muitas coisas: + +* O **código** que você escreve, os **arquivos Python**. +* O **arquivo** que pode ser **executado** pelo sistema operacional, por exemplo: `python`, `python.exe` ou `uvicorn`. +* Um programa específico enquanto está **em execução** no sistema operacional, usando a CPU e armazenando coisas na memória. Isso também é chamado de **processo**. + +### O que é um Processo + +A palavra **processo** normalmente é usada de forma mais específica, referindo-se apenas ao que está sendo executado no sistema operacional (como no último ponto acima): + +* Um programa específico enquanto está **em execução** no sistema operacional. + * Isso não se refere ao arquivo, nem ao código, refere-se **especificamente** à coisa que está sendo **executada** e gerenciada pelo sistema operacional. +* Qualquer programa, qualquer código, **só pode fazer coisas** quando está sendo **executado**. Então, quando há um **processo em execução**. +* O processo pode ser **terminado** (ou "morto") por você, ou pelo sistema operacional. Nesse ponto, ele para de rodar/ser executado, e ele **não pode mais fazer coisas**. +* Cada aplicativo que você tem em execução no seu computador tem algum processo por trás dele, cada programa em execução, cada janela, etc. E normalmente há muitos processos em execução **ao mesmo tempo** enquanto um computador está ligado. +* Pode haver **vários processos** do **mesmo programa** em execução ao mesmo tempo. + +Se você verificar o "gerenciador de tarefas" ou o "monitor do sistema" (ou ferramentas semelhantes) no seu sistema operacional, poderá ver muitos desses processos em execução. + +E, por exemplo, você provavelmente verá que há vários processos executando o mesmo programa de navegador (Firefox, Chrome, Edge, etc.). Eles normalmente executam um processo por aba, além de alguns outros processos extras. + + + +--- + +Agora que sabemos a diferença entre os termos **processo** e **programa**, vamos continuar falando sobre implantações. + +## Executando na inicialização + +Na maioria dos casos, quando você cria uma API web, você quer que ela esteja **sempre em execução**, ininterrupta, para que seus clientes possam sempre acessá-la. Isso é claro, a menos que você tenha um motivo específico para querer que ela seja executada somente em certas situações, mas na maioria das vezes você quer que ela esteja constantemente em execução e **disponível**. + +### Em um servidor remoto + +Ao configurar um servidor remoto (um servidor em nuvem, uma máquina virtual, etc.), a coisa mais simples que você pode fazer é usar `fastapi run` (que usa Uvicorn) ou algo semelhante, manualmente, da mesma forma que você faz ao desenvolver localmente. + +E funcionará e será útil **durante o desenvolvimento**. + +Mas se sua conexão com o servidor for perdida, o **processo em execução** provavelmente morrerá. + +E se o servidor for reiniciado (por exemplo, após atualizações ou migrações do provedor de nuvem), você provavelmente **não notará**. E por causa disso, você nem saberá que precisa reiniciar o processo manualmente. Então, sua API simplesmente permanecerá inativa. 😱 + +### Executar automaticamente na inicialização + +Em geral, você provavelmente desejará que o programa do servidor (por exemplo, Uvicorn) seja iniciado automaticamente na inicialização do servidor e, sem precisar de nenhuma **intervenção humana**, tenha um processo sempre em execução com sua API (por exemplo, Uvicorn executando seu aplicativo FastAPI). + +### Programa separado + +Para conseguir isso, você normalmente terá um **programa separado** que garantiria que seu aplicativo fosse executado na inicialização. E em muitos casos, ele também garantiria que outros componentes ou aplicativos também fossem executados, por exemplo, um banco de dados. + +### Ferramentas de exemplo para executar na inicialização + +Alguns exemplos de ferramentas que podem fazer esse trabalho são: + +* Docker +* Kubernetes +* Docker Compose +* Docker em Modo Swarm +* Systemd +* Supervisor +* Gerenciado internamente por um provedor de nuvem como parte de seus serviços +* Outros... + +Darei exemplos mais concretos nos próximos capítulos. + +## Reinicializações + +Semelhante a garantir que seu aplicativo seja executado na inicialização, você provavelmente também deseja garantir que ele seja **reiniciado** após falhas. + +### Nós cometemos erros + +Nós, como humanos, cometemos **erros** o tempo todo. O software quase *sempre* tem **bugs** escondidos em lugares diferentes. 🐛 + +E nós, como desenvolvedores, continuamos aprimorando o código à medida que encontramos esses bugs e implementamos novos recursos (possivelmente adicionando novos bugs também 😅). + +### Pequenos erros são tratados automaticamente + +Ao criar APIs da web com FastAPI, se houver um erro em nosso código, o FastAPI normalmente o conterá na única solicitação que acionou o erro. 🛡 + +O cliente receberá um **Erro Interno do Servidor 500** para essa solicitação, mas o aplicativo continuará funcionando para as próximas solicitações em vez de travar completamente. + +### Erros maiores - Travamentos + +No entanto, pode haver casos em que escrevemos algum código que **trava todo o aplicativo**, fazendo com que o Uvicorn e o Python travem. 💥 + +E ainda assim, você provavelmente não gostaria que o aplicativo permanecesse inativo porque houve um erro em um lugar, você provavelmente quer que ele **continue em execução** pelo menos para as *operações de caminho* que não estão quebradas. + +### Reiniciar após falha + +Mas nos casos com erros realmente graves que travam o **processo** em execução, você vai querer um componente externo que seja responsável por **reiniciar** o processo, pelo menos algumas vezes... + +/// tip | Dica + +...Embora se o aplicativo inteiro estiver **travando imediatamente**, provavelmente não faça sentido reiniciá-lo para sempre. Mas nesses casos, você provavelmente notará isso durante o desenvolvimento, ou pelo menos logo após a implantação. + +Então, vamos nos concentrar nos casos principais, onde ele pode travar completamente em alguns casos específicos **no futuro**, e ainda faz sentido reiniciá-lo. + +/// + +Você provavelmente gostaria de ter a coisa responsável por reiniciar seu aplicativo como um **componente externo**, porque a essa altura, o mesmo aplicativo com Uvicorn e Python já havia travado, então não há nada no mesmo código do mesmo aplicativo que possa fazer algo a respeito. + +### Ferramentas de exemplo para reiniciar automaticamente + +Na maioria dos casos, a mesma ferramenta usada para **executar o programa na inicialização** também é usada para lidar com **reinicializações** automáticas. + +Por exemplo, isso poderia ser resolvido por: + +* Docker +* Kubernetes +* Docker Compose +* Docker no Modo Swarm +* Systemd +* Supervisor +* Gerenciado internamente por um provedor de nuvem como parte de seus serviços +* Outros... + +## Replicação - Processos e Memória + +Com um aplicativo FastAPI, usando um programa de servidor como o comando `fastapi` que executa o Uvicorn, executá-lo uma vez em **um processo** pode atender a vários clientes simultaneamente. + +Mas em muitos casos, você desejará executar vários processos de trabalho ao mesmo tempo. + +### Processos Múltiplos - Trabalhadores + +Se você tiver mais clientes do que um único processo pode manipular (por exemplo, se a máquina virtual não for muito grande) e tiver **vários núcleos** na CPU do servidor, você poderá ter **vários processos** em execução com o mesmo aplicativo ao mesmo tempo e distribuir todas as solicitações entre eles. + +Quando você executa **vários processos** do mesmo programa de API, eles são comumente chamados de **trabalhadores**. + +### Processos do Trabalhador e Portas + +Lembra da documentação [Sobre HTTPS](https.md){.internal-link target=_blank} que diz que apenas um processo pode escutar em uma combinação de porta e endereço IP em um servidor? + +Isso ainda é verdade. + +Então, para poder ter **vários processos** ao mesmo tempo, tem que haver um **único processo escutando em uma porta** que então transmite a comunicação para cada processo de trabalho de alguma forma. + +### Memória por Processo + +Agora, quando o programa carrega coisas na memória, por exemplo, um modelo de aprendizado de máquina em uma variável, ou o conteúdo de um arquivo grande em uma variável, tudo isso **consome um pouco da memória (RAM)** do servidor. + +E vários processos normalmente **não compartilham nenhuma memória**. Isso significa que cada processo em execução tem suas próprias coisas, variáveis ​​e memória. E se você estiver consumindo uma grande quantidade de memória em seu código, **cada processo** consumirá uma quantidade equivalente de memória. + +### Memória do servidor + +Por exemplo, se seu código carrega um modelo de Machine Learning com **1 GB de tamanho**, quando você executa um processo com sua API, ele consumirá pelo menos 1 GB de RAM. E se você iniciar **4 processos** (4 trabalhadores), cada um consumirá 1 GB de RAM. Então, no total, sua API consumirá **4 GB de RAM**. + +E se o seu servidor remoto ou máquina virtual tiver apenas 3 GB de RAM, tentar carregar mais de 4 GB de RAM causará problemas. 🚨 + +### Processos Múltiplos - Um Exemplo + +Neste exemplo, há um **Processo Gerenciador** que inicia e controla dois **Processos de Trabalhadores**. + +Este Processo de Gerenciador provavelmente seria o que escutaria na **porta** no IP. E ele transmitiria toda a comunicação para os processos de trabalho. + +Esses processos de trabalho seriam aqueles que executariam seu aplicativo, eles executariam os cálculos principais para receber uma **solicitação** e retornar uma **resposta**, e carregariam qualquer coisa que você colocasse em variáveis ​​na RAM. + + + +E, claro, a mesma máquina provavelmente teria **outros processos** em execução, além do seu aplicativo. + +Um detalhe interessante é que a porcentagem da **CPU usada** por cada processo pode **variar** muito ao longo do tempo, mas a **memória (RAM)** normalmente fica mais ou menos **estável**. + +Se você tiver uma API que faz uma quantidade comparável de cálculos todas as vezes e tiver muitos clientes, então a **utilização da CPU** provavelmente *também será estável* (em vez de ficar constantemente subindo e descendo rapidamente). + +### Exemplos de ferramentas e estratégias de replicação + +Pode haver várias abordagens para conseguir isso, e falarei mais sobre estratégias específicas nos próximos capítulos, por exemplo, ao falar sobre Docker e contêineres. + +A principal restrição a ser considerada é que tem que haver um **único** componente manipulando a **porta** no **IP público**. E então tem que ter uma maneira de **transmitir** a comunicação para os **processos/trabalhadores** replicados. + +Aqui estão algumas combinações e estratégias possíveis: + +* **Uvicorn** com `--workers` + * Um **gerenciador de processos** Uvicorn escutaria no **IP** e na **porta** e iniciaria **vários processos de trabalho Uvicorn**. +* **Kubernetes** e outros **sistemas de contêineres** distribuídos + * Algo na camada **Kubernetes** escutaria no **IP** e na **porta**. A replicação seria por ter **vários contêineres**, cada um com **um processo Uvicorn** em execução. +* **Serviços de nuvem** que cuidam disso para você + * O serviço de nuvem provavelmente **cuidará da replicação para você**. Ele possivelmente deixaria você definir **um processo para executar**, ou uma **imagem de contêiner** para usar, em qualquer caso, provavelmente seria **um único processo Uvicorn**, e o serviço de nuvem seria responsável por replicá-lo. + +/// tip | Dica + +Não se preocupe se alguns desses itens sobre **contêineres**, Docker ou Kubernetes ainda não fizerem muito sentido. + +Falarei mais sobre imagens de contêiner, Docker, Kubernetes, etc. em um capítulo futuro: [FastAPI em contêineres - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Etapas anteriores antes de começar + +Há muitos casos em que você deseja executar algumas etapas **antes de iniciar** sua aplicação. + +Por exemplo, você pode querer executar **migrações de banco de dados**. + +Mas na maioria dos casos, você precisará executar essas etapas apenas **uma vez**. + +Portanto, você vai querer ter um **processo único** para executar essas **etapas anteriores** antes de iniciar o aplicativo. + +E você terá que se certificar de que é um único processo executando essas etapas anteriores *mesmo* se depois, você iniciar **vários processos** (vários trabalhadores) para o próprio aplicativo. Se essas etapas fossem executadas por **vários processos**, eles **duplicariam** o trabalho executando-o em **paralelo**, e se as etapas fossem algo delicado como uma migração de banco de dados, elas poderiam causar conflitos entre si. + +Claro, há alguns casos em que não há problema em executar as etapas anteriores várias vezes; nesse caso, é muito mais fácil de lidar. + +/// tip | Dica + +Além disso, tenha em mente que, dependendo da sua configuração, em alguns casos você **pode nem precisar de nenhuma etapa anterior** antes de iniciar sua aplicação. + +Nesse caso, você não precisaria se preocupar com nada disso. 🤷 + +/// + +### Exemplos de estratégias de etapas anteriores + +Isso **dependerá muito** da maneira como você **implanta seu sistema** e provavelmente estará conectado à maneira como você inicia programas, lida com reinicializações, etc. + +Aqui estão algumas ideias possíveis: + +* Um "Init Container" no Kubernetes que roda antes do seu app container +* Um script bash que roda os passos anteriores e então inicia seu aplicativo + * Você ainda precisaria de uma maneira de iniciar/reiniciar *aquele* script bash, detectar erros, etc. + +/// tip | Dica + +Darei exemplos mais concretos de como fazer isso com contêineres em um capítulo futuro: [FastAPI em contêineres - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Utilização de recursos + +Seu(s) servidor(es) é(são) um **recurso** que você pode consumir ou **utilizar**, com seus programas, o tempo de computação nas CPUs e a memória RAM disponível. + +Quanto dos recursos do sistema você quer consumir/utilizar? Pode ser fácil pensar "não muito", mas, na realidade, você provavelmente vai querer consumir **o máximo possível sem travar**. + +Se você está pagando por 3 servidores, mas está usando apenas um pouco de RAM e CPU, você provavelmente está **desperdiçando dinheiro** 💸, e provavelmente **desperdiçando energia elétrica do servidor** 🌎, etc. + +Nesse caso, seria melhor ter apenas 2 servidores e usar uma porcentagem maior de seus recursos (CPU, memória, disco, largura de banda de rede, etc). + +Por outro lado, se você tem 2 servidores e está usando **100% da CPU e RAM deles**, em algum momento um processo pedirá mais memória, e o servidor terá que usar o disco como "memória" (o que pode ser milhares de vezes mais lento), ou até mesmo **travar**. Ou um processo pode precisar fazer alguma computação e teria que esperar até que a CPU esteja livre novamente. + +Nesse caso, seria melhor obter **um servidor extra** e executar alguns processos nele para que todos tenham **RAM e tempo de CPU suficientes**. + +Também há a chance de que, por algum motivo, você tenha um **pico** de uso da sua API. Talvez ela tenha se tornado viral, ou talvez alguns outros serviços ou bots comecem a usá-la. E você pode querer ter recursos extras para estar seguro nesses casos. + +Você poderia colocar um **número arbitrário** para atingir, por exemplo, algo **entre 50% a 90%** da utilização de recursos. O ponto é que essas são provavelmente as principais coisas que você vai querer medir e usar para ajustar suas implantações. + +Você pode usar ferramentas simples como `htop` para ver a CPU e a RAM usadas no seu servidor ou a quantidade usada por cada processo. Ou você pode usar ferramentas de monitoramento mais complexas, que podem ser distribuídas entre servidores, etc. + +## Recapitular + +Você leu aqui alguns dos principais conceitos que provavelmente precisa ter em mente ao decidir como implantar seu aplicativo: + +* Segurança - HTTPS +* Executando na inicialização +* Reinicializações +* Replicação (o número de processos em execução) +* Memória +* Etapas anteriores antes de iniciar + +Entender essas ideias e como aplicá-las deve lhe dar a intuição necessária para tomar qualquer decisão ao configurar e ajustar suas implantações. 🤓 + +Nas próximas seções, darei exemplos mais concretos de possíveis estratégias que você pode seguir. 🚀 diff --git a/docs/pt/docs/deployment/docker.md b/docs/pt/docs/deployment/docker.md index df93bac2c4..cf18bb1532 100644 --- a/docs/pt/docs/deployment/docker.md +++ b/docs/pt/docs/deployment/docker.md @@ -4,7 +4,7 @@ Ao fazer o deploy de aplicações FastAPI uma abordagem comum é construir uma * Usando contêineres Linux você tem diversas vantagens incluindo **segurança**, **replicabilidade**, **simplicidade**, entre outras. -/// tip | "Dica" +/// tip | Dica Está com pressa e já sabe dessas coisas? Pode ir direto para [`Dockerfile` abaixo 👇](#construindo-uma-imagem-docker-para-fastapi). diff --git a/docs/pt/docs/deployment/https.md b/docs/pt/docs/deployment/https.md index 6ccc875fd4..904d04eaa5 100644 --- a/docs/pt/docs/deployment/https.md +++ b/docs/pt/docs/deployment/https.md @@ -4,7 +4,7 @@ Mas é bem mais complexo do que isso. -/// tip | "Dica" +/// tip | Dica Se você está com pressa ou não se importa, continue com as seções seguintes para instruções passo a passo para configurar tudo com diferentes técnicas. @@ -14,38 +14,186 @@ Para aprender o básico de HTTPS de uma perspectiva do usuário, verifique SNI. - * Esta extensão SNI permite que um único servidor (com um único endereço IP) tenha vários certificados HTTPS e atenda a vários domínios / aplicativos HTTPS. - * Para que isso funcione, um único componente (programa) em execução no servidor, ouvindo no endereço IP público, deve ter todos os certificados HTTPS no servidor. -* Depois de obter uma conexão segura, o protocolo de comunicação ainda é HTTP. - * Os conteúdos são criptografados, embora sejam enviados com o protocolo HTTP. + * No entanto, existe uma **solução** para isso. +* Há uma **extensão** para o protocolo **TLS** (aquele que lida com a criptografia no nível TCP, antes do HTTP) chamado **SNI**. + * Esta extensão SNI permite que um único servidor (com um **único endereço IP**) tenha **vários certificados HTTPS** e atenda a **vários domínios / aplicativos HTTPS**. + * Para que isso funcione, um **único** componente (programa) em execução no servidor, ouvindo no **endereço IP público**, deve ter **todos os certificados HTTPS** no servidor. +* **Depois** de obter uma conexão segura, o protocolo de comunicação **ainda é HTTP**. + * Os conteúdos são **criptografados**, embora sejam enviados com o **protocolo HTTP**. -É uma prática comum ter um programa/servidor HTTP em execução no servidor (máquina, host, etc.) e gerenciar todas as partes HTTPS: enviando as solicitações HTTP descriptografadas para o aplicativo HTTP real em execução no mesmo servidor (a aplicação **FastAPI**, neste caso), pegue a resposta HTTP do aplicativo, criptografe-a usando o certificado apropriado e envie-a de volta ao cliente usando HTTPS. Este servidor é frequentemente chamado de TLS Termination Proxy. +É uma prática comum ter um **programa/servidor HTTP** em execução no servidor (máquina, host, etc.) e **gerenciar todas as partes HTTPS**: **recebendo as requisições encriptadas**, enviando as **solicitações HTTP descriptografadas** para o aplicativo HTTP real em execução no mesmo servidor (a aplicação **FastAPI**, neste caso), pegue a **resposta HTTP** do aplicativo, **criptografe-a** usando o **certificado HTTPS** apropriado e envie-a de volta ao cliente usando **HTTPS**. Este servidor é frequentemente chamado de **Proxy de Terminação TLS**. + +Algumas das opções que você pode usar como Proxy de Terminação TLS são: + +* Traefik (que também pode gerenciar a renovação de certificados) +* Caddy (que também pode gerenciar a renovação de certificados) +* Nginx +* HAProxy ## Let's Encrypt -Antes de Let's Encrypt, esses certificados HTTPS eram vendidos por terceiros confiáveis. +Antes de Let's Encrypt, esses **certificados HTTPS** eram vendidos por terceiros confiáveis. O processo de aquisição de um desses certificados costumava ser complicado, exigia bastante papelada e os certificados eram bastante caros. -Mas então Let's Encrypt foi criado. +Mas então o **Let's Encrypt** foi criado. -Ele é um projeto da Linux Foundation que fornece certificados HTTPS gratuitamente. De forma automatizada. Esses certificados usam toda a segurança criptográfica padrão e têm vida curta (cerca de 3 meses), então a segurança é realmente melhor por causa de sua vida útil reduzida. +Ele é um projeto da Linux Foundation que fornece **certificados HTTPS gratuitamente** . De forma automatizada. Esses certificados usam toda a segurança criptográfica padrão e têm vida curta (cerca de 3 meses), então a **segurança é, na verdade, melhor** por causa de sua vida útil reduzida. Os domínios são verificados com segurança e os certificados são gerados automaticamente. Isso também permite automatizar a renovação desses certificados. -A ideia é automatizar a aquisição e renovação desses certificados, para que você tenha HTTPS seguro, de graça e para sempre. +A ideia é automatizar a aquisição e renovação desses certificados, para que você tenha **HTTPS seguro, de graça e para sempre**. + +## HTTPS para Desenvolvedores + +Aqui está um exemplo de como uma API HTTPS poderia ser estruturada, passo a passo, com foco principal nas ideias relevantes para desenvolvedores. + +### Nome do domínio + +A etapa inicial provavelmente seria **adquirir** algum **nome de domínio**. Então, você iria configurá-lo em um servidor DNS (possivelmente no mesmo provedor em nuvem). + +Você provavelmente usaria um servidor em nuvem (máquina virtual) ou algo parecido, e ele teria fixed **Endereço IP público**. + +No(s) servidor(es) DNS, você configuraria um registro (`registro A`) para apontar **seu domínio** para o **endereço IP público do seu servidor**. + +Você provavelmente fará isso apenas uma vez, na primeira vez em que tudo estiver sendo configurado. + +/// tip | Dica + +Essa parte do Nome do Domínio se dá muito antes do HTTPS, mas como tudo depende do domínio e endereço IP público, vale a pena mencioná-la aqui. + +/// + +### DNS + +Agora vamos focar em todas as partes que realmente fazem parte do HTTPS. + +Primeiro, o navegador iria verificar com os **servidores DNS** qual o **IP do domínio**, nesse caso, `someapp.example.com`. + +Os servidores DNS iriam informar o navegador para utilizar algum **endereço IP** específico. Esse seria o endereço IP público em uso no seu servidor, que você configurou nos servidores DNS. + + + +### Início do Handshake TLS + +O navegador então irá comunicar-se com esse endereço IP na **porta 443** (a porta HTTPS). + +A primeira parte dessa comunicação é apenas para estabelecer a conexão entre o cliente e o servidor e para decidir as chaves criptográficas a serem utilizadas, etc. + + + +Esse interação entre o cliente e o servidor para estabelecer uma conexão TLS é chamada de **Handshake TLS**. + +### TLS com a Extensão SNI + +**Apenas um processo** no servidor pode se conectar a uma **porta** em um **endereço IP**. Poderiam existir outros processos conectados em outras portas desse mesmo endereço IP, mas apenas um para cada combinação de endereço IP e porta. + +TLS (HTTPS) usa a porta `443` por padrão. Então essa é a porta que precisamos. + +Como apenas um único processo pode se comunicar com essa porta, o processo que faria isso seria o **Proxy de Terminação TLS**. + +O Proxy de Terminação TLS teria acesso a um ou mais **certificados TLS** (certificados HTTPS). + +Utilizando a **extensão SNI** discutida acima, o Proxy de Terminação TLS iria checar qual dos certificados TLS (HTTPS) disponíveis deve ser usado para essa conexão, utilizando o que corresponda ao domínio esperado pelo cliente. + +Nesse caso, ele usaria o certificado para `someapp.example.com`. + + + +O cliente já **confia** na entidade que gerou o certificado TLS (nesse caso, o Let's Encrypt, mas veremos sobre isso mais tarde), então ele pode **verificar** que o certificado é válido. + +Então, utilizando o certificado, o cliente e o Proxy de Terminação TLS **decidem como encriptar** o resto da **comunicação TCP**. Isso completa a parte do **Handshake TLS**. + +Após isso, o cliente e o servidor possuem uma **conexão TCP encriptada**, que é provida pelo TLS. E então eles podem usar essa conexão para começar a **comunicação HTTP** propriamente dita. + +E isso resume o que é **HTTPS**, apenas **HTTP** simples dentro de uma **conexão TLS segura** em vez de uma conexão TCP pura (não encriptada). + +/// tip | Dica + +Percebe que a encriptação da comunicação acontece no **nível do TCP**, não no nível do HTTP. + +/// + +### Solicitação HTTPS + +Agora que o cliente e servidor (especialmente o navegador e o Proxy de Terminação TLS) possuem uma **conexão TCP encriptada**, eles podem iniciar a **comunicação HTTP**. + +Então, o cliente envia uma **solicitação HTTPS**. Que é apenas uma solicitação HTTP sobre uma conexão TLS encriptada. + + + +### Desencriptando a Solicitação + +O Proxy de Terminação TLS então usaria a encriptação combinada para **desencriptar a solicitação**, e transmitiria a **solicitação básica (desencriptada)** para o processo executando a aplicação (por exemplo, um processo com Uvicorn executando a aplicação FastAPI). + + + +### Resposta HTTP + +A aplicação processaria a solicitação e retornaria uma **resposta HTTP básica (não encriptada)** para o Proxy de Terminação TLS. + + + +### Resposta HTTPS + +O Proxy de Terminação TLS iria **encriptar a resposta** utilizando a criptografia combinada anteriormente (que foi definida com o certificado para `someapp.example.com`), e devolveria para o navegador. + +No próximo passo, o navegador verifica que a resposta é válida e encriptada com a chave criptográfica correta, etc. E depois **desencripta a resposta** e a processa. + + + +O cliente (navegador) saberá que a resposta vem do servidor correto por que ela usa a criptografia que foi combinada entre eles usando o **certificado HTTPS** anterior. + +### Múltiplas Aplicações + +Podem existir **múltiplas aplicações** em execução no mesmo servidor (ou servidores), por exemplo: outras APIs ou um banco de dados. + +Apenas um processo pode estar vinculado a um IP e porta (o Proxy de Terminação TLS, por exemplo), mas outras aplicações/processos também podem estar em execução no(s) servidor(es), desde que não tentem usar a mesma **combinação de IP público e porta**. + + + +Dessa forma, o Proxy de Terminação TLS pode gerenciar o HTTPS e os certificados de **múltiplos domínios**, para múltiplas aplicações, e então transmitir as requisições para a aplicação correta em cada caso. + +### Renovação de Certificados + +Em algum momento futuro, cada certificado irá **expirar** (aproximadamente 3 meses após a aquisição). + +E então, haverá outro programa (em alguns casos pode ser o próprio Proxy de Terminação TLS) que irá interagir com o Let's Encrypt e renovar o(s) certificado(s). + + + +Os **certificados TLS** são **associados com um nome de domínio**, e não a um endereço IP. + +Então para renovar os certificados, o programa de renovação precisa **provar** para a autoridade (Let's Encrypt) que ele realmente **possui e controla esse domínio**> + +Para fazer isso, e acomodar as necessidades de diferentes aplicações, existem diferentes opções para esse programa. Algumas escolhas populares são: + +* **Modificar alguns registros DNS** + * Para isso, o programa de renovação precisa ter suporte as APIs do provedor DNS, então, dependendo do provedor DNS que você utilize, isso pode ou não ser uma opção viável. +* **Executar como um servidor** (ao menos durante o processo de aquisição do certificado) no endereço IP público associado com o domínio. + * Como dito anteriormente, apenas um processo pode estar ligado a uma porta e IP específicos. + * Essa é uma dos motivos que fazem utilizar o mesmo Proxy de Terminação TLS para gerenciar a renovação de certificados ser tão útil. + * Caso contrário, você pode ter que parar a execução do Proxy de Terminação TLS momentaneamente, inicializar o programa de renovação para renovar os certificados, e então reiniciar o Proxy de Terminação TLS. Isso não é o ideal, já que sua(s) aplicação(ões) não vão estar disponíveis enquanto o Proxy de Terminação TLS estiver desligado. + +Todo esse processo de renovação, enquanto o aplicativo ainda funciona, é uma das principais razões para preferir um **sistema separado para gerenciar HTTPS** com um Proxy de Terminação TLS em vez de usar os certificados TLS no servidor da aplicação diretamente (e.g. com o Uvicorn). + +## Recapitulando + +Possuir **HTTPS** habilitado na sua aplicação é bastante importante, e até **crítico** na maioria dos casos. A maior parte do esforço que você tem que colocar sobre o HTTPS como desenvolvedor está em **entender esses conceitos** e como eles funcionam. + +Mas uma vez que você saiba o básico de **HTTPS para desenvolvedores**, você pode combinar e configurar diferentes ferramentas facilmente para gerenciar tudo de uma forma simples. + +Em alguns dos próximos capítulos, eu mostrarei para você vários exemplos concretos de como configurar o **HTTPS** para aplicações **FastAPI**. 🔒 diff --git a/docs/pt/docs/deployment/manually.md b/docs/pt/docs/deployment/manually.md new file mode 100644 index 0000000000..c7caabbcd0 --- /dev/null +++ b/docs/pt/docs/deployment/manually.md @@ -0,0 +1,157 @@ +# Execute um Servidor Manualmente + +## Utilize o comando `fastapi run` + +Em resumo, utilize o comando `fastapi run` para inicializar sua aplicação FastAPI: + +
+ +```console +$ fastapi run main.py + + FastAPI Starting production server 🚀 + + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with + the following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +Isto deve funcionar para a maioria dos casos. 😎 + +Você pode utilizar esse comando, por exemplo, para iniciar sua aplicação **FastAPI** em um contêiner, em um servidor, etc. + +## Servidores ASGI + +Vamos nos aprofundar um pouco mais em detalhes. + +FastAPI utiliza um padrão para construir frameworks e servidores web em Python chamado ASGI. FastAPI é um framework web ASGI. + +A principal coisa que você precisa para executar uma aplicação **FastAPI** (ou qualquer outra aplicação ASGI) em uma máquina de servidor remoto é um programa de servidor ASGI como o **Uvicorn**, que é o que vem por padrão no comando `fastapi`. + +Existem diversas alternativas, incluindo: + +* Uvicorn: um servidor ASGI de alta performance. +* Hypercorn: um servidor ASGI compátivel com HTTP/2, Trio e outros recursos. +* Daphne: servidor ASGI construído para Django Channels. +* Granian: um servidor HTTP Rust para aplicações Python. +* NGINX Unit: NGINX Unit é um runtime de aplicação web leve e versátil. + +## Máquina Servidora e Programa Servidor + +Existe um pequeno detalhe sobre estes nomes para se manter em mente. 💡 + +A palavra "**servidor**" é comumente usada para se referir tanto ao computador remoto/nuvem (a máquina física ou virtual) quanto ao programa que está sendo executado nessa máquina (por exemplo, Uvicorn). + +Apenas tenha em mente que quando você ler "servidor" em geral, isso pode se referir a uma dessas duas coisas. + +Quando se refere à máquina remota, é comum chamá-la de **servidor**, mas também de **máquina**, **VM** (máquina virtual), **nó**. Todos esses termos se referem a algum tipo de máquina remota, normalmente executando Linux, onde você executa programas. + +## Instale o Programa Servidor + +Quando você instala o FastAPI, ele vem com um servidor de produção, o Uvicorn, e você pode iniciá-lo com o comando `fastapi run`. + +Mas você também pode instalar um servidor ASGI manualmente. + +Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e, em seguida, você pode instalar a aplicação do servidor. + +Por exemplo, para instalar o Uvicorn: + +
+ +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +
+ +Um processo semelhante se aplicaria a qualquer outro programa de servidor ASGI. + +/// tip | Dica + +Adicionando o `standard`, o Uvicorn instalará e usará algumas dependências extras recomendadas. + +Isso inclui o `uvloop`, a substituição de alto desempenho para `asyncio`, que fornece um grande aumento de desempenho de concorrência. + +Quando você instala o FastAPI com algo como `pip install "fastapi[standard]"`, você já obtém `uvicorn[standard]` também. + +/// + +## Execute o Programa Servidor + +Se você instalou um servidor ASGI manualmente, normalmente precisará passar uma string de importação em um formato especial para que ele importe sua aplicação FastAPI: + +
+ +```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) +``` + +
+ +/// note | Nota + +O comando `uvicorn main:app` refere-se a: + +* `main`: o arquivo `main.py` (o "módulo" Python). +* `app`: o objeto criado dentro de `main.py` com a linha `app = FastAPI()`. + +É equivalente a: + +```Python +from main import app +``` + +/// + +Cada programa de servidor ASGI alternativo teria um comando semelhante, você pode ler mais na documentação respectiva. + +/// warning | Aviso + +Uvicorn e outros servidores suportam a opção `--reload` que é útil durante o desenvolvimento. + +A opção `--reload` consome muito mais recursos, é mais instável, etc. + +Ela ajuda muito durante o **desenvolvimento**, mas você **não deve** usá-la em **produção**. + +/// + +## Conceitos de Implantação + +Esses exemplos executam o programa do servidor (por exemplo, Uvicorn), iniciando **um único processo**, ouvindo em todos os IPs (`0.0.0.0`) em uma porta predefinida (por exemplo, `80`). + +Esta é a ideia básica. Mas você provavelmente vai querer cuidar de algumas coisas adicionais, como: + +* 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 começar + +Vou te contar mais sobre cada um desses conceitos, como pensar sobre eles e alguns exemplos concretos com estratégias para lidar com eles nos próximos capítulos. 🚀 diff --git a/docs/pt/docs/deployment/server-workers.md b/docs/pt/docs/deployment/server-workers.md new file mode 100644 index 0000000000..a0db1bea42 --- /dev/null +++ b/docs/pt/docs/deployment/server-workers.md @@ -0,0 +1,139 @@ +# Trabalhadores do Servidor - Uvicorn com Trabalhadores + +Vamos rever os conceitos de implantação anteriores: + +* Segurança - HTTPS +* Executando na inicialização +* Reinicializações +* **Replicação (o número de processos em execução)** +* Memória +* Etapas anteriores antes de iniciar + +Até este ponto, com todos os tutoriais nos documentos, você provavelmente estava executando um **programa de servidor**, por exemplo, usando o comando `fastapi`, que executa o Uvicorn, executando um **único processo**. + +Ao implantar aplicativos, você provavelmente desejará ter alguma **replicação de processos** para aproveitar **vários núcleos** e poder lidar com mais solicitações. + +Como você viu no capítulo anterior sobre [Conceitos de implantação](concepts.md){.internal-link target=_blank}, há várias estratégias que você pode usar. + +Aqui mostrarei como usar o **Uvicorn** com **processos de trabalho** usando o comando `fastapi` ou o comando `uvicorn` diretamente. + +/// info | Informação + +Se você estiver usando contêineres, por exemplo com Docker ou Kubernetes, falarei mais sobre isso no próximo capítulo: [FastAPI em contêineres - Docker](docker.md){.internal-link target=_blank}. + +Em particular, ao executar no **Kubernetes** você provavelmente **não** vai querer usar vários trabalhadores e, em vez disso, executar **um único processo Uvicorn por contêiner**, mas falarei sobre isso mais adiante neste capítulo. + +/// + +## Vários trabalhadores + +Você pode iniciar vários trabalhadores com a opção de linha de comando `--workers`: + +//// tab | `fastapi` + +Se você usar o comando `fastapi`: + +
+ +```console +$ fastapi run --workers 4 main.py + + FastAPI Starting production server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. +``` + +
+ +//// + +//// tab | `uvicorn` + +Se você preferir usar o comando `uvicorn` diretamente: + +
+ +```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. +``` + +
+ +//// + +A única opção nova aqui é `--workers` informando ao Uvicorn para iniciar 4 processos de trabalho. + +Você também pode ver que ele mostra o **PID** de cada processo, `27365` para o processo pai (este é o **gerenciador de processos**) e um para cada processo de trabalho: `27368`, `27369`, `27370` e `27367`. + +## Conceitos de Implantação + +Aqui você viu como usar vários **trabalhadores** para **paralelizar** a execução do aplicativo, aproveitar **vários núcleos** na CPU e conseguir atender **mais solicitações**. + +Da lista de conceitos de implantação acima, o uso de trabalhadores ajudaria principalmente com a parte da **replicação** e um pouco com as **reinicializações**, mas você ainda precisa cuidar dos outros: + +* **Segurança - HTTPS** +* **Executando na inicialização** +* ***Reinicializações*** +* Replicação (o número de processos em execução) +* **Memória** +* **Etapas anteriores antes de iniciar** + +## Contêineres e Docker + +No próximo capítulo sobre [FastAPI em contêineres - Docker](docker.md){.internal-link target=_blank}, explicarei algumas estratégias que você pode usar para lidar com os outros **conceitos de implantação**. + +Vou mostrar como **construir sua própria imagem do zero** para executar um único processo Uvicorn. É um processo simples e provavelmente é o que você gostaria de fazer ao usar um sistema de gerenciamento de contêineres distribuídos como o **Kubernetes**. + +## Recapitular + +Você pode usar vários processos de trabalho com a opção CLI `--workers` com os comandos `fastapi` ou `uvicorn` para aproveitar as vantagens de **CPUs multi-core** e executar **vários processos em paralelo**. + +Você pode usar essas ferramentas e ideias se estiver configurando **seu próprio sistema de implantação** enquanto cuida dos outros conceitos de implantação. + +Confira o próximo capítulo para aprender sobre **FastAPI** com contêineres (por exemplo, Docker e Kubernetes). Você verá que essas ferramentas têm maneiras simples de resolver os outros **conceitos de implantação** também. ✨ diff --git a/docs/pt/docs/deployment/versions.md b/docs/pt/docs/deployment/versions.md index 79243a0146..323ddbd450 100644 --- a/docs/pt/docs/deployment/versions.md +++ b/docs/pt/docs/deployment/versions.md @@ -42,7 +42,7 @@ Seguindo as convenções de controle de versão semântica, qualquer versão aba FastAPI também segue a convenção de que qualquer alteração de versão "PATCH" é para correção de bugs e alterações não significativas. -/// tip | "Dica" +/// tip | Dica O "PATCH" é o último número, por exemplo, em `0.2.3`, a versão PATCH é `3`. @@ -56,7 +56,7 @@ fastapi>=0.45.0,<0.46.0 Mudanças significativas e novos recursos são adicionados em versões "MINOR". -/// tip | "Dica" +/// tip | Dica O "MINOR" é o número que está no meio, por exemplo, em `0.2.3`, a versão MINOR é `2`. diff --git a/docs/pt/docs/environment-variables.md b/docs/pt/docs/environment-variables.md new file mode 100644 index 0000000000..432f78af04 --- /dev/null +++ b/docs/pt/docs/environment-variables.md @@ -0,0 +1,298 @@ +# Variáveis de Ambiente + +/// tip | Dica + +Se você já sabe o que são "variáveis de ambiente" e como usá-las, pode pular esta seção. + +/// + +Uma variável de ambiente (também conhecida como "**env var**") é uma variável que existe **fora** do código Python, no **sistema operacional**, e pode ser lida pelo seu código Python (ou por outros programas também). + +Variáveis de ambiente podem ser úteis para lidar com **configurações** do aplicativo, como parte da **instalação** do Python, etc. + +## Criar e Usar Variáveis de Ambiente + +Você pode **criar** e usar variáveis de ambiente no **shell (terminal)**, sem precisar do Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Você pode criar uma variável de ambiente MY_NAME com +$ export MY_NAME="Wade Wilson" + +// Então você pode usá-la com outros programas, como +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Criar uma variável de ambiente MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Usá-la com outros programas, como +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Ler Variáveis de Ambiente no Python + +Você também pode criar variáveis de ambiente **fora** do Python, no terminal (ou com qualquer outro método) e depois **lê-las no Python**. + +Por exemplo, você poderia ter um arquivo `main.py` com: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip | Dica + +O segundo argumento para `os.getenv()` é o valor padrão a ser retornado. + +Se não for fornecido, é `None` por padrão, Aqui fornecemos `"World"` como o valor padrão a ser usado. + +/// + +Então você poderia chamar esse programa Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Aqui ainda não definimos a variável de ambiente +$ python main.py + +// Como não definimos a variável de ambiente, obtemos o valor padrão + +Hello World from Python + +// Mas se criarmos uma variável de ambiente primeiro +$ export MY_NAME="Wade Wilson" + +// E então chamar o programa novamente +$ python main.py + +// Agora ele pode ler a variável de ambiente + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Aqui ainda não definimos a variável de ambiente +$ python main.py + +// Como não definimos a variável de ambiente, obtemos o valor padrão + +Hello World from Python + +// Mas se criarmos uma variável de ambiente primeiro +$ $Env:MY_NAME = "Wade Wilson" + +// E então chamar o programa novamente +$ python main.py + +// Agora ele pode ler a variável de ambiente + +Hello Wade Wilson from Python +``` + +
+ +//// + +Como as variáveis de ambiente podem ser definidas fora do código, mas podem ser lidas pelo código e não precisam ser armazenadas (com versão no `git`) com o restante dos arquivos, é comum usá-las para configurações ou **definições**. + +Você também pode criar uma variável de ambiente apenas para uma **invocação específica do programa**, que só está disponível para aquele programa e apenas pela duração dele. + +Para fazer isso, crie-a na mesma linha, antes do próprio programa: + +
+ +```console +// Criar uma variável de ambiente MY_NAME para esta chamada de programa +$ MY_NAME="Wade Wilson" python main.py + +// Agora ele pode ler a variável de ambiente + +Hello Wade Wilson from Python + +// A variável de ambiente não existe mais depois +$ python main.py + +Hello World from Python +``` + +
+ +/// tip | Dica + +Você pode ler mais sobre isso em The Twelve-Factor App: Config. + +/// + +## Tipos e Validação + +Essas variáveis de ambiente só podem lidar com **strings de texto**, pois são externas ao Python e precisam ser compatíveis com outros programas e com o resto do sistema (e até mesmo com diferentes sistemas operacionais, como Linux, Windows, macOS). + +Isso significa que **qualquer valor** lido em Python de uma variável de ambiente **será uma `str`**, e qualquer conversão para um tipo diferente ou qualquer validação precisa ser feita no código. + +Você aprenderá mais sobre como usar variáveis de ambiente para lidar com **configurações do aplicativo** no [Guia do Usuário Avançado - Configurações e Variáveis de Ambiente](./advanced/settings.md){.internal-link target=_blank}. + +## Variável de Ambiente `PATH` + +Existe uma variável de ambiente **especial** chamada **`PATH`** que é usada pelos sistemas operacionais (Linux, macOS, Windows) para encontrar programas para executar. + +O valor da variável `PATH` é uma longa string composta por diretórios separados por dois pontos `:` no Linux e macOS, e por ponto e vírgula `;` no Windows. + +Por exemplo, a variável de ambiente `PATH` poderia ter esta aparência: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Isso significa que o sistema deve procurar programas nos diretórios: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +Isso significa que o sistema deve procurar programas nos diretórios: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +Quando você digita um **comando** no terminal, o sistema operacional **procura** o programa em **cada um dos diretórios** listados na variável de ambiente `PATH`. + +Por exemplo, quando você digita `python` no terminal, o sistema operacional procura um programa chamado `python` no **primeiro diretório** dessa lista. + +Se ele o encontrar, então ele o **usará**. Caso contrário, ele continua procurando nos **outros diretórios**. + +### Instalando o Python e Atualizando o `PATH` + +Durante a instalação do Python, você pode ser questionado sobre a atualização da variável de ambiente `PATH`. + +//// tab | Linux, macOS + +Vamos supor que você instale o Python e ele fique em um diretório `/opt/custompython/bin`. + +Se você concordar em atualizar a variável de ambiente `PATH`, o instalador adicionará `/opt/custompython/bin` para a variável de ambiente `PATH`. + +Poderia parecer assim: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +Dessa forma, ao digitar `python` no terminal, o sistema encontrará o programa Python em `/opt/custompython/bin` (último diretório) e o utilizará. + +//// + +//// tab | Windows + +Digamos que você instala o Python e ele acaba em um diretório `C:\opt\custompython\bin`. + +Se você disser sim para atualizar a variável de ambiente `PATH`, o instalador adicionará `C:\opt\custompython\bin` à variável de ambiente `PATH`. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +Dessa forma, quando você digitar `python` no terminal, o sistema encontrará o programa Python em `C:\opt\custompython\bin` (o último diretório) e o utilizará. + +//// + +Então, se você digitar: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +O sistema **encontrará** o programa `python` em `/opt/custompython/bin` e o executará. + +Seria aproximadamente equivalente a digitar: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +O sistema **encontrará** o programa `python` em `C:\opt\custompython\bin\python` e o executará. + +Seria aproximadamente equivalente a digitar: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +Essas informações serão úteis ao aprender sobre [Ambientes Virtuais](virtual-environments.md){.internal-link target=_blank}. + +## Conclusão + +Com isso, você deve ter uma compreensão básica do que são **variáveis ​​de ambiente** e como usá-las em Python. + +Você também pode ler mais sobre elas na Wikipedia para Variáveis ​​de Ambiente. + +Em muitos casos, não é muito óbvio como as variáveis ​​de ambiente seriam úteis e aplicáveis ​​imediatamente. Mas elas continuam aparecendo em muitos cenários diferentes quando você está desenvolvendo, então é bom saber sobre elas. + +Por exemplo, você precisará dessas informações na próxima seção, sobre [Ambientes Virtuais](virtual-environments.md). diff --git a/docs/pt/docs/fastapi-cli.md b/docs/pt/docs/fastapi-cli.md index 8296866313..f33c2ba2aa 100644 --- a/docs/pt/docs/fastapi-cli.md +++ b/docs/pt/docs/fastapi-cli.md @@ -60,7 +60,7 @@ O FastAPI CLI recebe o caminho do seu programa Python, detecta automaticamente a Para produção você usaria `fastapi run` no lugar. 🚀 -Internamente, **FastAPI CLI** usa Uvicorn, um servidor ASGI de alta performance e pronto para produção. 😎 +Internamente, **FastAPI CLI** usa Uvicorn, um servidor ASGI de alta performance e pronto para produção. 😎 ## `fastapi dev` diff --git a/docs/pt/docs/features.md b/docs/pt/docs/features.md index a90a8094ba..ccc3300d64 100644 --- a/docs/pt/docs/features.md +++ b/docs/pt/docs/features.md @@ -159,7 +159,7 @@ Qualquer integração é projetada para ser tão simples de usar (com dependênc ## Recursos do Starlette -**FastAPI** é totalmente compatível com (e baseado no) Starlette. Então, qualquer código adicional Starlette que você tiver, também funcionará. +**FastAPI** é totalmente compatível com (e baseado no) Starlette. Então, qualquer código adicional Starlette que você tiver, também funcionará. `FastAPI` é na verdade uma sub-classe do `Starlette`. Então, se você já conhece ou usa Starlette, a maioria das funcionalidades se comportará da mesma forma. diff --git a/docs/pt/docs/help-fastapi.md b/docs/pt/docs/help-fastapi.md index 61eeac0dcf..0de1ed6484 100644 --- a/docs/pt/docs/help-fastapi.md +++ b/docs/pt/docs/help-fastapi.md @@ -20,9 +20,9 @@ Você pode se inscrever (pouco frequente) [**FastAPI e amigos** newsletter](news * Mudanças de última hora 🚨 * Truques e dicas ✅ -## Siga o FastAPI no twitter +## Siga o FastAPI no X (Twitter) -Siga @fastapi no **Twitter** para receber as últimas notícias sobre o **FastAPI**. 🐦 +Siga @fastapi no **X (Twitter)** para receber as últimas notícias sobre o **FastAPI**. 🐦 ## Favorite o **FastAPI** no GitHub @@ -47,19 +47,19 @@ Você pode: * Me siga no **GitHub**. * Ver também outros projetos Open Source criados por mim que podem te ajudar. * Me seguir para saber quando um novo projeto Open Source for criado. -* Me siga no **Twitter**. +* Me siga no **X (Twitter)**. * Me dizer o motivo pelo o qual você está usando o FastAPI(Adoro ouvir esse tipo de comentário). * Saber quando eu soltar novos anúncios ou novas ferramentas. - * Também é possivel seguir o @fastapi no Twitter (uma conta aparte). + * Também é possivel seguir o @fastapi no X (Twitter) (uma conta aparte). * Conect-se comigo no **Linkedin**. - * Saber quando eu fizer novos anúncios ou novas ferramentas (apesar de que uso o twitter com mais frequência 🤷‍♂). + * Saber quando eu fizer novos anúncios ou novas ferramentas (apesar de que uso o X (Twitter) com mais frequência 🤷‍♂). * Ler meus artigos (ou me seguir) no **Dev.to** ou no **Medium**. * Ficar por dentro de novas ideias, artigos, e ferramentas criadas por mim. * Me siga para saber quando eu publicar algo novo. ## Tweete sobre **FastAPI** -Tweete sobre o **FastAPI** e compartilhe comigo e com os outros o porque de gostar do FastAPI. 🎉 +Tweete sobre o **FastAPI** e compartilhe comigo e com os outros o porque de gostar do FastAPI. 🎉 Adoro ouvir sobre como o **FastAPI** é usado, o que você gosta nele, em qual projeto/empresa está sendo usado, etc. @@ -109,7 +109,7 @@ Assim podendo tentar ajudar a resolver essas questões. Entre no 👥 server de conversa do Discord 👥 e conheça novas pessoas da comunidade do FastAPI. -/// tip | "Dica" +/// tip | Dica Para perguntas, pergunte nas questões do GitHub, lá tem um chance maior de você ser ajudado sobre o FastAPI [FastAPI Experts](fastapi-people.md#especialistas){.internal-link target=_blank}. diff --git a/docs/pt/docs/history-design-future.md b/docs/pt/docs/history-design-future.md index 4ec2174056..1d0768c629 100644 --- a/docs/pt/docs/history-design-future.md +++ b/docs/pt/docs/history-design-future.md @@ -58,7 +58,7 @@ Após testar várias alternativas, eu decidi que usaria o **Starlette**, outro requisito chave. +Durante o desenvolvimento, eu também contribuí com o **Starlette**, outro requisito chave. ## Desenvolvimento diff --git a/docs/pt/docs/how-to/conditional-openapi.md b/docs/pt/docs/how-to/conditional-openapi.md new file mode 100644 index 0000000000..6b44e9c813 --- /dev/null +++ b/docs/pt/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# OpenAPI condicional + +Se necessário, você pode usar configurações e variáveis ​​de ambiente para configurar o OpenAPI condicionalmente, dependendo do ambiente, e até mesmo desativá-lo completamente. + +## Sobre segurança, APIs e documentos + +Ocultar suas interfaces de usuário de documentação na produção *não deveria* ser a maneira de proteger sua API. + +Isso não adiciona nenhuma segurança extra à sua API; as *operações de rotas* ainda estarão disponíveis onde estão. + +Se houver uma falha de segurança no seu código, ela ainda existirá. + +Ocultar a documentação apenas torna mais difícil entender como interagir com sua API e pode dificultar sua depuração na produção. Pode ser considerado simplesmente uma forma de Segurança através da obscuridade. + +Se você quiser proteger sua API, há várias coisas melhores que você pode fazer, por exemplo: + +* Certifique-se de ter modelos Pydantic bem definidos para seus corpos de solicitação e respostas. +* Configure quaisquer permissões e funções necessárias usando dependências. +* Nunca armazene senhas em texto simples, apenas hashes de senha. +* Implemente e use ferramentas criptográficas bem conhecidas, como tokens JWT e Passlib, etc. +* Adicione controles de permissão mais granulares com escopos OAuth2 quando necessário. +* ...etc. + +No entanto, você pode ter um caso de uso muito específico em que realmente precisa desabilitar a documentação da API para algum ambiente (por exemplo, para produção) ou dependendo de configurações de variáveis ​​de ambiente. + +## OpenAPI condicional com configurações e variáveis ​​de ambiente + +Você pode usar facilmente as mesmas configurações do Pydantic para configurar sua OpenAPI gerada e as interfaces de usuário de documentos. + +Por exemplo: + +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} + +Aqui declaramos a configuração `openapi_url` com o mesmo padrão de `"/openapi.json"`. + +E então o usamos ao criar o aplicativo `FastAPI`. + +Então você pode desabilitar o OpenAPI (incluindo os documentos da interface do usuário) definindo a variável de ambiente `OPENAPI_URL` como uma string vazia, como: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Então, se você acessar as URLs em `/openapi.json`, `/docs` ou `/redoc`, você receberá apenas um erro `404 Não Encontrado` como: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/pt/docs/how-to/configure-swagger-ui.md b/docs/pt/docs/how-to/configure-swagger-ui.md new file mode 100644 index 0000000000..915b2b5c57 --- /dev/null +++ b/docs/pt/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Configurar Swagger UI + +Você pode configurar alguns parâmetros extras da UI do Swagger. + +Para configurá-los, passe o argumento `swagger_ui_parameters` ao criar o objeto de aplicativo `FastAPI()` ou para a função `get_swagger_ui_html()`. + +`swagger_ui_parameters` recebe um dicionário com as configurações passadas diretamente para o Swagger UI. + +O FastAPI converte as configurações para **JSON** para torná-las compatíveis com JavaScript, pois é disso que o Swagger UI precisa. + +## Desabilitar realce de sintaxe + +Por exemplo, você pode desabilitar o destaque de sintaxe na UI do Swagger. + +Sem alterar as configurações, o destaque de sintaxe é habilitado por padrão: + + + +Mas você pode desabilitá-lo definindo `syntaxHighlight` como `False`: + +{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} + +...e então o Swagger UI não mostrará mais o destaque de sintaxe: + + + +## Alterar o tema + +Da mesma forma que você pode definir o tema de destaque de sintaxe com a chave `"syntaxHighlight.theme"` (observe que há um ponto no meio): + +{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} + +Essa configuração alteraria o tema de cores de destaque de sintaxe: + + + +## Alterar parâmetros de UI padrão do Swagger + +O FastAPI inclui alguns parâmetros de configuração padrão apropriados para a maioria dos casos de uso. + +Inclui estas configurações padrão: + +{* ../../fastapi/openapi/docs.py ln[7:23] *} + +Você pode substituir qualquer um deles definindo um valor diferente no argumento `swagger_ui_parameters`. + +Por exemplo, para desabilitar `deepLinking` você pode passar essas configurações para `swagger_ui_parameters`: + +{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} + +## Outros parâmetros da UI do Swagger + +Para ver todas as outras configurações possíveis que você pode usar, leia a documentação oficial dos parâmetros da UI do Swagger. + +## Configurações somente JavaScript + +A interface do usuário do Swagger também permite que outras configurações sejam objetos **somente JavaScript** (por exemplo, funções JavaScript). + +O FastAPI também inclui estas configurações de `predefinições` somente para JavaScript: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +Esses são objetos **JavaScript**, não strings, então você não pode passá-los diretamente do código Python. + +Se você precisar usar configurações somente JavaScript como essas, você pode usar um dos métodos acima. Sobrescreva todas as *operações de rotas* do Swagger UI e escreva manualmente qualquer JavaScript que você precisar. diff --git a/docs/pt/docs/how-to/custom-docs-ui-assets.md b/docs/pt/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 0000000000..b7de6c8bdc --- /dev/null +++ b/docs/pt/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# Recursos Estáticos Personalizados para a UI de Documentação (Hospedagem Própria) + +A documentação da API usa **Swagger UI** e **ReDoc**, e cada um deles precisa de alguns arquivos JavaScript e CSS. + +Por padrão, esses arquivos são fornecidos por um CDN. + +Mas é possível personalizá-los, você pode definir um CDN específico ou providenciar os arquivos você mesmo. + +## CDN Personalizado para JavaScript e CSS + +Vamos supor que você deseja usar um CDN diferente, por exemplo, você deseja usar `https://unpkg.com/`. + +Isso pode ser útil se, por exemplo, você mora em um país que restringe algumas URLs. + +### Desativar a documentação automática + +O primeiro passo é desativar a documentação automática, pois por padrão, ela usa o CDN padrão. + +Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} + +### Incluir a documentação personalizada + +Agora você pode criar as *operações de rota* para a documentação personalizada. + +Você pode reutilizar as funções internas do FastAPI para criar as páginas HTML para a documentação e passar os argumentos necessários: + +* `openapi_url`: a URL onde a página HTML para a documentação pode obter o esquema OpenAPI para a sua API. Você pode usar aqui o atributo `app.openapi_url`. +* `title`: o título da sua API. +* `oauth2_redirect_url`: você pode usar `app.swagger_ui_oauth2_redirect_url` aqui para usar o padrão. +* `swagger_js_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **JavaScript**. Este é o URL do CDN personalizado. +* `swagger_css_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **CSS**. Este é o URL do CDN personalizado. + +E de forma semelhante para o ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | Dica + +A *operação de rota* para `swagger_ui_redirect` é um auxiliar para quando você usa OAuth2. + +Se você integrar sua API com um provedor OAuth2, você poderá autenticar e voltar para a documentação da API com as credenciais adquiridas. E interagir com ela usando a autenticação OAuth2 real. + +Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse auxiliar de "redirecionamento". + +/// + +### Criar uma *operação de rota* para testar + +Agora, para poder testar se tudo funciona, crie uma *operação de rota*: + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} + +### Teste + +Agora, você deve ser capaz de ir para a documentação em http://127.0.0.1:8000/docs, e recarregar a página, ela carregará esses recursos do novo CDN. + +## Hospedagem Própria de JavaScript e CSS para a documentação + +Hospedar o JavaScript e o CSS pode ser útil se, por exemplo, você precisa que seu aplicativo continue funcionando mesmo offline, sem acesso aberto à Internet, ou em uma rede local. + +Aqui você verá como providenciar esses arquivos você mesmo, no mesmo aplicativo FastAPI, e configurar a documentação para usá-los. + +### Estrutura de Arquivos do Projeto + +Vamos supor que a estrutura de arquivos do seu projeto se pareça com isso: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Agora crie um diretório para armazenar esses arquivos estáticos. + +Sua nova estrutura de arquivos poderia se parecer com isso: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Baixe os arquivos + +Baixe os arquivos estáticos necessários para a documentação e coloque-os no diretório `static/`. + +Você provavelmente pode clicar com o botão direito em cada link e selecionar uma opção semelhante a `Salvar link como...`. + +**Swagger UI** usa os arquivos: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +E o **ReDoc** usa os arquivos: + +* `redoc.standalone.js` + +Depois disso, sua estrutura de arquivos deve se parecer com: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Prover os arquivos estáticos + +* Importe `StaticFiles`. +* "Monte" a instância `StaticFiles()` em um caminho específico. + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} + +### Teste os arquivos estáticos + +Inicialize seu aplicativo e vá para http://127.0.0.1:8000/static/redoc.standalone.js. + +Você deverá ver um arquivo JavaScript muito longo para o **ReDoc**. + +Esse arquivo pode começar com algo como: + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +Isso confirma que você está conseguindo fornecer arquivos estáticos do seu aplicativo e que você colocou os arquivos estáticos para a documentação no local correto. + +Agora, podemos configurar o aplicativo para usar esses arquivos estáticos para a documentação. + +### Desativar a documentação automática para arquivos estáticos + +Da mesma forma que ao usar um CDN personalizado, o primeiro passo é desativar a documentação automática, pois ela usa o CDN padrão. + +Para desativá-los, defina suas URLs como `None` ao criar seu aplicativo `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} + +### Incluir a documentação personalizada para arquivos estáticos + +E da mesma forma que com um CDN personalizado, agora você pode criar as *operações de rota* para a documentação personalizada. + +Novamente, você pode reutilizar as funções internas do FastAPI para criar as páginas HTML para a documentação e passar os argumentos necessários: + +* `openapi_url`: a URL onde a página HTML para a documentação pode obter o esquema OpenAPI para a sua API. Você pode usar aqui o atributo `app.openapi_url`. +* `title`: o título da sua API. +* `oauth2_redirect_url`: Você pode usar `app.swagger_ui_oauth2_redirect_url` aqui para usar o padrão. +* `swagger_js_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **JavaScript**. Este é o URL do CDN personalizado. **Este é o URL que seu aplicativo está fornecendo**. +* `swagger_css_url`: a URL onde a página HTML para a sua documentação do Swagger UI pode obter o arquivo **CSS**. **Esse é o que seu aplicativo está fornecendo**. + +E de forma semelhante para o ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | Dica + +A *operação de rota* para `swagger_ui_redirect` é um auxiliar para quando você usa OAuth2. + +Se você integrar sua API com um provedor OAuth2, você poderá autenticar e voltar para a documentação da API com as credenciais adquiridas. E, então, interagir com ela usando a autenticação OAuth2 real. + +Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse auxiliar de "redirect". + +/// + +### Criar uma *operação de rota* para testar arquivos estáticos + +Agora, para poder testar se tudo funciona, crie uma *operação de rota*: + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} + +### Teste a UI de Arquivos Estáticos + +Agora, você deve ser capaz de desconectar o WiFi, ir para a documentação em http://127.0.0.1:8000/docs, e recarregar a página. + +E mesmo sem Internet, você será capaz de ver a documentação da sua API e interagir com ela. diff --git a/docs/pt/docs/how-to/custom-request-and-route.md b/docs/pt/docs/how-to/custom-request-and-route.md new file mode 100644 index 0000000000..151a0f5d44 --- /dev/null +++ b/docs/pt/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# Requisições Personalizadas e Classes da APIRoute + +Em algum casos, você pode querer sobreescrever a lógica usada pelas classes `Request`e `APIRoute`. + +Em particular, isso pode ser uma boa alternativa para uma lógica em um middleware + +Por exemplo, se você quiser ler ou manipular o corpo da requisição antes que ele seja processado pela sua aplicação. + +/// danger | Perigo + +Isso é um recurso "avançado". + +Se você for um iniciante em **FastAPI** você deve considerar pular essa seção. + +/// + +## Casos de Uso + +Alguns casos de uso incluem: + +* Converter requisições não-JSON para JSON (por exemplo, `msgpack`). +* Descomprimir corpos de requisição comprimidos com gzip. +* Registrar automaticamente todos os corpos de requisição. + +## Manipulando codificações de corpo de requisição personalizadas + +Vamos ver como usar uma subclasse personalizada de `Request` para descomprimir requisições gzip. + +E uma subclasse de `APIRoute` para usar essa classe de requisição personalizada. + +### Criar uma classe `GzipRequest` personalizada + +/// tip | Dica + +Isso é um exemplo de brincadeira para demonstrar como funciona, se você precisar de suporte para Gzip, você pode usar o [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank} fornecido. + +/// + +Primeiro, criamos uma classe `GzipRequest`, que irá sobrescrever o método `Request.body()` para descomprimir o corpo na presença de um cabeçalho apropriado. + +Se não houver `gzip` no cabeçalho, ele não tentará descomprimir o corpo. + +Dessa forma, a mesma classe de rota pode lidar com requisições comprimidas ou não comprimidas. + +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} + +### Criar uma classe `GzipRoute` personalizada + +Em seguida, criamos uma subclasse personalizada de `fastapi.routing.APIRoute` que fará uso do `GzipRequest`. + +Dessa vez, ele irá sobrescrever o método `APIRoute.get_route_handler()`. + +Esse método retorna uma função. E essa função é o que irá receber uma requisição e retornar uma resposta. + +Aqui nós usamos para criar um `GzipRequest` a partir da requisição original. + +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} + +/// note | Detalhes Técnicos + +Um `Request` também tem um `request.receive`, que é uma função para "receber" o corpo da requisição. + +Um `Request` também tem um `request.receive`, que é uma função para "receber" o corpo da requisição. + +O dicionário `scope` e a função `receive` são ambos parte da especificação ASGI. + +E essas duas coisas, `scope` e `receive`, são o que é necessário para criar uma nova instância de `Request`. + +Para aprender mais sobre o `Request` confira a documentação do Starlette sobre Requests. + +/// + +A única coisa que a função retornada por `GzipRequest.get_route_handler` faz de diferente é converter o `Request` para um `GzipRequest`. + +Fazendo isso, nosso `GzipRequest` irá cuidar de descomprimir os dados (se necessário) antes de passá-los para nossas *operações de rota*. + +Depois disso, toda a lógica de processamento é a mesma. + +Mas por causa das nossas mudanças em `GzipRequest.body`, o corpo da requisição será automaticamente descomprimido quando for carregado pelo **FastAPI** quando necessário. + +## Acessando o corpo da requisição em um manipulador de exceção + +/// tip | Dica + +Para resolver esse mesmo problema, é provavelmente muito mais fácil usar o `body` em um manipulador personalizado para `RequestValidationError` ([Tratando Erros](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + +Mas esse exemplo ainda é valido e mostra como interagir com os componentes internos. + +/// + +Também podemos usar essa mesma abordagem para acessar o corpo da requisição em um manipulador de exceção. + +Tudo que precisamos fazer é manipular a requisição dentro de um bloco `try`/`except`: + +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} + +Se uma exceção ocorrer, a instância `Request` ainda estará em escopo, então podemos ler e fazer uso do corpo da requisição ao lidar com o erro: + +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} + +## Classe `APIRoute` personalizada em um router + +você também pode definir o parametro `route_class` de uma `APIRouter`; + +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} + +Nesse exemplo, as *operações de rota* sob o `router` irão usar a classe `TimedRoute` personalizada, e terão um cabeçalho extra `X-Response-Time` na resposta com o tempo que levou para gerar a resposta: + +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} diff --git a/docs/pt/docs/how-to/extending-openapi.md b/docs/pt/docs/how-to/extending-openapi.md new file mode 100644 index 0000000000..b4785edc15 --- /dev/null +++ b/docs/pt/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# Extendendo o OpenAPI + +Existem alguns casos em que pode ser necessário modificar o esquema OpenAPI gerado. + +Nesta seção, você verá como fazer isso. + +## O processo normal + +O processo normal (padrão) é o seguinte: + +Uma aplicação (instância) do `FastAPI` possui um método `.openapi()` que deve retornar o esquema OpenAPI. + +Como parte da criação do objeto de aplicação, uma *operação de rota* para `/openapi.json` (ou para o que você definir como `openapi_url`) é registrada. + +Ela apenas retorna uma resposta JSON com o resultado do método `.openapi()` da aplicação. + +Por padrão, o que o método `.openapi()` faz é verificar se a propriedade `.openapi_schema` tem conteúdo e retorná-lo. + +Se não tiver, ele gera o conteúdo usando a função utilitária em `fastapi.openapi.utils.get_openapi`. + +E essa função `get_openapi()` recebe como parâmetros: + +* `title`: O título do OpenAPI, exibido na documentação. +* `version`: A versão da sua API, por exemplo, `2.5.0`. +* `openapi_version`: A versão da especificação OpenAPI utilizada. Por padrão, a mais recente: `3.1.0`. +* `summary`: Um resumo curto da API. +* `description`: A descrição da sua API, que pode incluir markdown e será exibida na documentação. +* `routes`: Uma lista de rotas, que são cada uma das *operações de rota* registradas. Elas são obtidas de `app.routes`. + +/// info | Informação + +O parâmetro `summary` está disponível no OpenAPI 3.1.0 e superior, suportado pelo FastAPI 0.99.0 e superior. + +/// + +## Sobrescrevendo os padrões + +Com as informações acima, você pode usar a mesma função utilitária para gerar o esquema OpenAPI e sobrescrever cada parte que precisar. + +Por exemplo, vamos adicionar Extensão OpenAPI do ReDoc para incluir um logo personalizado. + +### **FastAPI** Normal + +Primeiro, escreva toda a sua aplicação **FastAPI** normalmente: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} + +### Gerar o esquema OpenAPI + +Em seguida, use a mesma função utilitária para gerar o esquema OpenAPI, dentro de uma função `custom_openapi()`: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} + +### Modificar o esquema OpenAPI + +Agora, você pode adicionar a extensão do ReDoc, incluindo um `x-logo` personalizado ao "objeto" `info` no esquema OpenAPI: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} + +### Armazenar em cache o esquema OpenAPI + +Você pode usar a propriedade `.openapi_schema` como um "cache" para armazenar o esquema gerado. + +Dessa forma, sua aplicação não precisará gerar o esquema toda vez que um usuário abrir a documentação da sua API. + +Ele será gerado apenas uma vez, e o mesmo esquema armazenado em cache será utilizado nas próximas requisições. + +{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} + +### Sobrescrever o método + +Agora, você pode substituir o método `.openapi()` pela sua nova função. + +{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} + +### Verificar + +Uma vez que você acessar http://127.0.0.1:8000/redoc, verá que está usando seu logo personalizado (neste exemplo, o logo do **FastAPI**): + + diff --git a/docs/pt/docs/how-to/graphql.md b/docs/pt/docs/how-to/graphql.md new file mode 100644 index 0000000000..ef0bad7f67 --- /dev/null +++ b/docs/pt/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL + +Como o **FastAPI** é baseado no padrão **ASGI**, é muito fácil integrar qualquer biblioteca **GraphQL** também compatível com ASGI. + +Você pode combinar *operações de rota* normais do FastAPI com GraphQL na mesma aplicação. + +/// tip | Dica + +**GraphQL** resolve alguns casos de uso muito específicos. + +Ele tem **vantagens** e **desvantagens** quando comparado a **web APIs** comuns. + +Certifique-se de avaliar se os **benefícios** para o seu caso de uso compensam as **desvantagens**. 🤓 + +/// + +## Bibliotecas GraphQL + +Aqui estão algumas das bibliotecas **GraphQL** que têm suporte **ASGI**. Você pode usá-las com **FastAPI**: + +* Strawberry 🍓 + * Com docs para FastAPI +* Ariadne + * Com docs para FastAPI +* Tartiflette + * Com Tartiflette ASGI para fornecer integração ASGI +* Graphene + * Com starlette-graphene3 + +## GraphQL com Strawberry + +Se você precisar ou quiser trabalhar com **GraphQL**, **Strawberry** é a biblioteca **recomendada** pois tem o design mais próximo ao design do **FastAPI**, ela é toda baseada em **type annotations**. + +Dependendo do seu caso de uso, você pode preferir usar uma biblioteca diferente, mas se você me perguntasse, eu provavelmente sugeriria que você experimentasse o **Strawberry**. + +Aqui está uma pequena prévia de como você poderia integrar Strawberry com FastAPI: + +{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *} + +Você pode aprender mais sobre Strawberry na documentação do Strawberry. + +E também na documentação sobre Strawberry com FastAPI. + +## Antigo `GraphQLApp` do Starlette + +Versões anteriores do Starlette incluiam uma classe `GraphQLApp` para integrar com Graphene. + +Ela foi descontinuada do Starlette, mas se você tem código que a utilizava, você pode facilmente **migrar** para starlette-graphene3, que cobre o mesmo caso de uso e tem uma **interface quase idêntica**. + +/// tip | Dica + +Se você precisa de GraphQL, eu ainda recomendaria que você desse uma olhada no Strawberry, pois ele é baseado em type annotations em vez de classes e tipos personalizados. + +/// + +## Saiba Mais + +Você pode aprender mais sobre **GraphQL** na documentação oficial do GraphQL. + +Você também pode ler mais sobre cada uma das bibliotecas descritas acima em seus links. diff --git a/docs/pt/docs/how-to/separate-openapi-schemas.md b/docs/pt/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 0000000000..291b0e1639 --- /dev/null +++ b/docs/pt/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,104 @@ +# Esquemas OpenAPI Separados para Entrada e Saída ou Não + +Ao usar **Pydantic v2**, o OpenAPI gerado é um pouco mais exato e **correto** do que antes. 😎 + +Inclusive, em alguns casos, ele terá até **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saída, dependendo se eles possuem **valores padrão**. + +Vamos ver como isso funciona e como alterar se for necessário. + +## Modelos Pydantic para Entrada e Saída + +Digamos que você tenha um modelo Pydantic com valores padrão, como este: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### Modelo para Entrada + +Se você usar esse modelo como entrada, como aqui: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +... então o campo `description` não será obrigatório. Porque ele tem um valor padrão de `None`. + +### Modelo de Entrada na Documentação + +Você pode confirmar que na documentação, o campo `description` não tem um **asterisco vermelho**, não é marcado como obrigatório: + +
+ +
+ +### Modelo para Saída + +Mas se você usar o mesmo modelo como saída, como aqui: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +... então, como `description` tem um valor padrão, se você **não retornar nada** para esse campo, ele ainda terá o **valor padrão**. + +### Modelo para Dados de Resposta de Saída + +Se você interagir com a documentação e verificar a resposta, mesmo que o código não tenha adicionado nada em um dos campos `description`, a resposta JSON contém o valor padrão (`null`): + +
+ +
+ +Isso significa que ele **sempre terá um valor**, só que às vezes o valor pode ser `None` (ou `null` em termos de JSON). + +Isso quer dizer que, os clientes que usam sua API não precisam verificar se o valor existe ou não, eles podem **assumir que o campo sempre estará lá**, mas que em alguns casos terá o valor padrão de `None`. + +A maneira de descrever isso no OpenAPI é marcar esse campo como **obrigatório**, porque ele sempre estará lá. + +Por causa disso, o JSON Schema para um modelo pode ser diferente dependendo se ele é usado para **entrada ou saída**: + +* para **entrada**, o `description` **não será obrigatório** +* para **saída**, ele será **obrigatório** (e possivelmente `None`, ou em termos de JSON, `null`) + +### Modelo para Saída na Documentação + +Você pode verificar o modelo de saída na documentação também, ambos `name` e `description` são marcados como **obrigatórios** com um **asterisco vermelho**: + +
+ +
+ +### Modelo para Entrada e Saída na Documentação + +E se você verificar todos os Schemas disponíveis (JSON Schemas) no OpenAPI, verá que há dois, um `Item-Input` e um `Item-Output`. + +Para `Item-Input`, `description` **não é obrigatório**, não tem um asterisco vermelho. + +Mas para `Item-Output`, `description` **é obrigatório**, tem um asterisco vermelho. + +
+ +
+ +Com esse recurso do **Pydantic v2**, sua documentação da API fica mais **precisa**, e se você tiver clientes e SDKs gerados automaticamente, eles serão mais precisos também, proporcionando uma melhor **experiência para desenvolvedores** e consistência. 🎉 + +## Não Separe Schemas + +Agora, há alguns casos em que você pode querer ter o **mesmo esquema para entrada e saída**. + +Provavelmente, o principal caso de uso para isso é se você já tem algum código de cliente/SDK gerado automaticamente e não quer atualizar todo o código de cliente/SDK gerado ainda, você provavelmente vai querer fazer isso em algum momento, mas talvez não agora. + +Nesse caso, você pode desativar esse recurso no **FastAPI**, com o parâmetro `separate_input_output_schemas=False`. + +/// info | Informação + +O suporte para `separate_input_output_schemas` foi adicionado no FastAPI `0.102.0`. 🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### Mesmo Esquema para Modelos de Entrada e Saída na Documentação + +E agora haverá um único esquema para entrada e saída para o modelo, apenas `Item`, e `description` **não será obrigatório**: + +
+ +
+ +Esse é o mesmo comportamento do Pydantic v1. 🤓 diff --git a/docs/pt/docs/how-to/testing-database.md b/docs/pt/docs/how-to/testing-database.md new file mode 100644 index 0000000000..02f909f249 --- /dev/null +++ b/docs/pt/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Testando a Base de Dados + +Você pode estudar sobre bases de dados, SQL e SQLModel na documentação de SQLModel. 🤓 + +Aqui tem um mini tutorial de como usar SQLModel com FastAPI. ✨ + +Esse tutorial inclui uma sessão sobre testar bases de dados SQL. 😎 diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index f991446179..a361913c3f 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -11,15 +11,18 @@ Framework FastAPI, alta performance, fácil de aprender, fácil de codar, pronto para produção

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

--- @@ -60,7 +63,7 @@ Os recursos chave são: -Outros patrocinadores +Outros patrocinadores ## Opiniões @@ -70,15 +73,27 @@ Os recursos chave são: --- +"_Nós adotamos a biblioteca **FastAPI** para iniciar um servidor **REST** que pode ser consultado para obter **previsões**. [para o Ludwig]_" + +
Piero Molino, Yaroslav Dudin, e Sai Sumanth Miryala - Uber (ref)
+ +--- + +"_A **Netflix** tem o prazer de anunciar o lançamento open-source do nosso framework de orquestração de **gerenciamento de crises**: **Dispatch**! [criado com **FastAPI**]_" + +
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
+ +--- + "*Estou extremamente entusiasmado com o **FastAPI**. É tão divertido!*" -
Brian Okken - Python Bytes podcaster (ref)
+
Brian Okken - Python Bytes podcaster (ref)
--- "*Honestamente, o que você construiu parece super sólido e rebuscado. De muitas formas, eu queria que o **Hug** fosse assim - é realmente inspirador ver alguém que construiu ele.*" -
Timothy Crosley - criador doHug (ref)
+
Timothy Crosley - criador doHug (ref)
--- @@ -86,13 +101,13 @@ Os recursos chave são: "*Nós trocamos nossas **APIs** por **FastAPI** [...] Acredito que vocês gostarão dele [...]*" -
Ines Montani - Matthew Honnibal - fundadores da Explosion AI - criadores da spaCy (ref) - (ref)
+
Ines Montani - Matthew Honnibal - fundadores da Explosion AI - criadores da spaCy (ref) - (ref)
--- -"*Nós adotamos a biblioteca **FastAPI** para criar um servidor **REST** que possa ser chamado para obter **predições**. [para o Ludwig]*" +"_Se alguém estiver procurando construir uma API Python para produção, eu recomendaria fortemente o **FastAPI**. Ele é **lindamente projetado**, **simples de usar** e **altamente escalável**. Ele se tornou um **componente chave** para a nossa estratégia API first de desenvolvimento e está impulsionando diversas automações e serviços, como o nosso Virtual TAC Engineer._" -
Piero Molino, Yaroslav Dudin e Sai Sumanth Miryala - Uber (ref)
+
Deon Pillsbury - Cisco (ref)
--- @@ -108,32 +123,24 @@ Se você estiver construindo uma aplicação Starlette para as partes web. +* Starlette para as partes web. * Pydantic para a parte de dados. ## Instalação +Crie e ative um ambiente virtual, e então instale o FastAPI: +
```console -$ pip install fastapi +$ pip install "fastapi[standard]" ---> 100% ```
-Você também precisará de um servidor ASGI para produção, tal como Uvicorn ou Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
+**Nota**: Certifique-se de que você colocou `"fastapi[standard]"` com aspas, para garantir que funcione em todos os terminais. ## Exemplo @@ -184,7 +191,7 @@ async def read_item(item_id: int, q: Union[str, None] = None): **Nota**: -Se você não sabe, verifique a seção _"In a hurry?"_ sobre `async` e `await` nas docs. +Se você não sabe, verifique a seção _"Com pressa?"_ sobre `async` e `await` nas docs. @@ -195,11 +202,24 @@ Rode o servidor com:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + │ fastapi run │ + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] INFO: Waiting for application startup. INFO: Application startup complete. ``` @@ -207,13 +227,13 @@ INFO: Application startup complete.
-Sobre o comando uvicorn main:app --reload... +Sobre o comando fastapi dev main.py... -O comando `uvicorn main:app` se refere a: +O comando `fastapi dev` lê o seu arquivo `main.py`, identifica o aplicativo **FastAPI** nele, e inicia um servidor usando o Uvicorn. -* `main`: o arquivo `main.py` (o "módulo" Python). -* `app`: o objeto criado dentro de `main.py` com a linha `app = FastAPI()`. -* `--reload`: faz o servidor recarregar após mudanças de código. Somente faça isso para desenvolvimento. +Por padrão, o `fastapi dev` iniciará com *auto-reload* habilitado para desenvolvimento local. + +Você pode ler mais sobre isso na documentação do FastAPI CLI.
@@ -268,7 +288,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -286,7 +306,7 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -O servidor deverá recarregar automaticamente (porquê você adicionou `--reload` ao comando `uvicorn` acima). +O servidor `fastapi dev` deverá recarregar automaticamente. ### Evoluindo a Documentação Interativa da API @@ -316,7 +336,7 @@ E agora, vá para Tutorial - Guia do Usuário. +Para um exemplo mais completo incluindo mais recursos, veja Tutorial - Guia do Usuário. **Alerta de Spoiler**: o tutorial - guia do usuário inclui: @@ -416,9 +436,9 @@ Para um exemplo mais completo incluindo mais recursos, veja Injeção de Dependência
**. * Segurança e autenticação, incluindo suporte para **OAuth2** com autenticação **JWT tokens** e **HTTP Basic**. * Técnicas mais avançadas (mas igualmente fáceis) para declaração de **modelos JSON profundamente aninhados** (graças ao Pydantic). +* Integrações **GraphQL** com o Strawberry e outras bibliotecas. * Muitos recursos extras (graças ao Starlette) como: * **WebSockets** - * **GraphQL** * testes extrememamente fáceis baseados em HTTPX e `pytest` * **CORS** * **Cookie Sessions** @@ -428,30 +448,49 @@ Para um exemplo mais completo incluindo mais recursos, veja um dos _frameworks_ Python mais rápidos disponíveis, somente atrás de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*) -Para entender mais sobre performance, veja a seção Benchmarks. +Para entender mais sobre performance, veja a seção Comparações. -## Dependências opcionais +## Dependências -Usados por Pydantic: +O FastAPI depende do Pydantic e do Starlette. + + +### Dependências `standard` + +Quando você instala o FastAPI com `pip install "fastapi[standard]"`, ele vêm com o grupo `standard` (padrão) de dependências opcionais: + +Utilizado pelo Pydantic: * email-validator - para validação de email. -Usados por Starlette: +Utilizado pelo Starlette: -* httpx - Necessário se você quiser utilizar o `TestClient`. -* jinja2 - Necessário se você quiser utilizar a configuração padrão de templates. -* python-multipart - Necessário se você quiser suporte com "parsing" de formulário, com `request.form()`. -* itsdangerous - Necessário para suporte a `SessionMiddleware`. -* pyyaml - Necessário para suporte a `SchemaGenerator` da Starlette (você provavelmente não precisará disso com o FastAPI). -* graphene - Necessário para suporte a `GraphQLApp`. +* httpx - Obrigatório caso você queira utilizar o `TestClient`. +* jinja2 - Obrigatório se você quer utilizar a configuração padrão de templates. +* python-multipart - Obrigatório se você deseja suporte a "parsing" de formulário, com `request.form()`. -Usados por FastAPI / Starlette: +Utilizado pelo FastAPI / Starlette: -* uvicorn - para o servidor que carrega e serve sua aplicação. -* orjson - Necessário se você quer utilizar `ORJSONResponse`. -* ujson - Necessário se você quer utilizar `UJSONResponse`. +* uvicorn - para o servidor que carrega e serve a sua aplicação. Isto inclui `uvicorn[standard]`, que inclui algumas dependências (e.g. `uvloop`) necessárias para servir em alta performance. +* `fastapi-cli` - que disponibiliza o comando `fastapi`. -Você pode instalar todas essas dependências com `pip install fastapi[all]`. +### Sem as dependências `standard` + +Se você não deseja incluir as dependências opcionais `standard`, você pode instalar utilizando `pip install fastapi` ao invés de `pip install "fastapi[standard]"`. + +### Dpendências opcionais adicionais + +Existem algumas dependências adicionais que você pode querer instalar. + +Dependências opcionais adicionais do Pydantic: + +* pydantic-settings - para gerenciamento de configurações. +* pydantic-extra-types - tipos extras para serem utilizados com o Pydantic. + +Dependências opcionais adicionais do FastAPI: + +* orjson - Obrigatório se você deseja utilizar o `ORJSONResponse`. +* ujson - Obrigatório se você deseja utilizar o `UJSONResponse`. ## Licença diff --git a/docs/pt/docs/project-generation.md b/docs/pt/docs/project-generation.md index e5c935fd2d..e337ad762e 100644 --- a/docs/pt/docs/project-generation.md +++ b/docs/pt/docs/project-generation.md @@ -1,84 +1,28 @@ -# Geração de Projetos - Modelo +# Full Stack FastAPI Template -Você pode usar um gerador de projetos para começar, por já incluir configurações iniciais, segurança, banco de dados e os primeiros _endpoints_ API já feitos para você. +_Templates_, embora tipicamente venham com alguma configuração específica, são desenhados para serem flexíveis e customizáveis. Isso permite que você os modifique e adapte para as especificações do seu projeto, fazendo-os um excelente ponto de partida. 🏁 -Um gerador de projetos sempre terá uma pré-configuração que você pode atualizar e adaptar para suas próprias necessidades, mas pode ser um bom ponto de partida para seu projeto. +Você pode usar esse _template_ para começar, já que ele inclui várias configurações iniciais, segurança, banco de dados, e alguns _endpoints_ de API já feitos para você. -## Full Stack FastAPI PostgreSQL +Repositório GitHub: Full Stack FastAPI Template -GitHub: https://github.com/tiangolo/full-stack-fastapi-postgresql +## Full Stack FastAPI Template - Pilha de Tecnologias e Recursos -### Full Stack FastAPI PostgreSQL - Recursos - -* Integração completa **Docker**. -* Modo de implantação Docker Swarm. -* Integração e otimização **Docker Compose** para desenvolvimento local. -* **Pronto para Produção** com servidor _web_ usando Uvicorn e Gunicorn. -* _Backend_ **FastAPI** Python: - * **Rápido**: Alta performance, no nível de **NodeJS** e **Go** (graças ao Starlette e Pydantic). - * **Intuitivo**: Ótimo suporte de editor. _Auto-Complete_ em todo lugar. Menos tempo _debugando_. - * **Fácil**: Projetado para ser fácil de usar e aprender. Menos tempo lendo documentações. - * **Curto**: Minimize duplicação de código. Múltiplos recursos para cada declaração de parâmetro. - * **Robusto**: Tenha código pronto para produção. Com documentação interativa automática. - * **Baseado em Padrões**: Baseado em (e completamente compatível com) padrões abertos para APIs: OpenAPI e JSON Schema. - * **Muitos outros recursos** incluindo validação automática, serialização, documentação interativa, autenticação com _tokens_ OAuth2 JWT etc. -* **Senha segura** _hashing_ por padrão. -* Autenticação **Token JWT**. -* Modelos **SQLAlchemy** (independente de extensões Flask, para que eles possam ser usados com _workers_ Celery diretamente). -* Modelos básicos para usuários (modifique e remova conforme suas necessidades). -* Migrações **Alembic**. -* **CORS** (_Cross Origin Resource Sharing_ - Compartilhamento de Recursos Entre Origens). -* _Worker_ **Celery** que pode importar e usar modelos e códigos do resto do _backend_ seletivamente. -* Testes _backend_ _REST_ baseados no **Pytest**, integrados com Docker, então você pode testar a interação completa da API, independente do banco de dados. Como roda no Docker, ele pode construir um novo repositório de dados do zero toda vez (assim você pode usar ElasticSearch, MongoDB, CouchDB, ou o que quiser, e apenas testar que a API esteja funcionando). -* Fácil integração com Python através dos **Kernels Jupyter** para desenvolvimento remoto ou no Docker com extensões como Atom Hydrogen ou Visual Studio Code Jupyter. -* _Frontend_ **Vue**: - * Gerado com Vue CLI. - * Controle de **Autenticação JWT**. - * Visualização de _login_. - * Após o _login_, visualização do painel de controle principal. - * Painel de controle principal com criação e edição de usuário. - * Edição do próprio usuário. - * **Vuex**. - * **Vue-router**. - * **Vuetify** para belos componentes _material design_. - * **TypeScript**. - * Servidor Docker baseado em **Nginx** (configurado para rodar "lindamente" com Vue-router). - * Construção multi-estágio Docker, então você não precisa salvar ou _commitar_ código compilado. - * Testes _frontend_ rodados na hora da construção (pode ser desabilitado também). - * Feito tão modular quanto possível, então ele funciona fora da caixa, mas você pode gerar novamente com Vue CLI ou criar conforme você queira, e reutilizar o que quiser. -* **PGAdmin** para banco de dados PostgreSQL, você pode modificar para usar PHPMyAdmin e MySQL facilmente. -* **Flower** para monitoração de tarefas Celery. -* Balanceamento de carga entre _frontend_ e _backend_ com **Traefik**, então você pode ter ambos sob o mesmo domínio, separados por rota, mas servidos por diferentes containers. -* Integração Traefik, incluindo geração automática de certificados **HTTPS** Let's Encrypt. -* GitLab **CI** (integração contínua), incluindo testes _frontend_ e _backend_. - -## Full Stack FastAPI Couchbase - -GitHub: https://github.com/tiangolo/full-stack-fastapi-couchbase - -⚠️ **WARNING** ⚠️ - -Se você está iniciando um novo projeto do zero, verifique as alternativas aqui. - -Por exemplo, o gerador de projetos Full Stack FastAPI PostgreSQL pode ser uma alternativa melhor, como ele é ativamente mantido e utilizado. E ele inclui todos os novos recursos e melhorias. - -Você ainda é livre para utilizar o gerador baseado em Couchbase se quiser, ele provavelmente ainda funciona bem, e você já tem um projeto gerado com ele que roda bem também (e você provavelmente já atualizou ele para encaixar nas suas necessidades). - -Você pode ler mais sobre nas documentaçãoes do repositório. - -## Full Stack FastAPI MongoDB - -...pode demorar, dependendo do meu tempo disponível e outros fatores. 😅 🎉 - -## Modelos de Aprendizado de Máquina com spaCy e FastAPI - -GitHub: https://github.com/microsoft/cookiecutter-spacy-fastapi - -### Modelos de Aprendizado de Máquina com spaCy e FastAPI - Recursos - -* Integração com modelo NER **spaCy**. -* Formato de requisição **Busca Cognitiva Azure** acoplado. -* Servidor Python _web_ **Pronto para Produção** usando Uvicorn e Gunicorn. -* Implantação **Azure DevOps** Kubernetes (AKS) CI/CD acoplada. -* **Multilingual** facilmente escolhido como uma das linguagens spaCy acopladas durante a configuração do projeto. -* **Facilmente extensível** para outros modelos de _frameworks_ (Pytorch, Tensorflow), não apenas spaCy. +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) para a API do backend em Python. + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) para as interações do Python com bancos de dados SQL (ORM). + - 🔍 [Pydantic](https://docs.pydantic.dev), usado pelo FastAPI, para validação de dados e gerenciamento de configurações. + - 💾 [PostgreSQL](https://www.postgresql.org) como banco de dados SQL. +- 🚀 [React](https://react.dev) para o frontend. + - 💃 Usando TypeScript, hooks, [Vite](https://vitejs.dev), e outras partes de uma _stack_ frontend moderna. + - 🎨 [Chakra UI](https://chakra-ui.com) para os componentes de frontend. + - 🤖 Um cliente frontend automaticamente gerado. + - 🧪 [Playwright](https://playwright.dev) para testes Ponta-a-Ponta. + - 🦇 Suporte para modo escuro. +- 🐋 [Docker Compose](https://www.docker.com) para desenvolvimento e produção. +- 🔒 _Hash_ seguro de senhas por padrão. +- 🔑 Autenticação por token JWT. +- 📫 Recuperação de senhas baseada em email. +- ✅ Testes com [Pytest](https://pytest.org). +- 📞 [Traefik](https://traefik.io) como proxy reverso / balanceador de carga. +- 🚢 Instruções de _deployment_ usando Docker Compose, incluindo como configurar um proxy frontend com Traefik para gerenciar automaticamente certificados HTTPS. +- 🏭 CI (Integração Contínua) e CD (_Deploy_ Contínuo) baseado em GitHub Actions. diff --git a/docs/pt/docs/python-types.md b/docs/pt/docs/python-types.md index 86630cd2ad..90a361f403 100644 --- a/docs/pt/docs/python-types.md +++ b/docs/pt/docs/python-types.md @@ -1,18 +1,18 @@ # Introdução aos tipos Python -**Python 3.6 +** tem suporte para "type hints" opcionais. +O Python possui suporte para "dicas de tipo" ou "type hints" (também chamado de "anotações de tipo" ou "type annotations") -Esses **"type hints"** são uma nova sintaxe (desde Python 3.6+) que permite declarar o tipo de uma variável. +Esses **"type hints"** são uma sintaxe especial que permite declarar o tipo de uma variável. Ao declarar tipos para suas variáveis, editores e ferramentas podem oferecer um melhor suporte. -Este é apenas um **tutorial rápido / atualização** sobre type hints Python. Ele cobre apenas o mínimo necessário para usá-los com o **FastAPI** ... que é realmente muito pouco. +Este é apenas um **tutorial rápido / atualização** sobre type hints do Python. Ele cobre apenas o mínimo necessário para usá-los com o **FastAPI**... que é realmente muito pouco. O **FastAPI** é baseado nesses type hints, eles oferecem muitas vantagens e benefícios. Mas mesmo que você nunca use o **FastAPI**, você se beneficiaria de aprender um pouco sobre eles. -/// note | "Nota" +/// note | Nota Se você é um especialista em Python e já sabe tudo sobre type hints, pule para o próximo capítulo. @@ -22,9 +22,7 @@ Se você é um especialista em Python e já sabe tudo sobre type hints, pule par Vamos começar com um exemplo simples: -```Python -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py *} A chamada deste programa gera: @@ -35,12 +33,10 @@ John Doe A função faz o seguinte: * Pega um `first_name` e `last_name`. -* Converte a primeira letra de cada uma em maiúsculas com `title ()`. -* Concatena com um espaço no meio. +* Converte a primeira letra de cada uma em maiúsculas com `title()`. +* Concatena com um espaço no meio. -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py hl[2] *} ### Edite-o @@ -48,7 +44,7 @@ A função faz o seguinte: Mas agora imagine que você estava escrevendo do zero. -Em algum momento você teria iniciado a definição da função, já tinha os parâmetros prontos ... +Em algum momento você teria iniciado a definição da função, já tinha os parâmetros prontos... Mas então você deve chamar "esse método que converte a primeira letra em maiúscula". @@ -82,9 +78,7 @@ para: Esses são os "type hints": -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial002.py!} -``` +{* ../../docs_src/python_types/tutorial002.py hl[1] *} Isso não é o mesmo que declarar valores padrão como seria com: @@ -96,37 +90,33 @@ Isso não é o mesmo que declarar valores padrão como seria com: Estamos usando dois pontos (`:`), não é igual a (`=`). -E adicionar type hints normalmente não muda o que acontece do que aconteceria sem elas. +E adicionar type hints normalmente não muda o que acontece do que aconteceria sem eles. Mas agora, imagine que você está novamente no meio da criação dessa função, mas com type hints. -No mesmo ponto, você tenta acionar o preenchimento automático com o `Ctrl Space` e vê: +No mesmo ponto, você tenta acionar o preenchimento automático com o `Ctrl+Space` e vê: -Com isso, você pode rolar, vendo as opções, até encontrar o que "toca uma campainha": +Com isso, você pode rolar, vendo as opções, até encontrar o que "soa familiar": ## Mais motivação -Marque esta função, ela já possui type hints: +Verifique esta função, ela já possui type hints: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial003.py!} -``` +{* ../../docs_src/python_types/tutorial003.py hl[1] *} -Como o editor conhece os tipos de variáveis, você não apenas obtém a conclusão, mas também as verificações de erro: +Como o editor conhece os tipos de variáveis, você não obtém apenas o preenchimento automático, mas também as verificações de erro: -Agora você sabe que precisa corrigí-lo, converta `age` em uma string com `str (age)`: +Agora você sabe que precisa corrigí-lo, converta `age` em uma string com `str(age)`: -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial004.py!} -``` +{* ../../docs_src/python_types/tutorial004.py hl[2] *} -## Tipos de declaração +## Declarando Tipos Você acabou de ver o local principal para declarar type hints. Como parâmetros de função. @@ -143,47 +133,83 @@ Você pode usar, por exemplo: * `bool` * `bytes` -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial005.py!} -``` +{* ../../docs_src/python_types/tutorial005.py hl[1] *} ### Tipos genéricos com parâmetros de tipo Existem algumas estruturas de dados que podem conter outros valores, como `dict`, `list`, `set` e `tuple`. E os valores internos também podem ter seu próprio tipo. -Para declarar esses tipos e os tipos internos, você pode usar o módulo Python padrão `typing`. +Estes tipos que possuem tipos internos são chamados de tipos "**genéricos**". E é possível declará-los mesmo com os seus tipos internos. -Ele existe especificamente para suportar esses type hints. +Para declarar esses tipos e os tipos internos, você pode usar o módulo Python padrão `typing`. Ele existe especificamente para suportar esses type hints. -#### `List` +#### Versões mais recentes do Python -Por exemplo, vamos definir uma variável para ser uma `lista` de `str`. +A sintaxe utilizando `typing` é **compatível** com todas as versões, desde o Python 3.6 até as últimas, incluindo o Python 3.9, 3.10, etc. -Em `typing`, importe `List` (com um `L` maiúsculo): +Conforme o Python evolui, **novas versões** chegam com suporte melhorado para esses type annotations, e em muitos casos, você não precisará nem importar e utilizar o módulo `typing` para declarar os type annotations. + +Se você pode escolher uma versão mais recente do Python para o seu projeto, você poderá aproveitar isso ao seu favor. + +Em todos os documentos existem exemplos compatíveis com cada versão do Python (quando existem diferenças). + +Por exemplo, "**Python 3.6+**" significa que é compatível com o Python 3.6 ou superior (incluindo o 3.7, 3.8, 3.9, 3.10, etc). E "**Python 3.9+**" significa que é compatível com o Python 3.9 ou mais recente (incluindo o 3.10, etc). + +Se você pode utilizar a **versão mais recente do Python**, utilize os exemplos para as últimas versões. Eles terão as **melhores e mais simples sintaxes**, como por exemplo, "**Python 3.10+**". + +#### List + +Por exemplo, vamos definir uma variável para ser uma `list` de `str`. + +//// tab | Python 3.9+ + +Declare uma variável com a mesma sintaxe com dois pontos (`:`) + +Como tipo, coloque `list`. + +Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes: ```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial006.py!} +{!> ../../docs_src/python_types/tutorial006_py39.py!} ``` -Declare a variável com a mesma sintaxe de dois pontos (`:`). +//// -Como o tipo, coloque a `List`. +//// tab | Python 3.8+ -Como a lista é um tipo que contém alguns tipos internos, você os coloca entre colchetes: +De `typing`, importe `List` (com o `L` maiúsculo): + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial006.py!} +``` + +Declare uma variável com a mesma sintaxe com dois pontos (`:`) + +Como tipo, coloque o `List` que você importou de `typing`. + +Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes: ```Python hl_lines="4" -{!../../../docs_src/python_types/tutorial006.py!} +{!> ../../docs_src/python_types/tutorial006.py!} ``` -/// tip | "Dica" +//// -Esses tipos internos entre colchetes são chamados de "parâmetros de tipo". +/// info | Informação -Nesse caso, `str` é o parâmetro de tipo passado para `List`. +Estes tipos internos dentro dos colchetes são chamados "parâmetros de tipo" (type parameters). + +Neste caso, `str` é o parâmetro de tipo passado para `List` (ou `list` no Python 3.9 ou superior). /// -Isso significa que: "a variável `items` é uma `list`, e cada um dos itens desta lista é uma `str`". +Isso significa: "a variável `items` é uma `list`, e cada um dos itens desta lista é uma `str`". + +/// tip | Dica + +Se você usa o Python 3.9 ou superior, você não precisa importar `List` de `typing`. Você pode utilizar o mesmo tipo `list` no lugar. + +/// Ao fazer isso, seu editor pode fornecer suporte mesmo durante o processamento de itens da lista: @@ -195,20 +221,32 @@ Observe que a variável `item` é um dos elementos da lista `items`. E, ainda assim, o editor sabe que é um `str` e fornece suporte para isso. -#### `Tuple` e `Set` +#### Tuple e Set Você faria o mesmo para declarar `tuple`s e `set`s: -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial007.py!} +//// tab | Python 3.9+ + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial007_py39.py!} ``` +//// + +//// tab | Python 3.8+ + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial007.py!} +``` + +//// + Isso significa que: * A variável `items_t` é uma `tuple` com 3 itens, um `int`, outro `int` e uma `str`. * A variável `items_s` é um `set`, e cada um de seus itens é do tipo `bytes`. -#### `Dict` +#### Dict Para definir um `dict`, você passa 2 parâmetros de tipo, separados por vírgulas. @@ -216,38 +254,181 @@ O primeiro parâmetro de tipo é para as chaves do `dict`. O segundo parâmetro de tipo é para os valores do `dict`: -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial008.py!} +//// tab | Python 3.9+ + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial008_py39.py!} ``` +//// + +//// tab | Python 3.8+ + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial008.py!} +``` + +//// + Isso significa que: * A variável `prices` é um dict`: * As chaves deste `dict` são do tipo `str` (digamos, o nome de cada item). * Os valores deste `dict` são do tipo `float` (digamos, o preço de cada item). -#### `Opcional` +#### Union -Você também pode usar o `Opcional` para declarar que uma variável tem um tipo, como `str`, mas que é "opcional", o que significa que também pode ser `None`: +Você pode declarar que uma variável pode ser de qualquer um dentre **diversos tipos**. Por exemplo, um `int` ou um `str`. -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial009.py!} +No Python 3.6 e superior (incluindo o Python 3.10), você pode utilizar o tipo `Union` de `typing`, e colocar dentro dos colchetes os possíveis tipos aceitáveis. + +No Python 3.10 também existe uma **nova sintaxe** onde você pode colocar os possívels tipos separados por uma barra vertical (`|`). + +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -O uso de `Opcional [str]` em vez de apenas `str` permitirá que o editor o ajude a detectar erros, onde você pode estar assumindo que um valor é sempre um `str`, quando na verdade também pode ser `None`. +//// + +//// tab | Python 3.8+ + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial008b.py!} +``` + +//// + +Em ambos os casos, isso significa que `item` poderia ser um `int` ou um `str`. + + +#### Possívelmente `None` + +Você pode declarar que um valor pode ter um tipo, como `str`, mas que ele também pode ser `None`. + +No Python 3.6 e superior (incluindo o Python 3.10) você pode declará-lo importando e utilizando `Optional` do módulo `typing`. + +```Python hl_lines="1 4" +{!../../docs_src/python_types/tutorial009.py!} +``` + +O uso de `Optional[str]` em vez de apenas `str` permitirá que o editor o ajude a detectar erros, onde você pode estar assumindo que um valor é sempre um `str`, quando na verdade também pode ser `None`. + +`Optional[Something]` é na verdade um atalho para `Union[Something, None]`, eles são equivalentes. + +Isso também significa que no Python 3.10, você pode utilizar `Something | None`: + +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial009_py310.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial009.py!} +``` + +//// + +//// tab | Python 3.8+ alternative + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial009b.py!} +``` + +//// + +#### Utilizando `Union` ou `Optional` + +Se você está utilizando uma versão do Python abaixo da 3.10, aqui vai uma dica do meu ponto de vista bem **subjetivo**: + +* 🚨 Evite utilizar `Optional[SomeType]` +* No lugar, ✨ **use `Union[SomeType, None]`** ✨. + +Ambos são equivalentes, e no final das contas, eles são o mesmo. Mas eu recomendaria o `Union` ao invés de `Optional` porque a palavra **Optional** parece implicar que o valor é opcional, quando na verdade significa "isso pode ser `None`", mesmo que ele não seja opcional e ainda seja obrigatório. + +Eu penso que `Union[SomeType, None]` é mais explícito sobre o que ele significa. + +Isso é apenas sobre palavras e nomes. Mas estas palavras podem afetar como os seus colegas de trabalho pensam sobre o código. + +Por exemplo, vamos pegar esta função: + +{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} + +O paâmetro `name` é definido como `Optional[str]`, mas ele **não é opcional**, você não pode chamar a função sem o parâmetro: + +```Python +say_hi() # Oh, no, this throws an error! 😱 +``` + +O parâmetro `name` **ainda é obrigatório** (não *opicional*) porque ele não possui um valor padrão. Mesmo assim, `name` aceita `None` como valor: + +```Python +say_hi(name=None) # This works, None is valid 🎉 +``` + +A boa notícia é, quando você estiver no Python 3.10 você não precisará se preocupar mais com isso, pois você poderá simplesmente utilizar o `|` para definir uniões de tipos: + +{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} + +E então você não precisará mais se preocupar com nomes como `Optional` e `Union`. 😎 #### Tipos genéricos -Esses tipos que usam parâmetros de tipo entre colchetes, como: +Esses tipos que usam parâmetros de tipo entre colchetes são chamados **tipos genéricos** ou **genéricos**. Por exemplo: + +//// tab | Python 3.10+ + +Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e tipos dentro): + +* `list` +* `tuple` +* `set` +* `dict` + +E o mesmo como no Python 3.8, do módulo `typing`: + +* `Union` +* `Optional` (o mesmo que com o 3.8) +* ...entro outros. + +No Python 3.10, como uma alternativa para a utilização dos genéricos `Union` e `Optional`, você pode usar a barra vertical (`|`) para declarar uniões de tipos. Isso é muito melhor e mais simples. + +//// + +//// tab | Python 3.9+ + +Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e tipos dentro): + +* `list` +* `tuple` +* `set` +* `dict` + +E o mesmo como no Python 3.8, do módulo `typing`: + +* `Union` +* `Optional` +* ...entro outros. + +//// + +//// tab | Python 3.8+ * `List` * `Tuple` * `Set` * `Dict` -* `Opcional` -* ...e outros. +* `Union` +* `Optional` +* ...entro outros. -são chamados **tipos genéricos** ou **genéricos**. +//// ### Classes como tipos @@ -255,23 +436,23 @@ Você também pode declarar uma classe como o tipo de uma variável. Digamos que você tenha uma classe `Person`, com um nome: -```Python hl_lines="1 2 3" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} Então você pode declarar que uma variável é do tipo `Person`: -```Python hl_lines="6" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[6] *} E então, novamente, você recebe todo o suporte do editor: +Perceba que isso significa que "`one_person` é uma **instância** da classe `Person`". + +Isso não significa que "`one_person` é a **classe** chamada `Person`". + ## Modelos Pydantic - Pydantic é uma biblioteca Python para executar a validação de dados. +O Pydantic é uma biblioteca Python para executar a validação de dados. Você declara a "forma" dos dados como classes com atributos. @@ -283,21 +464,93 @@ E você recebe todo o suporte do editor com esse objeto resultante. Retirado dos documentos oficiais dos Pydantic: +//// tab | Python 3.10+ + ```Python -{!../../../docs_src/python_types/tutorial011.py!} +{!> ../../docs_src/python_types/tutorial011_py310.py!} ``` -/// info | "Informação" +//// -Para saber mais sobre o Pydantic, verifique seus documentos . +//// tab | Python 3.9+ + +```Python +{!> ../../docs_src/python_types/tutorial011_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python +{!> ../../docs_src/python_types/tutorial011.py!} +``` + +//// + +/// info | Informação + +Para saber mais sobre o Pydantic, verifique a sua documentação. /// -**FastAPI** é todo baseado em Pydantic. +O **FastAPI** é todo baseado em Pydantic. Você verá muito mais disso na prática no [Tutorial - Guia do usuário](tutorial/index.md){.internal-link target=_blank}. -## Type hints em **FastAPI** +/// tip | Dica + +O Pydantic tem um comportamento especial quando você usa `Optional` ou `Union[Something, None]` sem um valor padrão. Você pode ler mais sobre isso na documentação do Pydantic sobre campos Opcionais Obrigatórios. + +/// + + +## Type Hints com Metadados de Anotações + +O Python possui uma funcionalidade que nos permite incluir **metadados adicionais** nos type hints utilizando `Annotated`. + +//// tab | Python 3.9+ + +No Python 3.9, `Annotated` é parte da biblioteca padrão, então você pode importá-lo de `typing`. + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial013_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +Em versões abaixo do Python 3.9, você importa `Annotated` de `typing_extensions`. + +Ele já estará instalado com o **FastAPI**. + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial013.py!} +``` + +//// + +O Python em si não faz nada com este `Annotated`. E para editores e outras ferramentas, o tipo ainda é `str`. + +Mas você pode utilizar este espaço dentro do `Annotated` para fornecer ao **FastAPI** metadata adicional sobre como você deseja que a sua aplicação se comporte. + +O importante aqui de se lembrar é que **o primeiro *type parameter*** que você informar ao `Annotated` é o **tipo de fato**. O resto é apenas metadado para outras ferramentas. + +Por hora, você precisa apenas saber que o `Annotated` existe, e que ele é Python padrão. 😎 + +Mais tarde você verá o quão **poderoso** ele pode ser. + +/// tip | Dica + +O fato de que isso é **Python padrão** significa que você ainda obtém a **melhor experiência de desenvolvedor possível** no seu editor, com as ferramentas que você utiliza para analisar e refatorar o seu código, etc. ✨ + +E também que o seu código será muito compatível com diversas outras ferramentas e bibliotecas Python. 🚀 + +/// + + +## Type hints no **FastAPI** O **FastAPI** aproveita esses type hints para fazer várias coisas. @@ -306,20 +559,20 @@ Com o **FastAPI**, você declara parâmetros com type hints e obtém: * **Suporte ao editor**. * **Verificações de tipo**. -... e **FastAPI** usa as mesmas declarações para: +... e o **FastAPI** usa as mesmas declarações para: -* **Definir requisitos**: dos parâmetros do caminho da solicitação, parâmetros da consulta, cabeçalhos, corpos, dependências, etc. +* **Definir requisitos**: dos parâmetros de rota, parâmetros da consulta, cabeçalhos, corpos, dependências, etc. * **Converter dados**: da solicitação para o tipo necessário. * **Validar dados**: provenientes de cada solicitação: - * A geração de **erros automáticos** retornou ao cliente quando os dados são inválidos. -* **Documente** a API usando OpenAPI: + * Gerando **erros automáticos** retornados ao cliente quando os dados são inválidos. +* **Documentar** a API usando OpenAPI: * que é usado pelas interfaces de usuário da documentação interativa automática. Tudo isso pode parecer abstrato. Não se preocupe. Você verá tudo isso em ação no [Tutorial - Guia do usuário](tutorial/index.md){.internal-link target=_blank}. O importante é que, usando tipos padrão de Python, em um único local (em vez de adicionar mais classes, decoradores, etc.), o **FastAPI** fará muito trabalho para você. -/// info | "Informação" +/// info | Informação Se você já passou por todo o tutorial e voltou para ver mais sobre os tipos, um bom recurso é a "cheat sheet" do `mypy` . diff --git a/docs/pt/docs/tutorial/background-tasks.md b/docs/pt/docs/tutorial/background-tasks.md index 625fa2b111..b8ab58cda3 100644 --- a/docs/pt/docs/tutorial/background-tasks.md +++ b/docs/pt/docs/tutorial/background-tasks.md @@ -15,9 +15,7 @@ Isso inclui, por exemplo: Primeiro, importe `BackgroundTasks` e defina um parâmetro em sua _função de operação de caminho_ com uma declaração de tipo de `BackgroundTasks`: -```Python hl_lines="1 13" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} O **FastAPI** criará o objeto do tipo `BackgroundTasks` para você e o passará como esse parâmetro. @@ -33,17 +31,13 @@ Nesse caso, a função de tarefa gravará em um arquivo (simulando o envio de um E como a operação de gravação não usa `async` e `await`, definimos a função com `def` normal: -```Python hl_lines="6-9" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} ## Adicionar a tarefa em segundo plano Dentro de sua _função de operação de caminho_, passe sua função de tarefa para o objeto _tarefas em segundo plano_ com o método `.add_task()`: -```Python hl_lines="14" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} `.add_task()` recebe como argumentos: @@ -57,9 +51,7 @@ Usar `BackgroundTasks` também funciona com o sistema de injeção de dependênc O **FastAPI** sabe o que fazer em cada caso e como reutilizar o mesmo objeto, de forma que todas as tarefas em segundo plano sejam mescladas e executadas em segundo plano posteriormente: -```Python hl_lines="13 15 22 25" -{!../../../docs_src/background_tasks/tutorial002.py!} -``` +{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *} Neste exemplo, as mensagens serão gravadas no arquivo `log.txt` _após_ o envio da resposta. @@ -69,7 +61,7 @@ E então outra tarefa em segundo plano gerada na _função de operação de cami ## Detalhes técnicos -A classe `BackgroundTasks` vem diretamente de `starlette.background`. +A classe `BackgroundTasks` vem diretamente de `starlette.background`. Ela é importada/incluída diretamente no FastAPI para que você possa importá-la do `fastapi` e evitar a importação acidental da alternativa `BackgroundTask` (sem o `s` no final) de `starlette.background`. @@ -77,7 +69,7 @@ Usando apenas `BackgroundTasks` (e não `BackgroundTask`), é então possível u Ainda é possível usar `BackgroundTask` sozinho no FastAPI, mas você deve criar o objeto em seu código e retornar uma Starlette `Response` incluindo-o. -Você pode ver mais detalhes na documentação oficiais da Starlette para tarefas em segundo plano . +Você pode ver mais detalhes na documentação oficiais da Starlette para tarefas em segundo plano . ## Ressalva @@ -85,8 +77,6 @@ Se você precisa realizar cálculos pesados ​​em segundo plano e não necess Eles tendem a exigir configurações mais complexas, um gerenciador de fila de mensagens/tarefas, como RabbitMQ ou Redis, mas permitem que você execute tarefas em segundo plano em vários processos e, especialmente, em vários servidores. -Para ver um exemplo, verifique os [Geradores de projeto](../project-generation.md){.internal-link target=\_blank}, todos incluem celery já configurado. - Mas se você precisa acessar variáveis ​​e objetos do mesmo aplicativo **FastAPI**, ou precisa realizar pequenas tarefas em segundo plano (como enviar uma notificação por e-mail), você pode simplesmente usar `BackgroundTasks`. ## Recapitulando diff --git a/docs/pt/docs/tutorial/bigger-applications.md b/docs/pt/docs/tutorial/bigger-applications.md index 7137bf865d..b621f3c726 100644 --- a/docs/pt/docs/tutorial/bigger-applications.md +++ b/docs/pt/docs/tutorial/bigger-applications.md @@ -4,7 +4,7 @@ Se você está construindo uma aplicação ou uma API web, é raro que você pos **FastAPI** oferece uma ferramenta conveniente para estruturar sua aplicação, mantendo toda a flexibilidade. -/// info | "Informação" +/// info | Informação Se você vem do Flask, isso seria o equivalente aos Blueprints do Flask. @@ -29,7 +29,7 @@ Digamos que você tenha uma estrutura de arquivos como esta: │   └── admin.py ``` -/// tip | "Dica" +/// tip | Dica Existem vários arquivos `__init__.py` presentes em cada diretório ou subdiretório. @@ -52,7 +52,7 @@ from app.routers import items * Há também um subdiretório `app/internal/` com outro arquivo `__init__.py`, então ele é outro "subpacote Python":`app.internal`. * E o arquivo `app/internal/admin.py` é outro submódulo: `app.internal.admin`. - + A mesma estrutura de arquivos com comentários: @@ -86,7 +86,7 @@ Você pode criar as *operações de rotas* para esse módulo usando o `APIRouter você o importa e cria uma "instância" da mesma maneira que faria com a classe `FastAPI`: ```Python hl_lines="1 3" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` ### *Operações de Rota* com `APIRouter` @@ -96,7 +96,7 @@ E então você o utiliza para declarar suas *operações de rota*. Utilize-o da mesma maneira que utilizaria a classe `FastAPI`: ```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` Você pode pensar em `APIRouter` como uma classe "mini `FastAPI`". @@ -105,7 +105,7 @@ Todas as mesmas opções são suportadas. Todos os mesmos `parameters`, `responses`, `dependencies`, `tags`, etc. -/// tip | "Dica" +/// tip | Dica Neste exemplo, a variável é chamada de `router`, mas você pode nomeá-la como quiser. @@ -124,7 +124,7 @@ Agora usaremos uma dependência simples para ler um cabeçalho `X-Token` persona //// tab | Python 3.9+ ```Python hl_lines="3 6-8" title="app/dependencies.py" -{!> ../../../docs_src/bigger_applications/app_an_py39/dependencies.py!} +{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} ``` //// @@ -132,26 +132,26 @@ Agora usaremos uma dependência simples para ler um cabeçalho `X-Token` persona //// tab | Python 3.8+ ```Python hl_lines="1 5-7" title="app/dependencies.py" -{!> ../../../docs_src/bigger_applications/app_an/dependencies.py!} +{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} ``` //// //// tab | Python 3.8+ non-Annotated -/// tip | "Dica" +/// tip | Dica Prefira usar a versão `Annotated` se possível. /// ```Python hl_lines="1 4-6" title="app/dependencies.py" -{!> ../../../docs_src/bigger_applications/app/dependencies.py!} +{!> ../../docs_src/bigger_applications/app/dependencies.py!} ``` //// -/// tip | "Dica" +/// tip | Dica Estamos usando um cabeçalho inventado para simplificar este exemplo. @@ -182,7 +182,7 @@ Sabemos que todas as *operações de rota* neste módulo têm o mesmo: Então, em vez de adicionar tudo isso a cada *operação de rota*, podemos adicioná-lo ao `APIRouter`. ```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` Como o caminho de cada *operação de rota* deve começar com `/`, como em: @@ -201,7 +201,7 @@ Também podemos adicionar uma lista de `tags` e `responses` extras que serão ap E podemos adicionar uma lista de `dependencies` que serão adicionadas a todas as *operações de rota* no roteador e serão executadas/resolvidas para cada solicitação feita a elas. -/// tip | "Dica" +/// tip | Dica Observe que, assim como [dependências em *decoradores de operação de rota*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, nenhum valor será passado para sua *função de operação de rota*. @@ -222,7 +222,7 @@ O resultado final é que os caminhos dos itens agora são: * As dependências do roteador são executadas primeiro, depois as [`dependencies` no decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} e, em seguida, as dependências de parâmetros normais. * Você também pode adicionar [dependências de `Segurança` com `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. -/// tip | "Dica" +/// tip | Dica Ter `dependências` no `APIRouter` pode ser usado, por exemplo, para exigir autenticação para um grupo inteiro de *operações de rota*. Mesmo que as dependências não sejam adicionadas individualmente a cada uma delas. @@ -243,12 +243,12 @@ E precisamos obter a função de dependência do módulo `app.dependencies`, o a Então usamos uma importação relativa com `..` para as dependências: ```Python hl_lines="3" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` #### Como funcionam as importações relativas -/// tip | "Dica" +/// tip | Dica Se você sabe perfeitamente como funcionam as importações, continue para a próxima seção abaixo. @@ -270,7 +270,7 @@ Mas esse arquivo não existe, nossas dependências estão em um arquivo em `app/ Lembre-se de como nossa estrutura app/file se parece: - + --- @@ -316,10 +316,10 @@ Não estamos adicionando o prefixo `/items` nem `tags=["items"]` a cada *operaç Mas ainda podemos adicionar _mais_ `tags` que serão aplicadas a uma *operação de rota* específica, e também algumas `respostas` extras específicas para essa *operação de rota*: ```Python hl_lines="30-31" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` -/// tip | "Dica" +/// tip | Dica Esta última operação de caminho terá a combinação de tags: `["items", "custom"]`. @@ -344,7 +344,7 @@ Você importa e cria uma classe `FastAPI` normalmente. E podemos até declarar [dependências globais](dependencies/global-dependencies.md){.internal-link target=_blank} que serão combinadas com as dependências para cada `APIRouter`: ```Python hl_lines="1 3 7" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` ### Importe o `APIRouter` @@ -352,7 +352,7 @@ E podemos até declarar [dependências globais](dependencies/global-dependencies Agora importamos os outros submódulos que possuem `APIRouter`s: ```Python hl_lines="4-5" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` Como os arquivos `app/routers/users.py` e `app/routers/items.py` são submódulos que fazem parte do mesmo pacote Python `app`, podemos usar um único ponto `.` para importá-los usando "importações relativas". @@ -381,7 +381,7 @@ Também poderíamos importá-los como: from app.routers import items, users ``` -/// info | "Informação" +/// info | Informação A primeira versão é uma "importação relativa": @@ -417,7 +417,7 @@ o `router` de `users` sobrescreveria o de `items` e não poderíamos usá-los ao Então, para poder usar ambos no mesmo arquivo, importamos os submódulos diretamente: ```Python hl_lines="5" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` ### Incluir o `APIRouter`s para `usuários` e `itens` @@ -425,10 +425,10 @@ Então, para poder usar ambos no mesmo arquivo, importamos os submódulos direta Agora, vamos incluir os `roteadores` dos submódulos `usuários` e `itens`: ```Python hl_lines="10-11" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` -/// info | "Informação" +/// info | Informação `users.router` contém o `APIRouter` dentro do arquivo `app/routers/users.py`. @@ -440,7 +440,7 @@ Com `app.include_router()` podemos adicionar cada `APIRouter` ao aplicativo prin Ele incluirá todas as rotas daquele roteador como parte dele. -/// note | "Detalhe Técnico" +/// note | Detalhe Técnico Na verdade, ele criará internamente uma *operação de rota* para cada *operação de rota* que foi declarada no `APIRouter`. @@ -467,7 +467,7 @@ Ele contém um `APIRouter` com algumas *operações de rota* de administração Para este exemplo, será super simples. Mas digamos que, como ele é compartilhado com outros projetos na organização, não podemos modificá-lo e adicionar um `prefix`, `dependencies`, `tags`, etc. diretamente ao `APIRouter`: ```Python hl_lines="3" title="app/internal/admin.py" -{!../../../docs_src/bigger_applications/app/internal/admin.py!} +{!../../docs_src/bigger_applications/app/internal/admin.py!} ``` Mas ainda queremos definir um `prefixo` personalizado ao incluir o `APIRouter` para que todas as suas *operações de rota* comecem com `/admin`, queremos protegê-lo com as `dependências` que já temos para este projeto e queremos incluir `tags` e `responses`. @@ -475,7 +475,7 @@ Mas ainda queremos definir um `prefixo` personalizado ao incluir o `APIRouter` p Podemos declarar tudo isso sem precisar modificar o `APIRouter` original passando esses parâmetros para `app.include_router()`: ```Python hl_lines="14-17" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` Dessa forma, o `APIRouter` original permanecerá inalterado, para que possamos compartilhar o mesmo arquivo `app/internal/admin.py` com outros projetos na organização. @@ -498,12 +498,12 @@ Também podemos adicionar *operações de rota* diretamente ao aplicativo `FastA Aqui fazemos isso... só para mostrar que podemos 🤷: ```Python hl_lines="21-23" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` e funcionará corretamente, junto com todas as outras *operações de rota* adicionadas com `app.include_router()`. -/// info | "Detalhes Técnicos" +/// info | Detalhes Técnicos **Observação**: este é um detalhe muito técnico que você provavelmente pode **simplesmente pular**. diff --git a/docs/pt/docs/tutorial/body-fields.md b/docs/pt/docs/tutorial/body-fields.md index cce37cd55c..e7dfb07f2a 100644 --- a/docs/pt/docs/tutorial/body-fields.md +++ b/docs/pt/docs/tutorial/body-fields.md @@ -6,11 +6,9 @@ Da mesma forma que você pode declarar validações adicionais e metadados nos p Primeiro, você tem que importá-lo: -```Python hl_lines="4" -{!../../../docs_src/body_fields/tutorial001.py!} -``` +{* ../../docs_src/body_fields/tutorial001.py hl[4] *} -/// warning | "Aviso" +/// warning | Aviso Note que `Field` é importado diretamente do `pydantic`, não do `fastapi` como todo o resto (`Query`, `Path`, `Body`, etc). @@ -20,13 +18,11 @@ Note que `Field` é importado diretamente do `pydantic`, não do `fastapi` como Você pode então utilizar `Field` com atributos do modelo: -```Python hl_lines="11-14" -{!../../../docs_src/body_fields/tutorial001.py!} -``` +{* ../../docs_src/body_fields/tutorial001.py hl[11:14] *} `Field` funciona da mesma forma que `Query`, `Path` e `Body`, ele possui todos os mesmos parâmetros, etc. -/// note | "Detalhes técnicos" +/// note | Detalhes técnicos Na realidade, `Query`, `Path` e outros que você verá em seguida, criam objetos de subclasses de uma classe `Param` comum, que é ela mesma uma subclasse da classe `FieldInfo` do Pydantic. @@ -38,7 +34,7 @@ Lembre-se que quando você importa `Query`, `Path`, e outros de `fastapi`, esse /// -/// tip | "Dica" +/// tip | Dica Note como cada atributo do modelo com um tipo, valor padrão e `Field` possuem a mesma estrutura que parâmetros de *funções de operações de rota*, com `Field` ao invés de `Path`, `Query` e `Body`. diff --git a/docs/pt/docs/tutorial/body-multiple-params.md b/docs/pt/docs/tutorial/body-multiple-params.md index d36dd60b30..eda9b4dfff 100644 --- a/docs/pt/docs/tutorial/body-multiple-params.md +++ b/docs/pt/docs/tutorial/body-multiple-params.md @@ -8,23 +8,9 @@ Primeiro, é claro, você pode misturar `Path`, `Query` e declarações de parâ E você também pode declarar parâmetros de corpo como opcionais, definindo o valor padrão com `None`: -//// tab | Python 3.10+ +{* ../../docs_src/body_multiple_params/tutorial001_py310.py hl[17:19] *} -```Python hl_lines="17-19" -{!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="19-21" -{!> ../../../docs_src/body_multiple_params/tutorial001.py!} -``` - -//// - -/// note | "Nota" +/// note | Nota Repare que, neste caso, o `item` que seria capturado a partir do corpo é opcional. Visto que ele possui `None` como valor padrão. @@ -45,21 +31,7 @@ No exemplo anterior, as *operações de rota* esperariam um JSON no corpo conten Mas você pode também declarar múltiplos parâmetros de corpo, por exemplo, `item` e `user`: -//// tab | Python 3.10+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial002.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} 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). @@ -80,7 +52,7 @@ Então, ele usará o nome dos parâmetros como chaves (nome dos campos) no corpo } ``` -/// note | "Nota" +/// note | 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`. @@ -100,21 +72,7 @@ Se você declará-lo como é, porque é um valor singular, o **FastAPI** assumir Mas você pode instruir o **FastAPI** para tratá-lo como outra chave do corpo usando `Body`: -//// tab | Python 3.8+ - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial003.py!} -``` - -//// - -//// tab | Python 3.10+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial003.py hl[22] *} Neste caso, o **FastAPI** esperará um corpo como: @@ -154,23 +112,9 @@ q: str | None = None Por exemplo: -//// tab | Python 3.10+ +{* ../../docs_src/body_multiple_params/tutorial004_py310.py hl[26] *} -```Python hl_lines="26" -{!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004.py!} -``` - -//// - -/// info | "Informação" +/// 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. @@ -190,21 +134,7 @@ item: Item = Body(embed=True) como em: -//// tab | Python 3.10+ - -```Python hl_lines="15" -{!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial005_py310.py hl[15] *} Neste caso o **FastAPI** esperará um corpo como: diff --git a/docs/pt/docs/tutorial/body-nested-models.md b/docs/pt/docs/tutorial/body-nested-models.md index 7d933b27fc..2954ae3db3 100644 --- a/docs/pt/docs/tutorial/body-nested-models.md +++ b/docs/pt/docs/tutorial/body-nested-models.md @@ -6,9 +6,7 @@ Com o **FastAPI**, você pode definir, validar, documentar e usar modelos profun 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!} -``` +{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *} Isso fará com que tags seja uma lista de itens mesmo sem declarar o tipo dos elementos desta lista. @@ -20,9 +18,7 @@ Mas o Python tem uma maneira específica de declarar listas com tipos internos o 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!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} ### Declare a `List` com um parâmetro de tipo @@ -44,9 +40,7 @@ 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!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *} ## Tipo "set" @@ -58,9 +52,7 @@ E que o Python tem um tipo de dados especial para conjuntos de itens únicos, o 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!} -``` +{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *} Com isso, mesmo que você receba uma requisição contendo dados duplicados, ela será convertida em um conjunto de itens exclusivos. @@ -82,17 +74,13 @@ Tudo isso, aninhado arbitrariamente. Por exemplo, nós podemos definir um modelo `Image`: -```Python hl_lines="9-11" -{!../../../docs_src/body_nested_models/tutorial004.py!} -``` +{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *} ### 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!} -``` +{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *} Isso significa que o **FastAPI** vai esperar um corpo similar à: @@ -125,9 +113,7 @@ Para ver todas as opções possíveis, cheque a documentação para osHTTP `PUT`. + +Você pode usar `jsonable_encoder` para converter os dados de entrada em dados que podem ser armazenados como JSON (por exemplo, com um banco de dados NoSQL). Por exemplo, convertendo `datetime` em `str`. + +{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} + +`PUT` é usado para receber dados que devem substituir os dados existentes. + +### Aviso sobre a substituição + +Isso significa que, se você quiser atualizar o item `bar` usando `PUT` com um corpo contendo: + +```Python +{ + "name": "Barz", + "price": 3, + "description": None, +} +``` + +Como ele não inclui o atributo já armazenado `"tax": 20.2`, o modelo de entrada assumiria o valor padrão de `"tax": 10.5`. + +E os dados seriam salvos com esse "novo" `tax` de `10.5`. + +## Atualizações parciais com `PATCH` + +Você também pode usar a operação HTTP `PATCH` para *atualizar* parcialmente os dados. + +Isso significa que você pode enviar apenas os dados que deseja atualizar, deixando o restante intacto. + +/// note | Nota + +`PATCH` é menos comumente usado e conhecido do que `PUT`. + +E muitas equipes usam apenas `PUT`, mesmo para atualizações parciais. + +Você é **livre** para usá-los como preferir, **FastAPI** não impõe restrições. + +Mas este guia te dá uma ideia de como eles são destinados a serem usados. + +/// + +### Usando o parâmetro `exclude_unset` do Pydantic + +Se você quiser receber atualizações parciais, é muito útil usar o parâmetro `exclude_unset` no método `.model_dump()` do modelo do Pydantic. + +Como `item.model_dump(exclude_unset=True)`. + +/// info | Informação + +No Pydantic v1, o método que era chamado `.dict()` e foi depreciado (mas ainda suportado) no Pydantic v2. Agora, deve-se usar o método `.model_dump()`. + +Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` a partir do Pydantic v2. + +/// + +Isso gera um `dict` com apenas os dados definidos ao criar o modelo `item`, excluindo os valores padrão. + +Então, você pode usar isso para gerar um `dict` com apenas os dados definidos (enviados na solicitação), omitindo valores padrão: + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} + +### Usando o parâmetro `update` do Pydantic + +Agora, você pode criar uma cópia do modelo existente usando `.model_copy()`, e passar o parâmetro `update` com um `dict` contendo os dados para atualizar. + +/// info | Informação + +No Pydantic v1, o método era chamado `.copy()`, ele foi depreciado (mas ainda suportado) no Pydantic v2, e renomeado para `.model_copy()`. + +Os exemplos aqui usam `.copy()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_copy()` com o Pydantic v2. + +/// + +Como `stored_item_model.model_copy(update=update_data)`: + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} + +### Recapitulando as atualizações parciais + +Resumindo, para aplicar atualizações parciais você pode: + +* (Opcionalmente) usar `PATCH` em vez de `PUT`. +* Recuperar os dados armazenados. +* Colocar esses dados em um modelo do Pydantic. +* Gerar um `dict` sem valores padrão a partir do modelo de entrada (usando `exclude_unset`). + * Dessa forma, você pode atualizar apenas os valores definidos pelo usuário, em vez de substituir os valores já armazenados com valores padrão em seu modelo. +* Criar uma cópia do modelo armazenado, atualizando seus atributos com as atualizações parciais recebidas (usando o parâmetro `update`). +* Converter o modelo copiado em algo que possa ser armazenado no seu banco de dados (por exemplo, usando o `jsonable_encoder`). + * Isso é comparável ao uso do método `.model_dump()`, mas garante (e converte) os valores para tipos de dados que possam ser convertidos em JSON, por exemplo, `datetime` para `str`. +* Salvar os dados no seu banco de dados. +* Retornar o modelo atualizado. + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} + +/// tip | Dica + +Você pode realmente usar essa mesma técnica com uma operação HTTP `PUT`. + +Mas o exemplo aqui usa `PATCH` porque foi criado para esses casos de uso. + +/// + +/// note | Nota + +Observe que o modelo de entrada ainda é validado. + +Portanto, se você quiser receber atualizações parciais que possam omitir todos os atributos, precisará ter um modelo com todos os atributos marcados como opcionais (com valores padrão ou `None`). + +Para distinguir os modelos com todos os valores opcionais para **atualizações** e modelos com valores obrigatórios para **criação**, você pode usar as ideias descritas em [Modelos Adicionais](extra-models.md){.internal-link target=_blank}. + +/// diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md index f67687fb50..2508d7981b 100644 --- a/docs/pt/docs/tutorial/body.md +++ b/docs/pt/docs/tutorial/body.md @@ -8,7 +8,7 @@ Sua API quase sempre irá enviar um corpo na **resposta**. Mas os clientes não Para declarar um corpo da **requisição**, você utiliza os modelos do Pydantic com todos os seus poderes e benefícios. -/// info | "Informação" +/// info | Informação Para enviar dados, você deve usar utilizar um dos métodos: `POST` (Mais comum), `PUT`, `DELETE` ou `PATCH`. @@ -22,9 +22,7 @@ Como é desencorajado, a documentação interativa com Swagger UI não irá most Primeiro, você precisa importar `BaseModel` do `pydantic`: -```Python hl_lines="4" -{!../../../docs_src/body/tutorial001.py!} -``` +{* ../../docs_src/body/tutorial001.py hl[4] *} ## Crie seu modelo de dados @@ -32,9 +30,7 @@ Então você declara seu modelo de dados como uma classe que herda `BaseModel`. Utilize os tipos Python padrão para todos os atributos: -```Python hl_lines="7-11" -{!../../../docs_src/body/tutorial001.py!} -``` +{* ../../docs_src/body/tutorial001.py hl[7:11] *} Assim como quando declaramos parâmetros de consulta, quando um atributo do modelo possui um valor padrão, ele se torna opcional. Caso contrário, se torna obrigatório. Use `None` para torná-lo opcional. @@ -62,9 +58,7 @@ Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) com Para adicionar o corpo na *função de operação de rota*, declare-o da mesma maneira que você declarou parâmetros de rota e consulta: -```Python hl_lines="18" -{!../../../docs_src/body/tutorial001.py!} -``` +{* ../../docs_src/body/tutorial001.py hl[18] *} ...E declare o tipo como o modelo que você criou, `Item`. @@ -113,7 +107,7 @@ Mas você terá o mesmo suporte do editor no -/// tip | "Dica" +/// tip | Dica Se você utiliza o PyCharm como editor, você pode utilizar o Plugin do Pydantic para o PyCharm . @@ -131,9 +125,7 @@ Melhora o suporte do editor para seus modelos Pydantic com:: Dentro da função, você pode acessar todos os atributos do objeto do modelo diretamente: -```Python hl_lines="21" -{!../../../docs_src/body/tutorial002.py!} -``` +{* ../../docs_src/body/tutorial002.py hl[21] *} ## Corpo da requisição + parâmetros de rota @@ -141,9 +133,7 @@ Você pode declarar parâmetros de rota e corpo da requisição ao mesmo tempo. O **FastAPI** irá reconhecer que os parâmetros da função que combinam com parâmetros de rota devem ser **retirados da rota**, e parâmetros da função que são declarados como modelos Pydantic sejam **retirados do corpo da requisição**. -```Python hl_lines="17-18" -{!../../../docs_src/body/tutorial003.py!} -``` +{* ../../docs_src/body/tutorial003.py hl[17:18] *} ## Corpo da requisição + parâmetros de rota + parâmetros de consulta @@ -151,9 +141,7 @@ Você também pode declarar parâmetros de **corpo**, **rota** e **consulta**, a O **FastAPI** irá reconhecer cada um deles e retirar a informação do local correto. -```Python hl_lines="18" -{!../../../docs_src/body/tutorial004.py!} -``` +{* ../../docs_src/body/tutorial004.py hl[18] *} Os parâmetros da função serão reconhecidos conforme abaixo: @@ -161,7 +149,7 @@ Os parâmetros da função serão reconhecidos conforme abaixo: * Se o parâmetro é de um **tipo único** (como `int`, `float`, `str`, `bool`, etc) será interpretado como um parâmetro de **consulta**. * Se o parâmetro é declarado como um **modelo Pydantic**, será interpretado como o **corpo** da requisição. -/// note | "Observação" +/// note | Observação O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`. diff --git a/docs/pt/docs/tutorial/cookie-param-models.md b/docs/pt/docs/tutorial/cookie-param-models.md new file mode 100644 index 0000000000..3d46ba44cb --- /dev/null +++ b/docs/pt/docs/tutorial/cookie-param-models.md @@ -0,0 +1,78 @@ +# Modelos de Parâmetros de Cookie + +Se você possui um grupo de **cookies** que estão relacionados, você pode criar um **modelo Pydantic** para declará-los. 🍪 + +Isso lhe permitiria **reutilizar o modelo** em **diversos lugares** e também declarar validações e metadata para todos os parâmetros de uma vez. 😎 + +/// note | Nota + +Isso é suportado desde a versão `0.115.0` do FastAPI. 🤓 + +/// + +/// tip | Dica + +Essa mesma técnica se aplica para `Query`, `Cookie`, e `Header`. 😎 + +/// + +## Cookies com Modelos Pydantic + +Declare o parâmetro de **cookie** que você precisa em um **modelo Pydantic**, e depois declare o parâmetro como um `Cookie`: + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +O **FastAPI** irá **extrair** os dados para **cada campo** dos **cookies** recebidos na requisição e lhe fornecer o modelo Pydantic que você definiu. + +## Verifique os Documentos + +Você pode ver os cookies definidos na IU dos documentos em `/docs`: + +
+ +
+ +/// info | Informação + +Tenha em mente que, como os **navegadores lidam com cookies** de maneira especial e por baixo dos panos, eles **não** permitem facilmente que o **JavaScript** lidem com eles. + +Se você for na **IU de documentos da API** em `/docs` você poderá ver a **documentação** para cookies das suas *operações de rotas*. + +Mas mesmo que você **adicionar os dados** e clicar em "Executar", pelo motivo da IU dos documentos trabalharem com **JavaScript**, os cookies não serão enviados, e você verá uma mensagem de **erro** como se você não tivesse escrito nenhum dado. + +/// + +## Proibir Cookies Adicionais + +Em alguns casos especiais (provavelmente não muito comuns), você pode querer **restringir** os cookies que você deseja receber. + +Agora a sua API possui o poder de contrar o seu próprio consentimento de cookie. 🤪🍪 + + + Você pode utilizar a configuração do modelo Pydantic para `proibir` qualquer campo `extra`. + + +{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} + +Se o cliente tentar enviar alguns **cookies extras**, eles receberão um retorno de **erro**. + +Coitados dos banners de cookies com todo o seu esforço para obter o seu consentimento para a API rejeitá-lo. 🍪 + +Por exemplo, se o cliente tentar enviar um cookie `santa_tracker` com o valor de `good-list-please`, o cliente receberá uma resposta de **erro** informando que o cookie `santa_tracker` is not allowed: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## Resumo + +Você consegue utilizar **modelos Pydantic** para declarar **cookies** no **FastAPI**. 😎 diff --git a/docs/pt/docs/tutorial/cookie-params.md b/docs/pt/docs/tutorial/cookie-params.md index caed17632d..da85d796ea 100644 --- a/docs/pt/docs/tutorial/cookie-params.md +++ b/docs/pt/docs/tutorial/cookie-params.md @@ -6,21 +6,18 @@ Você pode definir parâmetros de Cookie da mesma maneira que define paramêtros Primeiro importe `Cookie`: -```Python hl_lines="3" -{!../../../docs_src/cookie_params/tutorial001.py!} -``` +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} ## Declare parâmetros de `Cookie` Então declare os paramêtros de cookie usando a mesma estrutura que em `Path` e `Query`. -O primeiro valor é o valor padrão, você pode passar todas as validações adicionais ou parâmetros de anotação: +Você pode definir o valor padrão, assim como todas as validações extras ou parâmetros de anotação: -```Python hl_lines="9" -{!../../../docs_src/cookie_params/tutorial001.py!} -``` -/// note | "Detalhes Técnicos" +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} + +/// note | Detalhes Técnicos `Cookie` é uma classe "irmã" de `Path` e `Query`. Ela também herda da mesma classe em comum `Param`. @@ -28,9 +25,9 @@ Mas lembre-se que quando você importa `Query`, `Path`, `Cookie` e outras de `fa /// -/// info | "Informação" +/// info | Informação -Para declarar cookies, você precisa usar `Cookie`, caso contrário, os parâmetros seriam interpretados como parâmetros de consulta. +Para declarar cookies, você precisa usar `Cookie`, pois caso contrário, os parâmetros seriam interpretados como parâmetros de consulta. /// diff --git a/docs/pt/docs/tutorial/cors.md b/docs/pt/docs/tutorial/cors.md index e5e2f8c277..0ab07a3c21 100644 --- a/docs/pt/docs/tutorial/cors.md +++ b/docs/pt/docs/tutorial/cors.md @@ -46,9 +46,7 @@ Você também pode especificar se o seu backend permite: * Métodos HTTP específicos (`POST`, `PUT`) ou todos eles com o curinga `"*"`. * Cabeçalhos HTTP específicos ou todos eles com o curinga `"*"`. -```Python hl_lines="2 6-11 13-19" -{!../../../docs_src/cors/tutorial001.py!} -``` +{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} Os parâmetros padrão usados ​​pela implementação `CORSMiddleware` são restritivos por padrão, então você precisará habilitar explicitamente as origens, métodos ou cabeçalhos específicos para que os navegadores tenham permissão para usá-los em um contexto de domínios diferentes. @@ -78,7 +76,7 @@ Qualquer solicitação com um cabeçalho `Origin`. Neste caso, o middleware pass Para mais informações CORS, acesse Mozilla CORS documentation. -/// note | "Detalhes técnicos" +/// note | Detalhes técnicos Você também pode usar `from starlette.middleware.cors import CORSMiddleware`. diff --git a/docs/pt/docs/tutorial/debugging.md b/docs/pt/docs/tutorial/debugging.md new file mode 100644 index 0000000000..67b7644577 --- /dev/null +++ b/docs/pt/docs/tutorial/debugging.md @@ -0,0 +1,113 @@ +# Depuração + +Você pode conectar o depurador no seu editor, por exemplo, com o Visual Studio Code ou PyCharm. + +## Chamar `uvicorn` + +Em seu aplicativo FastAPI, importe e execute `uvicorn` diretamente: + +{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} + +### Sobre `__name__ == "__main__"` + +O objetivo principal de `__name__ == "__main__"` é ter algum código que seja executado quando seu arquivo for chamado com: + +
+ +```console +$ python myapp.py +``` + +
+ +mas não é chamado quando outro arquivo o importa, como em: + +```Python +from myapp import app +``` + +#### Mais detalhes + +Digamos que seu arquivo se chama `myapp.py`. + +Se você executá-lo com: + +
+ +```console +$ python myapp.py +``` + +
+ +então a variável interna `__name__` no seu arquivo, criada automaticamente pelo Python, terá como valor a string `"__main__"`. + +Então, a seção: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +vai executar. + +--- + +Isso não acontecerá se você importar esse módulo (arquivo). + +Então, se você tiver outro arquivo `importer.py` com: + +```Python +from myapp import app + +# Mais um pouco de código +``` + +nesse caso, a variável criada automaticamente dentro de `myapp.py` não terá a variável `__name__` com o valor `"__main__"`. + +Então, a linha: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +não será executada. + +/// info | Informação + +Para mais informações, consulte a documentação oficial do Python. + +/// + +## Execute seu código com seu depurador + +Como você está executando o servidor Uvicorn diretamente do seu código, você pode chamar seu programa Python (seu aplicativo FastAPI) diretamente do depurador. + +--- + +Por exemplo, no Visual Studio Code, você pode: + +* Ir para o painel "Debug". +* "Add configuration...". +* Selecionar "Python" +* Executar o depurador com a opção "`Python: Current File (Integrated Terminal)`". + +Em seguida, ele iniciará o servidor com seu código **FastAPI**, parará em seus pontos de interrupção, etc. + +Veja como pode parecer: + + + +--- + +Se você usar o Pycharm, você pode: + +* Abrir o menu "Executar". +* Selecionar a opção "Depurar...". +* Então um menu de contexto aparece. +* Selecionar o arquivo para depurar (neste caso, `main.py`). + +Em seguida, ele iniciará o servidor com seu código **FastAPI**, parará em seus pontos de interrupção, etc. + +Veja como pode parecer: + + diff --git a/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md index 420503b878..ef67aa979e 100644 --- a/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md @@ -6,57 +6,7 @@ Antes de nos aprofundarmos no sistema de **Injeção de Dependência**, vamos me No exemplo anterior, nós retornávamos um `dict` da nossa dependência ("injetável"): -//// tab | Python 3.10+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="11" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} Mas assim obtemos um `dict` como valor do parâmetro `commons` na *função de operação de rota*. @@ -119,165 +69,15 @@ Isso também se aplica a objetos chamáveis que não recebem nenhum parâmetro. Então, podemos mudar o "injetável" na dependência `common_parameters` acima para a classe `CommonQueryParams`: -//// tab | Python 3.10+ - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12-16" -{!> ../../../docs_src/dependencies/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="9-13" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} Observe o método `__init__` usado para criar uma instância da classe: -//// tab | Python 3.10+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="13" -{!> ../../../docs_src/dependencies/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} ...ele possui os mesmos parâmetros que nosso `common_parameters` anterior: -//// tab | Python 3.10+ - -```Python hl_lines="8" -{!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="6" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} Esses parâmetros são utilizados pelo **FastAPI** para "definir" a dependência. @@ -293,57 +93,7 @@ Os dados serão convertidos, validados, documentados no esquema da OpenAPI e etc Agora você pode declarar sua dependência utilizando essa classe. -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} O **FastAPI** chama a classe `CommonQueryParams`. Isso cria uma "instância" dessa classe e é a instância que será passada para o parâmetro `commons` na sua função. @@ -361,7 +111,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// tab | Python 3.8+ non-Annotated -/// tip | "Dica" +/// tip | Dica Utilize a versão com `Annotated` se possível. @@ -397,7 +147,7 @@ commons: Annotated[CommonQueryParams, ... //// tab | Python 3.8+ non-Annotated -/// tip | "Dica" +/// tip | Dica Utilize a versão com `Annotated` se possível. @@ -423,7 +173,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// tab | Python 3.8+ non-Annotated -/// tip | "Dica" +/// tip | Dica Utilize a versão com `Annotated` se possível. @@ -437,57 +187,7 @@ commons = Depends(CommonQueryParams) ...como em: -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} Mas declarar o tipo é encorajado por que é a forma que o seu editor de texto sabe o que será passado como valor do parâmetro `commons`. @@ -507,7 +207,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// tab | Python 3.8+ non-Annotated -/// tip | "Dica" +/// tip | Dica Utilize a versão com `Annotated` se possível. @@ -535,7 +235,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// tab | Python 3.8+ non-Annotated -/// tip | "Dica" +/// tip | Dica Utilize a versão com `Annotated` se possível. @@ -559,7 +259,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// tab | Python 3.8 non-Annotated -/// tip | "Dica" +/// tip | Dica Utilize a versão com `Annotated` se possível. @@ -575,61 +275,11 @@ Você declara a dependência como o tipo do parâmetro, e utiliza `Depends()` se O mesmo exemplo ficaria então dessa forma: -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/dependencies/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} ...e o **FastAPI** saberá o que fazer. -/// tip | "Dica" +/// tip | Dica Se isso parece mais confuso do que útil, não utilize, você não *precisa* disso. diff --git a/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 4a7a293901..d7d31bb45e 100644 --- a/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,39 +14,11 @@ O *decorador da operação de rota* recebe um argumento opcional `dependencies`. Ele deve ser uma lista de `Depends()`: -//// tab | Python 3.9+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} Essas dependências serão executadas/resolvidas da mesma forma que dependências comuns. Mas o valor delas (se existir algum) não será passado para a sua *função de operação de rota*. -/// tip | "Dica" +/// tip | Dica Alguns editores de texto checam parâmetros de funções não utilizados, e os mostram como erros. @@ -56,7 +28,7 @@ Isso também pode ser útil para evitar confundir novos desenvolvedores que ao v /// -/// info | "Informação" +/// info | Informação Neste exemplo utilizamos cabeçalhos personalizados inventados `X-Keys` e `X-Token`. @@ -72,69 +44,13 @@ Você pode utilizar as mesmas *funções* de dependências que você usaria norm Dependências podem declarar requisitos de requisições (como cabeçalhos) ou outras subdependências: -//// tab | Python 3.9+ - -```Python hl_lines="8 13" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7 12" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível - -/// - -```Python hl_lines="6 11" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} ### Levantando exceções Essas dependências podem levantar exceções, da mesma forma que dependências comuns: -//// tab | Python 3.9+ - -```Python hl_lines="10 15" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 14" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 non-Annotated - -/// tip | "Dica" - -Utilize a versão com `Annotated` se possível - -/// - -```Python hl_lines="8 13" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} ### Valores de retorno @@ -142,37 +58,7 @@ E elas também podem ou não retornar valores, eles não serão utilizados. Então, você pode reutilizar uma dependência comum (que retorna um valor) que já seja utilizada em outro lugar, e mesmo que o valor não seja utilizado, a dependência será executada: -//// tab | Python 3.9+ - -```Python hl_lines="11 16" -{!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10 15" -{!> ../../../docs_src/dependencies/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8 non-Annotated - -/// tip | "Dica" - - - -/// - - Utilize a versão com `Annotated` se possível - -```Python hl_lines="9 14" -{!> ../../../docs_src/dependencies/tutorial006.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} ## Dependências para um grupo de *operações de rota* diff --git a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md index 16c2cf8997..eaf711197f 100644 --- a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md @@ -4,13 +4,13 @@ O FastAPI possui suporte para dependências que realizam . -Mas se você tiver cabeçalhos personalizados desejando que um cliente em um navegador esteja apto a ver, você precisa adicioná-los às suas configurações CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) usando o parâmetro `expose_headers` documentado em Documentos CORS da Starlette. +Mas se você tiver cabeçalhos personalizados desejando que um cliente em um navegador esteja apto a ver, você precisa adicioná-los às suas configurações CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) usando o parâmetro `expose_headers` documentado em Documentos CORS da Starlette. /// -/// note | "Detalhes técnicos" +/// note | Detalhes técnicos Você também pode usar `from starlette.requests import Request`. @@ -59,9 +57,7 @@ E também depois que a `response` é gerada, antes de retorná-la. Por exemplo, você pode adicionar um cabeçalho personalizado `X-Process-Time` contendo o tempo em segundos que levou para processar a solicitação e gerar uma resposta: -```Python hl_lines="10 12-13" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} ## Outros middlewares diff --git a/docs/pt/docs/tutorial/path-operation-configuration.md b/docs/pt/docs/tutorial/path-operation-configuration.md index c578137804..f183c9d23b 100644 --- a/docs/pt/docs/tutorial/path-operation-configuration.md +++ b/docs/pt/docs/tutorial/path-operation-configuration.md @@ -2,7 +2,7 @@ Existem vários parâmetros que você pode passar para o seu *decorador de operação de rota* para configurá-lo. -/// warning | "Aviso" +/// 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*. @@ -16,33 +16,11 @@ 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`: -//// tab | Python 3.8 and above - -```Python hl_lines="3 17" -{!> ../../../docs_src/path_operation_configuration/tutorial001.py!} -``` - -//// - -//// tab | Python 3.9 and above - -```Python hl_lines="3 17" -{!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.10 and above - -```Python hl_lines="1 15" -{!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *} Esse código de status será usado na resposta e será adicionado ao esquema OpenAPI. -/// note | "Detalhes Técnicos" +/// note | Detalhes Técnicos Você também poderia usar `from starlette import status`. @@ -54,29 +32,7 @@ Você também poderia usar `from starlette import status`. Você pode adicionar tags para sua *operação de rota*, passe o parâmetro `tags` com uma `list` de `str` (comumente apenas um `str`): -//// tab | Python 3.8 and above - -```Python hl_lines="17 22 27" -{!> ../../../docs_src/path_operation_configuration/tutorial002.py!} -``` - -//// - -//// tab | Python 3.9 and above - -```Python hl_lines="17 22 27" -{!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.10 and above - -```Python hl_lines="15 20 25" -{!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *} Eles serão adicionados ao esquema OpenAPI e usados pelas interfaces de documentação automática: @@ -90,37 +46,13 @@ 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!} -``` +{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *} ## Resumo e descrição Você pode adicionar um `summary` e uma `description`: -//// tab | Python 3.8 and above - -```Python hl_lines="20-21" -{!> ../../../docs_src/path_operation_configuration/tutorial003.py!} -``` - -//// - -//// tab | Python 3.9 and above - -```Python hl_lines="20-21" -{!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.10 and above - -```Python hl_lines="18-19" -{!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *} ## Descrição do docstring @@ -128,29 +60,7 @@ Como as descrições tendem a ser longas e cobrir várias linhas, você pode dec Você pode escrever Markdown na docstring, ele será interpretado e exibido corretamente (levando em conta a indentação da docstring). -//// tab | Python 3.8 and above - -```Python hl_lines="19-27" -{!> ../../../docs_src/path_operation_configuration/tutorial004.py!} -``` - -//// - -//// tab | Python 3.9 and above - -```Python hl_lines="19-27" -{!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.10 and above - -```Python hl_lines="17-25" -{!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} -``` - -//// +{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} Ela será usada nas documentações interativas: @@ -161,31 +71,9 @@ Ela será usada nas documentações interativas: Você pode especificar a descrição da resposta com o parâmetro `response_description`: -//// tab | Python 3.8 and above +{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *} -```Python hl_lines="21" -{!> ../../../docs_src/path_operation_configuration/tutorial005.py!} -``` - -//// - -//// tab | Python 3.9 and above - -```Python hl_lines="21" -{!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.10 and above - -```Python hl_lines="19" -{!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} -``` - -//// - -/// info | "Informação" +/// info | Informação Note que `response_description` se refere especificamente à resposta, a `description` se refere à *operação de rota* em geral. @@ -205,9 +93,7 @@ Então, se você não fornecer uma, o **FastAPI** irá gerar automaticamente uma 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!} -``` +{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} Ela será claramente marcada como descontinuada nas documentações interativas: diff --git a/docs/pt/docs/tutorial/path-params-numeric-validations.md b/docs/pt/docs/tutorial/path-params-numeric-validations.md index 08ed03f756..3aea1188dd 100644 --- a/docs/pt/docs/tutorial/path-params-numeric-validations.md +++ b/docs/pt/docs/tutorial/path-params-numeric-validations.md @@ -6,21 +6,7 @@ Do mesmo modo que você pode declarar mais validações e metadados para parâme Primeiro, importe `Path` de `fastapi`: -//// tab | Python 3.10+ - -```Python hl_lines="1" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` - -//// +{* ../../docs_src/path_params_numeric_validations/tutorial001_py310.py hl[1] *} ## Declare metadados @@ -28,23 +14,9 @@ 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: -//// tab | Python 3.10+ +{* ../../docs_src/path_params_numeric_validations/tutorial001_py310.py hl[8] *} -```Python hl_lines="8" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` - -//// - -/// note | "Nota" +/// note | Nota Um parâmetro de rota é sempre obrigatório, como se fizesse parte da rota. @@ -70,9 +42,7 @@ Isso não faz diferença para o **FastAPI**. Ele vai detectar os parâmetros pel Então, você pode declarar sua função assim: -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} ## Ordene os parâmetros de a acordo com sua necessidade, truques @@ -82,9 +52,7 @@ 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!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} ## Validações numéricas: maior que ou igual @@ -92,9 +60,7 @@ Com `Query` e `Path` (e outras que você verá mais tarde) você pode declarar r 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!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} ## Validações numéricas: maior que e menor que ou igual @@ -103,9 +69,7 @@ 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!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *} ## Validações numéricas: valores do tipo float, maior que e menor que @@ -117,9 +81,7 @@ 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!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} ## Recapitulando @@ -132,7 +94,7 @@ E você também pode declarar validações numéricas: * `lt`: menor que (`l`ess `t`han) * `le`: menor que ou igual (`l`ess than or `e`qual) -/// info | "Informação" +/// info | Informação `Query`, `Path` e outras classes que você verá a frente são subclasses de uma classe comum `Param`. @@ -140,7 +102,7 @@ Todas elas compartilham os mesmos parâmetros para validação adicional e metad /// -/// note | "Detalhes Técnicos" +/// note | Detalhes Técnicos Quando você importa `Query`, `Path` e outras de `fastapi`, elas são na verdade funções. diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md index fb872e4f52..ecf77d676d 100644 --- a/docs/pt/docs/tutorial/path-params.md +++ b/docs/pt/docs/tutorial/path-params.md @@ -2,9 +2,7 @@ Você pode declarar os "parâmetros" ou "variáveis" com a mesma sintaxe utilizada pelo formato de strings do Python: -```Python hl_lines="6-7" -{!../../../docs_src/path_params/tutorial001.py!} -``` +{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} O valor do parâmetro que foi passado à `item_id` será passado para a sua função como o argumento `item_id`. @@ -18,13 +16,11 @@ Então, se você rodar este exemplo e for até http://127.0.0.1:8000/items/4.2 -/// check | "Verifique" +/// check | Verifique @@ -91,7 +87,7 @@ Quando você abrir o seu navegador em -/// check | "Verifique" +/// check | Verifique @@ -129,9 +125,7 @@ E então você pode ter também uma rota `/users/{user_id}` para pegar dados sob Porque as operações de rota são avaliadas em ordem, você precisa ter certeza que a rota para `/users/me` está sendo declarado antes da rota `/users/{user_id}`: -```Python hl_lines="6 11" -{!../../../docs_src/path_params/tutorial003.py!} -``` +{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} Caso contrário, a rota para `/users/{user_id}` coincidiria também para `/users/me`, "pensando" que estaria recebendo o parâmetro `user_id` com o valor de `"me"`. @@ -147,17 +141,15 @@ Por herdar de `str` a documentação da API vai ser capaz de saber que os valore Assim, crie atributos de classe com valores fixos, que serão os valores válidos disponíveis. -```Python hl_lines="1 6-9" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} -/// info | "informação" +/// info | informação Enumerations (ou enums) estão disponíveis no Python desde a versão 3.4. /// -/// tip | "Dica" +/// tip | Dica @@ -169,9 +161,7 @@ Assim, crie atributos de classe com valores fixos, que serão os valores válido Logo, crie um *parâmetro de rota* com anotações de tipo usando a classe enum que você criou (`ModelName`): -```Python hl_lines="16" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[16] *} ### Revise a documentação @@ -187,19 +177,15 @@ O valor do *parâmetro da rota* será um *membro de enumeration*. Você pode comparar eles com o *membro de enumeration* no enum `ModelName` que você criou: -```Python hl_lines="17" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[17] *} #### Obtenha o *valor de enumerate* Você pode ter o valor exato de enumerate (um `str` nesse caso) usando `model_name.value`, ou em geral, `your_enum_member.value`: -```Python hl_lines="20" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[20] *} -/// tip | "Dica" +/// tip | Dica @@ -213,9 +199,7 @@ Você pode retornar *membros de enum* da sua *rota de operação*, em um corpo J Eles serão convertidos para o seus valores correspondentes (strings nesse caso) antes de serem retornados ao cliente: -```Python hl_lines="18 21 23" -{!../../../docs_src/path_params/tutorial005.py!} -``` +{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} No seu cliente você vai obter uma resposta JSON como: @@ -254,11 +238,9 @@ Nesse caso, o nome do parâmetro é `file_path`, e a última parte, `:path`, diz Então, você poderia usar ele com: -```Python hl_lines="6" -{!../../../docs_src/path_params/tutorial004.py!} -``` +{* ../../docs_src/path_params/tutorial004.py hl[6] *} -/// tip | "Dica" +/// tip | Dica diff --git a/docs/pt/docs/tutorial/query-param-models.md b/docs/pt/docs/tutorial/query-param-models.md new file mode 100644 index 0000000000..01a6e462f1 --- /dev/null +++ b/docs/pt/docs/tutorial/query-param-models.md @@ -0,0 +1,69 @@ +# Modelos de Parâmetros de Consulta + +Se você possui um grupo de **parâmetros de consultas** que são relacionados, você pode criar um **modelo Pydantic** para declará-los. + +Isso permitiria que você **reutilizasse o modelo** em **diversos lugares**, e também declarasse validações e metadados de todos os parâmetros de uma única vez. 😎 + +/// note | Nota + +Isso é suportado desde o FastAPI versão `0.115.0`. 🤓 + +/// + +## Parâmetros de Consulta com um Modelo Pydantic + +Declare os **parâmetros de consulta** que você precisa em um **modelo Pydantic**, e então declare o parâmetro como `Query`: + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +O **FastAPI** **extrairá** os dados para **cada campo** dos **parâmetros de consulta** presentes na requisição, e fornecerá o modelo Pydantic que você definiu. + + +## Verifique os Documentos + +Você pode ver os parâmetros de consulta nos documentos de IU em `/docs`: + +
+ +
+ +## Restrinja Parâmetros de Consulta Extras + +Em alguns casos especiais (provavelmente não muito comuns), você queira **restrinjir** os parâmetros de consulta que deseja receber. + +Você pode usar a configuração do modelo Pydantic para `forbid` (proibir) qualquer campo `extra`: + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +Caso um cliente tente enviar alguns dados **extras** nos **parâmetros de consulta**, eles receberão um retorno de **erro**. + +Por exemplo, se o cliente tentar enviar um parâmetro de consulta `tool` com o valor `plumbus`, como: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +Eles receberão um retorno de **erro** informando-os que o parâmentro de consulta `tool` não é permitido: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## Resumo + +Você pode utilizar **modelos Pydantic** para declarar **parâmetros de consulta** no **FastAPI**. 😎 + +/// tip | Dica + +Alerta de spoiler: você também pode utilizar modelos Pydantic para declarar cookies e cabeçalhos, mas você irá ler sobre isso mais a frente no tutorial. 🤫 + +/// diff --git a/docs/pt/docs/tutorial/query-params-str-validations.md b/docs/pt/docs/tutorial/query-params-str-validations.md index eac8795937..8c4f2e655e 100644 --- a/docs/pt/docs/tutorial/query-params-str-validations.md +++ b/docs/pt/docs/tutorial/query-params-str-validations.md @@ -4,13 +4,11 @@ O **FastAPI** permite que você declare informações adicionais e validações Vamos utilizar essa aplicação como exemplo: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial001.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *} 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" +/// note | Observação O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`. @@ -26,17 +24,13 @@ Nós iremos forçar que mesmo o parâmetro `q` seja opcional, sempre que informa Para isso, primeiro importe `Query` de `fastapi`: -```Python hl_lines="3" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *} ## Use `Query` como o valor padrão Agora utilize-o como valor padrão do seu parâmetro, definindo o parâmetro `max_length` para 50: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} 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. @@ -54,7 +48,7 @@ q: Union[str, None] = None Mas o declara explicitamente como um parâmetro de consulta. -/// info | "Informação" +/// info | Informação Tenha em mente que o FastAPI se preocupa com a parte: @@ -86,17 +80,13 @@ 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="10" -{!../../../docs_src/query_params_str_validations/tutorial003.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *} ## Adicionando expressões regulares Você pode definir uma expressão regular que combine com um padrão esperado pelo parâmetro: -```Python hl_lines="11" -{!../../../docs_src/query_params_str_validations/tutorial004.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *} Essa expressão regular específica verifica se o valor recebido no parâmetro: @@ -114,11 +104,9 @@ Da mesma maneira que você utiliza `None` como o primeiro argumento para ser uti Vamos dizer que você queira que o parâmetro de consulta `q` tenha um `min_length` de `3`, e um valor padrão de `"fixedquery"`, então declararíamos assim: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial005.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} -/// note | "Observação" +/// note | Observação O parâmetro torna-se opcional quando possui um valor padrão. @@ -146,11 +134,9 @@ 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: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial006.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} -/// info | "Informação" +/// info | Informação Se você nunca viu os `...` antes: é um valor único especial, faz parte do Python e é chamado "Ellipsis". @@ -164,9 +150,7 @@ Quando você declara explicitamente um parâmetro com `Query` você pode declar Por exemplo, para declarar que o parâmetro `q` pode aparecer diversas vezes na URL, você escreveria: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial011.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} Então, com uma URL assim: @@ -187,7 +171,7 @@ Assim, a resposta para essa URL seria: } ``` -/// tip | "Dica" +/// tip | Dica Para declarar um parâmetro de consulta com o tipo `list`, como no exemplo acima, você precisa usar explicitamente o `Query`, caso contrário será interpretado como um corpo da requisição. @@ -201,9 +185,7 @@ A documentação interativa da API irá atualizar de acordo, permitindo múltipl E você também pode definir uma lista (`list`) de valores padrão caso nenhum seja informado: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial012.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} Se você for até: @@ -226,11 +208,9 @@ O valor padrão de `q` será: `["foo", "bar"]` e sua resposta será: Você também pode utilizar o tipo `list` diretamente em vez de `List[str]`: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial013.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} -/// note | "Observação" +/// note | Observação Tenha em mente que neste caso, o FastAPI não irá validar os conteúdos da lista. @@ -244,7 +224,7 @@ Você pode adicionar mais informações sobre o parâmetro. Essa informações serão inclusas no esquema do OpenAPI e utilizado pela documentação interativa e ferramentas externas. -/// note | "Observação" +/// note | Observação Tenha em mente que cada ferramenta oferece diferentes níveis de suporte ao OpenAPI. @@ -254,15 +234,11 @@ Algumas delas não exibem todas as informações extras que declaramos, ainda qu Você pode adicionar um `title`: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial007.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *} E uma `description`: -```Python hl_lines="13" -{!../../../docs_src/query_params_str_validations/tutorial008.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} ## Apelidos (alias) de parâmetros @@ -282,9 +258,7 @@ Mas ainda você precisa que o nome seja exatamente `item-query`... Então você pode declarar um `alias`, e esse apelido (alias) que será utilizado para encontrar o valor do parâmetro: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial009.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} ## Parâmetros descontinuados @@ -294,9 +268,7 @@ Você tem que deixá-lo ativo por um tempo, já que existem clientes o utilizand Então você passa o parâmetro `deprecated=True` para `Query`: -```Python hl_lines="18" -{!../../../docs_src/query_params_str_validations/tutorial010.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} Na documentação aparecerá assim: diff --git a/docs/pt/docs/tutorial/query-params.md b/docs/pt/docs/tutorial/query-params.md index 78d54f09bf..8199de5af9 100644 --- a/docs/pt/docs/tutorial/query-params.md +++ b/docs/pt/docs/tutorial/query-params.md @@ -2,9 +2,7 @@ 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!} -``` +{* ../../docs_src/query_params/tutorial001.py hl[9] *} A consulta é o conjunto de pares chave-valor que vai depois de `?` na URL, separado pelo caractere `&`. @@ -63,25 +61,11 @@ Os valores dos parâmetros na sua função serão: Da mesma forma, você pode declarar parâmetros de consulta opcionais, definindo o valor padrão para `None`: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial002.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} Nesse caso, o parâmetro da função `q` será opcional, e `None` será o padrão. -/// check | "Verificar" +/// 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. @@ -91,21 +75,7 @@ Você também pode notar que o **FastAPI** é esperto o suficiente para perceber Você também pode declarar tipos `bool`, e eles serão convertidos: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial003.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} Nesse caso, se você for para: @@ -147,21 +117,7 @@ E você não precisa declarar eles em nenhuma ordem específica. Eles serão detectados pelo nome: -//// tab | Python 3.10+ - -```Python hl_lines="6 8" -{!> ../../../docs_src/query_params/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8 10" -{!> ../../../docs_src/query_params/tutorial004.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} ## Parâmetros de consulta obrigatórios @@ -171,9 +127,7 @@ Caso você não queira adicionar um valor específico mas queira apenas torná-l 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!} -``` +{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} Aqui o parâmetro de consulta `needy` é um valor obrigatório, do tipo `str`. @@ -217,21 +171,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy E claro, você pode definir alguns parâmetros como obrigatórios, alguns possuindo um valor padrão, e outros sendo totalmente opcionais: -//// tab | Python 3.10+ - -```Python hl_lines="8" -{!> ../../../docs_src/query_params/tutorial006_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/query_params/tutorial006.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} Nesse caso, existem 3 parâmetros de consulta: @@ -239,7 +179,7 @@ Nesse caso, existem 3 parâmetros de consulta: * `skip`, um `int` com o valor padrão `0`. * `limit`, um `int` opcional. -/// tip | "Dica" +/// tip | Dica Você também poderia usar `Enum` da mesma forma que com [Path Parameters](path-params.md#valores-predefinidos){.internal-link target=_blank}. diff --git a/docs/pt/docs/tutorial/request-files.md b/docs/pt/docs/tutorial/request-files.md new file mode 100644 index 0000000000..c22c1c5133 --- /dev/null +++ b/docs/pt/docs/tutorial/request-files.md @@ -0,0 +1,176 @@ +# Arquivos de Requisição + +Você pode definir arquivos para serem enviados pelo cliente usando `File`. + +/// info | Informação + +Para receber arquivos enviados, primeiro instale o `python-multipart`. + +Garanta que você criou um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, o ativou e então o instalou, por exemplo: + +```console +$ pip install python-multipart +``` + +Isso é necessário, visto que os arquivos enviados são enviados como "dados de formulário". + +/// + +## Importe `File` + +Importe `File` e `UploadFile` de `fastapi`: + +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} + +## Definir Parâmetros `File` + +Crie parâmetros de arquivo da mesma forma que você faria para `Body` ou `Form`: + +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} + +/// info | Informação + +`File` é uma classe que herda diretamente de `Form`. + +Mas lembre-se que quando você importa `Query`, `Path`, `File` e outros de `fastapi`, eles são, na verdade, funções que retornam classes especiais. + +/// + +/// tip | Dica + +Para declarar corpos de arquivos, você precisa usar `File`, caso contrário, os parâmetros seriam interpretados como parâmetros de consulta ou parâmetros de corpo (JSON). + +/// + +Os arquivos serão enviados como "dados de formulário". + +Se você declarar o tipo do parâmetro da função da sua *operação de rota* como `bytes`, o **FastAPI** lerá o arquivo para você e você receberá o conteúdo como `bytes`. + +Mantenha em mente que isso significa que todo o conteúdo será armazenado na memória. Isso funcionará bem para arquivos pequenos. + +Mas há muitos casos em que você pode se beneficiar do uso de `UploadFile`. + +## Parâmetros de Arquivo com `UploadFile` + +Defina um parâmetro de arquivo com um tipo de `UploadFile`: + +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} + +Utilizar `UploadFile` tem várias vantagens sobre `bytes`: + +* Você não precisa utilizar o `File()` no valor padrão do parâmetro. +* Ele utiliza um arquivo "spooled": + * Um arquivo armazenado na memória até um limite máximo de tamanho, e após passar esse limite, ele será armazenado no disco. +* Isso significa que funcionará bem para arquivos grandes como imagens, vídeos, binários grandes, etc., sem consumir toda a memória. +* Você pode receber metadados do arquivo enviado. +* Ele tem uma file-like interface `assíncrona`. +* Ele expõe um objeto python `SpooledTemporaryFile` que você pode passar diretamente para outras bibliotecas que esperam um objeto semelhante a um arquivo("file-like"). + +### `UploadFile` + +`UploadFile` tem os seguintes atributos: + +* `filename`: Uma `str` com o nome do arquivo original que foi enviado (por exemplo, `myimage.jpg`). +* `content_type`: Uma `str` com o tipo de conteúdo (tipo MIME / tipo de mídia) (por exemplo, `image/jpeg`). +* `file`: Um `SpooledTemporaryFile` (um file-like objeto). Este é o objeto de arquivo Python que você pode passar diretamente para outras funções ou bibliotecas que esperam um objeto semelhante a um arquivo("file-like"). + +`UploadFile` tem os seguintes métodos `assíncronos`. Todos eles chamam os métodos de arquivo correspondentes por baixo dos panos (usando o `SpooledTemporaryFile` interno). + +* `write(data)`: Escreve `data` (`str` ou `bytes`) no arquivo. +* `read(size)`: Lê `size` (`int`) bytes/caracteres do arquivo. +* `seek(offset)`: Vai para o byte na posição `offset` (`int`) no arquivo. + * Por exemplo, `await myfile.seek(0)` irá para o início do arquivo. + * Isso é especialmente útil se você executar `await myfile.read()` uma vez e precisar ler o conteúdo novamente. +* `close()`: Fecha o arquivo. + +Como todos esses métodos são métodos `assíncronos`, você precisa "aguardar" por eles. + +Por exemplo, dentro de uma função de *operação de rota* `assíncrona`, você pode obter o conteúdo com: + +```Python +contents = await myfile.read() +``` + +Se você estiver dentro de uma função de *operação de rota* normal `def`, você pode acessar o `UploadFile.file` diretamente, por exemplo: + +```Python +contents = myfile.file.read() +``` + +/// note | Detalhes Técnicos do `async` + +Quando você usa os métodos `async`, o **FastAPI** executa os métodos de arquivo em um threadpool e aguarda por eles. + +/// + +/// note | Detalhes Técnicos do Starlette + +O `UploadFile` do ***FastAPI** herda diretamente do `UploadFile` do **Starlette** , mas adiciona algumas partes necessárias para torná-lo compatível com o **Pydantic** e as outras partes do FastAPI. + +/// + +## O que é "Form Data" + +O jeito que os formulários HTML (`
`) enviam os dados para o servidor normalmente usa uma codificação "especial" para esses dados, a qual é diferente do JSON. + +**FastAPI** se certificará de ler esses dados do lugar certo, ao invés de JSON. + +/// note | Detalhes Técnicos + +Dados de formulários normalmente são codificados usando o "media type" (tipo de mídia) `application/x-www-form-urlencoded` quando não incluem arquivos. + +Mas quando o formulário inclui arquivos, ele é codificado como `multipart/form-data`. Se você usar `File`, o **FastAPI** saberá que tem que pegar os arquivos da parte correta do corpo da requisição. + +Se você quiser ler mais sobre essas codificações e campos de formulário, vá para a MDN web docs para POST. + +/// + +/// warning | Aviso + +Você pode declarar múltiplos parâmetros `File` e `Form` em uma *operação de rota*, mas você não pode declarar campos `Body` que você espera 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. + +/// + +## Upload de Arquivo Opcional + +Você pode tornar um arquivo opcional usando anotações de tipo padrão e definindo um valor padrão de `None`: + +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} + +## `UploadFile` com Metadados Adicionais + +Você também pode usar `File()` com `UploadFile`, por exemplo, para definir metadados adicionais: + +{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} + +## Uploads de Múltiplos Arquivos + +É possível realizar o upload de vários arquivos ao mesmo tempo. + +Eles serão associados ao mesmo "campo de formulário" enviado usando "dados de formulário". + +Para usar isso, declare uma lista de `bytes` ou `UploadFile`: + +{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} + +Você receberá, tal como declarado, uma `list` de `bytes` ou `UploadFile`. + +/// note | Detalhes Técnicos + +Você pode também pode usar `from starlette.responses import HTMLResponse`. + +**FastAPI** providencia o mesmo `starlette.responses` que `fastapi.responses` apenas como uma conveniência para você, o desenvolvedor. Mas a maioria das respostas disponíveis vem diretamente do Starlette. + +/// + +### Uploads de Múltiplos Arquivos com Metadados Adicionais + +Da mesma forma de antes, você pode usar `File()` para definir parâmetros adicionais, mesmo para `UploadFile`: + +{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} + +## Recapitulando + +Utilize `File`, `bytes` e `UploadFile` para declarar arquivos a serem enviados na requisição, enviados como dados de formulário. diff --git a/docs/pt/docs/tutorial/request-form-models.md b/docs/pt/docs/tutorial/request-form-models.md new file mode 100644 index 0000000000..ea0e63d381 --- /dev/null +++ b/docs/pt/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# Modelos de Formulários + +Você pode utilizar **Modelos Pydantic** para declarar **campos de formulários** no FastAPI. + +/// info | Informação + +Para utilizar formulários, instale primeiramente o `python-multipart`. + +Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo, e então instalar. Por exemplo: + +```console +$ pip install python-multipart +``` + +/// + +/// note | Nota + +Isto é suportado desde a versão `0.113.0` do FastAPI. 🤓 + +/// + +## Modelos Pydantic para Formulários + +Você precisa apenas declarar um **modelo Pydantic** com os campos que deseja receber como **campos de formulários**, e então declarar o parâmetro como um `Form`: + +{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} + +O **FastAPI** irá **extrair** as informações para **cada campo** dos **dados do formulário** na requisição e dar para você o modelo Pydantic que você definiu. + +## Confira os Documentos + +Você pode verificar na UI de documentação em `/docs`: + +
+ +
+ +## Proibir Campos Extras de Formulários + +Em alguns casos de uso especiais (provavelmente não muito comum), você pode desejar **restringir** os campos do formulário para aceitar apenas os declarados no modelo Pydantic. E **proibir** qualquer campo **extra**. + +/// note | Nota + +Isso é suportado deste a versão `0.114.0` do FastAPI. 🤓 + +/// + +Você pode utilizar a configuração de modelo do Pydantic para `proibir` qualquer campo `extra`: + +{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} + +Caso um cliente tente enviar informações adicionais, ele receberá um retorno de **erro**. + +Por exemplo, se o cliente tentar enviar os campos de formulário: + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +Ele receberá um retorno de erro informando-o que o campo `extra` não é permitido: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## Resumo + +Você pode utilizar modelos Pydantic para declarar campos de formulários no FastAPI. 😎 diff --git a/docs/pt/docs/tutorial/request-forms-and-files.md b/docs/pt/docs/tutorial/request-forms-and-files.md index 2cf4063861..b08d870132 100644 --- a/docs/pt/docs/tutorial/request-forms-and-files.md +++ b/docs/pt/docs/tutorial/request-forms-and-files.md @@ -2,7 +2,7 @@ Você pode definir arquivos e campos de formulário ao mesmo tempo usando `File` e `Form`. -/// info | "Informação" +/// info | Informação Para receber arquivos carregados e/ou dados de formulário, primeiro instale `python-multipart`. @@ -12,23 +12,19 @@ Por exemplo: `pip install python-multipart`. ## Importe `File` e `Form` -```Python hl_lines="1" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *} ## 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!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *} 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" +/// 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`. diff --git a/docs/pt/docs/tutorial/request-forms.md b/docs/pt/docs/tutorial/request-forms.md index fc8c7bbada..572ddf003b 100644 --- a/docs/pt/docs/tutorial/request-forms.md +++ b/docs/pt/docs/tutorial/request-forms.md @@ -2,11 +2,15 @@ Quando você precisar receber campos de formulário ao invés de JSON, você pode usar `Form`. -/// info | "Informação" +/// info | Informação Para usar formulários, primeiro instale `python-multipart`. -Ex: `pip install python-multipart`. +Lembre-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar a dependência, por exemplo: + +```console +$ pip install python-multipart +``` /// @@ -14,17 +18,13 @@ Ex: `pip install python-multipart`. Importe `Form` de `fastapi`: -```Python hl_lines="1" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[1] *} ## 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!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[7] *} 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. @@ -32,13 +32,13 @@ A spec exige que os campos sejam exatamente Com `Form` você pode declarar os mesmos metadados e validação que com `Body` (e `Query`, `Path`, `Cookie`). -/// info | "Informação" +/// info | Informação `Form` é uma classe que herda diretamente de `Body`. /// -/// tip | "Dica" +/// 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). @@ -50,7 +50,7 @@ A forma como os formulários HTML (`
`) enviam os dados para o servi O **FastAPI** fará a leitura desses dados no lugar certo em vez de JSON. -/// note | "Detalhes técnicos" +/// note | Detalhes técnicos Os dados dos formulários são normalmente codificados usando o "tipo de mídia" `application/x-www-form-urlencoded`. @@ -60,7 +60,7 @@ Se você quiser ler mais sobre essas codificações e campos de formulário, vá /// -/// warning | "Aviso" +/// 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`. diff --git a/docs/pt/docs/tutorial/request_files.md b/docs/pt/docs/tutorial/request_files.md index 60e4ecb26a..15c1ad825a 100644 --- a/docs/pt/docs/tutorial/request_files.md +++ b/docs/pt/docs/tutorial/request_files.md @@ -16,69 +16,13 @@ Isso se deve por que arquivos enviados são enviados como "dados de formulário" Importe `File` e `UploadFile` do `fastapi`: -//// tab | Python 3.9+ - -```Python hl_lines="3" -{!> ../../../docs_src/request_files/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1" -{!> ../../../docs_src/request_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/request_files/tutorial001.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} ## Defina os parâmetros de `File` Cria os parâmetros do arquivo da mesma forma que você faria para `Body` ou `Form`: -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/request_files/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8" -{!> ../../../docs_src/request_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/request_files/tutorial001.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} /// info | Informação @@ -106,35 +50,7 @@ Mas existem vários casos em que você pode se beneficiar ao usar `UploadFile`. Defina um parâmetro de arquivo com o tipo `UploadFile` -//// tab | Python 3.9+ - -```Python hl_lines="14" -{!> ../../../docs_src/request_files/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="13" -{!> ../../../docs_src/request_files/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="12" -{!> ../../../docs_src/request_files/tutorial001.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} Utilizando `UploadFile` tem várias vantagens sobre `bytes`: @@ -217,91 +133,13 @@ Isso não é uma limitação do **FastAPI**, é uma parte do protocolo HTTP. Você pode definir um arquivo como opcional utilizando as anotações de tipo padrão e definindo o valor padrão como `None`: -//// tab | Python 3.10+ - -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10 18" -{!> ../../../docs_src/request_files/tutorial001_02_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated`, se possível - -/// - -```Python hl_lines="7 15" -{!> ../../../docs_src/request_files/tutorial001_02_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated`, se possível - -/// - -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} ## `UploadFile` com Metadados Adicionais Você também pode utilizar `File()` com `UploadFile`, por exemplo, para definir metadados adicionais: -//// tab | Python 3.9+ - -```Python hl_lines="9 15" -{!> ../../../docs_src/request_files/tutorial001_03_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8 14" -{!> ../../../docs_src/request_files/tutorial001_03_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível - -/// - -```Python hl_lines="7 13" -{!> ../../../docs_src/request_files/tutorial001_03.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} ## Envio de Múltiplos Arquivos @@ -311,49 +149,7 @@ Ele ficam associados ao mesmo "campo do formulário" enviado com "form data". Para usar isso, declare uma lista de `bytes` ou `UploadFile`: -//// tab | Python 3.9+ - -```Python hl_lines="10 15" -{!> ../../../docs_src/request_files/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11 16" -{!> ../../../docs_src/request_files/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível - -/// - -```Python hl_lines="8 13" -{!> ../../../docs_src/request_files/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível - -/// - -```Python hl_lines="10 15" -{!> ../../../docs_src/request_files/tutorial002.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} Você irá receber, como delcarado uma lista (`list`) de `bytes` ou `UploadFile`s, @@ -369,49 +165,7 @@ O **FastAPI** fornece as mesmas `starlette.responses` como `fastapi.responses` a E da mesma forma que antes, você pode utilizar `File()` para definir parâmetros adicionais, até mesmo para `UploadFile`: -//// tab | Python 3.9+ - -```Python hl_lines="11 18-20" -{!> ../../../docs_src/request_files/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12 19-21" -{!> ../../../docs_src/request_files/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.9+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="9 16" -{!> ../../../docs_src/request_files/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Utilize a versão com `Annotated` se possível. - -/// - -```Python hl_lines="11 18" -{!> ../../../docs_src/request_files/tutorial003.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} ## Recapitulando diff --git a/docs/pt/docs/tutorial/response-model.md b/docs/pt/docs/tutorial/response-model.md new file mode 100644 index 0000000000..6726a20a72 --- /dev/null +++ b/docs/pt/docs/tutorial/response-model.md @@ -0,0 +1,357 @@ +# Modelo de resposta - Tipo de retorno + +Você pode declarar o tipo usado para a resposta anotando o **tipo de retorno** *da função de operação de rota*. + +Você pode usar **anotações de tipo** da mesma forma que usaria para dados de entrada em **parâmetros** de função, você pode usar modelos Pydantic, listas, dicionários, valores escalares como inteiros, booleanos, etc. + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +O FastAPI usará este tipo de retorno para: + +* **Validar** os dados retornados. + * Se os dados forem inválidos (por exemplo, se estiver faltando um campo), significa que o código do *seu* aplicativo está quebrado, não retornando o que deveria, e retornará um erro de servidor em vez de retornar dados incorretos. Dessa forma, você e seus clientes podem ter certeza de que receberão os dados e o formato de dados esperados. +* Adicionar um **Esquema JSON** para a resposta, na *operação de rota* do OpenAPI. + * Isso será usado pela **documentação automática**. + * Também será usado por ferramentas de geração automática de código do cliente. + +Mas o mais importante: + +* Ele **limitará e filtrará** os dados de saída para o que está definido no tipo de retorno. + * Isso é particularmente importante para a **segurança**, veremos mais sobre isso abaixo. + +## Parâmetro `response_model` + +Existem alguns casos em que você precisa ou deseja retornar alguns dados que não são exatamente o que o tipo declara. + +Por exemplo, você pode querer **retornar um dicionário** ou um objeto de banco de dados, mas **declará-lo como um modelo Pydantic**. Dessa forma, o modelo Pydantic faria toda a documentação de dados, validação, etc. para o objeto que você retornou (por exemplo, um dicionário ou objeto de banco de dados). + +Se você adicionasse a anotação do tipo de retorno, ferramentas e editores reclamariam com um erro (correto) informando que sua função está retornando um tipo (por exemplo, um dict) diferente do que você declarou (por exemplo, um modelo Pydantic). + +Nesses casos, você pode usar o parâmetro `response_model` do *decorador de operação de rota* em vez do tipo de retorno. + +Você pode usar o parâmetro `response_model` em qualquer uma das *operações de rota*: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* etc. + +{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} + +/// note | Nota + +Observe que `response_model` é um parâmetro do método "decorator" (`get`, `post`, etc). Não da sua *função de operação de rota*, como todos os parâmetros e corpo. + +/// + +`response_model` recebe o mesmo tipo que você declararia para um campo de modelo Pydantic, então, pode ser um modelo Pydantic, mas também pode ser, por exemplo, uma `lista` de modelos Pydantic, como `List[Item]`. + +O FastAPI usará este `response_model` para fazer toda a documentação de dados, validação, etc. e também para **converter e filtrar os dados de saída** para sua declaração de tipo. + +/// tip | Dica + +Se você tiver verificações de tipo rigorosas em seu editor, mypy, etc, você pode declarar o tipo de retorno da função como `Any`. + +Dessa forma, você diz ao editor que está retornando qualquer coisa intencionalmente. Mas o FastAPI ainda fará a documentação de dados, validação, filtragem, etc. com o `response_model`. + +/// + +### Prioridade `response_model` + +Se você declarar tanto um tipo de retorno quanto um `response_model`, o `response_model` terá prioridade e será usado pelo FastAPI. + +Dessa forma, você pode adicionar anotações de tipo corretas às suas funções, mesmo quando estiver retornando um tipo diferente do modelo de resposta, para ser usado pelo editor e ferramentas como mypy. E ainda assim você pode fazer com que o FastAPI faça a validação de dados, documentação, etc. usando o `response_model`. + +Você também pode usar `response_model=None` para desabilitar a criação de um modelo de resposta para essa *operação de rota*, você pode precisar fazer isso se estiver adicionando anotações de tipo para coisas que não são campos Pydantic válidos, você verá um exemplo disso em uma das seções abaixo. + +## Retorna os mesmos dados de entrada + +Aqui estamos declarando um modelo `UserIn`, ele conterá uma senha em texto simples: + +{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} + +/// info | Informação + +Para usar `EmailStr`, primeiro instale `email-validator`. + +Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ative-o e instale-o, por exemplo: + +```console +$ pip install email-validator +``` + +ou com: + +```console +$ pip install "pydantic[email]" +``` + +/// + +E estamos usando este modelo para declarar nossa entrada e o mesmo modelo para declarar nossa saída: + +{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} + +Agora, sempre que um navegador estiver criando um usuário com uma senha, a API retornará a mesma senha na resposta. + +Neste caso, pode não ser um problema, porque é o mesmo usuário enviando a senha. + +Mas se usarmos o mesmo modelo para outra *operação de rota*, poderíamos estar enviando as senhas dos nossos usuários para todos os clientes. + +/// danger | Perigo + +Nunca armazene a senha simples de um usuário ou envie-a em uma resposta como esta, a menos que você saiba todas as ressalvas e saiba o que está fazendo. + +/// + +## Adicionar um modelo de saída + +Podemos, em vez disso, criar um modelo de entrada com a senha em texto simples e um modelo de saída sem ela: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} + +Aqui, embora nossa *função de operação de rota* esteja retornando o mesmo usuário de entrada que contém a senha: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} + +...declaramos o `response_model` como nosso modelo `UserOut`, que não inclui a senha: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} + +Então, **FastAPI** cuidará de filtrar todos os dados que não são declarados no modelo de saída (usando Pydantic). + +### `response_model` ou Tipo de Retorno + +Neste caso, como os dois modelos são diferentes, se anotássemos o tipo de retorno da função como `UserOut`, o editor e as ferramentas reclamariam que estamos retornando um tipo inválido, pois são classes diferentes. + +É por isso que neste exemplo temos que declará-lo no parâmetro `response_model`. + +...mas continue lendo abaixo para ver como superar isso. + +## Tipo de Retorno e Filtragem de Dados + +Vamos continuar do exemplo anterior. Queríamos **anotar a função com um tipo**, mas queríamos poder retornar da função algo que realmente incluísse **mais dados**. + +Queremos que o FastAPI continue **filtrando** os dados usando o modelo de resposta. Para que, embora a função retorne mais dados, a resposta inclua apenas os campos declarados no modelo de resposta. + +No exemplo anterior, como as classes eram diferentes, tivemos que usar o parâmetro `response_model`. Mas isso também significa que não temos suporte do editor e das ferramentas verificando o tipo de retorno da função. + +Mas na maioria dos casos em que precisamos fazer algo assim, queremos que o modelo apenas **filtre/remova** alguns dados como neste exemplo. + +E nesses casos, podemos usar classes e herança para aproveitar as **anotações de tipo** de função para obter melhor suporte no editor e nas ferramentas, e ainda obter a **filtragem de dados** FastAPI. + +{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} + +Com isso, temos suporte de ferramentas, de editores e mypy, pois este código está correto em termos de tipos, mas também obtemos a filtragem de dados do FastAPI. + +Como isso funciona? Vamos verificar. 🤓 + +### Anotações de tipo e ferramentas + +Primeiro, vamos ver como editores, mypy e outras ferramentas veriam isso. + +`BaseUser` tem os campos base. Então `UserIn` herda de `BaseUser` e adiciona o campo `password`, então, ele incluirá todos os campos de ambos os modelos. + +Anotamos o tipo de retorno da função como `BaseUser`, mas na verdade estamos retornando uma instância `UserIn`. + +O editor, mypy e outras ferramentas não reclamarão disso porque, em termos de digitação, `UserIn` é uma subclasse de `BaseUser`, o que significa que é um tipo *válido* quando o que é esperado é qualquer coisa que seja um `BaseUser`. + +### Filtragem de dados FastAPI + +Agora, para FastAPI, ele verá o tipo de retorno e garantirá que o que você retornar inclua **apenas** os campos que são declarados no tipo. + +O FastAPI faz várias coisas internamente com o Pydantic para garantir que essas mesmas regras de herança de classe não sejam usadas para a filtragem de dados retornados, caso contrário, você pode acabar retornando muito mais dados do que o esperado. + +Dessa forma, você pode obter o melhor dos dois mundos: anotações de tipo com **suporte a ferramentas** e **filtragem de dados**. + +## Veja na documentação + +Quando você vê a documentação automática, pode verificar se o modelo de entrada e o modelo de saída terão seus próprios esquemas JSON: + + + +E ambos os modelos serão usados ​​para a documentação interativa da API: + + + +## Outras anotações de tipo de retorno + +Pode haver casos em que você retorna algo que não é um campo Pydantic válido e anota na função, apenas para obter o suporte fornecido pelas ferramentas (o editor, mypy, etc). + +### Retornar uma resposta diretamente + +O caso mais comum seria [retornar uma resposta diretamente, conforme explicado posteriormente na documentação avançada](../advanced/response-directly.md){.internal-link target=_blank}. + +{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} + +Este caso simples é tratado automaticamente pelo FastAPI porque a anotação do tipo de retorno é a classe (ou uma subclasse de) `Response`. + +E as ferramentas também ficarão felizes porque `RedirectResponse` e ​​`JSONResponse` são subclasses de `Response`, então a anotação de tipo está correta. + +### Anotar uma subclasse de resposta + +Você também pode usar uma subclasse de `Response` na anotação de tipo: + +{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} + +Isso também funcionará porque `RedirectResponse` é uma subclasse de `Response`, e o FastAPI tratará automaticamente este caso simples. + +### Anotações de Tipo de Retorno Inválido + +Mas quando você retorna algum outro objeto arbitrário que não é um tipo Pydantic válido (por exemplo, um objeto de banco de dados) e você o anota dessa forma na função, o FastAPI tentará criar um modelo de resposta Pydantic a partir dessa anotação de tipo e falhará. + +O mesmo aconteceria se você tivesse algo como uma união entre tipos diferentes onde um ou mais deles não são tipos Pydantic válidos, por exemplo, isso falharia 💥: + +{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} + +... isso falha porque a anotação de tipo não é um tipo Pydantic e não é apenas uma única classe ou subclasse `Response`, é uma união (qualquer uma das duas) entre um `Response` e ​​um `dict`. + +### Desabilitar modelo de resposta + +Continuando com o exemplo acima, você pode não querer ter a validação de dados padrão, documentação, filtragem, etc. que é realizada pelo FastAPI. + +Mas você pode querer manter a anotação do tipo de retorno na função para obter o suporte de ferramentas como editores e verificadores de tipo (por exemplo, mypy). + +Neste caso, você pode desabilitar a geração do modelo de resposta definindo `response_model=None`: + +{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} + +Isso fará com que o FastAPI pule a geração do modelo de resposta e, dessa forma, você pode ter quaisquer anotações de tipo de retorno que precisar sem afetar seu aplicativo FastAPI. 🤓 + +## Parâmetros de codificação do modelo de resposta + +Seu modelo de resposta pode ter valores padrão, como: + +{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *} + +* `description: Union[str, None] = None` (ou `str | None = None` no Python 3.10) tem um padrão de `None`. +* `tax: float = 10.5` tem um padrão de `10.5`. +* `tags: List[str] = []` tem um padrão de uma lista vazia: `[]`. + +mas você pode querer omiti-los do resultado se eles não foram realmente armazenados. + +Por exemplo, se você tem modelos com muitos atributos opcionais em um banco de dados NoSQL, mas não quer enviar respostas JSON muito longas cheias de valores padrão. + +### Usar o parâmetro `response_model_exclude_unset` + +Você pode definir o parâmetro `response_model_exclude_unset=True` do *decorador de operação de rota* : + +{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *} + +e esses valores padrão não serão incluídos na resposta, apenas os valores realmente definidos. + +Então, se você enviar uma solicitação para essa *operação de rota* para o item com ID `foo`, a resposta (sem incluir valores padrão) será: + +```JSON +{ +"name": "Foo", +"price": 50.2 +} +``` + +/// info | Informação + +No Pydantic v1, o método era chamado `.dict()`, ele foi descontinuado (mas ainda suportado) no Pydantic v2 e renomeado para `.model_dump()`. + +Os exemplos aqui usam `.dict()` para compatibilidade com Pydantic v1, mas você deve usar `.model_dump()` em vez disso se puder usar Pydantic v2. + +/// + +/// info | Informação + +O FastAPI usa `.dict()` do modelo Pydantic com seu parâmetro `exclude_unset` para chegar a isso. + +/// + +/// info | Informação + +Você também pode usar: + +* `response_model_exclude_defaults=True` +* `response_model_exclude_none=True` + +conforme descrito na documentação do Pydantic para `exclude_defaults` e `exclude_none`. + +/// + +#### Dados com valores para campos com padrões + +Mas se seus dados tiverem valores para os campos do modelo com valores padrões, como o item com ID `bar`: + +```Python hl_lines="3 5" +{ +"name": "Bar", +"description": "The bartenders", +"price": 62, +"tax": 20.2 +} +``` + +eles serão incluídos na resposta. + +#### Dados com os mesmos valores que os padrões + +Se os dados tiverem os mesmos valores que os padrões, como o item com ID `baz`: + +```Python hl_lines="3 5-6" +{ +"name": "Baz", +"description": None, +"price": 50.2, +"tax": 10.5, +"tags": [] +} +``` + +O FastAPI é inteligente o suficiente (na verdade, o Pydantic é inteligente o suficiente) para perceber que, embora `description`, `tax` e `tags` tenham os mesmos valores que os padrões, eles foram definidos explicitamente (em vez de retirados dos padrões). + +Portanto, eles serão incluídos na resposta JSON. + +/// tip | Dica + +Observe que os valores padrão podem ser qualquer coisa, não apenas `None`. + +Eles podem ser uma lista (`[]`), um `float` de `10.5`, etc. + +/// + +### `response_model_include` e `response_model_exclude` + +Você também pode usar os parâmetros `response_model_include` e `response_model_exclude` do *decorador de operação de rota*. + +Eles pegam um `set` de `str` com o nome dos atributos para incluir (omitindo o resto) ou para excluir (incluindo o resto). + +Isso pode ser usado como um atalho rápido se você tiver apenas um modelo Pydantic e quiser remover alguns dados da saída. + +/// tip | Dica + +Mas ainda é recomendado usar as ideias acima, usando várias classes, em vez desses parâmetros. + +Isso ocorre porque o Schema JSON gerado no OpenAPI do seu aplicativo (e a documentação) ainda será o único para o modelo completo, mesmo que você use `response_model_include` ou `response_model_exclude` para omitir alguns atributos. + +Isso também se aplica ao `response_model_by_alias` que funciona de forma semelhante. + +/// + +{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *} + +/// tip | Dica + +A sintaxe `{"nome", "descrição"}` cria um `conjunto` com esses dois valores. + +É equivalente a `set(["nome", "descrição"])`. + +/// + +#### Usando `list`s em vez de `set`s + +Se você esquecer de usar um `set` e usar uma `lista` ou `tupla` em vez disso, o FastAPI ainda o converterá em um `set` e funcionará corretamente: + +{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} + +## Recapitulação + +Use o parâmetro `response_model` do *decorador de operação de rota* para definir modelos de resposta e, especialmente, para garantir que dados privados sejam filtrados. + +Use `response_model_exclude_unset` para retornar apenas os valores definidos explicitamente. diff --git a/docs/pt/docs/tutorial/response-status-code.md b/docs/pt/docs/tutorial/response-status-code.md index dc8e120485..48957f67ac 100644 --- a/docs/pt/docs/tutorial/response-status-code.md +++ b/docs/pt/docs/tutorial/response-status-code.md @@ -8,11 +8,9 @@ Da mesma forma que você pode especificar um modelo de resposta, você também p * `@app.delete()` * etc. -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} -/// note | "Nota" +/// 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. @@ -20,7 +18,7 @@ Observe que `status_code` é um parâmetro do método "decorador" (get, post, et O parâmetro `status_code` recebe um número com o código de status HTTP. -/// info | "Informação" +/// info | Informação `status_code` também pode receber um `IntEnum`, como o do Python `http.HTTPStatus`. @@ -33,7 +31,7 @@ Dessa forma: -/// note | "Nota" +/// note | Nota Alguns códigos de resposta (consulte a próxima seção) indicam que a resposta não possui um corpo. @@ -43,7 +41,7 @@ O FastAPI sabe disso e produzirá documentos OpenAPI informando que não há cor ## Sobre os códigos de status HTTP -/// note | "Nota" +/// note | Nota Se você já sabe o que são códigos de status HTTP, pule para a próxima seção. @@ -67,7 +65,7 @@ Resumidamente: * 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" +/// 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. @@ -77,9 +75,7 @@ Para saber mais sobre cada código de status e qual código serve para quê, ver Vamos ver o exemplo anterior novamente: -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} `201` é o código de status para "Criado". @@ -87,15 +83,13 @@ 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!} -``` +{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} 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" +/// note | Detalhes técnicos Você também pode usar `from starlette import status`. diff --git a/docs/pt/docs/tutorial/schema-extra-example.md b/docs/pt/docs/tutorial/schema-extra-example.md index a291db045b..5d3498d7de 100644 --- a/docs/pt/docs/tutorial/schema-extra-example.md +++ b/docs/pt/docs/tutorial/schema-extra-example.md @@ -8,13 +8,11 @@ Aqui estão várias formas de se fazer isso. Você pode declarar um `example` para um modelo Pydantic usando `Config` e `schema_extra`, conforme descrito em Documentação do Pydantic: Schema customization: -```Python hl_lines="15-23" -{!../../../docs_src/schema_extra_example/tutorial001.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial001.py hl[15:23] *} Essas informações extras serão adicionadas como se encontram no **JSON Schema** de resposta desse modelo e serão usadas na documentação da API. -/// tip | "Dica" +/// tip | Dica Você pode usar a mesma técnica para estender o JSON Schema e adicionar suas próprias informações extras de forma personalizada. @@ -28,11 +26,9 @@ Ao usar `Field ()` com modelos Pydantic, você também pode declarar informaçõ Você pode usar isso para adicionar um `example` para cada campo: -```Python hl_lines="4 10-13" -{!../../../docs_src/schema_extra_example/tutorial002.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial002.py hl[4,10:13] *} -/// warning | "Atenção" +/// warning | Atenção Lembre-se de que esses argumentos extras passados ​​não adicionarão nenhuma validação, apenas informações extras, para fins de documentação. @@ -56,9 +52,7 @@ você também pode declarar um dado `example` ou um grupo de `examples` com info Aqui nós passamos um `example` dos dados esperados por `Body()`: -```Python hl_lines="21-26" -{!../../../docs_src/schema_extra_example/tutorial003.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial003.py hl[21:26] *} ### Exemplo na UI da documentação @@ -79,9 +73,7 @@ Cada `dict` de exemplo específico em `examples` pode conter: * `value`: O próprio exemplo mostrado, ex: um `dict`. * `externalValue`: alternativa ao `value`, uma URL apontando para o exemplo. Embora isso possa não ser suportado por tantas ferramentas quanto `value`. -```Python hl_lines="22-48" -{!../../../docs_src/schema_extra_example/tutorial004.py!} -``` +{* ../../docs_src/schema_extra_example/tutorial004.py hl[22:48] *} ### Exemplos na UI da documentação @@ -91,7 +83,7 @@ Com `examples` adicionado a `Body()`, os `/docs` vão ficar assim: ## Detalhes técnicos -/// warning | "Atenção" +/// warning | Atenção Esses são detalhes muito técnicos sobre os padrões **JSON Schema** e **OpenAPI**. diff --git a/docs/pt/docs/tutorial/security/first-steps.md b/docs/pt/docs/tutorial/security/first-steps.md index 007fefcb96..f4dea8e14b 100644 --- a/docs/pt/docs/tutorial/security/first-steps.md +++ b/docs/pt/docs/tutorial/security/first-steps.md @@ -19,13 +19,11 @@ Vamos primeiro usar o código e ver como funciona, e depois voltaremos para ente ## Crie um `main.py` Copie o exemplo em um arquivo `main.py`: -```Python -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py *} ## Execute-o -/// info | "informação" +/// info | informação @@ -57,7 +55,7 @@ Você verá algo deste tipo: -/// check | "Botão de Autorizar!" +/// check | Botão de Autorizar! @@ -71,7 +69,7 @@ E se você clicar, você terá um pequeno formulário de autorização para digi -/// note | "Nota" +/// note | Nota @@ -119,7 +117,7 @@ Então, vamos rever de um ponto de vista simplificado: Neste exemplo, nós vamos usar o **OAuth2** com o fluxo de **Senha**, usando um token **Bearer**. Fazemos isso usando a classe `OAuth2PasswordBearer`. -/// info | "informação" +/// info | informação @@ -135,11 +133,9 @@ Neste exemplo, nós vamos usar o **OAuth2** com o fluxo de **Senha**, usando um 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!} -``` +{* ../../docs_src/security/tutorial001.py hl[6] *} -/// tip | "Dica" +/// tip | Dica @@ -155,7 +151,7 @@ Esse parâmetro não cria um endpoint / *path operation*, mas declara que a URL Em breve também criaremos o atual path operation. -/// info | "informação" +/// info | informação @@ -179,15 +175,13 @@ Então, pode ser usado com `Depends`. Agora você pode passar aquele `oauth2_scheme` em uma dependência com `Depends`. -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} 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). -/// info | "Detalhes técnicos" +/// info | Detalhes técnicos diff --git a/docs/pt/docs/tutorial/security/get-current-user.md b/docs/pt/docs/tutorial/security/get-current-user.md new file mode 100644 index 0000000000..1a2badb83b --- /dev/null +++ b/docs/pt/docs/tutorial/security/get-current-user.md @@ -0,0 +1,105 @@ +# Obter Usuário Atual + +No capítulo anterior, o sistema de segurança (que é baseado no sistema de injeção de dependências) estava fornecendo à *função de operação de rota* um `token` como uma `str`: + +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} + +Mas isso ainda não é tão útil. + +Vamos fazer com que ele nos forneça o usuário atual. + +## Criar um modelo de usuário + +Primeiro, vamos criar um modelo de usuário com Pydantic. + +Da mesma forma que usamos o Pydantic para declarar corpos, podemos usá-lo em qualquer outro lugar: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *} + +## Criar uma dependência `get_current_user` + +Vamos criar uma dependência chamada `get_current_user`. + +Lembra que as dependências podem ter subdependências? + +`get_current_user` terá uma dependência com o mesmo `oauth2_scheme` que criamos antes. + +Da mesma forma que estávamos fazendo antes diretamente na *operação de rota*, a nossa nova dependência `get_current_user` receberá um `token` como uma `str` da subdependência `oauth2_scheme`: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *} + +## Obter o usuário + +`get_current_user` usará uma função utilitária (falsa) que criamos, que recebe um token como uma `str` e retorna nosso modelo Pydantic `User`: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *} + +## Injetar o usuário atual + +Então agora nós podemos usar o mesmo `Depends` com nosso `get_current_user` na *operação de rota*: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} + +Observe que nós declaramos o tipo de `current_user` como o modelo Pydantic `User`. + +Isso nos ajudará dentro da função com todo o preenchimento automático e verificações de tipo. + +/// tip | Dica + +Você pode se lembrar que corpos de requisição também são declarados com modelos Pydantic. + +Aqui, o **FastAPI** não ficará confuso porque você está usando `Depends`. + +/// + +/// check | Verifique + +A forma como esse sistema de dependências foi projetado nos permite ter diferentes dependências (diferentes "dependables") que retornam um modelo `User`. + +Não estamos restritos a ter apenas uma dependência que possa retornar esse tipo de dado. + +/// + +## Outros modelos + +Agora você pode obter o usuário atual diretamente nas *funções de operação de rota* e lidar com os mecanismos de segurança no nível da **Injeção de Dependências**, usando `Depends`. + +E você pode usar qualquer modelo ou dado para os requisitos de segurança (neste caso, um modelo Pydantic `User`). + +Mas você não está restrito a usar um modelo de dados, classe ou tipo específico. + +Você quer ter apenas um `id` e `email`, sem incluir nenhum `username` no modelo? Claro. Você pode usar essas mesmas ferramentas. + +Você quer ter apenas uma `str`? Ou apenas um `dict`? Ou uma instância de modelo de classe de banco de dados diretamente? Tudo funciona da mesma forma. + +Na verdade, você não tem usuários que fazem login no seu aplicativo, mas sim robôs, bots ou outros sistemas, que possuem apenas um token de acesso? Novamente, tudo funciona da mesma forma. + +Apenas use qualquer tipo de modelo, qualquer tipo de classe, qualquer tipo de banco de dados que você precise para a sua aplicação. O **FastAPI** cobre tudo com o sistema de injeção de dependências. + +## Tamanho do código + +Este exemplo pode parecer verboso. Lembre-se de que estamos misturando segurança, modelos de dados, funções utilitárias e *operações de rota* no mesmo arquivo. + +Mas aqui está o ponto principal. + +O código relacionado à segurança e à injeção de dependências é escrito apenas uma vez. + +E você pode torná-lo tão complexo quanto quiser. E ainda assim, tê-lo escrito apenas uma vez, em um único lugar. Com toda a flexibilidade. + +Mas você pode ter milhares de endpoints (*operações de rota*) usando o mesmo sistema de segurança. + +E todos eles (ou qualquer parte deles que você desejar) podem aproveitar o reuso dessas dependências ou de quaisquer outras dependências que você criar. + +E todos esses milhares de *operações de rota* podem ter apenas 3 linhas: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} + +## Recapitulação + +Agora você pode obter o usuário atual diretamente na sua *função de operação de rota*. + +Já estamos na metade do caminho. + +Só precisamos adicionar uma *operação de rota* para que o usuário/cliente realmente envie o `username` e `password`. + +Isso vem a seguir. diff --git a/docs/pt/docs/tutorial/security/index.md b/docs/pt/docs/tutorial/security/index.md index 2f23aa47ee..2ebb87fcd8 100644 --- a/docs/pt/docs/tutorial/security/index.md +++ b/docs/pt/docs/tutorial/security/index.md @@ -22,7 +22,7 @@ Ela é bastante extensiva na especificação e cobre casos de uso muito complexo Ela inclui uma forma para autenticação usando “third party”/aplicações de terceiros. -Isso é o que todos os sistemas com “Login with Facebook, Google, Twitter, GitHub” usam por baixo. +Isso é o que todos os sistemas com “Login with Facebook, Google, X (Twitter), GitHub” usam por baixo. ### OAuth 1 @@ -32,7 +32,7 @@ Não é muito popular ou usado nos dias atuais. OAuth2 não especifica como criptografar a comunicação, ele espera que você tenha sua aplicação em um servidor HTTPS. -/// tip | "Dica" +/// tip | Dica Na seção sobre **deployment** você irá ver como configurar HTTPS de modo gratuito, usando Traefik e Let’s Encrypt. @@ -79,7 +79,7 @@ OpenAPI define os seguintes esquemas de segurança: * HTTP Basic authentication. * HTTP Digest, etc. * `oauth2`: todas as formas do OAuth2 para lidar com segurança (chamados "fluxos"). - * Vários desses fluxos são apropriados para construir um provedor de autenticação OAuth2 (como Google, Facebook, Twitter, GitHub, etc): + * Vários desses fluxos são apropriados para construir um provedor de autenticação OAuth2 (como Google, Facebook, X (Twitter), GitHub, etc): * `implicit` * `clientCredentials` * `authorizationCode` @@ -89,9 +89,9 @@ OpenAPI define os seguintes esquemas de segurança: * Essa descoberta automática é o que é definido na especificação OpenID Connect. -/// tip | "Dica" +/// tip | Dica -Integração com outros provedores de autenticação/autorização como Google, Facebook, Twitter, GitHub, etc. é bem possível e relativamente fácil. +Integração com outros provedores de autenticação/autorização como Google, Facebook, X (Twitter), GitHub, etc. é bem possível e relativamente fácil. O problema mais complexo é criar um provedor de autenticação/autorização como eles, mas o FastAPI dá a você ferramentas para fazer isso facilmente, enquanto faz o trabalho pesado para você. diff --git a/docs/pt/docs/tutorial/security/oauth2-jwt.md b/docs/pt/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 0000000000..7d80d12fab --- /dev/null +++ b/docs/pt/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,274 @@ +# OAuth2 com Senha (e hashing), Bearer com tokens JWT + +Agora que temos todo o fluxo de segurança, vamos tornar a aplicação realmente segura, usando tokens JWT e hashing de senhas seguras. + +Este código é algo que você pode realmente usar na sua aplicação, salvar os hashes das senhas no seu banco de dados, etc. + +Vamos começar de onde paramos no capítulo anterior e incrementá-lo. + +## Sobre o JWT + +JWT significa "JSON Web Tokens". + +É um padrão para codificar um objeto JSON em uma string longa e densa sem espaços. Ele se parece com isso: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +Ele não é criptografado, então qualquer pessoa pode recuperar as informações do seu conteúdo. + +Mas ele é assinado. Assim, quando você recebe um token que você emitiu, você pode verificar que foi realmente você quem o emitiu. + +Dessa forma, você pode criar um token com um prazo de expiração, digamos, de 1 semana. E então, quando o usuário voltar no dia seguinte com o token, você sabe que ele ainda está logado no seu sistema. + +Depois de uma semana, o token expirará e o usuário não estará autorizado, precisando fazer login novamente para obter um novo token. E se o usuário (ou uma terceira parte) tentar modificar o token para alterar a expiração, você seria capaz de descobrir isso, pois as assinaturas não iriam corresponder. + +Se você quiser brincar com tokens JWT e ver como eles funcionam, visite https://jwt.io. + +## Instalar `PyJWT` + +Nós precisamos instalar o `PyJWT` para criar e verificar os tokens JWT em Python. + +Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o `pyjwt`: + +
+ +```console +$ pip install pyjwt + +---> 100% +``` + +
+ +/// info | Informação + +Se você pretente utilizar algoritmos de assinatura digital como o RSA ou o ECDSA, você deve instalar a dependência da biblioteca de criptografia `pyjwt[crypto]`. + +Você pode ler mais sobre isso na documentação de instalação do PyJWT. + +/// + +## Hashing de senhas + +"Hashing" significa converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece um monte de caracteres sem sentido. + +Sempre que você passar exatamente o mesmo conteúdo (exatamente a mesma senha), você obterá exatamente o mesmo resultado. + +Mas não é possível converter os caracteres sem sentido de volta para a senha original. + +### Por que usar hashing de senhas + +Se o seu banco de dados for roubado, o invasor não terá as senhas em texto puro dos seus usuários, apenas os hashes. + +Então, o invasor não poderá tentar usar essas senhas em outro sistema (como muitos usuários utilizam a mesma senha em vários lugares, isso seria perigoso). + +## Instalar o `passlib` + +O PassLib é uma excelente biblioteca Python para lidar com hashes de senhas. + +Ele suporta muitos algoritmos de hashing seguros e utilitários para trabalhar com eles. + +O algoritmo recomendado é o "Bcrypt". + +Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o PassLib com Bcrypt: + +
+ +```console +$ pip install "passlib[bcrypt]" + +---> 100% +``` + +
+ +/// tip | Dica + +Com o `passlib`, você poderia até configurá-lo para ser capaz de ler senhas criadas pelo **Django**, um plug-in de segurança do **Flask** ou muitos outros. + +Assim, você poderia, por exemplo, compartilhar os mesmos dados de um aplicativo Django em um banco de dados com um aplicativo FastAPI. Ou migrar gradualmente uma aplicação Django usando o mesmo banco de dados. + +E seus usuários poderiam fazer login tanto pela sua aplicação Django quanto pela sua aplicação **FastAPI**, ao mesmo tempo. + +/// + +## Criar o hash e verificar as senhas + +Importe as ferramentas que nós precisamos de `passlib`. + +Crie um "contexto" do PassLib. Este será usado para criar o hash e verificar as senhas. + +/// tip | Dica + +O contexto do PassLib também possui funcionalidades para usar diferentes algoritmos de hashing, incluindo algoritmos antigos que estão obsoletos, apenas para permitir verificá-los, etc. + +Por exemplo, você poderia usá-lo para ler e verificar senhas geradas por outro sistema (como Django), mas criar o hash de novas senhas com um algoritmo diferente, como o Bcrypt. + +E ser compatível com todos eles ao mesmo tempo. + +/// + +Crie uma função utilitária para criar o hash de uma senha fornecida pelo usuário. + +E outra função utilitária para verificar se uma senha recebida corresponde ao hash armazenado. + +E outra para autenticar e retornar um usuário. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} + +/// note | Nota + +Se você verificar o novo banco de dados (falso) `fake_users_db`, você verá como o hash da senha se parece agora: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. + +/// + +## Manipular tokens JWT + +Importe os módulos instalados. + +Crie uma chave secreta aleatória que será usada para assinar os tokens JWT. + +Para gerar uma chave secreta aleatória e segura, use o comando: + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +E copie a saída para a variável `SECRET_KEY` (não use a do exemplo). + +Crie uma variável `ALGORITHM` com o algoritmo usado para assinar o token JWT e defina como `"HS256"`. + +Crie uma variável para a expiração do token. + +Defina um modelo Pydantic que será usado no endpoint de token para a resposta. + +Crie uma função utilitária para gerar um novo token de acesso. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} + +## Atualize as dependências + +Atualize `get_current_user` para receber o mesmo token de antes, mas desta vez, usando tokens JWT. + +Decodifique o token recebido, verifique-o e retorne o usuário atual. + +Se o token for inválido, retorne um erro HTTP imediatamente. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} + +## Atualize a *operação de rota* `/token` + +Crie um `timedelta` com o tempo de expiração do token. + +Crie um token de acesso JWT real e o retorne. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} + +### Detalhes técnicos sobre o "sujeito" `sub` do JWT + +A especificação JWT diz que existe uma chave `sub`, com o sujeito do token. + +É opcional usá-la, mas é onde você colocaria a identificação do usuário, então nós estamos usando aqui. + +O JWT pode ser usado para outras coisas além de identificar um usuário e permitir que ele execute operações diretamente na sua API. + +Por exemplo, você poderia identificar um "carro" ou uma "postagem de blog". + +Depois, você poderia adicionar permissões sobre essa entidade, como "dirigir" (para o carro) ou "editar" (para o blog). + +E então, poderia dar esse token JWT para um usuário (ou bot), e ele poderia usá-lo para realizar essas ações (dirigir o carro ou editar o blog) sem sequer precisar ter uma conta, apenas com o token JWT que sua API gerou para isso. + +Usando essas ideias, o JWT pode ser usado para cenários muito mais sofisticados. + +Nesses casos, várias dessas entidades poderiam ter o mesmo ID, digamos `foo` (um usuário `foo`, um carro `foo` e uma postagem de blog `foo`). + +Então, para evitar colisões de ID, ao criar o token JWT para o usuário, você poderia prefixar o valor da chave `sub`, por exemplo, com `username:`. Assim, neste exemplo, o valor de `sub` poderia ser: `username:johndoe`. + +O importante a se lembrar é que a chave `sub` deve ter um identificador único em toda a aplicação e deve ser uma string. + +## Testando + +Execute o servidor e vá para a documentação: http://127.0.0.1:8000/docs. + +Você verá a interface de usuário assim: + + + +Autorize a aplicação da mesma maneira que antes. + +Usando as credenciais: + +Username: `johndoe` +Password: `secret` + +/// check | Verifique + +Observe que em nenhuma parte do código está a senha em texto puro "`secret`", nós temos apenas o hash. + +/// + + + +Chame o endpoint `/users/me/`, você receberá o retorno como: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +Se você abrir as ferramentas de desenvolvedor, poderá ver que os dados enviados incluem apenas o token. A senha é enviada apenas na primeira requisição para autenticar o usuário e obter o token de acesso, mas não é enviada nas próximas requisições: + + + +/// note | Nota + +Perceba que o cabeçalho `Authorization`, com o valor que começa com `Bearer `. + +/// + +## Uso avançado com `scopes` + +O OAuth2 tem a noção de "scopes" (escopos). + +Você pode usá-los para adicionar um conjunto específico de permissões a um token JWT. + +Então, você pode dar este token diretamente a um usuário ou a uma terceira parte para interagir com sua API com um conjunto de restrições. + +Você pode aprender como usá-los e como eles são integrados ao **FastAPI** mais adiante no **Guia Avançado do Usuário**. + + +## Recapitulação + +Com o que você viu até agora, você pode configurar uma aplicação **FastAPI** segura usando padrões como OAuth2 e JWT. + +Em quase qualquer framework, lidar com a segurança se torna rapidamente um assunto bastante complexo. + +Muitos pacotes que simplificam bastante isso precisam fazer muitas concessões com o modelo de dados, o banco de dados e os recursos disponíveis. E alguns desses pacotes que simplificam demais na verdade têm falhas de segurança subjacentes. + +--- + +O **FastAPI** não faz nenhuma concessão com nenhum banco de dados, modelo de dados ou ferramenta. + +Ele oferece toda a flexibilidade para você escolher as opções que melhor se ajustam ao seu projeto. + +E você pode usar diretamente muitos pacotes bem mantidos e amplamente utilizados, como `passlib` e `PyJWT`, porque o **FastAPI** não exige mecanismos complexos para integrar pacotes externos. + +Mas ele fornece as ferramentas para simplificar o processo o máximo possível, sem comprometer a flexibilidade, robustez ou segurança. + +E você pode usar e implementar protocolos padrão seguros, como o OAuth2, de uma maneira relativamente simples. + +Você pode aprender mais no **Guia Avançado do Usuário** sobre como usar os "scopes" do OAuth2 para um sistema de permissões mais refinado, seguindo esses mesmos padrões. O OAuth2 com scopes é o mecanismo usado por muitos provedores grandes de autenticação, como o Facebook, Google, GitHub, Microsoft, X (Twitter), etc. para autorizar aplicativos de terceiros a interagir com suas APIs em nome de seus usuários. diff --git a/docs/pt/docs/tutorial/security/simple-oauth2.md b/docs/pt/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 0000000000..1cf05785ee --- /dev/null +++ b/docs/pt/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,289 @@ +# Simples OAuth2 com senha e Bearer + +Agora vamos construir a partir do capítulo anterior e adicionar as partes que faltam para ter um fluxo de segurança completo. + +## Pegue o `username` (nome de usuário) e `password` (senha) + +É utilizado o utils de segurança da **FastAPI** para obter o `username` e a `password`. + +OAuth2 especifica que ao usar o "password flow" (fluxo de senha), que estamos usando, o cliente/usuário deve enviar os campos `username` e `password` como dados do formulário. + +E a especificação diz que os campos devem ser nomeados assim. Portanto, `user-name` ou `email` não funcionariam. + +Mas não se preocupe, você pode mostrá-lo como quiser aos usuários finais no frontend. + +E seus modelos de banco de dados podem usar qualquer outro nome que você desejar. + +Mas para a *operação de rota* de login, precisamos usar esses nomes para serem compatíveis com a especificação (e poder, por exemplo, usar o sistema integrado de documentação da API). + +A especificação também afirma que o `username` e a `password` devem ser enviados como dados de formulário (portanto, não há JSON aqui). + +### `scope` + +A especificação também diz que o cliente pode enviar outro campo de formulário "`scope`" (Escopo). + +O nome do campo do formulário é `scope` (no singular), mas na verdade é uma longa string com "escopos" separados por espaços. + +Cada “scope” é apenas uma string (sem espaços). + +Normalmente são usados para declarar permissões de segurança específicas, por exemplo: + +* `users:read` ou `users:write` são exemplos comuns. +* `instagram_basic` é usado pelo Facebook e Instagram. +* `https://www.googleapis.com/auth/drive` é usado pelo Google. + +/// info | Informação + +No OAuth2, um "scope" é apenas uma string que declara uma permissão específica necessária. + +Não importa se tem outros caracteres como `:` ou se é uma URL. + +Esses detalhes são específicos da implementação. + +Para OAuth2 são apenas strings. + +/// + +## Código para conseguir o `username` e a `password` + +Agora vamos usar os utilitários fornecidos pelo **FastAPI** para lidar com isso. + +### `OAuth2PasswordRequestForm` + +Primeiro, importe `OAuth2PasswordRequestForm` e use-o como uma dependência com `Depends` na *operação de rota* para `/token`: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} + +`OAuth2PasswordRequestForm` é uma dependência de classe que declara um corpo de formulário com: + +* O `username`. +* A `password`. +* Um campo `scope` opcional como uma string grande, composta de strings separadas por espaços. +* Um `grant_type` (tipo de concessão) opcional. + +/// tip | Dica + +A especificação OAuth2 na verdade *requer* um campo `grant_type` com um valor fixo de `password`, mas `OAuth2PasswordRequestForm` não o impõe. + +Se você precisar aplicá-lo, use `OAuth2PasswordRequestFormStrict` em vez de `OAuth2PasswordRequestForm`. + +/// + +* Um `client_id` opcional (não precisamos dele em nosso exemplo). +* Um `client_secret` opcional (não precisamos dele em nosso exemplo). + +/// info | Informação + +O `OAuth2PasswordRequestForm` não é uma classe especial para **FastAPI** como é `OAuth2PasswordBearer`. + +`OAuth2PasswordBearer` faz com que **FastAPI** saiba que é um esquema de segurança. Portanto, é adicionado dessa forma ao OpenAPI. + +Mas `OAuth2PasswordRequestForm` é apenas uma dependência de classe que você mesmo poderia ter escrito ou poderia ter declarado os parâmetros do `Form` (formulário) diretamente. + +Mas como é um caso de uso comum, ele é fornecido diretamente pelo **FastAPI**, apenas para facilitar. + +/// + +### Use os dados do formulário + +/// tip | Dica + +A instância da classe de dependência `OAuth2PasswordRequestForm` não terá um atributo `scope` com a string longa separada por espaços, em vez disso, terá um atributo `scopes` com a lista real de strings para cada escopo enviado. + +Não estamos usando `scopes` neste exemplo, mas a funcionalidade está disponível se você precisar. + +/// + +Agora, obtenha os dados do usuário do banco de dados (falso), usando o `username` do campo do formulário. + +Se não existir tal usuário, retornaremos um erro dizendo "Incorrect username or password" (Nome de usuário ou senha incorretos). + +Para o erro, usamos a exceção `HTTPException`: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} + +### Confira a password (senha) + +Neste ponto temos os dados do usuário do nosso banco de dados, mas não verificamos a senha. + +Vamos colocar esses dados primeiro no modelo `UserInDB` do Pydantic. + +Você nunca deve salvar senhas em texto simples, portanto, usaremos o sistema de hashing de senhas (falsas). + +Se as senhas não corresponderem, retornaremos o mesmo erro. + +#### Hashing de senha + +"Hashing" significa: converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece algo sem sentido. + +Sempre que você passa exatamente o mesmo conteúdo (exatamente a mesma senha), você obtém exatamente a mesma sequência aleatória de caracteres. + +Mas você não pode converter a sequência aleatória de caracteres de volta para a senha. + +##### Porque usar hashing de senha + +Se o seu banco de dados for roubado, o ladrão não terá as senhas em texto simples dos seus usuários, apenas os hashes. + +Assim, o ladrão não poderá tentar usar essas mesmas senhas em outro sistema (como muitos usuários usam a mesma senha em todos os lugares, isso seria perigoso). + +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} + +#### Sobre `**user_dict` + +`UserInDB(**user_dict)` significa: + +*Passe as keys (chaves) e values (valores) de `user_dict` diretamente como argumentos de valor-chave, equivalente a:* + +```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 | Informação + +Para uma explicação mais completa de `**user_dict`, verifique [a documentação para **Extra Models**](../extra-models.md#about-user_indict){.internal-link target=_blank}. + +/// + +## Retorne o token + +A resposta do endpoint `token` deve ser um objeto JSON. + +Deve ter um `token_type`. No nosso caso, como estamos usando tokens "Bearer", o tipo de token deve ser "`bearer`". + +E deve ter um `access_token`, com uma string contendo nosso token de acesso. + +Para este exemplo simples, seremos completamente inseguros e retornaremos o mesmo `username` do token. + +/// tip | Dica + +No próximo capítulo, você verá uma implementação realmente segura, com hash de senha e tokens JWT. + +Mas, por enquanto, vamos nos concentrar nos detalhes específicos de que precisamos. + +/// + +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} + +/// tip | Dica + +Pela especificação, você deve retornar um JSON com um `access_token` e um `token_type`, o mesmo que neste exemplo. + +Isso é algo que você mesmo deve fazer em seu código e certifique-se de usar essas chaves JSON. + +É quase a única coisa que você deve se lembrar de fazer corretamente, para estar em conformidade com as especificações. + +De resto, **FastAPI** cuida disso para você. + +/// + +## Atualize as dependências + +Agora vamos atualizar nossas dependências. + +Queremos obter o `user_user` *somente* se este usuário estiver ativo. + +Portanto, criamos uma dependência adicional `get_current_active_user` que por sua vez usa `get_current_user` como dependência. + +Ambas as dependências retornarão apenas um erro HTTP se o usuário não existir ou se estiver inativo. + +Portanto, em nosso endpoint, só obteremos um usuário se o usuário existir, tiver sido autenticado corretamente e estiver ativo: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} + +/// info | Informação + +O cabeçalho adicional `WWW-Authenticate` com valor `Bearer` que estamos retornando aqui também faz parte da especificação. + +Qualquer código de status HTTP (erro) 401 "UNAUTHORIZED" também deve retornar um cabeçalho `WWW-Authenticate`. + +No caso de tokens ao portador (nosso caso), o valor desse cabeçalho deve ser `Bearer`. + +Na verdade, você pode pular esse cabeçalho extra e ainda funcionaria. + +Mas é fornecido aqui para estar em conformidade com as especificações. + +Além disso, pode haver ferramentas que esperam e usam isso (agora ou no futuro) e que podem ser úteis para você ou seus usuários, agora ou no futuro. + +Esse é o benefício dos padrões... + +/// + +## Veja em ação + +Abra o docs interativo: http://127.0.0.1:8000/docs. + +### Autenticação + +Clique no botão "Authorize". + +Use as credenciais: + +User: `johndoe` + +Password: `secret` + + + +Após autenticar no sistema, você verá assim: + + + +### Obtenha seus próprios dados de usuário + +Agora use a operação `GET` com o caminho `/users/me`. + +Você obterá os dados do seu usuário, como: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +Se você clicar no ícone de cadeado, sair e tentar a mesma operação novamente, receberá um erro HTTP 401 de: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### Usuário inativo + +Agora tente com um usuário inativo, autentique-se com: + +User: `alice` + +Password: `secret2` + +E tente usar a operação `GET` com o caminho `/users/me`. + +Você receberá um erro "Usuário inativo", como: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## Recaptulando + +Agora você tem as ferramentas para implementar um sistema de segurança completo baseado em `username` e `password` para sua API. + +Usando essas ferramentas, você pode tornar o sistema de segurança compatível com qualquer banco de dados e com qualquer usuário ou modelo de dados. + +O único detalhe que falta é que ainda não é realmente "seguro". + +No próximo capítulo você verá como usar uma biblioteca de hashing de senha segura e tokens JWT. diff --git a/docs/pt/docs/tutorial/sql-databases.md b/docs/pt/docs/tutorial/sql-databases.md new file mode 100644 index 0000000000..3d76a532cc --- /dev/null +++ b/docs/pt/docs/tutorial/sql-databases.md @@ -0,0 +1,359 @@ +# Bancos de Dados SQL (Relacionais) + +**FastAPI** não exige que você use um banco de dados SQL (relacional). Mas você pode usar **qualquer banco de dados** que quiser. + +Aqui veremos um exemplo usando SQLModel. + +**SQLModel** é construído sobre SQLAlchemy e Pydantic. Ele foi criado pelo mesmo autor do **FastAPI** para ser o par perfeito para aplicações **FastAPI** que precisam usar **bancos de dados SQL**. + +/// tip | Dica + +Você pode usar qualquer outra biblioteca de banco de dados SQL ou NoSQL que quiser (em alguns casos chamadas de "ORMs"), o FastAPI não obriga você a usar nada. 😎 + +/// + +Como o SQLModel é baseado no SQLAlchemy, você pode facilmente usar **qualquer banco de dados suportado** pelo SQLAlchemy (o que também os torna suportados pelo SQLModel), como: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server, etc. + +Neste exemplo, usaremos **SQLite**, porque ele usa um único arquivo e o Python tem suporte integrado. Assim, você pode copiar este exemplo e executá-lo como está. + +Mais tarde, para sua aplicação em produção, você pode querer usar um servidor de banco de dados como o **PostgreSQL**. + +/// tip | Dica + +Existe um gerador de projetos oficial com **FastAPI** e **PostgreSQL** incluindo um frontend e mais ferramentas: https://github.com/fastapi/full-stack-fastapi-template + +/// + +Este é um tutorial muito simples e curto, se você quiser aprender sobre bancos de dados em geral, sobre SQL ou recursos mais avançados, acesse a documentação do SQLModel. + +## Instalar o `SQLModel` + +Primeiro, certifique-se de criar seu [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e, em seguida, instalar o `sqlmodel`: + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## Criar o App com um Único Modelo + +Vamos criar a primeira versão mais simples do app com um único modelo **SQLModel**. + +Depois, vamos melhorá-lo aumentando a segurança e versatilidade com **múltiplos modelos** abaixo. 🤓 + +### Criar Modelos + +Importe o `SQLModel` e crie um modelo de banco de dados: + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +A classe `Hero` é muito semelhante a um modelo Pydantic (na verdade, por baixo dos panos, ela *é um modelo Pydantic*). + +Existem algumas diferenças: + +* `table=True` informa ao SQLModel que este é um *modelo de tabela*, ele deve representar uma **tabela** no banco de dados SQL, não é apenas um *modelo de dados* (como seria qualquer outra classe Pydantic comum). + +* `Field(primary_key=True)` informa ao SQLModel que o `id` é a **chave primária** no banco de dados SQL (você pode aprender mais sobre chaves primárias SQL na documentação do SQLModel). + + Ao ter o tipo como `int | None`, o SQLModel saberá que essa coluna deve ser um `INTEGER` no banco de dados SQL e que ela deve ser `NULLABLE`. + +* `Field(index=True)` informa ao SQLModel que ele deve criar um **índice SQL** para essa coluna, o que permitirá buscas mais rápidas no banco de dados ao ler dados filtrados por essa coluna. + + O SQLModel saberá que algo declarado como `str` será uma coluna SQL do tipo `TEXT` (ou `VARCHAR`, dependendo do banco de dados). + +### Criar um Engine +Um `engine` SQLModel (por baixo dos panos, ele é na verdade um `engine` do SQLAlchemy) é o que **mantém as conexões** com o banco de dados. + +Você teria **um único objeto `engine`** para todo o seu código se conectar ao mesmo banco de dados. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +Usar `check_same_thread=False` permite que o FastAPI use o mesmo banco de dados SQLite em diferentes threads. Isso é necessário, pois **uma única requisição** pode usar **mais de uma thread** (por exemplo, em dependências). + +Não se preocupe, com a forma como o código está estruturado, garantiremos que usamos **uma única *sessão* SQLModel por requisição** mais tarde, isso é realmente o que o `check_same_thread` está tentando conseguir. + +### Criar as Tabelas + +Em seguida, adicionamos uma função que usa `SQLModel.metadata.create_all(engine)` para **criar as tabelas** para todos os *modelos de tabela*. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### Criar uma Dependência de Sessão + +Uma **`Session`** é o que armazena os **objetos na memória** e acompanha as alterações necessárias nos dados, para então **usar o `engine`** para se comunicar com o banco de dados. + +Vamos criar uma **dependência** do FastAPI com `yield` que fornecerá uma nova `Session` para cada requisição. Isso é o que garante que usamos uma única sessão por requisição. 🤓 + +Então, criamos uma dependência `Annotated` chamada `SessionDep` para simplificar o restante do código que usará essa dependência. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### Criar Tabelas de Banco de Dados na Inicialização + +Vamos criar as tabelas do banco de dados quando o aplicativo for iniciado. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +Aqui, criamos as tabelas em um evento de inicialização do aplicativo. + +Para produção, você provavelmente usaria um script de migração que é executado antes de iniciar seu app. 🤓 + +/// tip | Dica + +O SQLModel terá utilitários de migração envolvendo o Alembic, mas por enquanto, você pode usar o Alembic diretamente. + +/// + +### Criar um Hero + +Como cada modelo SQLModel também é um modelo Pydantic, você pode usá-lo nas mesmas **anotações de tipo** que usaria para modelos Pydantic. + +Por exemplo, se você declarar um parâmetro do tipo `Hero`, ele será lido do **corpo JSON**. + +Da mesma forma, você pode declará-lo como o **tipo de retorno** da função, e então o formato dos dados aparecerá na interface de documentação automática da API. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + + + +Aqui, usamos a dependência `SessionDep` (uma `Session`) para adicionar o novo `Hero` à instância `Session`, fazer commit das alterações no banco de dados, atualizar os dados no `hero` e então retorná-lo. + +### Ler Heroes + +Podemos **ler** `Hero`s do banco de dados usando um `select()`. Podemos incluir um `limit` e `offset` para paginar os resultados. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### Ler um Único Hero + +Podemos **ler** um único `Hero`. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### Deletar um Hero + +Também podemos **deletar** um `Hero`. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### Executar o App + +Você pode executar o app: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Então, vá para a interface `/docs`, você verá que o **FastAPI** está usando esses **modelos** para **documentar** a API, e ele também os usará para **serializar** e **validar** os dados. + +
+ +
+ +## Atualizar o App com Múltiplos Modelos + +Agora vamos **refatorar** este app um pouco para aumentar a **segurança** e **versatilidade**. + +Se você verificar o app anterior, na interface você pode ver que, até agora, ele permite que o cliente decida o `id` do `Hero` a ser criado. 😱 + +Não deveríamos deixar isso acontecer, eles poderiam sobrescrever um `id` que já atribuimos na base de dados. Decidir o `id` deve ser feito pelo **backend** ou pelo **banco de dados**, **não pelo cliente**. + +Além disso, criamos um `secret_name` para o hero, mas até agora estamos retornando ele em todos os lugares, isso não é muito **secreto**... 😅 + +Vamos corrigir essas coisas adicionando alguns **modelos extras**. Aqui é onde o SQLModel vai brilhar. ✨ + +### Criar Múltiplos Modelos + +No **SQLModel**, qualquer classe de modelo que tenha `table=True` é um **modelo de tabela**. + +E qualquer classe de modelo que não tenha `table=True` é um **modelo de dados**, esses são na verdade apenas modelos Pydantic (com alguns recursos extras pequenos). 🤓 + +Com o SQLModel, podemos usar a **herança** para **evitar duplicação** de todos os campos em todos os casos. + +#### `HeroBase` - a classe base + +Vamos começar com um modelo `HeroBase` que tem todos os **campos compartilhados** por todos os modelos: + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` - o *modelo de tabela* + +Em seguida, vamos criar `Hero`, o verdadeiro *modelo de tabela*, com os **campos extras** que nem sempre estão nos outros modelos: + +* `id` +* `secret_name` + +Como `Hero` herda de `HeroBase`, ele **também** tem os **campos** declarados em `HeroBase`, então todos os campos para `Hero` são: + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` - o *modelo de dados* público + +Em seguida, criamos um modelo `HeroPublic`, que será **retornado** para os clientes da API. + +Ele tem os mesmos campos que `HeroBase`, então não incluirá `secret_name`. + +Finalmente, a identidade dos nossos heróis está protegida! 🥷 + +Ele também declara novamente `id: int`. Ao fazer isso, estamos fazendo um **contrato** com os clientes da API, para que eles possam sempre esperar que o `id` estará lá e será um `int` (nunca será `None`). + +/// tip | Dica + +Fazer com que o modelo de retorno garanta que um valor esteja sempre disponível e sempre seja um `int` (não `None`) é muito útil para os clientes da API, eles podem escrever código muito mais simples com essa certeza. + +Além disso, **clientes gerados automaticamente** terão interfaces mais simples, para que os desenvolvedores que se comunicam com sua API possam ter uma experiência muito melhor trabalhando com sua API. 😎 + +/// + +Todos os campos em `HeroPublic` são os mesmos que em `HeroBase`, com `id` declarado como `int` (não `None`): + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - o *modelo de dados* para criar um hero + +Agora criamos um modelo `HeroCreate`, este é o que **validará** os dados dos clientes. + +Ele tem os mesmos campos que `HeroBase`, e também tem `secret_name`. + +Agora, quando os clientes **criarem um novo hero**, eles enviarão o `secret_name`, ele será armazenado no banco de dados, mas esses nomes secretos não serão retornados na API para os clientes. + +/// tip | Dica + +É assim que você trataria **senhas**. Receba-as, mas não as retorne na API. + +Você também faria um **hash** com os valores das senhas antes de armazená-los, **nunca os armazene em texto simples**. + +/// + +Os campos de `HeroCreate` são: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - o *modelo de dados* para atualizar um hero + +Não tínhamos uma maneira de **atualizar um hero** na versão anterior do app, mas agora com **múltiplos modelos**, podemos fazer isso. 🎉 + +O *modelo de dados* `HeroUpdate` é um pouco especial, ele tem **todos os mesmos campos** que seriam necessários para criar um novo hero, mas todos os campos são **opcionais** (todos têm um valor padrão). Dessa forma, quando você atualizar um hero, poderá enviar apenas os campos que deseja atualizar. + +Como todos os **campos realmente mudam** (o tipo agora inclui `None` e eles agora têm um valor padrão de `None`), precisamos **declarar novamente** todos eles. + +Não precisamos herdar de `HeroBase`, pois estamos redeclarando todos os campos. Vou deixá-lo herdando apenas por consistência, mas isso não é necessário. É mais uma questão de gosto pessoal. 🤷 + +Os campos de `HeroUpdate` são: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### Criar com `HeroCreate` e retornar um `HeroPublic` + +Agora que temos **múltiplos modelos**, podemos atualizar as partes do app que os utilizam. + +Recebemos na requisição um *modelo de dados* `HeroCreate`, e a partir dele, criamos um *modelo de tabela* `Hero`. + +Esse novo *modelo de tabela* `Hero` terá os campos enviados pelo cliente, e também terá um `id` gerado pelo banco de dados. + +Em seguida, retornamos o mesmo *modelo de tabela* `Hero` como está na função. Mas como declaramos o `response_model` com o *modelo de dados* `HeroPublic`, o **FastAPI** usará `HeroPublic` para validar e serializar os dados. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | Dica + +Agora usamos `response_model=HeroPublic` em vez da **anotação de tipo de retorno** `-> HeroPublic` porque o valor que estamos retornando na verdade *não* é um `HeroPublic`. + +Se tivéssemos declarado `-> HeroPublic`, seu editor e o linter reclamariam (com razão) que você está retornando um `Hero` em vez de um `HeroPublic`. + +Ao declará-lo no `response_model`, estamos dizendo ao **FastAPI** para fazer o seu trabalho, sem interferir nas anotações de tipo e na ajuda do seu editor e de outras ferramentas. + +/// + +### Ler Heroes com `HeroPublic` + +Podemos fazer o mesmo que antes para **ler** `Hero`s, novamente, usamos `response_model=list[HeroPublic]` para garantir que os dados sejam validados e serializados corretamente. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### Ler Um Hero com `HeroPublic` + +Podemos **ler** um único herói: + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### Atualizar um Hero com `HeroUpdate` + +Podemos **atualizar um hero**. Para isso, usamos uma operação HTTP `PATCH`. + +E no código, obtemos um `dict` com todos os dados enviados pelo cliente, **apenas os dados enviados pelo cliente**, excluindo quaisquer valores que estariam lá apenas por serem os valores padrão. Para fazer isso, usamos `exclude_unset=True`. Este é o truque principal. 🪄 + +Em seguida, usamos `hero_db.sqlmodel_update(hero_data)` para atualizar o `hero_db` com os dados de `hero_data`. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### Deletar um Hero Novamente + +**Deletar** um hero permanece praticamente o mesmo. + +Não vamos satisfazer o desejo de refatorar tudo neste aqui. 😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### Executar o App Novamente + +Você pode executar o app novamente: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +If you go to the `/docs` API UI, you will see that it is now updated, and it won't expect to receive the `id` from the client when creating a hero, etc. + +
+ +
+ +## Recapitulando + +Você pode usar **SQLModel** para interagir com um banco de dados SQL e simplificar o código com *modelos de dados* e *modelos de tabela*. + +Você pode aprender muito mais na documentação do **SQLModel**, há um mini tutorial sobre como usar SQLModel com **FastAPI** mais longo. 🚀 diff --git a/docs/pt/docs/tutorial/static-files.md b/docs/pt/docs/tutorial/static-files.md index efaf07dfbb..30e1af8e64 100644 --- a/docs/pt/docs/tutorial/static-files.md +++ b/docs/pt/docs/tutorial/static-files.md @@ -7,11 +7,9 @@ Você pode servir arquivos estáticos automaticamente de um diretório usando `S * Importe `StaticFiles`. * "Monte" uma instância de `StaticFiles()` em um caminho específico. -```Python hl_lines="2 6" -{!../../../docs_src/static_files/tutorial001.py!} -``` +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} -/// note | "Detalhes técnicos" +/// note | Detalhes técnicos Você também pode usar `from starlette.staticfiles import StaticFiles`. @@ -39,4 +37,4 @@ Todos esses parâmetros podem ser diferentes de "`static`", ajuste-os de acordo ## Mais informações -Para mais detalhes e opções, verifique Starlette's docs about Static Files. +Para mais detalhes e opções, verifique Starlette's docs about Static Files. diff --git a/docs/pt/docs/tutorial/testing.md b/docs/pt/docs/tutorial/testing.md new file mode 100644 index 0000000000..dc505105ac --- /dev/null +++ b/docs/pt/docs/tutorial/testing.md @@ -0,0 +1,241 @@ +# Testando + +Graças ao Starlette, testar aplicativos **FastAPI** é fácil e agradável. + +Ele é baseado no HTTPX, que por sua vez é projetado com base em Requests, por isso é muito familiar e intuitivo. + +Com ele, você pode usar o pytest diretamente com **FastAPI**. + +## Usando `TestClient` + +/// info | Informação + +Para usar o `TestClient`, primeiro instale o `httpx`. + +Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e instalá-lo, por exemplo: + +```console +$ pip install httpx +``` + +/// + +Importe `TestClient`. + +Crie um `TestClient` passando seu aplicativo **FastAPI** para ele. + +Crie funções com um nome que comece com `test_` (essa é a convenção padrão do `pytest`). + +Use o objeto `TestClient` da mesma forma que você faz com `httpx`. + +Escreva instruções `assert` simples com as expressões Python padrão que você precisa verificar (novamente, `pytest` padrão). + +{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} + +/// tip | Dica + +Observe que as funções de teste são `def` normais, não `async def`. + +E as chamadas para o cliente também são chamadas normais, não usando `await`. + +Isso permite que você use `pytest` diretamente sem complicações. + +/// + +/// note | Detalhes técnicos + +Você também pode usar `from starlette.testclient import TestClient`. + +**FastAPI** fornece o mesmo `starlette.testclient` que `fastapi.testclient` apenas como uma conveniência para você, o desenvolvedor. Mas ele vem diretamente da Starlette. + +/// + +/// tip | Dica + +Se você quiser chamar funções `async` em seus testes além de enviar solicitações ao seu aplicativo FastAPI (por exemplo, funções de banco de dados assíncronas), dê uma olhada em [Testes assíncronos](../advanced/async-tests.md){.internal-link target=_blank} no tutorial avançado. + +/// + +## Separando testes + +Em uma aplicação real, você provavelmente teria seus testes em um arquivo diferente. + +E seu aplicativo **FastAPI** também pode ser composto de vários arquivos/módulos, etc. + +### Arquivo do aplicativo **FastAPI** + +Digamos que você tenha uma estrutura de arquivo conforme descrito em [Aplicativos maiores](bigger-applications.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +No arquivo `main.py` você tem seu aplicativo **FastAPI**: + + +{* ../../docs_src/app_testing/main.py *} + +### Arquivo de teste + +Então você poderia ter um arquivo `test_main.py` com seus testes. Ele poderia estar no mesmo pacote Python (o mesmo diretório com um arquivo `__init__.py`): + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Como esse arquivo está no mesmo pacote, você pode usar importações relativas para importar o objeto `app` do módulo `main` (`main.py`): + +{* ../../docs_src/app_testing/test_main.py hl[3] *} + +...e ter o código para os testes como antes. + +## Testando: exemplo estendido + +Agora vamos estender este exemplo e adicionar mais detalhes para ver como testar diferentes partes. + +### Arquivo de aplicativo **FastAPI** estendido + +Vamos continuar com a mesma estrutura de arquivo de antes: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Digamos que agora o arquivo `main.py` com seu aplicativo **FastAPI** tenha algumas outras **operações de rotas**. + +Ele tem uma operação `GET` que pode retornar um erro. + +Ele tem uma operação `POST` que pode retornar vários erros. + +Ambas as *operações de rotas* requerem um cabeçalho `X-Token`. + +//// tab | Python 3.10+ + +```Python +{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python +{!> ../../docs_src/app_testing/app_b_an/main.py!} +``` + +//// + +//// tab | Python 3.10+ non-Annotated + +/// tip | Dica + +Prefira usar a versão `Annotated` se possível. + +/// + +```Python +{!> ../../docs_src/app_testing/app_b_py310/main.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip | Dica + +Prefira usar a versão `Annotated` se possível. + +/// + +```Python +{!> ../../docs_src/app_testing/app_b/main.py!} +``` + +//// + +### Arquivo de teste estendido + +Você pode então atualizar `test_main.py` com os testes estendidos: + +{* ../../docs_src/app_testing/app_b/test_main.py *} + +Sempre que você precisar que o cliente passe informações na requisição e não souber como, você pode pesquisar (no Google) como fazer isso no `httpx`, ou até mesmo como fazer isso com `requests`, já que o design do HTTPX é baseado no design do Requests. + +Depois é só fazer o mesmo nos seus testes. + +Por exemplo: + +* Para passar um parâmetro *path* ou *query*, adicione-o à própria URL. +* Para passar um corpo JSON, passe um objeto Python (por exemplo, um `dict`) para o parâmetro `json`. +* Se você precisar enviar *Dados de Formulário* em vez de JSON, use o parâmetro `data`. +* Para passar *headers*, use um `dict` no parâmetro `headers`. +* Para *cookies*, um `dict` no parâmetro `cookies`. + +Para mais informações sobre como passar dados para o backend (usando `httpx` ou `TestClient`), consulte a documentação do HTTPX. + +/// info | Informação + +Observe que o `TestClient` recebe dados que podem ser convertidos para JSON, não para modelos Pydantic. + +Se você tiver um modelo Pydantic em seu teste e quiser enviar seus dados para o aplicativo durante o teste, poderá usar o `jsonable_encoder` descrito em [Codificador compatível com JSON](encoder.md){.internal-link target=_blank}. + +/// + +## Execute-o + +Depois disso, você só precisa instalar o `pytest`. + +Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e instalá-lo, por exemplo: + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +Ele detectará os arquivos e os testes automaticamente, os executará e informará os resultados para você. + +Execute os testes com: + +
+ +```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/pt/docs/virtual-environments.md b/docs/pt/docs/virtual-environments.md new file mode 100644 index 0000000000..5fc1a88666 --- /dev/null +++ b/docs/pt/docs/virtual-environments.md @@ -0,0 +1,844 @@ +# Ambientes Virtuais + +Ao trabalhar em projetos Python, você provavelmente deve usar um **ambiente virtual** (ou um mecanismo similar) para isolar os pacotes que você instala para cada projeto. + +/// info | Informação + +Se você já sabe sobre ambientes virtuais, como criá-los e usá-los, talvez seja melhor pular esta seção. 🤓 + +/// + +/// tip | Dica + +Um **ambiente virtual** é diferente de uma **variável de ambiente**. + +Uma **variável de ambiente** é uma variável no sistema que pode ser usada por programas. + +Um **ambiente virtual** é um diretório com alguns arquivos. + +/// + +/// info | Informação + +Esta página lhe ensinará como usar **ambientes virtuais** e como eles funcionam. + +Se você estiver pronto para adotar uma **ferramenta que gerencia tudo** para você (incluindo a instalação do Python), experimente uv. + +/// + +## Criar um Projeto + +Primeiro, crie um diretório para seu projeto. + +O que normalmente faço é criar um diretório chamado `code` dentro do meu diretório home/user. + +E dentro disso eu crio um diretório por projeto. + +
+ +```console +// Vá para o diretório inicial +$ cd +// Crie um diretório para todos os seus projetos de código +$ mkdir code +// Entre nesse diretório de código +$ cd code +// Crie um diretório para este projeto +$ mkdir awesome-project +// Entre no diretório do projeto +$ cd awesome-project +``` + +
+ +## Crie um ambiente virtual + +Ao começar a trabalhar em um projeto Python **pela primeira vez**, crie um ambiente virtual **dentro do seu projeto**. + +/// tip | Dica + +Você só precisa fazer isso **uma vez por projeto**, não toda vez que trabalhar. + +/// + +//// tab | `venv` + +Para criar um ambiente virtual, você pode usar o módulo `venv` que vem com o Python. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | O que esse comando significa + +* `python`: usa o programa chamado `python` +* `-m`: chama um módulo como um script, nós diremos a ele qual módulo vem em seguida +* `venv`: usa o módulo chamado `venv` que normalmente vem instalado com o Python +* `.venv`: cria o ambiente virtual no novo diretório `.venv` + +/// + +//// + +//// tab | `uv` + +Se você tiver o `uv` instalado, poderá usá-lo para criar um ambiente virtual. + +
+ +```console +$ uv venv +``` + +
+ +/// tip | Dica + +Por padrão, `uv` criará um ambiente virtual em um diretório chamado `.venv`. + +Mas você pode personalizá-lo passando um argumento adicional com o nome do diretório. + +/// + +//// + +Esse comando cria um novo ambiente virtual em um diretório chamado `.venv`. + +/// details | `.venv` ou outro nome + +Você pode criar o ambiente virtual em um diretório diferente, mas há uma convenção para chamá-lo de `.venv`. + +/// + +## Ative o ambiente virtual + +Ative o novo ambiente virtual para que qualquer comando Python que você executar ou pacote que você instalar o utilize. + +/// tip | Dica + +Faça isso **toda vez** que iniciar uma **nova sessão de terminal** para trabalhar no projeto. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Ou se você usa o Bash para Windows (por exemplo, Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip | Dica + +Toda vez que você instalar um **novo pacote** naquele ambiente, **ative** o ambiente novamente. + +Isso garante que, se você usar um **programa de terminal (CLI)** instalado por esse pacote, você usará aquele do seu ambiente virtual e não qualquer outro que possa ser instalado globalmente, provavelmente com uma versão diferente do que você precisa. + +/// + +## Verifique se o ambiente virtual está ativo + +Verifique se o ambiente virtual está ativo (o comando anterior funcionou). + +/// tip | Dica + +Isso é **opcional**, mas é uma boa maneira de **verificar** se tudo está funcionando conforme o esperado e se você está usando o ambiente virtual pretendido. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +Se ele mostrar o binário `python` em `.venv/bin/python`, dentro do seu projeto (neste caso `awesome-project`), então funcionou. 🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +Se ele mostrar o binário `python` em `.venv\Scripts\python`, dentro do seu projeto (neste caso `awesome-project`), então funcionou. 🎉 + +//// + +## Atualizar `pip` + +/// tip | Dica + +Se você usar `uv`, você o usará para instalar coisas em vez do `pip`, então não precisará atualizar o `pip`. 😎 + +/// + +Se você estiver usando `pip` para instalar pacotes (ele vem por padrão com o Python), você deve **atualizá-lo** para a versão mais recente. + +Muitos erros exóticos durante a instalação de um pacote são resolvidos apenas atualizando o `pip` primeiro. + +/// tip | Dica + +Normalmente, você faria isso **uma vez**, logo após criar o ambiente virtual. + +/// + +Certifique-se de que o ambiente virtual esteja ativo (com o comando acima) e execute: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## Adicionar `.gitignore` + +Se você estiver usando **Git** (você deveria), adicione um arquivo `.gitignore` para excluir tudo em seu `.venv` do Git. + +/// tip | Dica + +Se você usou `uv` para criar o ambiente virtual, ele já fez isso para você, você pode pular esta etapa. 😎 + +/// + +/// tip | Dica + +Faça isso **uma vez**, logo após criar o ambiente virtual. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | O que esse comando significa + +* `echo "*"`: irá "imprimir" o texto `*` no terminal (a próxima parte muda isso um pouco) +* `>`: qualquer coisa impressa no terminal pelo comando à esquerda de `>` não deve ser impressa, mas sim escrita no arquivo que vai à direita de `>` +* `.gitignore`: o nome do arquivo onde o texto deve ser escrito + +E `*` para Git significa "tudo". Então, ele ignorará tudo no diretório `.venv`. + +Esse comando criará um arquivo `.gitignore` com o conteúdo: + +```gitignore +* +``` + +/// + +## Instalar Pacotes + +Após ativar o ambiente, você pode instalar pacotes nele. + +/// tip | Dica + +Faça isso **uma vez** ao instalar ou atualizar os pacotes que seu projeto precisa. + +Se precisar atualizar uma versão ou adicionar um novo pacote, você **fará isso novamente**. + +/// + +### Instalar pacotes diretamente + +Se estiver com pressa e não quiser usar um arquivo para declarar os requisitos de pacote do seu projeto, você pode instalá-los diretamente. + +/// tip | Dica + +É uma (muito) boa ideia colocar os pacotes e versões que seu programa precisa em um arquivo (por exemplo `requirements.txt` ou `pyproject.toml`). + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Se você tem o `uv`: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### Instalar a partir de `requirements.txt` + +Se você tiver um `requirements.txt`, agora poderá usá-lo para instalar seus pacotes. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Se você tem o `uv`: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +Um `requirements.txt` com alguns pacotes poderia se parecer com: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## Execute seu programa + +Depois de ativar o ambiente virtual, você pode executar seu programa, e ele usará o Python dentro do seu ambiente virtual com os pacotes que você instalou lá. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Configure seu editor + +Você provavelmente usaria um editor. Certifique-se de configurá-lo para usar o mesmo ambiente virtual que você criou (ele provavelmente o detectará automaticamente) para que você possa obter erros de preenchimento automático e em linha. + +Por exemplo: + +* VS Code +* PyCharm + +/// tip | Dica + +Normalmente, você só precisa fazer isso **uma vez**, ao criar o ambiente virtual. + +/// + +## Desativar o ambiente virtual + +Quando terminar de trabalhar no seu projeto, você pode **desativar** o ambiente virtual. + +
+ +```console +$ deactivate +``` + +
+ +Dessa forma, quando você executar `python`, ele não tentará executá-lo naquele ambiente virtual com os pacotes instalados nele. + +## Pronto para trabalhar + +Agora você está pronto para começar a trabalhar no seu projeto. + + + +/// tip | Dica + +Você quer entender o que é tudo isso acima? + +Continue lendo. 👇🤓 + +/// + +## Por que ambientes virtuais + +Para trabalhar com o FastAPI, você precisa instalar o Python. + +Depois disso, você precisará **instalar** o FastAPI e quaisquer outros **pacotes** que queira usar. + +Para instalar pacotes, você normalmente usaria o comando `pip` que vem com o Python (ou alternativas semelhantes). + +No entanto, se você usar `pip` diretamente, os pacotes serão instalados no seu **ambiente Python global** (a instalação global do Python). + +### O Problema + +Então, qual é o problema em instalar pacotes no ambiente global do Python? + +Em algum momento, você provavelmente acabará escrevendo muitos programas diferentes que dependem de **pacotes diferentes**. E alguns desses projetos em que você trabalha dependerão de **versões diferentes** do mesmo pacote. 😱 + +Por exemplo, você pode criar um projeto chamado `philosophers-stone`, este programa depende de outro pacote chamado **`harry`, usando a versão `1`**. Então, você precisa instalar `harry`. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +Então, em algum momento depois, você cria outro projeto chamado `prisoner-of-azkaban`, e esse projeto também depende de `harry`, mas esse projeto precisa do **`harry` versão `3`**. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +Mas agora o problema é que, se você instalar os pacotes globalmente (no ambiente global) em vez de em um **ambiente virtual** local, você terá que escolher qual versão do `harry` instalar. + +Se você quiser executar `philosophers-stone`, precisará primeiro instalar `harry` versão `1`, por exemplo com: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +E então você acabaria com `harry` versão `1` instalado em seu ambiente Python global. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +Mas se você quiser executar `prisoner-of-azkaban`, você precisará desinstalar `harry` versão `1` e instalar `harry` versão `3` (ou apenas instalar a versão `3` desinstalaria automaticamente a versão `1`). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +E então você acabaria com `harry` versão `3` instalado em seu ambiente Python global. + +E se você tentar executar `philosophers-stone` novamente, há uma chance de que **não funcione** porque ele precisa de `harry` versão `1`. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip | Dica + +É muito comum em pacotes Python tentar ao máximo **evitar alterações drásticas** em **novas versões**, mas é melhor prevenir do que remediar e instalar versões mais recentes intencionalmente e, quando possível, executar os testes para verificar se tudo está funcionando corretamente. + +/// + +Agora, imagine isso com **muitos** outros **pacotes** dos quais todos os seus **projetos dependem**. Isso é muito difícil de gerenciar. E você provavelmente acabaria executando alguns projetos com algumas **versões incompatíveis** dos pacotes, e não saberia por que algo não está funcionando. + +Além disso, dependendo do seu sistema operacional (por exemplo, Linux, Windows, macOS), ele pode ter vindo com o Python já instalado. E, nesse caso, provavelmente tinha alguns pacotes pré-instalados com algumas versões específicas **necessárias para o seu sistema**. Se você instalar pacotes no ambiente global do Python, poderá acabar **quebrando** alguns dos programas que vieram com seu sistema operacional. + +## Onde os pacotes são instalados + +Quando você instala o Python, ele cria alguns diretórios com alguns arquivos no seu computador. + +Alguns desses diretórios são os responsáveis ​​por ter todos os pacotes que você instala. + +Quando você executa: + +
+ +```console +// Não execute isso agora, é apenas um exemplo 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +Isso fará o download de um arquivo compactado com o código FastAPI, normalmente do PyPI. + +Ele também fará o **download** de arquivos para outros pacotes dos quais o FastAPI depende. + +Em seguida, ele **extrairá** todos esses arquivos e os colocará em um diretório no seu computador. + +Por padrão, ele colocará os arquivos baixados e extraídos no diretório que vem com a instalação do Python, que é o **ambiente global**. + +## O que são ambientes virtuais + +A solução para os problemas de ter todos os pacotes no ambiente global é usar um **ambiente virtual para cada projeto** em que você trabalha. + +Um ambiente virtual é um **diretório**, muito semelhante ao global, onde você pode instalar os pacotes para um projeto. + +Dessa forma, cada projeto terá seu próprio ambiente virtual (diretório `.venv`) com seus próprios pacotes. + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## O que significa ativar um ambiente virtual + +Quando você ativa um ambiente virtual, por exemplo com: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Ou se você usa o Bash para Windows (por exemplo, Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +Esse comando criará ou modificará algumas [variáveis ​​de ambiente](environment-variables.md){.internal-link target=_blank} que estarão disponíveis para os próximos comandos. + +Uma dessas variáveis ​​é a variável `PATH`. + +/// tip | Dica + +Você pode aprender mais sobre a variável de ambiente `PATH` na seção [Variáveis ​​de ambiente](environment-variables.md#path-environment-variable){.internal-link target=_blank}. + +/// + +A ativação de um ambiente virtual adiciona seu caminho `.venv/bin` (no Linux e macOS) ou `.venv\Scripts` (no Windows) à variável de ambiente `PATH`. + +Digamos que antes de ativar o ambiente, a variável `PATH` estava assim: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +Isso significa que o sistema procuraria programas em: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +Isso significa que o sistema procuraria programas em: + +* `C:\Windows\System32` + +//// + +Após ativar o ambiente virtual, a variável `PATH` ficaria mais ou menos assim: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Isso significa que o sistema agora começará a procurar primeiro por programas em: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +antes de procurar nos outros diretórios. + +Então, quando você digita `python` no terminal, o sistema encontrará o programa Python em + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +e usa esse. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +Isso significa que o sistema agora começará a procurar primeiro por programas em: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +antes de procurar nos outros diretórios. + +Então, quando você digita `python` no terminal, o sistema encontrará o programa Python em + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +e usa esse. + +//// + +Um detalhe importante é que ele colocará o caminho do ambiente virtual no **início** da variável `PATH`. O sistema o encontrará **antes** de encontrar qualquer outro Python disponível. Dessa forma, quando você executar `python`, ele usará o Python **do ambiente virtual** em vez de qualquer outro `python` (por exemplo, um `python` de um ambiente global). + +Ativar um ambiente virtual também muda algumas outras coisas, mas esta é uma das mais importantes. + +## Verificando um ambiente virtual + +Ao verificar se um ambiente virtual está ativo, por exemplo com: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +Isso significa que o programa `python` que será usado é aquele **no ambiente virtual**. + +você usa `which` no Linux e macOS e `Get-Command` no Windows PowerShell. + +A maneira como esse comando funciona é que ele vai e verifica na variável de ambiente `PATH`, passando por **cada caminho em ordem**, procurando pelo programa chamado `python`. Uma vez que ele o encontre, ele **mostrará o caminho** para esse programa. + +A parte mais importante é que quando você chama ``python`, esse é exatamente o "`python`" que será executado. + +Assim, você pode confirmar se está no ambiente virtual correto. + +/// tip | Dica + +É fácil ativar um ambiente virtual, obter um Python e então **ir para outro projeto**. + +E o segundo projeto **não funcionaria** porque você está usando o **Python incorreto**, de um ambiente virtual para outro projeto. + +É útil poder verificar qual `python` está sendo usado. 🤓 + +/// + +## Por que desativar um ambiente virtual + +Por exemplo, você pode estar trabalhando em um projeto `philosophers-stone`, **ativar esse ambiente virtual**, instalar pacotes e trabalhar com esse ambiente. + +E então você quer trabalhar em **outro projeto** `prisoner-of-azkaban`. + +Você vai para aquele projeto: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +Se você não desativar o ambiente virtual para `philosophers-stone`, quando você executar `python` no terminal, ele tentará usar o Python de `philosophers-stone`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Erro ao importar o Sirius, ele não está instalado 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +Mas se você desativar o ambiente virtual e ativar o novo para `prisoner-of-askaban`, quando você executar `python`, ele usará o Python do ambiente virtual em `prisoner-of-azkaban`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// Você não precisa estar no diretório antigo para desativar, você pode fazer isso de onde estiver, mesmo depois de ir para o outro projeto 😎 +$ deactivate + +// Ative o ambiente virtual em prisoner-of-azkaban/.venv 🚀 +$ source .venv/bin/activate + +// Agora, quando você executar o python, ele encontrará o pacote sirius instalado neste ambiente virtual ✨ +$ python main.py + +Eu juro solenemente 🐺 +``` + +
+ +## Alternativas + +Este é um guia simples para você começar e lhe ensinar como tudo funciona **por baixo**. + +Existem muitas **alternativas** para gerenciar ambientes virtuais, dependências de pacotes (requisitos) e projetos. + +Quando estiver pronto e quiser usar uma ferramenta para **gerenciar todo o projeto**, dependências de pacotes, ambientes virtuais, etc., sugiro que você experimente o uv. + +`uv` pode fazer muitas coisas, ele pode: + +* **Instalar o Python** para você, incluindo versões diferentes +* Gerenciar o **ambiente virtual** para seus projetos +* Instalar **pacotes** +* Gerenciar **dependências e versões** de pacotes para seu projeto +* Certifique-se de ter um conjunto **exato** de pacotes e versões para instalar, incluindo suas dependências, para que você possa ter certeza de que pode executar seu projeto em produção exatamente da mesma forma que em seu computador durante o desenvolvimento, isso é chamado de **bloqueio** +* E muitas outras coisas + +## Conclusão + +Se você leu e entendeu tudo isso, agora **você sabe muito mais** sobre ambientes virtuais do que muitos desenvolvedores por aí. 🤓 + +Saber esses detalhes provavelmente será útil no futuro, quando você estiver depurando algo que parece complexo, mas você saberá **como tudo funciona**. 😎 diff --git a/docs/ru/docs/_llm-test.md b/docs/ru/docs/_llm-test.md new file mode 100644 index 0000000000..476cc19244 --- /dev/null +++ b/docs/ru/docs/_llm-test.md @@ -0,0 +1,503 @@ +# Тестовый файл LLM { #llm-test-file } + +Этот документ проверяет, понимает ли LLM, переводящая документацию, `general_prompt` в `scripts/translate.py` и языковой специфичный промпт в `docs/{language code}/llm-prompt.md`. Языковой специфичный промпт добавляется к `general_prompt`. + +Тесты, добавленные здесь, увидят все создатели языковых промптов. + +Использование: + +* Подготовьте языковой специфичный промпт — `docs/{language code}/llm-prompt.md`. +* Выполните новый перевод этого документа на нужный целевой язык (см., например, команду `translate-page` в `translate.py`). Это создаст перевод в `docs/{language code}/docs/_llm-test.md`. +* Проверьте, всё ли в порядке в переводе. +* При необходимости улучшите ваш языковой специфичный промпт, общий промпт или английский документ. +* Затем вручную исправьте оставшиеся проблемы в переводе, чтобы он был хорошим. +* Переведите заново, имея хороший перевод на месте. Идеальным результатом будет ситуация, когда LLM больше не вносит изменений в перевод. Это означает, что общий промпт и ваш языковой специфичный промпт максимально хороши (иногда он будет делать несколько, казалось бы, случайных изменений, причина в том, что LLM — недетерминированные алгоритмы). + +Тесты: + +## Фрагменты кода { #code-snippets} + +//// tab | Тест + +Это фрагмент кода: `foo`. А это ещё один фрагмент кода: `bar`. И ещё один: `baz quux`. + +//// + +//// tab | Информация + +Содержимое фрагментов кода должно оставаться как есть. + +См. раздел `### Content of code snippets` в общем промпте в `scripts/translate.py`. + +//// + +## Кавычки { #quotes } + +//// tab | Тест + +Вчера мой друг написал: "Если вы написали incorrectly правильно, значит вы написали это неправильно". На что я ответил: "Верно, но 'incorrectly' — это неправильно, а не '"incorrectly"'". + +/// note | Примечание + +LLM, вероятно, переведёт это неправильно. Интересно лишь то, сохранит ли она фиксированный перевод при повторном переводе. + +/// + +//// + +//// tab | Информация + +Автор промпта может выбрать, хочет ли он преобразовывать нейтральные кавычки в типографские. Допускается оставить их как есть. + +См., например, раздел `### Quotes` в `docs/de/llm-prompt.md`. + +//// + +## Кавычки во фрагментах кода { #quotes-in-code-snippets} + +//// tab | Тест + +`pip install "foo[bar]"` + +Примеры строковых литералов во фрагментах кода: `"this"`, `'that'`. + +Сложный пример строковых литералов во фрагментах кода: `f"I like {'oranges' if orange else "apples"}"` + +Хардкор: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | Информация + +... Однако кавычки внутри фрагментов кода должны оставаться как есть. + +//// + +## Блоки кода { #code-blocks } + +//// tab | Тест + +Пример кода Bash... + +```bash +# Вывести приветствие вселенной +echo "Hello universe" +``` + +...и пример вывода в консоли... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...и ещё один пример вывода в консоли... + +```console +// Создать директорию "Code" +$ mkdir code +// Перейти в эту директорию +$ cd code +``` + +...и пример кода на Python... + +```Python +wont_work() # Это не сработает 😱 +works(foo="bar") # Это работает 🎉 +``` + +...и на этом всё. + +//// + +//// tab | Информация + +Код в блоках кода не должен изменяться, за исключением комментариев. + +См. раздел `### Content of code blocks` в общем промпте в `scripts/translate.py`. + +//// + +## Вкладки и цветные блоки { #tabs-and-colored-boxes } + +//// tab | Тест + +/// info | Информация +Некоторый текст +/// + +/// note | Примечание +Некоторый текст +/// + +/// note | Технические подробности +Некоторый текст +/// + +/// check | Проверка +Некоторый текст +/// + +/// tip | Совет +Некоторый текст +/// + +/// warning | Предупреждение +Некоторый текст +/// + +/// danger | Опасность +Некоторый текст +/// + +//// + +//// tab | Информация + +Для вкладок и блоков `Info`/`Note`/`Warning`/и т.п. нужно добавить перевод их заголовка после вертикальной черты (`|`). + +См. разделы `### Special blocks` и `### Tab blocks` в общем промпте в `scripts/translate.py`. + +//// + +## Веб- и внутренние ссылки { #web-and-internal-links } + +//// tab | Тест + +Текст ссылок должен переводиться, адрес ссылки не должен изменяться: + +* [Ссылка на заголовок выше](#code-snippets) +* [Внутренняя ссылка](index.md#installation){.internal-link target=_blank} +* Внешняя ссылка +* Ссылка на стиль +* Ссылка на скрипт +* Ссылка на изображение + +Текст ссылок должен переводиться, адрес ссылки должен указывать на перевод: + +* Ссылка на FastAPI + +//// + +//// tab | Информация + +Ссылки должны переводиться, но их адреса не должны изменяться. Исключение — абсолютные ссылки на страницы документации FastAPI. В этом случае ссылка должна вести на перевод. + +См. раздел `### Links` в общем промпте в `scripts/translate.py`. + +//// + +## HTML-элементы "abbr" { #html-abbr-elements } + +//// tab | Тест + +Вот некоторые элементы, обёрнутые в HTML-элементы "abbr" (часть выдумана): + +### abbr даёт полную расшифровку { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### abbr даёт объяснение { #the-abbr-gives-an-explanation } + +* кластер +* Глубокое обучение + +### abbr даёт полную расшифровку и объяснение { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | Информация + +Атрибуты "title" элементов "abbr" переводятся по определённым правилам. + +Переводы могут добавлять свои собственные элементы "abbr", которые LLM не должна удалять. Например, чтобы объяснить английские слова. + +См. раздел `### HTML abbr elements` в общем промпте в `scripts/translate.py`. + +//// + +## Заголовки { #headings } + +//// tab | Тест + +### Разработка веб‑приложения — руководство { #develop-a-webapp-a-tutorial } + +Привет. + +### Аннотации типов и -аннотации { #type-hints-and-annotations } + +Снова привет. + +### Супер- и подклассы { #super-and-subclasses } + +Снова привет. + +//// + +//// tab | Информация + +Единственное жёсткое правило для заголовков — LLM должна оставить часть хеша в фигурных скобках без изменений, чтобы ссылки не ломались. + +См. раздел `### Headings` в общем промпте в `scripts/translate.py`. + +Для некоторых языковых инструкций см., например, раздел `### Headings` в `docs/de/llm-prompt.md`. + +//// + +## Термины, используемые в документации { #terms-used-in-the-docs } + +//// tab | Тест + +* вы +* ваш + +* например +* и т.д. + +* `foo` как `int` +* `bar` как `str` +* `baz` как `list` + +* Учебник — Руководство пользователя +* Расширенное руководство пользователя +* Документация по SQLModel +* Документация API +* Автоматическая документация + +* Наука о данных +* Глубокое обучение +* Машинное обучение +* Внедрение зависимостей +* Аутентификация HTTP Basic +* HTTP Digest +* формат ISO +* стандарт JSON Schema +* JSON-схема +* определение схемы +* password flow +* Мобильный + +* устаревший +* спроектированный +* некорректный +* на лету +* стандарт +* по умолчанию +* чувствительный к регистру +* нечувствительный к регистру + +* обслуживать приложение +* отдавать страницу + +* приложение +* приложение + +* HTTP-запрос +* HTTP-ответ +* ответ с ошибкой + +* операция пути +* декоратор операции пути +* функция-обработчик пути + +* тело +* тело запроса +* тело ответа +* JSON-тело +* тело формы +* тело файла +* тело функции + +* параметр +* body-параметр +* path-параметр +* query-параметр +* cookie-параметр +* параметр заголовка +* параметр формы +* параметр функции + +* событие +* событие запуска +* запуск сервера +* событие остановки +* событие lifespan + +* обработчик +* обработчик события +* обработчик исключений +* обрабатывать + +* модель +* Pydantic-модель +* модель данных +* модель базы данных +* модель формы +* объект модели + +* класс +* базовый класс +* родительский класс +* подкласс +* дочерний класс +* родственный класс +* метод класса + +* заголовок +* HTTP-заголовки +* заголовок авторизации +* заголовок `Authorization` +* заголовок `Forwarded` + +* система внедрения зависимостей +* зависимость +* зависимый объект +* зависимый + +* ограниченный вводом/выводом +* ограниченный процессором +* конкурентность +* параллелизм +* многопроцессность + +* переменная окружения +* переменная окружения +* `PATH` +* переменная `PATH` + +* аутентификация +* провайдер аутентификации +* авторизация +* форма авторизации +* провайдер авторизации +* пользователь аутентифицируется +* система аутентифицирует пользователя + +* CLI +* интерфейс командной строки + +* сервер +* клиент + +* облачный провайдер +* облачный сервис + +* разработка +* этапы разработки + +* dict +* словарь +* перечисление +* enum +* член перечисления + +* кодировщик +* декодировщик +* кодировать +* декодировать + +* исключение +* вызвать + +* выражение +* оператор + +* фронтенд +* бэкенд + +* обсуждение на GitHub +* Issue на GitHub (тикет/обращение) + +* производительность +* оптимизация производительности + +* тип возвращаемого значения +* возвращаемое значение + +* безопасность +* схема безопасности + +* задача +* фоновая задача +* функция задачи + +* шаблон +* шаблонизатор + +* аннотация типов +* аннотация типов + +* воркер сервера +* воркер Uvicorn +* воркер Gunicorn +* воркер-процесс +* класс воркера +* рабочая нагрузка + +* деплой +* развернуть + +* SDK +* набор средств разработки ПО + +* `APIRouter` +* `requirements.txt` +* токен Bearer +* несовместимое изменение +* баг +* кнопка +* вызываемый объект +* код +* коммит +* менеджер контекста +* корутина +* сессия базы данных +* диск +* домен +* движок +* фиктивный X +* метод HTTP GET +* элемент +* библиотека +* lifespan +* блокировка +* middleware (Промежуточный слой) +* мобильное приложение +* модуль +* монтирование +* сеть +* origin (источник) +* переопределение +* полезная нагрузка +* процессор +* свойство +* прокси +* пулл-реквест (запрос на изменение) +* запрос +* ОЗУ +* удалённая машина +* статус-код +* строка +* тег +* веб‑фреймворк +* подстановочный знак +* вернуть +* валидировать + +//// + +//// tab | Информация + +Это неполный и ненормативный список (в основном) технических терминов, встречающихся в документации. Он может помочь автору промпта понять, по каким терминам LLM нужна подсказка. Например, когда она продолжает возвращать действительно хороший перевод к неоптимальному. Или когда у неё возникают проблемы со склонением/спряжением термина на вашем языке. + +См., например, раздел `### List of English terms and their preferred German translations` в `docs/de/llm-prompt.md`. + +//// diff --git a/docs/ru/docs/about/index.md b/docs/ru/docs/about/index.md index 1015b667a8..4f48266a7a 100644 --- a/docs/ru/docs/about/index.md +++ b/docs/ru/docs/about/index.md @@ -1,3 +1,3 @@ -# О проекте +# О проекте { #about } -FastAPI: внутреннее устройство, повлиявшие технологии и всё такое прочее. 🤓 +О FastAPI, его дизайне, источниках вдохновения и многом другом. 🤓 diff --git a/docs/ru/docs/advanced/additional-responses.md b/docs/ru/docs/advanced/additional-responses.md new file mode 100644 index 0000000000..c63c0c08b0 --- /dev/null +++ b/docs/ru/docs/advanced/additional-responses.md @@ -0,0 +1,247 @@ +# Дополнительные ответы в OpenAPI { #additional-responses-in-openapi } + +/// warning | Предупреждение + +Это довольно продвинутая тема. + +Если вы только начинаете работать с **FastAPI**, возможно, вам это пока не нужно. + +/// + +Вы можете объявлять дополнительные ответы с дополнительными статус-кодами, типами содержимого, описаниями и т.д. + +Эти дополнительные ответы будут включены в схему OpenAPI, и поэтому появятся в документации API. + +Но для таких дополнительных ответов убедитесь, что вы возвращаете `Response`, например `JSONResponse`, напрямую, со своим статус-кодом и содержимым. + +## Дополнительный ответ с `model` { #additional-response-with-model } + +Вы можете передать вашим декораторам операции пути параметр `responses`. + +Он принимает `dict`: ключи — это статус-коды для каждого ответа (например, `200`), а значения — другие `dict` с информацией для каждого из них. + +Каждый из этих `dict` для ответа может иметь ключ `model`, содержащий Pydantic-модель, аналогично `response_model`. + +**FastAPI** возьмёт эту модель, сгенерирует для неё JSON‑схему и включит её в нужное место в OpenAPI. + +Например, чтобы объявить ещё один ответ со статус-кодом `404` и Pydantic-моделью `Message`, можно написать: + +{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} + +/// note | Примечание + +Имейте в виду, что необходимо возвращать `JSONResponse` напрямую. + +/// + +/// info | Информация + +Ключ `model` не является частью OpenAPI. + +**FastAPI** возьмёт Pydantic-модель оттуда, сгенерирует JSON‑схему и поместит её в нужное место. + +Нужное место: + +* В ключе `content`, значением которого является другой JSON‑объект (`dict`), содержащий: + * Ключ с типом содержимого, например `application/json`, значением которого является другой JSON‑объект, содержащий: + * Ключ `schema`, значением которого является JSON‑схема из модели — вот нужное место. + * **FastAPI** добавляет здесь ссылку на глобальные JSON‑схемы в другом месте вашего OpenAPI вместо того, чтобы включать схему напрямую. Так другие приложения и клиенты смогут использовать эти JSON‑схемы напрямую, предоставлять лучшие инструменты генерации кода и т.д. + +/// + +Сгенерированные в OpenAPI ответы для этой операции пути будут такими: + +```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" + } + } + } + } + } +} +``` + +Схемы даны как ссылки на другое место внутри схемы 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" + } + } + } + } + } + } +} +``` + +## Дополнительные типы содержимого для основного ответа { #additional-media-types-for-the-main-response } + +Вы можете использовать этот же параметр `responses`, чтобы добавить разные типы содержимого для того же основного ответа. + +Например, вы можете добавить дополнительный тип содержимого `image/png`, объявив, что ваша операция пути может возвращать JSON‑объект (с типом содержимого `application/json`) или PNG‑изображение: + +{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} + +/// note | Примечание + +Учтите, что изображение нужно возвращать напрямую, используя `FileResponse`. + +/// + +/// info | Информация + +Если вы явно не укажете другой тип содержимого в параметре `responses`, FastAPI будет считать, что ответ имеет тот же тип содержимого, что и основной класс ответа (по умолчанию `application/json`). + +Но если вы указали пользовательский класс ответа с `None` в качестве его типа содержимого, FastAPI использует `application/json` для любого дополнительного ответа, у которого есть связанная модель. + +/// + +## Комбинирование информации { #combining-information } + +Вы также можете комбинировать информацию об ответах из нескольких мест, включая параметры `response_model`, `status_code` и `responses`. + +Вы можете объявить `response_model`, используя статус-код по умолчанию `200` (или свой, если нужно), а затем объявить дополнительную информацию для этого же ответа в `responses`, напрямую в схеме OpenAPI. + +**FastAPI** сохранит дополнительную информацию из `responses` и объединит её с JSON‑схемой из вашей модели. + +Например, вы можете объявить ответ со статус-кодом `404`, который использует Pydantic-модель и имеет пользовательское `description`. + +А также ответ со статус-кодом `200`, который использует ваш `response_model`, но включает пользовательский `example`: + +{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} + +Всё это будет объединено и включено в ваш OpenAPI и отображено в документации API: + + + +## Комбинирование предопределённых и пользовательских ответов { #combine-predefined-responses-and-custom-ones } + +Возможно, вы хотите иметь некоторые предопределённые ответы, применимые ко многим операциям пути, но при этом комбинировать их с пользовательскими ответами, необходимыми для каждой конкретной операции пути. + +В таких случаях вы можете использовать приём Python «распаковки» `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", +} +``` + +Вы можете использовать этот приём, чтобы переиспользовать некоторые предопределённые ответы в ваших операциях пути и комбинировать их с дополнительными пользовательскими. + +Например: + +{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} + +## Дополнительная информация об ответах OpenAPI { #more-information-about-openapi-responses } + +Чтобы увидеть, что именно можно включать в ответы, посмотрите эти разделы спецификации OpenAPI: + +* Объект Responses OpenAPI, он включает `Response Object`. +* Объект Response OpenAPI, вы можете включить всё из этого объекта напрямую в каждый ответ внутри вашего параметра `responses`. Включая `description`, `headers`, `content` (внутри него вы объявляете разные типы содержимого и JSON‑схемы) и `links`. diff --git a/docs/ru/docs/advanced/additional-status-codes.md b/docs/ru/docs/advanced/additional-status-codes.md new file mode 100644 index 0000000000..7c73cf5d5d --- /dev/null +++ b/docs/ru/docs/advanced/additional-status-codes.md @@ -0,0 +1,41 @@ +# Дополнительные статус-коды { #additional-status-codes } + +По умолчанию **FastAPI** будет возвращать ответы, используя `JSONResponse`, помещая содержимое, которое вы возвращаете из вашей *операции пути*, внутрь этого `JSONResponse`. + +Он будет использовать статус-код по умолчанию или тот, который вы укажете в вашей *операции пути*. + +## Дополнительные статус-коды { #additional-status-codes_1 } + +Если вы хотите возвращать дополнительные статус-коды помимо основного, вы можете сделать это, возвращая `Response` напрямую, например `JSONResponse`, и устанавливая дополнительный статус-код напрямую. + +Например, предположим, что вы хотите иметь *операцию пути*, которая позволяет обновлять элементы и возвращает HTTP статус-код 200 «OK» при успешном выполнении. + +Но вы также хотите, чтобы она принимала новые элементы. И если элементы ранее не существовали, она создаёт их и возвращает HTTP статус-код 201 «Created». + +Чтобы добиться этого, импортируйте `JSONResponse` и верните туда свой контент напрямую, установив нужный вам `status_code`: + +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} + +/// warning | Внимание + +Когда вы возвращаете `Response` напрямую, как в примере выше, он будет возвращён как есть. + +Он не будет сериализован с помощью модели и т.п. + +Убедитесь, что в нём именно те данные, которые вы хотите, и что значения являются валидным JSON (если вы используете `JSONResponse`). + +/// + +/// note | Технические детали + +Вы также можете использовать `from starlette.responses import JSONResponse`. + +**FastAPI** предоставляет тот же `starlette.responses` через `fastapi.responses` просто для вашего удобства как разработчика. Но большинство доступных Response-классов приходят напрямую из Starlette. То же самое со `status`. + +/// + +## OpenAPI и документация API { #openapi-and-api-docs } + +Если вы возвращаете дополнительные статус-коды и ответы напрямую, они не будут включены в схему OpenAPI (документацию API), потому что у FastAPI нет способа заранее знать, что вы собираетесь вернуть. + +Но вы можете задокументировать это в своём коде, используя: [Дополнительные ответы](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/advanced/advanced-dependencies.md b/docs/ru/docs/advanced/advanced-dependencies.md new file mode 100644 index 0000000000..75a6f0d1fa --- /dev/null +++ b/docs/ru/docs/advanced/advanced-dependencies.md @@ -0,0 +1,153 @@ +# Продвинутые зависимости { #advanced-dependencies } + +## Параметризованные зависимости { #parameterized-dependencies } + +Все зависимости, которые мы видели, — это конкретная функция или класс. + +Но бывают случаи, когда нужно задавать параметры зависимости, не объявляя много разных функций или классов. + +Представим, что нам нужна зависимость, которая проверяет, содержит ли query-параметр `q` некоторое фиксированное содержимое. + +Но при этом мы хотим иметь возможность параметризовать это фиксированное содержимое. + +## «Вызываемый» экземпляр { #a-callable-instance } + +В Python есть способ сделать экземпляр класса «вызываемым» объектом. + +Не сам класс (он уже является вызываемым), а экземпляр этого класса. + +Для этого объявляем метод `__call__`: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} + +В этом случае именно `__call__` **FastAPI** использует для проверки дополнительных параметров и подзависимостей, и именно он будет вызван, чтобы позже передать значение параметру в вашей *функции-обработчике пути*. + +## Параметризуем экземпляр { #parameterize-the-instance } + +Теперь мы можем использовать `__init__`, чтобы объявить параметры экземпляра, с помощью которых будем «параметризовать» зависимость: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} + +В этом случае **FastAPI** вовсе не трогает `__init__` и не зависит от него — мы используем его напрямую в нашем коде. + +## Создаём экземпляр { #create-an-instance } + +Мы можем создать экземпляр этого класса так: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} + +Так мы «параметризуем» нашу зависимость: теперь внутри неё хранится "bar" в атрибуте `checker.fixed_content`. + +## Используем экземпляр как зависимость { #use-the-instance-as-a-dependency } + +Затем мы можем использовать этот `checker` в `Depends(checker)` вместо `Depends(FixedContentQueryChecker)`, потому что зависимостью является экземпляр `checker`, а не сам класс. + +И при разрешении зависимости **FastAPI** вызовет `checker` примерно так: + +```Python +checker(q="somequery") +``` + +…и передаст возвращённое значение как значение зависимости в нашу *функцию-обработчике пути* в параметр `fixed_content_included`: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} + +/// tip | Совет + +Все это может показаться притянутым за уши. И пока может быть не совсем понятно, чем это полезно. + +Эти примеры намеренно простые, но они показывают, как всё устроено. + +В главах про безопасность есть вспомогательные функции, реализованные тем же способом. + +Если вы поняли всё выше, вы уже знаете, как «под капотом» работают эти утилиты для безопасности. + +/// + +## Зависимости с `yield`, `HTTPException`, `except` и фоновыми задачами { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | Предупреждение + +Скорее всего, вам не понадобятся эти технические детали. + +Они полезны главным образом, если у вас было приложение FastAPI версии ниже 0.118.0 и вы столкнулись с проблемами зависимостей с `yield`. + +/// + +Зависимости с `yield` со временем изменялись, чтобы учитывать разные случаи применения и исправлять проблемы. Ниже — краткое резюме изменений. + +### Зависимости с `yield` и `StreamingResponse`, технические детали { #dependencies-with-yield-and-streamingresponse-technical-details } + +До FastAPI 0.118.0, если вы использовали зависимость с `yield`, код после `yield` выполнялся после возврата из *функции-обработчика пути*, но прямо перед отправкой ответа. + +Идея состояла в том, чтобы не удерживать ресурсы дольше необходимого, пока ответ «путешествует» по сети. + +Это изменение также означало, что если вы возвращали `StreamingResponse`, код после `yield` в зависимости уже успевал выполниться. + +Например, если у вас была сессия базы данных в зависимости с `yield`, `StreamingResponse` не смог бы использовать эту сессию во время стриминга данных, потому что сессия уже была закрыта в коде после `yield`. + +В версии 0.118.0 это поведение было возвращено к тому, что код после `yield` выполняется после отправки ответа. + +/// info | Информация + +Как вы увидите ниже, это очень похоже на поведение до версии 0.106.0, но с несколькими улучшениями и исправлениями краевых случаев. + +/// + +#### Сценарии с ранним выполнением кода после `yield` { #use-cases-with-early-exit-code } + +Есть некоторые сценарии со специфическими условиями, которым могло бы помочь старое поведение — выполнение кода после `yield` перед отправкой ответа. + +Например, представьте, что вы используете сессию базы данных в зависимости с `yield` только для проверки пользователя, а в самой *функции-обработчике пути* эта сессия больше не используется, и при этом ответ отправляется долго, например, это `StreamingResponse`, который медленно отправляет данные и по какой-то причине не использует базу данных. + +В таком случае сессия базы данных будет удерживаться до завершения отправки ответа, хотя если вы её не используете, удерживать её не требуется. + +Это могло бы выглядеть так: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +Код после `yield`, автоматическое закрытие `Session` в: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +…будет выполнен после того, как ответ закончит отправку медленных данных: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +Но поскольку `generate_stream()` не использует сессию базы данных, нет реальной необходимости держать сессию открытой во время отправки ответа. + +Если у вас именно такой сценарий с SQLModel (или SQLAlchemy), вы можете явно закрыть сессию, когда она больше не нужна: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +Так сессия освободит подключение к базе данных, и другие запросы смогут его использовать. + +Если у вас есть другой сценарий, где нужно раннее завершение зависимости с `yield`, пожалуйста, создайте вопрос в GitHub Discussions с описанием конкретного кейса и почему вам было бы полезно иметь раннее закрытие для зависимостей с `yield`. + +Если появятся веские причины для раннего закрытия в зависимостях с `yield`, я рассмотрю добавление нового способа опционально включать раннее закрытие. + +### Зависимости с `yield` и `except`, технические детали { #dependencies-with-yield-and-except-technical-details } + +До FastAPI 0.110.0, если вы использовали зависимость с `yield`, затем перехватывали исключение с `except` в этой зависимости и не пробрасывали исключение снова, исключение автоматически пробрасывалось дальше к обработчикам исключений или к обработчику внутренней ошибки сервера. + +В версии 0.110.0 это было изменено, чтобы исправить неконтролируемое потребление памяти из‑за проброшенных исключений без обработчика (внутренние ошибки сервера) и привести поведение в соответствие с обычным поведением Python-кода. + +### Фоновые задачи и зависимости с `yield`, технические детали { #background-tasks-and-dependencies-with-yield-technical-details } + +До FastAPI 0.106.0 вызывать исключения после `yield` было невозможно: код после `yield` в зависимостях выполнялся уже после отправки ответа, поэтому [Обработчики исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} к тому моменту уже отработали. + +Так было сделано в основном для того, чтобы можно было использовать те же объекты, «отданные» зависимостями через `yield`, внутри фоновых задач, потому что код после `yield` выполнялся после завершения фоновых задач. + +В FastAPI 0.106.0 это изменили, чтобы не удерживать ресурсы, пока ответ передаётся по сети. + +/// tip | Совет + +Кроме того, фоновая задача обычно — это самостоятельный фрагмент логики, который следует обрабатывать отдельно, со своими ресурсами (например, со своим подключением к базе данных). + +Так код, скорее всего, будет чище. + +/// + +Если вы полагались на прежнее поведение, теперь ресурсы для фоновых задач следует создавать внутри самой фоновой задачи и использовать внутри неё только данные, которые не зависят от ресурсов зависимостей с `yield`. + +Например, вместо использования той же сессии базы данных, создайте новую сессию в фоновой задаче и получите объекты из базы данных с помощью этой новой сессии. И затем, вместо передачи объекта из базы данных параметром в функцию фоновой задачи, передавайте идентификатор этого объекта и заново получайте объект внутри функции фоновой задачи. diff --git a/docs/ru/docs/advanced/async-tests.md b/docs/ru/docs/advanced/async-tests.md new file mode 100644 index 0000000000..5062bc52e7 --- /dev/null +++ b/docs/ru/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# Асинхронное тестирование { #async-tests } + +Вы уже видели как тестировать **FastAPI** приложение, используя имеющийся класс `TestClient`. К этому моменту вы видели только как писать тесты в синхронном стиле без использования `async` функций. + +Возможность использования асинхронных функций в ваших тестах может быть полезнa, когда, например, вы асинхронно обращаетесь к вашей базе данных. Представьте, что вы хотите отправить запросы в ваше FastAPI приложение, а затем при помощи асинхронной библиотеки для работы с базой данных удостовериться, что ваш бекэнд корректно записал данные в базу данных. + +Давайте рассмотрим, как мы можем это реализовать. + +## pytest.mark.anyio { #pytest-mark-anyio } + +Если мы хотим вызывать асинхронные функции в наших тестах, то наши тестовые функции должны быть асинхронными. AnyIO предоставляет для этого отличный плагин, который позволяет нам указывать, какие тестовые функции должны вызываться асинхронно. + +## HTTPX { #httpx } + +Даже если **FastAPI** приложение использует обычные функции `def` вместо `async def`, это все равно `async` приложение 'под капотом'. + +Чтобы работать с асинхронным FastAPI приложением в ваших обычных тестовых функциях `def`, используя стандартный pytest, `TestClient` внутри себя делает некоторую магию. Но эта магия перестает работать, когда мы используем его внутри асинхронных функций. Запуская наши тесты асинхронно, мы больше не можем использовать `TestClient` внутри наших тестовых функций. + +`TestClient` основан на HTTPX, и, к счастью, мы можем использовать его (`HTTPX`) напрямую для тестирования API. + +## Пример { #example } + +В качестве простого примера, давайте рассмотрим файловую структуру, схожую с описанной в [Большие приложения](../tutorial/bigger-applications.md){.internal-link target=_blank} и [Тестирование](../tutorial/testing.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Файл `main.py`: + +{* ../../docs_src/async_tests/main.py *} + +Файл `test_main.py` содержит тесты для `main.py`, теперь он может выглядеть так: + +{* ../../docs_src/async_tests/test_main.py *} + +## Запуск тестов { #run-it } + +Вы можете запустить свои тесты как обычно: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## Подробнее { #in-detail } + +Маркер `@pytest.mark.anyio` говорит pytest, что тестовая функция должна быть вызвана асинхронно: + +{* ../../docs_src/async_tests/test_main.py hl[7] *} + +/// tip | Подсказка + +Обратите внимание, что тестовая функция теперь `async def` вместо простого `def`, как это было при использовании `TestClient`. + +/// + +Затем мы можем создать `AsyncClient` со ссылкой на приложение и посылать асинхронные запросы, используя `await`. + +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} + +Это эквивалентно следующему: + +```Python +response = client.get('/') +``` + +...которое мы использовали для отправки наших запросов с `TestClient`. + +/// tip | Подсказка + +Обратите внимание, что мы используем async/await с `AsyncClient` - запрос асинхронный. + +/// + +/// warning | Внимание + +Если ваше приложение полагается на lifespan события, то `AsyncClient` не запустит эти события. Чтобы обеспечить их срабатывание используйте `LifespanManager` из florimondmanca/asgi-lifespan. + +/// + +## Вызов других асинхронных функций { #other-asynchronous-function-calls } + +Теперь тестовая функция стала асинхронной, поэтому внутри нее вы можете вызывать также и другие `async` функции, не связанные с отправлением запросов в ваше FastAPI приложение. Как если бы вы вызывали их в любом другом месте вашего кода. + +/// tip | Подсказка + +Если вы столкнулись с `RuntimeError: Task attached to a different loop` при вызове асинхронных функций в ваших тестах (например, при использовании MongoDB's MotorClient), то не забывайте инициализировать объекты, которым нужен цикл событий (event loop), только внутри асинхронных функций, например, в `'@app.on_event("startup")` callback. + +/// diff --git a/docs/ru/docs/advanced/behind-a-proxy.md b/docs/ru/docs/advanced/behind-a-proxy.md new file mode 100644 index 0000000000..281cb7f735 --- /dev/null +++ b/docs/ru/docs/advanced/behind-a-proxy.md @@ -0,0 +1,458 @@ +# За прокси‑сервером { #behind-a-proxy } + +Во многих случаях перед приложением FastAPI используется прокси‑сервер, например Traefik или Nginx. + +Такие прокси могут обрабатывать HTTPS‑сертификаты и многое другое. + +## Пересылаемые заголовки прокси { #proxy-forwarded-headers } + +Прокси перед вашим приложением обычно на лету добавляет некоторые HTTP‑заголовки перед отправкой запроса на ваш сервер, чтобы сообщить ему, что запрос был переслан прокси, а также передать исходный (публичный) URL (включая домен), информацию об использовании HTTPS и т.д. + +Программа сервера (например, Uvicorn, запущенный через FastAPI CLI) умеет интерпретировать эти заголовки и передавать соответствующую информацию вашему приложению. + +Но из соображений безопасности, пока сервер не уверен, что находится за доверенным прокси, он не будет интерпретировать эти заголовки. + +/// note | Технические детали + +Заголовки прокси: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### Включить пересылаемые заголовки прокси { #enable-proxy-forwarded-headers } + +Вы можете запустить FastAPI CLI с опцией командной строки `--forwarded-allow-ips` и передать IP‑адреса, которым следует доверять при чтении этих пересылаемых заголовков. + +Если указать `--forwarded-allow-ips="*"`, приложение будет доверять всем входящим IP. + +Если ваш сервер находится за доверенным прокси и только прокси обращается к нему, этого достаточно, чтобы он принимал IP этого прокси. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Редиректы с HTTPS { #redirects-with-https } + +Например, вы объявили операцию пути `/items/`: + +{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} + +Если клиент обратится к `/items`, по умолчанию произойдёт редирект на `/items/`. + +Но до установки опции `--forwarded-allow-ips` редирект может вести на `http://localhost:8000/items/`. + +Однако приложение может быть доступно по `https://mysuperapp.com`, и редирект должен вести на `https://mysuperapp.com/items/`. + +Указав `--proxy-headers`, FastAPI сможет редиректить на корректный адрес. 😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | Совет + +Если хотите узнать больше об HTTPS, смотрите руководство [О HTTPS](../deployment/https.md){.internal-link target=_blank}. + +/// + +### Как работают пересылаемые заголовки прокси + +Ниже показано, как прокси добавляет пересылаемые заголовки между клиентом и сервером приложения: + +```mermaid +sequenceDiagram + participant Client as Клиент + participant Proxy as Прокси/Балансировщик нагрузки + participant Server as FastAPI-сервер + + Client->>Proxy: HTTPS-запрос
Host: mysuperapp.com
Path: /items + + Note over Proxy: Прокси-сервер добавляет пересылаемые заголовки + + Proxy->>Server: HTTP-запрос
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server интерпретирует HTTP-заголовки
(если --forwarded-allow-ips установлен) + + Server->>Proxy: HTTP-ответ
с верными HTTPS URLs + + Proxy->>Client: HTTPS-ответ +``` + +Прокси перехватывает исходный клиентский запрос и добавляет специальные пересылаемые заголовки (`X-Forwarded-*`) перед передачей запроса на сервер приложения. + +Эти заголовки сохраняют информацию об исходном запросе, которая иначе была бы потеряна: + +* X-Forwarded-For: исходный IP‑адрес клиента +* X-Forwarded-Proto: исходный протокол (`https`) +* X-Forwarded-Host: исходный хост (`mysuperapp.com`) + +Когда FastAPI CLI сконфигурирован с `--forwarded-allow-ips`, он доверяет этим заголовкам и использует их, например, чтобы формировать корректные URL в редиректах. + +## Прокси с функцией удаления префикса пути { #proxy-with-a-stripped-path-prefix } + +Прокси может добавлять к вашему приложению префикс пути (размещать приложение по пути с дополнительным префиксом). + +В таких случаях вы можете использовать `root_path` для настройки приложения. + +Механизм `root_path` определён спецификацией ASGI (на которой построен FastAPI, через Starlette). + +`root_path` используется для обработки таких специфических случаев. + +Он также используется внутри при монтировании вложенных приложений. + +Прокси с функцией удаления префикса пути в этом случае означает, что вы объявляете путь `/app` в коде, а затем добавляете сверху слой (прокси), который размещает ваше приложение FastAPI под путём вида `/api/v1`. + +Тогда исходный путь `/app` фактически будет обслуживаться по адресу `/api/v1/app`. + +Хотя весь ваш код написан с расчётом, что путь один — `/app`. + +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} + +Прокси будет «обрезать» префикс пути на лету перед передачей запроса на сервер приложения (скорее всего Uvicorn, запущенный через FastAPI CLI), поддерживая у вашего приложения иллюзию, что его обслуживают по `/app`, чтобы вам не пришлось менять весь код и добавлять префикс `/api/v1`. + +До этого момента всё будет работать как обычно. + +Но когда вы откроете встроенный интерфейс документации (фронтенд), он будет ожидать получить схему OpenAPI по адресу `/openapi.json`, а не `/api/v1/openapi.json`. + +Поэтому фронтенд (который работает в браузере) попытается обратиться к `/openapi.json` и не сможет получить схему OpenAPI. + +Так как для нашего приложения используется прокси с префиксом пути `/api/v1`, фронтенду нужно забирать схему OpenAPI по `/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 | Совет + +IP `0.0.0.0` обычно означает, что программа слушает на всех IP‑адресах, доступных на этой машине/сервере. + +/// + +Интерфейсу документации также нужна схема OpenAPI, в которой будет указано, что этот API `server` находится по пути `/api/v1` (за прокси). Например: + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // Здесь ещё что-то + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // Здесь ещё что-то + } +} +``` + +В этом примере «Proxy» может быть, например, Traefik. А сервером будет что‑то вроде FastAPI CLI с Uvicorn, на котором запущено ваше приложение FastAPI. + +### Указание `root_path` { #providing-the-root-path } + +Для этого используйте опцию командной строки `--root-path`, например так: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Если вы используете Hypercorn, у него тоже есть опция `--root-path`. + +/// note | Технические детали + +Спецификация ASGI определяет `root_path` для такого случая. + +А опция командной строки `--root-path` передаёт этот `root_path`. + +/// + +### Проверка текущего `root_path` { #checking-the-current-root-path } + +Вы можете получить текущий `root_path`, используемый вашим приложением для каждого запроса, — он входит в словарь `scope` (часть спецификации ASGI). + +Здесь мы добавляем его в сообщение лишь для демонстрации. + +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} + +Затем, если вы запустите Uvicorn так: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --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 { #setting-the-root-path-in-the-fastapi-app } + +Если нет возможности передать опцию командной строки `--root-path` (или аналог), вы можете указать параметр `root_path` при создании приложения FastAPI: + +{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} + +Передача `root_path` в `FastAPI` эквивалентна опции командной строки `--root-path` для Uvicorn или Hypercorn. + +### О `root_path` { #about-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 ожидает, что прокси обратится к нему по `http://127.0.0.1:8000/app`, а уже задача прокси — добавить сверху префикс `/api/v1`. + +## О прокси с урезанным префиксом пути { #about-proxies-with-a-stripped-path-prefix } + +Помните, что прокси с урезанным префиксом пути — лишь один из вариантов настройки. + +Во многих случаях по умолчанию прокси будет без урезанного префикса пути. + +В таком случае (без урезанного префикса) прокси слушает, например, по адресу `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 { #testing-locally-with-traefik } + +Вы можете легко поэкспериментировать локально с урезанным префиксом пути, используя Traefik. + +Скачайте Traefik — это один бинарный файл; распакуйте архив и запустите его прямо из терминала. + +Затем создайте файл `traefik.toml` со следующим содержимым: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +Это говорит Traefik слушать порт 9999 и использовать другой файл `routes.toml`. + +/// tip | Совет + +Мы используем порт 9999 вместо стандартного HTTP‑порта 80, чтобы не нужно было запускать с правами администратора (`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`. + +Далее Traefik будет проксировать запросы на ваш Uvicorn, работающий на `http://127.0.0.1:8000`. + +Теперь запустите Traefik: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +И запустите приложение с опцией `--root-path`: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Проверьте ответы { #check-the-responses } + +Теперь, если вы перейдёте на URL с портом 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`. + +/// + +А теперь откройте URL с портом Traefik и префиксом пути: http://127.0.0.1:9999/api/v1/app. + +Мы получим тот же ответ: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +но уже по URL с префиксом, который добавляет прокси: `/api/v1`. + +Разумеется, задумывается, что все будут обращаться к приложению через прокси, поэтому вариант с префиксом пути `/api/v1` является «правильным». + +А вариант без префикса (`http://127.0.0.1:8000/app`), выдаваемый напрямую Uvicorn, предназначен исключительно для того, чтобы прокси (Traefik) мог к нему обращаться. + +Это демонстрирует, как прокси (Traefik) использует префикс пути и как сервер (Uvicorn) использует `root_path`, переданный через опцию `--root-path`. + +### Проверьте интерфейс документации { #check-the-docs-ui } + +А вот самое интересное. ✨ + +«Официальный» способ доступа к приложению — через прокси с заданным префиксом пути. Поэтому, как и ожидается, если открыть интерфейс документации, отдаваемый напрямую Uvicorn, без префикса пути в URL, он не будет работать, так как предполагается доступ через прокси. + +Проверьте по адресу http://127.0.0.1:8000/docs: + + + +А вот если открыть интерфейс документации по «официальному» URL через прокси на порту `9999`, по `/api/v1/docs`, всё работает корректно! 🎉 + +Проверьте по адресу http://127.0.0.1:9999/api/v1/docs: + + + +Именно как и хотелось. ✔️ + +Это потому, что FastAPI использует `root_path`, чтобы создать в OpenAPI сервер по умолчанию с URL из `root_path`. + +## Дополнительные серверы { #additional-servers } + +/// warning | Предупреждение + +Это более продвинутый сценарий. Можно пропустить. + +/// + +По умолчанию FastAPI создаёт в схеме OpenAPI `server` с URL из `root_path`. + +Но вы также можете указать дополнительные `servers`, например, если хотите, чтобы один и тот же интерфейс документации работал и со стейджингом, и с продакшн. + +Если вы передадите свой список `servers` и при этом задан `root_path` (потому что ваш API работает за прокси), FastAPI вставит «server» с этим `root_path` в начало списка. + +Например: + +{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} + +Будет сгенерирована схема OpenAPI примерно такая: + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // Здесь ещё что-то + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // Здесь ещё что-то + } +} +``` + +/// tip | Совет + +Обратите внимание на автоматически добавленный сервер с `url` равным `/api/v1`, взятым из `root_path`. + +/// + +В интерфейсе документации по адресу http://127.0.0.1:9999/api/v1/docs это будет выглядеть так: + + + +/// tip | Совет + +Интерфейс документации будет взаимодействовать с сервером, который вы выберете. + +/// + +### Отключить автоматическое добавление сервера из `root_path` { #disable-automatic-server-from-root-path } + +Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя `root_path`, укажите параметр `root_path_in_servers=False`: + +{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} + +и тогда этот сервер не будет добавлен в схему OpenAPI. + +## Монтирование вложенного приложения { #mounting-a-sub-application } + +Если вам нужно смонтировать вложенное приложение (как описано в [Вложенные приложения — монтирование](sub-applications.md){.internal-link target=_blank}), и при этом вы используете прокси с `root_path`, делайте это обычным образом — всё будет работать, как ожидается. + +FastAPI умно использует `root_path` внутри, так что всё просто работает. ✨ diff --git a/docs/ru/docs/advanced/custom-response.md b/docs/ru/docs/advanced/custom-response.md new file mode 100644 index 0000000000..2c238bd95b --- /dev/null +++ b/docs/ru/docs/advanced/custom-response.md @@ -0,0 +1,312 @@ +# Кастомные ответы — HTML, поток, файл и другие { #custom-response-html-stream-file-others } + +По умолчанию **FastAPI** возвращает ответы с помощью `JSONResponse`. + +Вы можете переопределить это, вернув `Response` напрямую, как показано в разделе [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}. + +Но если вы возвращаете `Response` напрямую (или любой его подкласс, например `JSONResponse`), данные не будут автоматически преобразованы (даже если вы объявили `response_model`), и документация не будет автоматически сгенерирована (например, со специфичным «типом содержимого» в HTTP-заголовке `Content-Type` как частью сгенерированного OpenAPI). + +Но вы можете также объявить `Response`, который хотите использовать (например, любой подкласс `Response`), в декораторе операции пути, используя параметр `response_class`. + +Содержимое, которое вы возвращаете из своей функции-обработчика пути, будет помещено внутрь этого `Response`. + +И если у этого `Response` тип содержимого JSON (`application/json`), как в случае с `JSONResponse` и `UJSONResponse`, данные, которые вы возвращаете, будут автоматически преобразованы (и отфильтрованы) любым объявленным вами в декораторе операции пути Pydantic `response_model`. + +/// note | Примечание + +Если вы используете класс ответа без типа содержимого, FastAPI будет ожидать, что у вашего ответа нет содержимого, поэтому он не будет документировать формат ответа в сгенерированной документации OpenAPI. + +/// + +## Используйте `ORJSONResponse` { #use-orjsonresponse } + +Например, если вы выжимаете максимум производительности, вы можете установить и использовать `orjson` и задать ответ как `ORJSONResponse`. + +Импортируйте класс (подкласс) `Response`, который вы хотите использовать, и объявите его в декораторе операции пути. + +Для больших ответов возвращать `Response` напрямую значительно быстрее, чем возвращать словарь. + +Это потому, что по умолчанию FastAPI проверяет каждый элемент внутри и убеждается, что он сериализуем в JSON, используя тот же [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}, объяснённый в руководстве. Это позволяет возвращать **произвольные объекты**, например модели из базы данных. + +Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо в JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительных накладных расходов, которые FastAPI понёс бы, пропуская возвращаемое содержимое через `jsonable_encoder` перед передачей в класс ответа. + +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} + +/// info | Информация + +Параметр `response_class` также используется для указания «типа содержимого» ответа. + +В этом случае HTTP-заголовок `Content-Type` будет установлен в `application/json`. + +И это будет задокументировано как таковое в OpenAPI. + +/// + +/// tip | Совет + +`ORJSONResponse` доступен только в FastAPI, а не в Starlette. + +/// + +## HTML-ответ { #html-response } + +Чтобы вернуть ответ с HTML напрямую из **FastAPI**, используйте `HTMLResponse`. + +- Импортируйте `HTMLResponse`. +- Передайте `HTMLResponse` в параметр `response_class` вашего декоратора операции пути. + +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} + +/// info | Информация + +Параметр `response_class` также используется для указания «типа содержимого» ответа. + +В этом случае HTTP-заголовок `Content-Type` будет установлен в `text/html`. + +И это будет задокументировано как таковое в OpenAPI. + +/// + +### Вернуть `Response` { #return-a-response } + +Как показано в разделе [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, вы также можете переопределить ответ прямо в своей операции пути, просто вернув его. + +Тот же пример сверху, возвращающий `HTMLResponse`, может выглядеть так: + +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} + +/// warning | Предупреждение + +`Response`, возвращённый напрямую вашей функцией-обработчиком пути, не будет задокументирован в OpenAPI (например, `Content-Type` нне будет задокументирова) и не будет виден в автоматически сгенерированной интерактивной документации. + +/// + +/// info | Информация + +Разумеется, фактические заголовок `Content-Type`, статус-код и т.д. возьмутся из объекта `Response`, который вы вернули. + +/// + +### Задокументировать в OpenAPI и переопределить `Response` { #document-in-openapi-and-override-response } + +Если вы хотите переопределить ответ внутри функции, но при этом задокументировать «тип содержимого» в OpenAPI, вы можете использовать параметр `response_class` И вернуть объект `Response`. + +Тогда `response_class` будет использоваться только для документирования *операции пути* в OpenAPI, а ваш `Response` будет использован как есть. + +#### Вернуть `HTMLResponse` напрямую { #return-an-htmlresponse-directly } + +Например, это может быть что-то вроде: + +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} + +В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в `str`. + +Возвращая результат вызова `generate_html_response()`, вы уже возвращаете `Response`, который переопределит поведение **FastAPI** по умолчанию. + +Но поскольку вы также передали `HTMLResponse` в `response_class`, **FastAPI** будет знать, как задокументировать это в OpenAPI и интерактивной документации как HTML с `text/html`: + + + +## Доступные ответы { #available-responses } + +Ниже перечислены некоторые доступные классы ответов. + +Учтите, что вы можете использовать `Response`, чтобы вернуть что угодно ещё, или даже создать собственный подкласс. + +/// note | Технические детали + +Вы также могли бы использовать `from starlette.responses import HTMLResponse`. + +**FastAPI** предоставляет те же `starlette.responses` как `fastapi.responses` для вашего удобства как разработчика. Но большинство доступных классов ответов приходят непосредственно из Starlette. + +/// + +### `Response` { #response } + +Базовый класс `Response`, от него наследуются все остальные ответы. + +Его можно возвращать напрямую. + +Он принимает следующие параметры: + +- `content` — `str` или `bytes`. +- `status_code` — целое число, HTTP статус-код. +- `headers` — словарь строк. +- `media_type` — строка, задающая тип содержимого. Например, `"text/html"`. + +FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Также будет добавлен заголовок Content-Type, основанный на `media_type` и с добавлением charset для текстовых типов. + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +### `HTMLResponse` { #htmlresponse } + +Принимает текст или байты и возвращает HTML-ответ, как описано выше. + +### `PlainTextResponse` { #plaintextresponse } + +Принимает текст или байты и возвращает ответ в виде простого текста. + +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} + +### `JSONResponse` { #jsonresponse } + +Принимает данные и возвращает ответ, кодированный как `application/json`. + +Это ответ по умолчанию, используемый в **FastAPI**, как было сказано выше. + +### `ORJSONResponse` { #orjsonresponse } + +Быстрая альтернативная реализация JSON-ответа с использованием `orjson`, как было сказано выше. + +/// info | Информация + +Требуется установка `orjson`, например командой `pip install orjson`. + +/// + +### `UJSONResponse` { #ujsonresponse } + +Альтернативная реализация JSON-ответа с использованием `ujson`. + +/// info | Информация + +Требуется установка `ujson`, например командой `pip install ujson`. + +/// + +/// warning | Предупреждение + +`ujson` менее аккуратен, чем встроенная реализация Python, в обработке некоторых крайних случаев. + +/// + +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} + +/// tip | Совет + +Возможно, `ORJSONResponse` окажется более быстрым вариантом. + +/// + +### `RedirectResponse` { #redirectresponse } + +Возвращает HTTP-редирект. По умолчанию использует статус-код 307 (Temporary Redirect — временное перенаправление). + +Вы можете вернуть `RedirectResponse` напрямую: + +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} + +--- + +Или можно использовать его в параметре `response_class`: + +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} + +Если вы сделаете так, то сможете возвращать URL напрямую из своей функции-обработчика пути. + +В этом случае будет использован статус-код по умолчанию для `RedirectResponse`, то есть `307`. + +--- + +Также вы можете использовать параметр `status_code` в сочетании с параметром `response_class`: + +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} + +### `StreamingResponse` { #streamingresponse } + +Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа потоково. + +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} + +#### Использование `StreamingResponse` с файлоподобными объектами { #using-streamingresponse-with-file-like-objects } + +Если у вас есть файлоподобный объект (например, объект, возвращаемый `open()`), вы можете создать функцию-генератор для итерации по этому файлоподобному объекту. + +Таким образом, вам не нужно сначала читать всё в память, вы можете передать эту функцию-генератор в `StreamingResponse` и вернуть его. + +Это включает многие библиотеки для работы с облачным хранилищем, обработки видео и т.д. + +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} + +1. Это функция-генератор. Она является «функцией-генератором», потому что содержит оператор(ы) `yield` внутри. +2. Используя блок `with`, мы гарантируем, что файлоподобный объект будет закрыт после завершения работы функции-генератора. То есть после того, как она закончит отправку ответа. +3. Этот `yield from` говорит функции итерироваться по объекту с именем `file_like`. И затем, для каждой итерации, отдавать эту часть как исходящую из этой функции-генератора (`iterfile`). + + Таким образом, это функция-генератор, которая внутренне передаёт работу по «генерации» чему-то другому. + + Делая это таким образом, мы можем поместить её в блок `with` и тем самым гарантировать, что файлоподобный объект будет закрыт после завершения. + +/// tip | Совет + +Заметьте, что здесь мы используем стандартный `open()`, который не поддерживает `async` и `await`, поэтому объявляем операцию пути обычной `def`. + +/// + +### `FileResponse` { #fileresponse } + +Асинхронно отправляет файл как ответ. + +Для создания экземпляра принимает иной набор аргументов, чем другие типы ответов: + +- `path` — путь к файлу, который будет отправлен. +- `headers` — любые дополнительные заголовки для включения, в виде словаря. +- `media_type` — строка, задающая тип содержимого. Если не задан, для определения типа содержимого будет использовано имя файла или путь. +- `filename` — если задан, будет включён в заголовок ответа `Content-Disposition`. + +Файловые ответы будут содержать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`. + +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} + +Вы также можете использовать параметр `response_class`: + +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} + +В этом случае вы можете возвращать путь к файлу напрямую из своей функции-обработчика пути. + +## Пользовательский класс ответа { #custom-response-class } + +Вы можете создать собственный класс ответа, унаследовавшись от `Response`, и использовать его. + +Например, предположим, что вы хотите использовать `orjson`, но с некоторыми пользовательскими настройками, которые не используются во встроенном классе `ORJSONResponse`. + +Скажем, вы хотите, чтобы возвращался отформатированный JSON с отступами, то есть хотите использовать опцию orjson `orjson.OPT_INDENT_2`. + +Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать — реализовать метод `Response.render(content)`, который возвращает содержимое как `bytes`: + +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} + +Теперь вместо того, чтобы возвращать: + +```json +{"message": "Hello World"} +``` + +...этот ответ вернёт: + +```json +{ + "message": "Hello World" +} +``` + +Разумеется, вы наверняка найдёте гораздо более полезные способы воспользоваться этим, чем просто форматирование JSON. 😉 + +## Класс ответа по умолчанию { #default-response-class } + +При создании экземпляра класса **FastAPI** или `APIRouter` вы можете указать, какой класс ответа использовать по умолчанию. + +Параметр, который это определяет, — `default_response_class`. + +В примере ниже **FastAPI** будет использовать `ORJSONResponse` по умолчанию во всех операциях пути вместо `JSONResponse`. + +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} + +/// tip | Совет + +Вы по-прежнему можете переопределять `response_class` в операциях пути, как и раньше. + +/// + +## Дополнительная документация { #additional-documentation } + +Вы также можете объявить тип содержимого и многие другие детали в OpenAPI с помощью `responses`: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/advanced/dataclasses.md b/docs/ru/docs/advanced/dataclasses.md new file mode 100644 index 0000000000..816f744048 --- /dev/null +++ b/docs/ru/docs/advanced/dataclasses.md @@ -0,0 +1,95 @@ +# Использование dataclasses { #using-dataclasses } + +FastAPI построен поверх **Pydantic**, и я показывал вам, как использовать Pydantic-модели для объявления HTTP-запросов и HTTP-ответов. + +Но FastAPI также поддерживает использование `dataclasses` тем же способом: + +{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} + +Это по-прежнему поддерживается благодаря **Pydantic**, так как в нём есть встроенная поддержка `dataclasses`. + +Так что даже если в коде выше Pydantic не используется явно, FastAPI использует Pydantic, чтобы конвертировать стандартные dataclasses в собственный вариант dataclasses от Pydantic. + +И, конечно, поддерживаются те же возможности: + +- валидация данных +- сериализация данных +- документирование данных и т.д. + +Это работает так же, как с Pydantic-моделями. И на самом деле под капотом это достигается тем же образом, с использованием Pydantic. + +/// info | Информация + +Помните, что dataclasses не умеют всего того, что умеют Pydantic-модели. + +Поэтому вам всё ещё может потребоваться использовать Pydantic-модели. + +Но если у вас уже есть набор dataclasses, это полезный приём — задействовать их для веб-API на FastAPI. 🤓 + +/// + +## Dataclasses в `response_model` { #dataclasses-in-response-model } + +Вы также можете использовать `dataclasses` в параметре `response_model`: + +{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} + +Этот dataclass будет автоматически преобразован в Pydantic dataclass. + +Таким образом, его схема появится в интерфейсе документации API: + + + +## Dataclasses во вложенных структурах данных { #dataclasses-in-nested-data-structures } + +Вы также можете комбинировать `dataclasses` с другими аннотациями типов, чтобы создавать вложенные структуры данных. + +В некоторых случаях вам всё же может понадобиться использовать версию `dataclasses` из Pydantic. Например, если у вас возникают ошибки с автоматически генерируемой документацией API. + +В таком случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, которая является полностью совместимой заменой (drop-in replacement): + +{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} + +1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`. + +2. `pydantic.dataclasses` — полностью совместимая замена (drop-in replacement) для `dataclasses`. + +3. Dataclass `Author` содержит список dataclass `Item`. + +4. Dataclass `Author` используется в параметре `response_model`. + +5. Вы можете использовать и другие стандартные аннотации типов вместе с dataclasses в качестве тела запроса. + + В этом случае это список dataclass `Item`. + +6. Здесь мы возвращаем словарь, содержащий `items`, который является списком dataclass. + + FastAPI по-прежнему способен сериализовать данные в JSON. + +7. Здесь `response_model` использует аннотацию типа — список dataclass `Author`. + + Снова, вы можете комбинировать `dataclasses` со стандартными аннотациями типов. + +8. Обратите внимание, что эта *функция-обработчик пути* использует обычный `def` вместо `async def`. + + Как и всегда в FastAPI, вы можете сочетать `def` и `async def` по необходимости. + + Если хотите освежить в памяти, когда что использовать, посмотрите раздел _"Нет времени?"_ в документации про [`async` и `await`](../async.md#in-a-hurry){.internal-link target=_blank}. + +9. Эта *функция-обработчик пути* возвращает не dataclasses (хотя могла бы), а список словарей с внутренними данными. + + FastAPI использует параметр `response_model` (в котором заданы dataclasses), чтобы преобразовать HTTP-ответ. + +Вы можете комбинировать `dataclasses` с другими аннотациями типов множеством способов, чтобы формировать сложные структуры данных. + +Смотрите подсказки в коде выше, чтобы увидеть более конкретные детали. + +## Узнать больше { #learn-more } + +Вы также можете комбинировать `dataclasses` с другими Pydantic-моделями, наследоваться от них, включать их в свои модели и т.д. + +Чтобы узнать больше, посмотрите документацию Pydantic о dataclasses. + +## Версия { #version } + +Доступно начиная с версии FastAPI `0.67.0`. 🔖 diff --git a/docs/ru/docs/advanced/events.md b/docs/ru/docs/advanced/events.md new file mode 100644 index 0000000000..20d1df98a9 --- /dev/null +++ b/docs/ru/docs/advanced/events.md @@ -0,0 +1,165 @@ +# События lifespan { #lifespan-events } + +Вы можете определить логику (код), которую нужно выполнить перед тем, как приложение начнет запускаться. Это означает, что этот код будет выполнен один раз, перед тем как приложение начнет получать HTTP-запросы. + +Аналогично, вы можете определить логику (код), которую нужно выполнить, когда приложение завершает работу. В этом случае код будет выполнен один раз, после обработки, возможно, многих запросов. + +Поскольку этот код выполняется до того, как приложение начинает принимать запросы, и сразу после того, как оно заканчивает их обрабатывать, он охватывает весь lifespan (жизненный цикл) приложения (слово «lifespan» станет важным через секунду 😉). + +Это может быть очень полезно для настройки ресурсов, которые нужны для всего приложения, которые разделяются между запросами и/или которые нужно затем очистить. Например, пул подключений к базе данных или загрузка общей модели Машинного обучения. + +## Вариант использования { #use-case } + +Начнем с примера варианта использования, а затем посмотрим, как это решить. + +Представим, что у вас есть несколько моделей Машинного обучения, которые вы хотите использовать для обработки запросов. 🤖 + +Эти же модели разделяются между запросами, то есть это не одна модель на запрос, не одна на пользователя и т.п. + +Представим, что загрузка модели может занимать довольно много времени, потому что ей нужно прочитать много данных с диска. Поэтому вы не хотите делать это для каждого запроса. + +Вы могли бы загрузить её на верхнем уровне модуля/файла, но это означало бы, что модель загружается даже если вы просто запускаете простой автоматический тест; тогда этот тест будет медленным, так как ему придется ждать загрузки модели перед запуском независимой части кода. + +Именно это мы и решим: давайте загружать модель перед тем, как начнётся обработка запросов, но только непосредственно перед тем, как приложение начнет принимать запросы, а не во время загрузки кода. + +## Lifespan { #lifespan } + +Вы можете определить логику для startup и shutdown, используя параметр `lifespan` приложения `FastAPI` и «менеджер контекста» (через секунду покажу что это). + +Начнем с примера, а затем разберём его подробнее. + +Мы создаём асинхронную функцию `lifespan()` с `yield` примерно так: + +{* ../../docs_src/events/tutorial003.py hl[16,19] *} + +Здесь мы симулируем дорогую операцию startup по загрузке модели, помещая (фиктивную) функцию модели в словарь с моделями Машинного обучения до `yield`. Этот код будет выполнен до того, как приложение начнет принимать запросы, во время startup. + +А затем сразу после `yield` мы выгружаем модель. Этот код будет выполнен после того, как приложение закончит обрабатывать запросы, непосредственно перед shutdown. Это может, например, освободить ресурсы, такие как память или GPU. + +/// tip | Совет + +`shutdown` произойдёт, когда вы останавливаете приложение. + +Возможно, вам нужно запустить новую версию, или вы просто устали от него. 🤷 + +/// + +### Функция lifespan { #lifespan-function } + +Первое, на что стоит обратить внимание, — мы определяем асинхронную функцию с `yield`. Это очень похоже на Зависимости с `yield`. + +{* ../../docs_src/events/tutorial003.py hl[14:19] *} + +Первая часть функции, до `yield`, будет выполнена до запуска приложения. + +А часть после `yield` будет выполнена после завершения работы приложения. + +### Асинхронный менеджер контекста { #async-context-manager } + +Если присмотреться, функция декорирована `@asynccontextmanager`. + +Это превращает функцию в «асинхронный менеджер контекста». + +{* ../../docs_src/events/tutorial003.py hl[1,13] *} + +Менеджер контекста в Python — это то, что можно использовать в операторе `with`. Например, `open()` можно использовать как менеджер контекста: + +```Python +with open("file.txt") as file: + file.read() +``` + +В последних версиях Python есть также асинхронный менеджер контекста. Его используют с `async with`: + +```Python +async with lifespan(app): + await do_stuff() +``` + +Когда вы создаёте менеджер контекста или асинхронный менеджер контекста, как выше, он перед входом в блок `with` выполнит код до `yield`, а после выхода из блока `with` выполнит код после `yield`. + +В нашем примере выше мы не используем его напрямую, а передаём его в FastAPI, чтобы он использовал его сам. + +Параметр `lifespan` приложения `FastAPI` принимает асинхронный менеджер контекста, поэтому мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`. + +{* ../../docs_src/events/tutorial003.py hl[22] *} + +## Альтернативные события (устаревшие) { #alternative-events-deprecated } + +/// warning | Предупреждение + +Рекомендуемый способ обрабатывать startup и shutdown — использовать параметр `lifespan` приложения `FastAPI`, как описано выше. Если вы укажете параметр `lifespan`, обработчики событий `startup` и `shutdown` больше вызываться не будут. Либо всё через `lifespan`, либо всё через события — не одновременно. + +Эту часть, скорее всего, можно пропустить. + +/// + +Есть альтернативный способ определить логику, которую нужно выполнить во время startup и во время shutdown. + +Вы можете определить обработчики событий (функции), которые нужно выполнить до старта приложения или при его завершении. + +Эти функции можно объявить с `async def` или обычным `def`. + +### Событие `startup` { #startup-event } + +Чтобы добавить функцию, которую нужно запустить до старта приложения, объявите её как обработчик события `"startup"`: + +{* ../../docs_src/events/tutorial001.py hl[8] *} + +В этом случае функция-обработчик события `startup` инициализирует «базу данных» items (это просто `dict`) некоторыми значениями. + +Вы можете добавить более одного обработчика события. + +И ваше приложение не начнет принимать запросы, пока все обработчики события `startup` не завершатся. + +### Событие `shutdown` { #shutdown-event } + +Чтобы добавить функцию, которую нужно запустить при завершении работы приложения, объявите её как обработчик события `"shutdown"`: + +{* ../../docs_src/events/tutorial002.py hl[6] *} + +Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`. + +/// info | Информация + +В функции `open()` параметр `mode="a"` означает «добавление» (append), то есть строка будет добавлена в конец файла, без перезаписи предыдущего содержимого. + +/// + +/// tip | Совет + +Обратите внимание, что в этом случае мы используем стандартную Python-функцию `open()`, которая взаимодействует с файлом. + +То есть это I/O (ввод/вывод), требующий «ожидания» записи на диск. + +Но `open()` не использует `async` и `await`. + +Поэтому мы объявляем обработчик события обычным `def` вместо `async def`. + +/// + +### `startup` и `shutdown` вместе { #startup-and-shutdown-together } + +С высокой вероятностью логика для вашего startup и shutdown связана: вы можете хотеть что-то запустить, а затем завершить, получить ресурс, а затем освободить его и т.д. + +Делать это в отдельных функциях, которые не разделяют общую логику или переменные, сложнее, так как придётся хранить значения в глобальных переменных или использовать похожие приёмы. + +Поэтому теперь рекомендуется использовать `lifespan`, как описано выше. + +## Технические детали { #technical-details } + +Немного технических подробностей для любопытных умников. 🤓 + +Под капотом, в ASGI-технической спецификации, это часть Протокола Lifespan, и он определяет события `startup` и `shutdown`. + +/// info | Информация + +Вы можете прочитать больше про обработчики `lifespan` в Starlette в документации Starlette по Lifespan. + +Включая то, как работать с состоянием lifespan, которое можно использовать в других частях вашего кода. + +/// + +## Подприложения { #sub-applications } + +🚨 Имейте в виду, что эти события lifespan (startup и shutdown) будут выполнены только для основного приложения, а не для [Подприложения — Mounts](sub-applications.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/advanced/generate-clients.md b/docs/ru/docs/advanced/generate-clients.md new file mode 100644 index 0000000000..ee52412c6d --- /dev/null +++ b/docs/ru/docs/advanced/generate-clients.md @@ -0,0 +1,208 @@ +# Генерация SDK { #generating-sdks } + +Поскольку **FastAPI** основан на спецификации **OpenAPI**, его API можно описать в стандартном формате, понятном множеству инструментов. + +Это упрощает генерацию актуальной **документации**, клиентских библиотек (**SDKs**) на разных языках, а также **тестирования** или **воркфлоу автоматизации**, которые остаются синхронизированными с вашим кодом. + +В этом руководстве вы узнаете, как сгенерировать **TypeScript SDK** для вашего бэкенда на FastAPI. + +## Генераторы SDK с открытым исходным кодом { #open-source-sdk-generators } + +Гибкий вариант — OpenAPI Generator, который поддерживает **многие языки программирования** и умеет генерировать SDK из вашей спецификации OpenAPI. + +Для **TypeScript‑клиентов** Hey API — специализированное решение, обеспечивающее оптимальный опыт для экосистемы TypeScript. + +Больше генераторов SDK можно найти на OpenAPI.Tools. + +/// tip | Совет + +FastAPI автоматически генерирует спецификации **OpenAPI 3.1**, поэтому любой используемый инструмент должен поддерживать эту версию. + +/// + +## Генераторы SDK от спонсоров FastAPI { #sdk-generators-from-fastapi-sponsors } + +В этом разделе представлены решения с **венчурной поддержкой** и **поддержкой компаний** от компаний, которые спонсируют FastAPI. Эти продукты предоставляют **дополнительные возможности** и **интеграции** сверх высококачественно генерируемых SDK. + +Благодаря ✨ [**спонсорству FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ эти компании помогают обеспечивать, чтобы фреймворк и его **экосистема** оставались здоровыми и **устойчивыми**. + +Их спонсорство также демонстрирует серьёзную приверженность **сообществу** FastAPI (вам), показывая, что им важно не только предоставлять **отличный сервис**, но и поддерживать **надёжный и процветающий фреймворк** FastAPI. 🙇 + +Например, вы можете попробовать: + +* Speakeasy +* Stainless +* liblab + +Некоторые из этих решений также могут быть open source или иметь бесплатные тарифы, так что вы сможете попробовать их без финансовых затрат. Другие коммерческие генераторы SDK доступны и их можно найти онлайн. 🤓 + +## Создать TypeScript SDK { #create-a-typescript-sdk } + +Начнём с простого приложения FastAPI: + +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} + +Обратите внимание, что *операции пути (обработчики пути)* определяют модели, которые они используют для полезной нагрузки запроса и полезной нагрузки ответа, с помощью моделей `Item` и `ResponseMessage`. + +### Документация API { #api-docs } + +Если перейти на `/docs`, вы увидите **схемы** данных, отправляемых в запросах и принимаемых в ответах: + + + +Вы видите эти схемы, потому что они были объявлены с моделями в приложении. + +Эта информация доступна в **схеме OpenAPI** приложения и затем отображается в документации API. + +Та же информация из моделей, включённая в OpenAPI, может использоваться для **генерации клиентского кода**. + +### Hey API { #hey-api } + +Как только у нас есть приложение FastAPI с моделями, мы можем использовать Hey API для генерации TypeScript‑клиента. Самый быстрый способ сделать это — через npx. + +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client +``` + +Это сгенерирует TypeScript SDK в `./src/client`. + +Вы можете узнать, как установить `@hey-api/openapi-ts` и почитать о сгенерированном результате на их сайте. + +### Использование SDK { #using-the-sdk } + +Теперь вы можете импортировать и использовать клиентский код. Это может выглядеть так, обратите внимание, что вы получаете автозавершение для методoв: + + + +Вы также получите автозавершение для отправляемой полезной нагрузки: + + + +/// tip | Совет + +Обратите внимание на автозавершение для `name` и `price`, это было определено в приложении FastAPI, в модели `Item`. + +/// + +Вы получите ошибки прямо в редакторе для отправляемых данных: + + + +Объект ответа также будет иметь автозавершение: + + + +## Приложение FastAPI с тегами { #fastapi-app-with-tags } + +Во многих случаях ваше приложение FastAPI будет больше, и вы, вероятно, будете использовать теги, чтобы разделять разные группы *операций пути*. + +Например, у вас может быть раздел для **items** и другой раздел для **users**, и они могут быть разделены тегами: + +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} + +### Генерация TypeScript‑клиента с тегами { #generate-a-typescript-client-with-tags } + +Если вы генерируете клиент для приложения FastAPI с использованием тегов, обычно клиентский код также будет разделён по тегам. + +Таким образом вы сможете иметь всё правильно упорядоченным и сгруппированным в клиентском коде: + + + +В этом случае у вас есть: + +* `ItemsService` +* `UsersService` + +### Имена методов клиента { #client-method-names } + +Сейчас сгенерированные имена методов вроде `createItemItemsPost` выглядят не очень аккуратно: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...это потому, что генератор клиента использует внутренний **ID операции** OpenAPI для каждой *операции пути*. + +OpenAPI требует, чтобы каждый ID операции был уникален среди всех *операций пути*, поэтому FastAPI использует **имя функции**, **путь** и **HTTP‑метод/операцию** для генерации этого ID операции, так как таким образом можно гарантировать уникальность ID операций. + +Но далее я покажу, как это улучшить. 🤓 + +## Пользовательские ID операций и лучшие имена методов { #custom-operation-ids-and-better-method-names } + +Вы можете **изменить** способ **генерации** этих ID операций, чтобы сделать их проще, а имена методов в клиентах — **более простыми**. + +В этом случае вам нужно будет обеспечить, чтобы каждый ID операции был **уникальным** другим способом. + +Например, вы можете гарантировать, что у каждой *операции пути* есть тег, и затем генерировать ID операции на основе **тега** и **имени** *операции пути* (имени функции). + +### Пользовательская функция генерации уникального ID { #custom-generate-unique-id-function } + +FastAPI использует **уникальный ID** для каждой *операции пути*, который применяется для **ID операции**, а также для имён любых необходимых пользовательских моделей запросов или ответов. + +Вы можете кастомизировать эту функцию. Она принимает `APIRoute` и возвращает строку. + +Например, здесь берётся первый тег (скорее всего у вас один тег) и имя *операции пути* (имя функции). + +Затем вы можете передать эту пользовательскую функцию в **FastAPI** через параметр `generate_unique_id_function`: + +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} + +### Генерация TypeScript‑клиента с пользовательскими ID операций { #generate-a-typescript-client-with-custom-operation-ids } + +Теперь, если снова сгенерировать клиент, вы увидите, что имена методов улучшились: + + + +Как видите, теперь имена методов содержат тег, а затем имя функции; больше они не включают информацию из URL‑пути и HTTP‑операции. + +### Предобработка спецификации OpenAPI для генератора клиента { #preprocess-the-openapi-specification-for-the-client-generator } + +Сгенерированном коде всё ещё есть **дублирующаяся информация**. + +Мы уже знаем, что этот метод относится к **items**, потому что это слово есть в `ItemsService` (взято из тега), но при этом имя тега всё ещё добавлено префиксом к имени метода. 😕 + +Скорее всего мы захотим оставить это в OpenAPI в целом, так как это гарантирует, что ID операций будут **уникальны**. + +Но для сгенерированного клиента мы можем **модифицировать** ID операций OpenAPI непосредственно перед генерацией клиентов, чтобы сделать имена методов более приятными и **чистыми**. + +Мы можем скачать OpenAPI JSON в файл `openapi.json`, а затем **убрать этот префикс‑тег** таким скриптом: + +{* ../../docs_src/generate_clients/tutorial004.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +После этого ID операций будут переименованы с чего‑то вроде `items-get_items` просто в `get_items`, и генератор клиента сможет создавать более простые имена методов. + +### Генерация TypeScript‑клиента с предобработанным OpenAPI { #generate-a-typescript-client-with-the-preprocessed-openapi } + +Так как конечный результат теперь в файле `openapi.json`, нужно обновить входное расположение: + +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client +``` + +После генерации нового клиента у вас будут **чистые имена методов**, со всем **автозавершением**, **ошибками прямо в редакторе** и т.д.: + + + +## Преимущества { #benefits } + +При использовании автоматически сгенерированных клиентов вы получите **автозавершение** для: + +* Методов. +* Данных запроса — в теле запроса, query‑параметрах и т.д. +* Данных ответа. + +У вас также будут **ошибки прямо в редакторе** для всего. + +И каждый раз, когда вы обновляете код бэкенда и **перегенерируете** фронтенд, в нём появятся новые *операции пути* как методы, старые будут удалены, а любые другие изменения отразятся в сгенерированном коде. 🤓 + +Это также означает, что если что‑то изменилось, это будет **отражено** в клиентском коде автоматически. И если вы **соберёте** клиент, он завершится с ошибкой, если где‑то есть **несоответствие** в используемых данных. + +Таким образом, вы **обнаружите многие ошибки** очень рано в цикле разработки, вместо того чтобы ждать, когда ошибки проявятся у конечных пользователей в продакшн, и затем пытаться отладить, в чём проблема. ✨ diff --git a/docs/ru/docs/advanced/index.md b/docs/ru/docs/advanced/index.md new file mode 100644 index 0000000000..c0a52c6c14 --- /dev/null +++ b/docs/ru/docs/advanced/index.md @@ -0,0 +1,21 @@ +# Расширенное руководство пользователя { #advanced-user-guide } + +## Дополнительные возможности { #additional-features } + +Основное [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} должно быть достаточно, чтобы познакомить вас со всеми основными функциями **FastAPI**. + +В следующих разделах вы увидите другие варианты, конфигурации и дополнительные возможности. + +/// tip | Совет + +Следующие разделы **не обязательно являются "продвинутыми"**. + +И вполне возможно, что для вашего случая использования решение находится в одном из них. + +/// + +## Сначала прочитайте Учебник - Руководство пользователя { #read-the-tutorial-first } + +Вы все еще можете использовать большинство функций **FastAPI** со знаниями из [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank}. + +И следующие разделы предполагают, что вы уже прочитали его, и предполагают, что вы знаете эти основные идеи. diff --git a/docs/ru/docs/advanced/middleware.md b/docs/ru/docs/advanced/middleware.md new file mode 100644 index 0000000000..82c86b2317 --- /dev/null +++ b/docs/ru/docs/advanced/middleware.md @@ -0,0 +1,97 @@ +# Расширенное использование middleware { #advanced-middleware } + +В основном руководстве вы читали, как добавить [пользовательское middleware](../tutorial/middleware.md){.internal-link target=_blank} в ваше приложение. + +А затем — как работать с [CORS с помощью `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. + +В этом разделе посмотрим, как использовать другие middleware. + +## Добавление ASGI middleware { #adding-asgi-middlewares } + +Так как **FastAPI** основан на Starlette и реализует спецификацию ASGI, вы можете использовать любое ASGI middleware. + +Middleware не обязательно должно быть сделано специально для FastAPI или Starlette — достаточно, чтобы оно соответствовало спецификации ASGI. + +В общем случае ASGI middleware — это классы, которые ожидают получить ASGI‑приложение первым аргументом. + +Поэтому в документации к сторонним ASGI middleware, скорее всего, вы увидите что‑то вроде: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +Но FastAPI (точнее, Starlette) предоставляет более простой способ, который гарантирует корректную обработку внутренних ошибок сервера и корректную работу пользовательских обработчиков исключений. + +Для этого используйте `app.add_middleware()` (как в примере с CORS). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` принимает класс middleware в качестве первого аргумента и любые дополнительные аргументы, которые будут переданы этому middleware. + +## Встроенные middleware { #integrated-middlewares } + +**FastAPI** включает несколько middleware для распространённых сценариев. Ниже рассмотрим, как их использовать. + +/// note | Технические детали + +В следующих примерах вы также можете использовать `from starlette.middleware.something import SomethingMiddleware`. + +**FastAPI** предоставляет несколько middleware в `fastapi.middleware` для удобства разработчика. Но большинство доступных middleware приходит напрямую из Starlette. + +/// + +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } + +Гарантирует, что все входящие запросы должны использовать либо `https`, либо `wss`. + +Любой входящий запрос по `http` или `ws` будет перенаправлен на безопасную схему. + +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} + +## `TrustedHostMiddleware` { #trustedhostmiddleware } + +Гарантирует, что во всех входящих запросах корректно установлен `Host`‑заголовок, чтобы защититься от атак на HTTP‑заголовок Host. + +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} + +Поддерживаются следующие аргументы: + +- `allowed_hosts` — список доменных имён, которые следует разрешить как имена хостов. Подстановки вида `*.example.com` поддерживаются для сопоставления поддоменов. Чтобы разрешить любой хост, используйте либо `allowed_hosts=["*"]`, либо не добавляйте это middleware. +- `www_redirect` — если установлено в True, запросы к не‑www версиям разрешённых хостов будут перенаправляться на их www‑аналоги. По умолчанию — `True`. + +Если входящий запрос не проходит валидацию, будет отправлен ответ `400`. + +## `GZipMiddleware` { #gzipmiddleware } + +Обрабатывает GZip‑ответы для любых запросов, которые включают `"gzip"` в заголовке `Accept-Encoding`. + +Это middleware обрабатывает как обычные, так и потоковые ответы. + +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} + +Поддерживаются следующие аргументы: + +- `minimum_size` — не сжимать GZip‑ом ответы, размер которых меньше этого минимального значения в байтах. По умолчанию — `500`. +- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстреее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла. + +## Другие middleware { #other-middlewares } + +Существует много других ASGI middleware. + +Например: + +- `ProxyHeadersMiddleware` от Uvicorn +- MessagePack + +Чтобы увидеть другие доступные middleware, посмотрите документацию по middleware в Starlette и список ASGI Awesome. diff --git a/docs/ru/docs/advanced/openapi-callbacks.md b/docs/ru/docs/advanced/openapi-callbacks.md new file mode 100644 index 0000000000..faf58370be --- /dev/null +++ b/docs/ru/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# Обратные вызовы в OpenAPI { #openapi-callbacks } + +Вы можете создать API с *операцией пути* (обработчиком пути), которая будет инициировать HTTP-запрос к *внешнему API*, созданному кем-то другим (скорее всего тем же разработчиком, который будет использовать ваш API). + +Процесс, происходящий, когда ваше приложение API обращается к *внешнему API*, называется «callback» (обратный вызов). Программное обеспечение, написанное внешним разработчиком, отправляет HTTP-запрос вашему API, а затем ваш API выполняет обратный вызов, отправляя HTTP-запрос во *внешний API* (который, вероятно, тоже создал тот же разработчик). + +В этом случае вам может понадобиться задокументировать, как должно выглядеть это внешнее API: какую *операцию пути* оно должно иметь, какое тело запроса ожидать, какой ответ возвращать и т.д. + +## Приложение с обратными вызовами { #an-app-with-callbacks } + +Давайте рассмотрим это на примере. + +Представьте, что вы разрабатываете приложение, позволяющее создавать счета. + +Эти счета будут иметь `id`, `title` (необязательный), `customer` и `total`. + +Пользователь вашего API (внешний разработчик) создаст счет в вашем API с помощью POST-запроса. + +Затем ваш API (предположим) сделает следующее: + +* Отправит счет клиенту внешнего разработчика. +* Получит оплату. +* Отправит уведомление обратно пользователю API (внешнему разработчику). + * Это будет сделано отправкой POST-запроса (из *вашего API*) в *внешний API*, предоставленный этим внешним разработчиком (это и есть «callback»). + +## Обычное приложение **FastAPI** { #the-normal-fastapi-app } + +Сначала посмотрим, как будет выглядеть обычное приложение API до добавления обратного вызова. + +В нём будет *операция пути*, которая получит тело запроса `Invoice`, и query-параметр `callback_url`, содержащий URL для обратного вызова. + +Эта часть вполне обычна, большая часть кода вам уже знакома: + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} + +/// tip | Совет + +Query-параметр `callback_url` использует тип Pydantic Url. + +/// + +Единственное новое — это `callbacks=invoices_callback_router.routes` в качестве аргумента *декоратора операции пути*. Далее разберёмся, что это такое. + +## Документирование обратного вызова { #documenting-the-callback } + +Реальный код обратного вызова будет сильно зависеть от вашего приложения API. + +И, вероятно, он будет заметно отличаться от одного приложения к другому. + +Это могут быть буквально одна-две строки кода, например: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +Но, возможно, самая важная часть обратного вызова — это убедиться, что пользователь вашего API (внешний разработчик) правильно реализует *внешний API* в соответствии с данными, которые *ваш API* будет отправлять в теле запроса обратного вызова и т.п. + +Поэтому далее мы добавим код, документирующий, как должен выглядеть этот *внешний API*, чтобы получать обратный вызов от *вашего API*. + +Эта документация отобразится в Swagger UI по адресу `/docs` в вашем API и позволит внешним разработчикам понять, как построить *внешний API*. + +В этом примере сам обратный вызов не реализуется (это может быть всего одна строка кода), реализуется только часть с документацией. + +/// tip | Совет + +Сам обратный вызов — это всего лишь HTTP-запрос. + +Реализуя обратный вызов, вы можете использовать, например, HTTPX или Requests. + +/// + +## Напишите код документации обратного вызова { #write-the-callback-documentation-code } + +Этот код не будет выполняться в вашем приложении, он нужен только для *документирования* того, как должен выглядеть *внешний API*. + +Но вы уже знаете, как легко получить автоматическую документацию для API с **FastAPI**. + +Мы используем те же знания, чтобы задокументировать, как должен выглядеть *внешний API*... создав *операции пути*, которые внешний API должен реализовать (те, которые ваш API будет вызывать). + +/// tip | Совет + +Когда вы пишете код для документирования обратного вызова, полезно представить, что вы — тот самый *внешний разработчик*. И что вы сейчас реализуете *внешний API*, а не *свой API*. + +Временное принятие этой точки зрения (внешнего разработчика) поможет интуитивно понять, куда поместить параметры, какую Pydantic-модель использовать для тела запроса, для ответа и т.д. во *внешнем API*. + +/// + +### Создайте `APIRouter` для обратного вызова { #create-a-callback-apirouter } + +Сначала создайте новый `APIRouter`, который будет содержать один или несколько обратных вызовов. + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} + +### Создайте *операцию пути* для обратного вызова { #create-the-callback-path-operation } + +Чтобы создать *операцию пути* для обратного вызова, используйте тот же `APIRouter`, который вы создали выше. + +Она должна выглядеть как обычная *операция пути* FastAPI: + +* Вероятно, в ней должно быть объявление тела запроса, например `body: InvoiceEvent`. +* А также может быть объявление модели ответа, например `response_model=InvoiceEventReceived`. + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} + +Есть 2 основных отличия от обычной *операции пути*: + +* Ей не нужен реальный код, потому что ваше приложение никогда не будет вызывать эту функцию. Она используется только для документирования *внешнего API*. Поэтому в функции может быть просто `pass`. +* *Путь* может содержать выражение OpenAPI 3 (подробнее ниже), где можно использовать переменные с параметрами и части исходного HTTP-запроса, отправленного *вашему API*. + +### Выражение пути для обратного вызова { #the-callback-path-expression } + +*Путь* обратного вызова может содержать выражение OpenAPI 3, которое может включать части исходного запроса, отправленного *вашему API*. + +В нашем случае это `str`: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +Итак, если пользователь вашего API (внешний разработчик) отправляет HTTP-запрос вашему API по адресу: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +с телом JSON: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +то *ваш API* обработает счёт и, в какой-то момент позже, отправит запрос обратного вызова на `callback_url` (*внешний API*): + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +с телом JSON примерно такого вида: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +и будет ожидать от *внешнего API* ответ с телом JSON вида: + +```JSON +{ + "ok": true +} +``` + +/// tip | Совет + +Обратите внимание, что используемый URL обратного вызова содержит URL, полученный как query-параметр в `callback_url` (`https://www.external.org/events`), а также `id` счёта из тела JSON (`2expen51ve`). + +/// + +### Подключите маршрутизатор обратного вызова { #add-the-callback-router } + +К этому моменту у вас есть необходимые *операции пути* обратного вызова (те, которые *внешний разработчик* должен реализовать во *внешнем API*) в созданном выше маршрутизаторе обратных вызовов. + +Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (это, по сути, просто `list` маршрутов/*операций пути*) из этого маршрутизатора обратных вызовов: + +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} + +/// tip | Совет + +Обратите внимание, что вы передаёте не сам маршрутизатор (`invoices_callback_router`) в `callback=`, а его атрибут `.routes`, то есть `invoices_callback_router.routes`. + +/// + +### Проверьте документацию { #check-the-docs } + +Теперь вы можете запустить приложение и перейти по адресу http://127.0.0.1:8000/docs. + +Вы увидите документацию, включающую раздел «Callbacks» для вашей *операции пути*, который показывает, как должен выглядеть *внешний API*: + + diff --git a/docs/ru/docs/advanced/openapi-webhooks.md b/docs/ru/docs/advanced/openapi-webhooks.md new file mode 100644 index 0000000000..d38cf315f7 --- /dev/null +++ b/docs/ru/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# Вебхуки OpenAPI { #openapi-webhooks } + +Бывают случаи, когда вы хотите сообщить пользователям вашего API, что ваше приложение может вызвать их приложение (отправив HTTP-запрос) с некоторыми данными, обычно чтобы уведомить о каком-то событии. + +Это означает, что вместо обычного процесса, когда пользователи отправляют запросы вашему API, ваш API (или ваше приложение) может отправлять запросы в их систему (в их API, их приложение). + +Обычно это называется вебхуком. + +## Шаги вебхуков { #webhooks-steps } + +Обычно процесс таков: вы определяете в своем коде, какое сообщение вы будете отправлять, то есть тело запроса. + +Вы также определяете, в какие моменты (при каких событиях) ваше приложение будет отправлять эти запросы. + +А ваши пользователи каким-то образом (например, в веб‑панели) указывают URL-адрес, на который ваше приложение должно отправлять эти запросы. + +Вся логика регистрации URL-адресов для вебхуков и код, который реально отправляет эти запросы, целиком на вашей стороне. Вы пишете это так, как вам нужно, в своем собственном коде. + +## Документирование вебхуков с помощью FastAPI и OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } + +С FastAPI, используя OpenAPI, вы можете определить имена этих вебхуков, типы HTTP-операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), а также тела запросов, которые ваше приложение будет отправлять. + +Это значительно упростит вашим пользователям реализацию их API для приема ваших вебхук-запросов; возможно, они даже смогут автоматически сгенерировать часть кода своего API. + +/// info | Информация + +Вебхуки доступны в OpenAPI 3.1.0 и выше, поддерживаются в FastAPI `0.99.0` и новее. + +/// + +## Приложение с вебхуками { #an-app-with-webhooks } + +При создании приложения на **FastAPI** есть атрибут `webhooks`, с помощью которого можно объявлять вебхуки так же, как вы объявляете операции пути (обработчики пути), например с `@app.webhooks.post()`. + +{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} + +Определенные вами вебхуки попадут в схему **OpenAPI** и в автоматический **интерфейс документации**. + +/// info | Информация + +Объект `app.webhooks` на самом деле — это обычный `APIRouter`, тот же тип, который вы используете при структурировании приложения по нескольким файлам. + +/// + +Обратите внимание: в случае с вебхуками вы на самом деле не объявляете путь (например, `/items/`), передаваемый туда текст — это лишь идентификатор вебхука (имя события). Например, в `@app.webhooks.post("new-subscription")` имя вебхука — `new-subscription`. + +Это связано с тем, что предполагается: фактический URL‑путь, по которому они хотят получать запрос вебхука, ваши пользователи укажут каким-то другим образом (например, в веб‑панели). + +### Посмотрите документацию { #check-the-docs } + +Теперь вы можете запустить приложение и перейти по ссылке http://127.0.0.1:8000/docs. + +Вы увидите, что в документации есть обычные операции пути, а также появились вебхуки: + + diff --git a/docs/ru/docs/advanced/path-operation-advanced-configuration.md b/docs/ru/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 0000000000..fcb3cd47f2 --- /dev/null +++ b/docs/ru/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,204 @@ +# Расширенная конфигурация операций пути { #path-operation-advanced-configuration } + +## OpenAPI operationId { #openapi-operationid } + +/// warning | Предупреждение + +Если вы не «эксперт» по OpenAPI, скорее всего, это вам не нужно. + +/// + +Вы можете задать OpenAPI `operationId`, который будет использоваться в вашей *операции пути*, с помощью параметра `operation_id`. + +Нужно убедиться, что он уникален для каждой операции. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} + +### Использование имени функции-обработчика пути как operationId { #using-the-path-operation-function-name-as-the-operationid } + +Если вы хотите использовать имена функций ваших API в качестве `operationId`, вы можете пройти по всем из них и переопределить `operation_id` каждой *операции пути* с помощью их `APIRoute.name`. + +Делать это следует после добавления всех *операций пути*. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} + +/// tip | Совет + +Если вы вызываете `app.openapi()` вручную, обновите `operationId` до этого. + +/// + +/// warning | Предупреждение + +Если вы делаете это, убедитесь, что каждая из ваших *функций-обработчиков пути* имеет уникальное имя. + +Даже если они находятся в разных модулях (файлах Python). + +/// + +## Исключить из OpenAPI { #exclude-from-openapi } + +Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (а значит, и из автоматической документации), используйте параметр `include_in_schema` и установите его в `False`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} + +## Расширенное описание из docstring { #advanced-description-from-docstring } + +Вы можете ограничить количество строк из docstring *функции-обработчика пути*, используемых для OpenAPI. + +Добавление `\f` (экранированного символа «form feed») заставит **FastAPI** обрезать текст, используемый для OpenAPI, в этой точке. + +Эта часть не попадёт в документацию, но другие инструменты (например, Sphinx) смогут использовать остальное. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} + +## Дополнительные ответы { #additional-responses } + +Вы, вероятно, уже видели, как объявлять `response_model` и `status_code` для *операции пути*. + +Это определяет метаданные об основном ответе *операции пути*. + +Также можно объявлять дополнительные ответы с их моделями, статус-кодами и т.д. + +В документации есть целая глава об этом — [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +## Дополнительные данные OpenAPI { #openapi-extra } + +Когда вы объявляете *операцию пути* в своём приложении, **FastAPI** автоматически генерирует соответствующие метаданные об этой *операции пути* для включения в схему OpenAPI. + +/// note | Технические детали + +В спецификации OpenAPI это называется Объект операции. + +/// + +Он содержит всю информацию об *операции пути* и используется для генерации автоматической документации. + +Там есть `tags`, `parameters`, `requestBody`, `responses` и т.д. + +Эта спецификация OpenAPI, специфичная для *операции пути*, обычно генерируется автоматически **FastAPI**, но вы также можете её расширить. + +/// tip | Совет + +Это низкоуровневая возможность расширения. + +Если вам нужно лишь объявить дополнительные ответы, удобнее сделать это через [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +/// + +Вы можете расширить схему OpenAPI для *операции пути* с помощью параметра `openapi_extra`. + +### Расширения OpenAPI { #openapi-extensions } + +`openapi_extra` может пригодиться, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): + +{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} + +Если вы откроете автоматическую документацию API, ваше расширение появится внизу страницы конкретной *операции пути*. + + + +И если вы посмотрите на итоговый OpenAPI (по адресу `/openapi.json` вашего API), вы также увидите своё расширение в составе описания соответствующей *операции пути*: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### Пользовательская схема OpenAPI для операции пути { #custom-openapi-path-operation-schema } + +Словарь в `openapi_extra` будет объединён с автоматически сгенерированной схемой OpenAPI для *операции пути*. + +Таким образом, вы можете добавить дополнительные данные к автоматически сгенерированной схеме. + +Например, вы можете решить читать и валидировать запрос своим кодом, не используя автоматические возможности FastAPI и Pydantic, но при этом захотите описать запрос в схеме OpenAPI. + +Это можно сделать с помощью `openapi_extra`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} + +В этом примере мы не объявляли никакую Pydantic-модель. Фактически тело запроса даже не распарсено как JSON, оно читается напрямую как `bytes`, а функция `magic_data_reader()` будет отвечать за его парсинг каким-то способом. + +Тем не менее, мы можем объявить ожидаемую схему для тела запроса. + +### Пользовательский тип содержимого в OpenAPI { #custom-openapi-content-type } + +Используя тот же приём, вы можете воспользоваться Pydantic-моделью, чтобы определить JSON Schema, которая затем будет включена в пользовательский раздел схемы OpenAPI для *операции пути*. + +И вы можете сделать это, даже если тип данных в запросе — не JSON. + +Например, в этом приложении мы не используем встроенную функциональность FastAPI для извлечения JSON Schema из моделей Pydantic, равно как и автоматическую валидацию JSON. Мы объявляем тип содержимого запроса как YAML, а не JSON: + +//// tab | Pydantic v2 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} + +//// + +/// info | Информация + +В Pydantic версии 1 метод для получения JSON Schema модели назывался `Item.schema()`, в Pydantic версии 2 метод называется `Item.model_json_schema()`. + +/// + +Тем не менее, хотя мы не используем встроенную функциональность по умолчанию, мы всё равно используем Pydantic-модель, чтобы вручную сгенерировать JSON Schema для данных, которые мы хотим получить в YAML. + +Затем мы работаем с запросом напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не попытается распарсить полезную нагрузку запроса как JSON. + +А затем в нашем коде мы напрямую парсим этот YAML и снова используем ту же Pydantic-модель для валидации YAML-содержимого: + +//// tab | Pydantic v2 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} + +//// + +/// info | Информация + +В Pydantic версии 1 метод для парсинга и валидации объекта назывался `Item.parse_obj()`, в Pydantic версии 2 метод называется `Item.model_validate()`. + +/// + +/// tip | Совет + +Здесь мы переиспользуем ту же Pydantic-модель. + +Но аналогично мы могли бы валидировать данные и каким-то другим способом. + +/// diff --git a/docs/ru/docs/advanced/response-change-status-code.md b/docs/ru/docs/advanced/response-change-status-code.md new file mode 100644 index 0000000000..e9e1c9470f --- /dev/null +++ b/docs/ru/docs/advanced/response-change-status-code.md @@ -0,0 +1,31 @@ +# Response - Изменение статус-кода { #response-change-status-code } + +Вы, вероятно, уже читали о том, что можно установить [статус-код ответа по умолчанию](../tutorial/response-status-code.md){.internal-link target=_blank}. + +Но в некоторых случаях нужно вернуть другой статус-код, отличный от значения по умолчанию. + +## Пример использования { #use-case } + +Например, представьте, что вы хотите по умолчанию возвращать HTTP статус-код «OK» `200`. + +Но если данные не существовали, вы хотите создать их и вернуть HTTP статус-код «CREATED» `201`. + +При этом вы всё ещё хотите иметь возможность фильтровать и преобразовывать возвращаемые данные с помощью `response_model`. + +Для таких случаев вы можете использовать параметр `Response`. + +## Использование параметра `Response` { #use-a-response-parameter } + +Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (как и для cookies и HTTP-заголовков). + +И затем вы можете установить `status_code` в этом *временном* объекте ответа. + +{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} + +После этого вы можете вернуть любой объект, который вам нужен, как обычно (`dict`, модель базы данных и т.д.). + +И если вы объявили `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта. + +**FastAPI** будет использовать этот *временный* ответ для извлечения статус-кода (а также cookies и HTTP-заголовков) и поместит их в финальный ответ, который содержит возвращаемое вами значение, отфильтрованное любым `response_model`. + +Вы также можете объявить параметр `Response` в зависимостях и установить в них статус-код. Но помните, что последнее установленное значение будет иметь приоритет. diff --git a/docs/ru/docs/advanced/response-cookies.md b/docs/ru/docs/advanced/response-cookies.md new file mode 100644 index 0000000000..9319aba6e2 --- /dev/null +++ b/docs/ru/docs/advanced/response-cookies.md @@ -0,0 +1,51 @@ +# Cookies в ответе { #response-cookies } + +## Использование параметра `Response` { #use-a-response-parameter } + +Вы можете объявить параметр типа `Response` в вашей функции-обработчике пути. + +Затем установить cookies в этом временном объекте ответа. + +{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} + +После этого можно вернуть любой объект, как и раньше (например, `dict`, объект модели базы данных и так далее). + +Если вы указали `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта. + +**FastAPI** извлечет cookies (а также HTTP-заголовки и статус-код) из временного ответа и включит их в окончательный ответ, содержащий ваше возвращаемое значение, отфильтрованное через `response_model`. + +Вы также можете объявить параметр типа `Response` в зависимостях и устанавливать cookies (и HTTP-заголовки) там. + +## Возвращение `Response` напрямую { #return-a-response-directly } + +Вы также можете установить Cookies, если возвращаете `Response` напрямую в вашем коде. + +Для этого создайте объект `Response`, как описано в разделе [Возвращение ответа напрямую](response-directly.md){.internal-link target=_blank}. + +Затем установите cookies и верните этот объект: + +{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} + +/// tip | Совет + +Имейте в виду, что если вы возвращаете ответ напрямую, вместо использования параметра `Response`, FastAPI вернёт его напрямую. + +Убедитесь, что ваши данные имеют корректный тип. Например, они должны быть совместимы с JSON, если вы возвращаете `JSONResponse`. + +Также убедитесь, что вы не отправляете данные, которые должны были быть отфильтрованы через `response_model`. + +/// + +### Дополнительная информация { #more-info } + +/// note | Технические детали + +Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`. + +**FastAPI** предоставляет `fastapi.responses`, которые являются теми же объектами, что и `starlette.responses`, просто для удобства. Однако большинство доступных типов ответов поступает непосредственно из **Starlette**. + +И так как `Response` часто используется для установки HTTP-заголовков и cookies, **FastAPI** также предоставляет его как `fastapi.Response`. + +/// + +Чтобы увидеть все доступные параметры и настройки, ознакомьтесь с документацией Starlette. diff --git a/docs/ru/docs/advanced/response-directly.md b/docs/ru/docs/advanced/response-directly.md new file mode 100644 index 0000000000..febd40ed4a --- /dev/null +++ b/docs/ru/docs/advanced/response-directly.md @@ -0,0 +1,65 @@ +# Возврат ответа напрямую { #return-a-response-directly } + +Когда вы создаёте **FastAPI** *операцию пути*, вы можете возвращать из неё любые данные: `dict`, `list`, Pydantic-модель, модель базы данных и т.д. + +По умолчанию **FastAPI** автоматически преобразует возвращаемое значение в JSON с помощью `jsonable_encoder`, как описано в [JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}. + +Затем "под капотом" эти данные, совместимые с JSON (например `dict`), помещаются в `JSONResponse`, который используется для отправки ответа клиенту. + +Но вы можете возвращать `JSONResponse` напрямую из ваших *операций пути*. + +Это может быть полезно, например, если нужно вернуть пользовательские HTTP-заголовки или cookie. + +## Возврат `Response` { #return-a-response } + +На самом деле, вы можете возвращать любой объект `Response` или его подкласс. + +/// tip | Подсказка + +`JSONResponse` сам по себе является подклассом `Response`. + +/// + +И когда вы возвращаете `Response`, **FastAPI** передаст его напрямую. + +Это не приведет к преобразованию данных с помощью Pydantic-моделей, содержимое не будет преобразовано в какой-либо тип и т.д. + +Это даёт вам большую гибкость. Вы можете возвращать любые типы данных, переопределять любые объявления или валидацию данных и т.д. + +## Использование `jsonable_encoder` в `Response` { #using-the-jsonable-encoder-in-a-response } + +Поскольку **FastAPI** не изменяет объект `Response`, который вы возвращаете, вы должны убедиться, что его содержимое готово к отправке. + +Например, вы не можете поместить Pydantic-модель в `JSONResponse`, не преобразовав её сначала в `dict` с помощью преобразования всех типов данных (таких как `datetime`, `UUID` и т.д.) в совместимые с JSON типы. + +В таких случаях вы можете использовать `jsonable_encoder` для преобразования данных перед передачей их в ответ: + +{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} + +/// note | Технические детали + +Вы также можете использовать `from starlette.responses import JSONResponse`. + +**FastAPI** предоставляет `starlette.responses` через `fastapi.responses` просто для вашего удобства, как разработчика. Но большинство доступных Response-классов поступают напрямую из Starlette. + +/// + +## Возврат пользовательского `Response` { #returning-a-custom-response } + +Пример выше показывает все необходимые части, но он пока не очень полезен, так как вы могли бы просто вернуть `item` напрямую, и **FastAPI** поместил бы его в `JSONResponse`, преобразовав в `dict` и т.д. Всё это происходит по умолчанию. + +Теперь давайте посмотрим, как можно использовать это для возврата пользовательского ответа. + +Допустим, вы хотите вернуть ответ в формате XML. + +Вы можете поместить ваш XML-контент в строку, поместить её в `Response` и вернуть: + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +## Примечания { #notes } + +Когда вы возвращаете объект `Response` напрямую, его данные не валидируются, не преобразуются (не сериализуются) и не документируются автоматически. + +Но вы всё равно можете задокументировать это, как описано в [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +В следующих разделах вы увидите, как использовать/объявлять такие кастомные `Response`, при этом сохраняя автоматическое преобразование данных, документацию и т.д. diff --git a/docs/ru/docs/advanced/response-headers.md b/docs/ru/docs/advanced/response-headers.md new file mode 100644 index 0000000000..1c9360b31d --- /dev/null +++ b/docs/ru/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# HTTP-заголовки ответа { #response-headers } + +## Использовать параметр `Response` { #use-a-response-parameter } + +Вы можете объявить параметр типа `Response` в вашей функции-обработчике пути (как можно сделать и для cookie). + +А затем вы можете устанавливать HTTP-заголовки в этом *временном* объекте ответа. + +{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} + +После этого вы можете вернуть любой нужный объект, как обычно (например, `dict`, модель из базы данных и т.д.). + +И, если вы объявили `response_model`, он всё равно будет использован для фильтрации и преобразования возвращённого объекта. + +**FastAPI** использует этот *временный* ответ, чтобы извлечь HTTP-заголовки (а также cookie и статус-код) и поместит их в финальный HTTP-ответ, который содержит возвращённое вами значение, отфильтрованное согласно `response_model`. + +Вы также можете объявлять параметр `Response` в зависимостях и устанавливать в них заголовки (и cookie). + +## Вернуть `Response` напрямую { #return-a-response-directly } + +Вы также можете добавить HTTP-заголовки, когда возвращаете `Response` напрямую. + +Создайте ответ, как описано в [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, и передайте заголовки как дополнительный параметр: + +{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} + +/// note | Технические детали + +Вы также можете использовать `from starlette.responses import Response` или `from starlette.responses import JSONResponse`. + +**FastAPI** предоставляет те же самые `starlette.responses` как `fastapi.responses` — для вашего удобства как разработчика. Но большинство доступных классов ответов поступают напрямую из Starlette. + +И поскольку `Response` часто используется для установки заголовков и cookie, **FastAPI** также предоставляет его как `fastapi.Response`. + +/// + +## Пользовательские HTTP-заголовки { #custom-headers } + +Помните, что собственные проприетарные заголовки можно добавлять, используя префикс `X-`. + +Но если у вас есть пользовательские заголовки, которые вы хотите показывать клиенту в браузере, вам нужно добавить их в настройки CORS (подробнее см. в [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, описанный в документации Starlette по CORS. diff --git a/docs/ru/docs/advanced/security/http-basic-auth.md b/docs/ru/docs/advanced/security/http-basic-auth.md new file mode 100644 index 0000000000..41e62d4bf3 --- /dev/null +++ b/docs/ru/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,107 @@ +# HTTP Basic Auth { #http-basic-auth } + +Для самых простых случаев можно использовать HTTP Basic Auth. + +При HTTP Basic Auth приложение ожидает HTTP-заголовок, который содержит имя пользователя и пароль. + +Если его нет, возвращается ошибка HTTP 401 «Unauthorized». + +Также возвращается заголовок `WWW-Authenticate` со значением `Basic` и необязательным параметром `realm`. + +Это говорит браузеру показать встроенное окно запроса имени пользователя и пароля. + +Затем, когда вы вводите эти данные, браузер автоматически отправляет их в заголовке. + +## Простой HTTP Basic Auth { #simple-http-basic-auth } + +* Импортируйте `HTTPBasic` и `HTTPBasicCredentials`. +* Создайте «схему» `security` с помощью `HTTPBasic`. +* Используйте эту `security` как зависимость в вашей *операции пути*. +* Она возвращает объект типа `HTTPBasicCredentials`: + * Он содержит отправленные `username` и `password`. + +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} + +Когда вы впервые откроете URL (или нажмёте кнопку «Execute» в документации), браузер попросит ввести имя пользователя и пароль: + + + +## Проверка имени пользователя { #check-the-username } + +Вот более полный пример. + +Используйте зависимость, чтобы проверить, корректны ли имя пользователя и пароль. + +Для этого используйте стандартный модуль Python `secrets` для проверки имени пользователя и пароля. + +`secrets.compare_digest()` должен получать `bytes` или `str`, который содержит только символы ASCII (английские символы). Это значит, что он не будет работать с символами вроде `á`, как в `Sebastián`. + +Чтобы это обработать, сначала преобразуем `username` и `password` в `bytes`, закодировав их в UTF-8. + +Затем можно использовать `secrets.compare_digest()`, чтобы убедиться, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password` — `"swordfish"`. + +{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} + +Это было бы похоже на: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Вернуть ошибку + ... +``` + +Но используя `secrets.compare_digest()`, вы защитите код от атак типа «тайминговая атака» (атака по времени). + +### Тайминговые атаки { #timing-attacks } + +Что такое «тайминговая атака»? + +Представим, что злоумышленники пытаются угадать имя пользователя и пароль. + +И они отправляют запрос с именем пользователя `johndoe` и паролем `love123`. + +Тогда Python-код в вашем приложении будет эквивалентен чему-то вроде: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Но в момент, когда Python сравнит первую `j` в `johndoe` с первой `s` в `stanleyjobson`, он вернёт `False`, потому что уже ясно, что строки не совпадают, решив, что «нет смысла тратить ресурсы на сравнение остальных букв». И ваше приложение ответит «Неверное имя пользователя или пароль». + +Затем злоумышленники попробуют имя пользователя `stanleyjobsox` и пароль `love123`. + +И ваш код сделает что-то вроде: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Pythonу придётся сравнить весь общий префикс `stanleyjobso` в `stanleyjobsox` и `stanleyjobson`, прежде чем понять, что строки отличаются. Поэтому на ответ «Неверное имя пользователя или пароль» уйдёт на несколько микросекунд больше. + +#### Время ответа помогает злоумышленникам { #the-time-to-answer-helps-the-attackers } + +Замечая, что сервер прислал «Неверное имя пользователя или пароль» на несколько микросекунд позже, злоумышленники поймут, что какая-то часть была угадана — начальные буквы верны. + +Тогда они могут попробовать снова, зная, что правильнее что-то ближе к `stanleyjobsox`, чем к `johndoe`. + +#### «Профессиональная» атака { #a-professional-attack } + +Конечно, злоумышленники не будут делать всё это вручную — они напишут программу, возможно, с тысячами или миллионами попыток в секунду. И будут подбирать по одной дополнительной верной букве за раз. + +Так за минуты или часы они смогут угадать правильные имя пользователя и пароль — с «помощью» нашего приложения — используя лишь время, затраченное на ответ. + +#### Исправление с помощью `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest } + +Но в нашем коде мы используем `secrets.compare_digest()`. + +Вкратце: сравнение `stanleyjobsox` с `stanleyjobson` займёт столько же времени, сколько и сравнение `johndoe` с `stanleyjobson`. То же относится и к паролю. + +Таким образом, используя `secrets.compare_digest()` в коде приложения, вы защитите его от всего этого класса атак на безопасность. + +### Возврат ошибки { #return-the-error } + +После того как обнаружено, что учётные данные некорректны, верните `HTTPException` со статус-кодом ответа 401 (тем же, что и при отсутствии учётных данных) и добавьте HTTP-заголовок `WWW-Authenticate`, чтобы браузер снова показал окно входа: + +{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/ru/docs/advanced/security/index.md b/docs/ru/docs/advanced/security/index.md new file mode 100644 index 0000000000..912e4812a5 --- /dev/null +++ b/docs/ru/docs/advanced/security/index.md @@ -0,0 +1,19 @@ +# Расширенная безопасность { #advanced-security } + +## Дополнительные возможности { #additional-features } + +Есть дополнительные возможности для работы с безопасностью помимо тех, что описаны в [Учебник — Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. + +/// tip | Совет + +Следующие разделы **не обязательно являются «продвинутыми»**. + +И возможно, что решение для вашего варианта использования находится в одном из них. + +/// + +## Сначала прочитайте руководство { #read-the-tutorial-first } + +В следующих разделах предполагается, что вы уже прочитали основной [Учебник — Руководство пользователя: Безопасность](../../tutorial/security/index.md){.internal-link target=_blank}. + +Все они основаны на тех же концепциях, но предоставляют дополнительные возможности. diff --git a/docs/ru/docs/advanced/security/oauth2-scopes.md b/docs/ru/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 0000000000..8788df1991 --- /dev/null +++ b/docs/ru/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# OAuth2 scopes { #oauth2-scopes } + +Вы можете использовать OAuth2 scopes (scope - область, рамки) напрямую с **FastAPI** — они интегрированы и работают бесшовно. + +Это позволит вам иметь более детальную систему разрешений по стандарту OAuth2, интегрированную в ваше OpenAPI‑приложение (и документацию API). + +OAuth2 со scopes — это механизм, который используют многие крупные провайдеры аутентификации: Facebook, Google, GitHub, Microsoft, X (Twitter) и т.д. Они применяют его, чтобы предоставлять конкретные разрешения пользователям и приложениям. + +Каждый раз, когда вы «входите через» Facebook, Google, GitHub, Microsoft, X (Twitter), это приложение использует OAuth2 со scopes. + +В этом разделе вы увидите, как управлять аутентификацией и авторизацией с теми же OAuth2 scopes в вашем приложении на **FastAPI**. + +/// warning | Предупреждение + +Это более-менее продвинутый раздел. Если вы только начинаете, можете пропустить его. + +Вам не обязательно нужны OAuth2 scopes — аутентификацию и авторизацию можно реализовать любым нужным вам способом. + +Но OAuth2 со scopes можно красиво интегрировать в ваш API (через OpenAPI) и документацию API. + +Так или иначе, вы все равно будете применять эти scopes или какие-то другие требования безопасности/авторизации, как вам нужно, в вашем коде. + +Во многих случаях OAuth2 со scopes может быть избыточным. + +Но если вы знаете, что это нужно, или вам просто интересно — продолжайте чтение. + +/// + +## OAuth2 scopes и OpenAPI { #oauth2-scopes-and-openapi } + +Спецификация OAuth2 определяет «scopes» как список строк, разделённых пробелами. + +Содержимое каждой такой строки может иметь любой формат, но не должно содержать пробелов. + +Эти scopes представляют «разрешения». + +В OpenAPI (например, в документации API) можно определить «схемы безопасности» (security schemes). + +Когда одна из таких схем безопасности использует OAuth2, вы также можете объявлять и использовать scopes. + +Каждый «scope» — это просто строка (без пробелов). + +Обычно они используются для объявления конкретных разрешений безопасности, например: + +- `users:read` или `users:write` — распространённые примеры. +- `instagram_basic` используется Facebook / Instagram. +- `https://www.googleapis.com/auth/drive` используется Google. + +/// info | Информация + +В OAuth2 «scope» — это просто строка, объявляющая требуемое конкретное разрешение. + +Неважно, есть ли там другие символы, такие как `:`, или это URL. + +Эти детали зависят от реализации. + +Для OAuth2 это просто строки. + +/// + +## Взгляд издалека { #global-view } + +Сначала быстро посмотрим, что изменилось по сравнению с примерами из основного раздела **Учебник - Руководство пользователя** — [OAuth2 с паролем (и хешированием), Bearer с JWT-токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Теперь — с использованием OAuth2 scopes: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} + +Теперь рассмотрим эти изменения шаг за шагом. + +## OAuth2 схема безопасности { #oauth2-security-scheme } + +Первое изменение — мы объявляем схему безопасности OAuth2 с двумя доступными scopes: `me` и `items`. + +Параметр `scopes` получает `dict`, где каждый scope — это ключ, а описание — значение: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +Так как теперь мы объявляем эти scopes, они появятся в документации API при входе/авторизации. + +И вы сможете выбрать, какие scopes вы хотите выдать доступ: `me` и `items`. + +Это тот же механизм, когда вы даёте разрешения при входе через Facebook, Google, GitHub и т.д.: + + + +## JWT-токены со scopes { #jwt-token-with-scopes } + +Теперь измените операцию пути, выдающую токен, чтобы возвращать запрошенные scopes. + +Мы всё ещё используем тот же `OAuth2PasswordRequestForm`. Он включает свойство `scopes` с `list` из `str` — каждый scope, полученный в запросе. + +И мы возвращаем scopes как часть JWT‑токена. + +/// danger | Опасность + +Для простоты здесь мы просто добавляем полученные scopes прямо в токен. + +Но в вашем приложении, в целях безопасности, следует убедиться, что вы добавляете только те scopes, которые пользователь действительно может иметь, или те, которые вы заранее определили. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} + +## Объявление scopes в *обработчиках путей* и зависимостях { #declare-scopes-in-path-operations-and-dependencies } + +Теперь объявим, что операция пути для `/users/me/items/` требует scope `items`. + +Для этого импортируем и используем `Security` из `fastapi`. + +Вы можете использовать `Security` для объявления зависимостей (как `Depends`), но `Security` также принимает параметр `scopes` со списком scopes (строк). + +В этом случае мы передаём функцию‑зависимость `get_current_active_user` в `Security` (точно так же, как сделали бы с `Depends`). + +Но мы также передаём `list` scopes — в данном случае только один scope: `items` (их могло быть больше). + +И функция‑зависимость `get_current_active_user` тоже может объявлять подзависимости не только через `Depends`, но и через `Security`, объявляя свою подзависимость (`get_current_user`) и дополнительные требования по scopes. + +В данном случае требуется scope `me` (их также могло быть больше одного). + +/// note | Примечание + +Вам не обязательно добавлять разные scopes в разных местах. + +Мы делаем это здесь, чтобы показать, как **FastAPI** обрабатывает scopes, объявленные на разных уровнях. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} + +/// info | Технические детали + +`Security` на самом деле является подклассом `Depends` и имеет всего один дополнительный параметр, который мы рассмотрим позже. + +Но используя `Security` вместо `Depends`, **FastAPI** будет знать, что можно объявлять security scopes, использовать их внутри и документировать API в OpenAPI. + +Однако когда вы импортируете `Query`, `Path`, `Depends`, `Security` и другие из `fastapi`, это на самом деле функции, возвращающие специальные классы. + +/// + +## Использование `SecurityScopes` { #use-securityscopes } + +Теперь обновим зависимость `get_current_user`. + +Именно её используют зависимости выше. + +Здесь мы используем ту же схему OAuth2, созданную ранее, объявляя её как зависимость: `oauth2_scheme`. + +Поскольку у этой функции‑зависимости нет собственных требований по scopes, мы можем использовать `Depends` с `oauth2_scheme` — нам не нужно использовать `Security`, если не требуется указывать security scopes. + +Мы также объявляем специальный параметр типа `SecurityScopes`, импортированный из `fastapi.security`. + +Класс `SecurityScopes` похож на `Request` (через `Request` мы получали сам объект запроса). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## Использование `scopes` { #use-the-scopes } + +Параметр `security_scopes` будет типа `SecurityScopes`. + +У него есть свойство `scopes` со списком, содержащим все scopes, требуемые им самим и всеми зависимостями, использующими его как подзависимость. То есть всеми «зависящими»… это может звучать запутанно, ниже есть дополнительное объяснение. + +Объект `security_scopes` (класс `SecurityScopes`) также предоставляет атрибут `scope_str` — это одна строка с этими scopes, разделёнными пробелами (мы будем её использовать). + +Мы создаём `HTTPException`, который можем переиспользовать (`raise`) в нескольких местах. + +В этом исключении мы включаем требуемые scopes (если есть) в виде строки, разделённой пробелами (используя `scope_str`). Эту строку со scopes мы помещаем в HTTP‑заголовок `WWW-Authenticate` (это часть спецификации). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## Проверка `username` и формата данных { #verify-the-username-and-data-shape } + +Мы проверяем, что получили `username`, и извлекаем scopes. + +Затем валидируем эти данные с помощью Pydantic‑модели (перехватывая исключение `ValidationError`), и если возникает ошибка при чтении JWT‑токена или при валидации данных с Pydantic, мы вызываем `HTTPException`, созданное ранее. + +Для этого мы обновляем Pydantic‑модель `TokenData`, добавляя новое свойство `scopes`. + +Валидируя данные с помощью Pydantic, мы можем удостовериться, что у нас, например, именно `list` из `str` со scopes и `str` с `username`. + +А не, скажем, `dict` или что‑то ещё — ведь это могло бы где‑то позже сломать приложение и создать риск для безопасности. + +Мы также проверяем, что существует пользователь с таким именем, и если нет — вызываем то же исключение, созданное ранее. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} + +## Проверка `scopes` { #verify-the-scopes } + +Теперь проверяем, что все требуемые scopes — этой зависимостью и всеми зависящими (включая операции пути) — присутствуют среди scopes, предоставленных в полученном токене, иначе вызываем `HTTPException`. + +Для этого используем `security_scopes.scopes`, содержащий `list` со всеми этими scopes как `str`. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} + +## Дерево зависимостей и scopes { #dependency-tree-and-scopes } + +Ещё раз рассмотрим дерево зависимостей и scopes. + +Так как у зависимости `get_current_active_user` есть подзависимость `get_current_user`, scope `"me"`, объявленный в `get_current_active_user`, будет включён в список требуемых scopes в `security_scopes.scopes`, передаваемый в `get_current_user`. + +Сама операция пути тоже объявляет scope — `"items"`, поэтому он также будет в списке `security_scopes.scopes`, передаваемом в `get_current_user`. + +Иерархия зависимостей и scopes выглядит так: + +- Операция пути `read_own_items`: + - Запрашивает scopes `["items"]` с зависимостью: + - `get_current_active_user`: + - Функция‑зависимость `get_current_active_user`: + - Запрашивает scopes `["me"]` с зависимостью: + - `get_current_user`: + - Функция‑зависимость `get_current_user`: + - Собственных scopes не запрашивает. + - Имеет зависимость, использующую `oauth2_scheme`. + - Имеет параметр `security_scopes` типа `SecurityScopes`: + - Этот параметр `security_scopes` имеет свойство `scopes` с `list`, содержащим все объявленные выше scopes, то есть: + - `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` тоже не объявляет `scopes`. + +/// tip | Совет + +Важный и «магический» момент здесь в том, что `get_current_user` будет иметь разный список `scopes` для проверки для каждой операции пути. + +Всё это зависит от `scopes`, объявленных в каждой операции пути и в каждой зависимости в дереве зависимостей конкретной операции пути. + +/// + +## Больше деталей о `SecurityScopes` { #more-details-about-securityscopes } + +Вы можете использовать `SecurityScopes` в любой точке и в нескольких местах — необязательно в «корневой» зависимости. + +Он всегда будет содержать security scopes, объявленные в текущих зависимостях `Security`, и всеми зависящими — для этой конкретной операции пути и этого конкретного дерева зависимостей. + +Поскольку `SecurityScopes` будет содержать все scopes, объявленные зависящими, вы можете использовать его, чтобы централизованно проверять наличие требуемых scopes в токене в одной функции‑зависимости, а затем объявлять разные требования по scopes в разных операциях пути. + +Они будут проверяться независимо для каждой операции пути. + +## Проверим это { #check-it } + +Откройте документацию API — вы сможете аутентифицироваться и указать, какие scopes вы хотите авторизовать. + + + +Если вы не выберете ни один scope, вы будете «аутентифицированы», но при попытке доступа к `/users/me/` или `/users/me/items/` получите ошибку о недостаточных разрешениях. При этом доступ к `/status/` будет возможен. + +Если вы выберете scope `me`, но не `items`, вы сможете получить доступ к `/users/me/`, но не к `/users/me/items/`. + +Так и будет происходить со сторонним приложением, которое попытается обратиться к одной из этих операций пути с токеном, предоставленным пользователем, — в зависимости от того, сколько разрешений пользователь дал приложению. + +## О сторонних интеграциях { #about-third-party-integrations } + +В этом примере мы используем OAuth2 «password flow» (аутентификация по паролю). + +Это уместно, когда мы входим в наше собственное приложение, вероятно, с нашим собственным фронтендом. + +Мы можем ему доверять при получении `username` и `password`, потому что он под нашим контролем. + +Но если вы создаёте OAuth2‑приложение, к которому будут подключаться другие (т.е. вы строите провайдера аутентификации наподобие Facebook, Google, GitHub и т.п.), вам следует использовать один из других «flows». + +Самый распространённый — «implicit flow». + +Самый безопасный — «code flow», но он сложнее в реализации, так как требует больше шагов. Из‑за сложности многие провайдеры в итоге рекомендуют «implicit flow». + +/// note | Примечание + +Часто каждый провайдер аутентификации называет свои «flows» по‑разному — как часть бренда. + +Но в итоге они реализуют один и тот же стандарт OAuth2. + +/// + +FastAPI включает утилиты для всех этих OAuth2‑flows в `fastapi.security.oauth2`. + +## `Security` в параметре `dependencies` декоратора { #security-in-decorator-dependencies } + +Точно так же, как вы можете определить `list` из `Depends` в параметре `dependencies` декоратора (см. [Зависимости в декораторах операции пути](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), вы можете использовать там и `Security` со `scopes`. diff --git a/docs/ru/docs/advanced/settings.md b/docs/ru/docs/advanced/settings.md new file mode 100644 index 0000000000..a335548c3c --- /dev/null +++ b/docs/ru/docs/advanced/settings.md @@ -0,0 +1,346 @@ +# Настройки и переменные окружения { #settings-and-environment-variables } + +Во многих случаях вашему приложению могут понадобиться внешние настройки или конфигурации, например секретные ключи, учетные данные для базы данных, учетные данные для email‑сервисов и т.д. + +Большинство таких настроек являются изменяемыми (могут меняться), например URL базы данных. И многие из них могут быть «чувствительными», например секреты. + +По этой причине обычно их передают через переменные окружения, которые считываются приложением. + +/// tip | Совет + +Чтобы понять, что такое переменные окружения, вы можете прочитать [Переменные окружения](../environment-variables.md){.internal-link target=_blank}. + +/// + +## Типы и валидация { #types-and-validation } + +Переменные окружения могут содержать только текстовые строки, так как они внешние по отношению к Python и должны быть совместимы с другими программами и остальной системой (и даже с разными операционными системами, такими как Linux, Windows, macOS). + +Это означает, что любое значение, прочитанное в Python из переменной окружения, будет `str`, а любые преобразования к другим типам или любая валидация должны выполняться в коде. + +## Pydantic `Settings` { #pydantic-settings } + +К счастью, Pydantic предоставляет отличную утилиту для работы с этими настройками, поступающими из переменных окружения, — Pydantic: управление настройками. + +### Установка `pydantic-settings` { #install-pydantic-settings } + +Сначала убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установили пакет `pydantic-settings`: + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +Он также включен при установке набора `all` с: + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +/// info | Информация + +В Pydantic v1 он входил в основной пакет. Теперь он распространяется как отдельный пакет, чтобы вы могли установить его только при необходимости. + +/// + +### Создание объекта `Settings` { #create-the-settings-object } + +Импортируйте `BaseSettings` из Pydantic и создайте подкласс, очень похожий на Pydantic‑модель. + +Аналогично Pydantic‑моделям, вы объявляете атрибуты класса с аннотациями типов и, при необходимости, значениями по умолчанию. + +Вы можете использовать все те же возможности валидации и инструменты, что и для Pydantic‑моделей, например разные типы данных и дополнительную валидацию через `Field()`. + +//// tab | Pydantic v2 + +{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} + +//// + +//// tab | Pydantic v1 + +/// info | Информация + +В Pydantic v1 вы бы импортировали `BaseSettings` напрямую из `pydantic`, а не из `pydantic_settings`. + +/// + +{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} + +//// + +/// tip | Совет + +Если вам нужно что-то быстро скопировать и вставить, не используйте этот пример — воспользуйтесь последним ниже. + +/// + +Затем, когда вы создаете экземпляр этого класса `Settings` (в нашем случае объект `settings`), Pydantic прочитает переменные окружения регистронезависимо, то есть переменная в верхнем регистре `APP_NAME` будет прочитана для атрибута `app_name`. + +Далее он преобразует и провалидирует данные. Поэтому при использовании объекта `settings` вы получите данные тех типов, которые объявили (например, `items_per_user` будет `int`). + +### Использование `settings` { #use-the-settings } + +Затем вы можете использовать новый объект `settings` в вашем приложении: + +{* ../../docs_src/settings/tutorial001.py hl[18:20] *} + +### Запуск сервера { #run-the-server } + +Далее вы можете запустить сервер, передав конфигурации через переменные окружения. Например, можно задать `ADMIN_EMAIL` и `APP_NAME` так: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +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`. + +## Настройки в другом модуле { #settings-in-another-module } + +Вы можете вынести эти настройки в другой модуль, как показано в разделе [Большие приложения — несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +Например, у вас может быть файл `config.py` со следующим содержимым: + +{* ../../docs_src/settings/app01/config.py *} + +А затем использовать его в файле `main.py`: + +{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} + +/// tip | Совет + +Вам также понадобится файл `__init__.py`, как в разделе [Большие приложения — несколько файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +/// + +## Настройки как зависимость { #settings-in-a-dependency } + +Иногда может быть полезно предоставлять настройки через зависимость, вместо глобального объекта `settings`, используемого повсюду. + +Это особенно удобно при тестировании, так как очень легко переопределить зависимость своими настройками. + +### Файл конфигурации { #the-config-file } + +Продолжая предыдущий пример, ваш файл `config.py` может выглядеть так: + +{* ../../docs_src/settings/app02/config.py hl[10] *} + +Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`. + +### Основной файл приложения { #the-main-app-file } + +Теперь мы создаем зависимость, которая возвращает новый `config.Settings()`. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} + +/// tip | Совет + +Скоро мы обсудим `@lru_cache`. + +Пока можно считать, что `get_settings()` — это обычная функция. + +/// + +Затем мы можем запросить ее в *функции-обработчике пути* как зависимость и использовать там, где нужно. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} + +### Настройки и тестирование { #settings-and-testing } + +Далее будет очень просто предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`: + +{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} + +В переопределении зависимости мы задаем новое значение `admin_email` при создании нового объекта `Settings`, а затем возвращаем этот новый объект. + +После этого можно протестировать, что он используется. + +## Чтение файла `.env` { #reading-a-env-file } + +Если у вас много настроек, которые могут часто меняться, возможно в разных окружениях, может быть удобно поместить их в файл и читать оттуда как переменные окружения. + +Эта практика достаточно распространена и имеет название: такие переменные окружения обычно размещают в файле `.env`, а сам файл называют «dotenv». + +/// tip | Совет + +Файл, начинающийся с точки (`.`), является скрытым в системах, подобных Unix, таких как Linux и macOS. + +Но файл dotenv не обязательно должен иметь именно такое имя. + +/// + +Pydantic поддерживает чтение таких файлов с помощью внешней библиотеки. Подробнее вы можете прочитать здесь: Pydantic Settings: поддержка Dotenv (.env). + +/// tip | Совет + +Чтобы это работало, вам нужно `pip install python-dotenv`. + +/// + +### Файл `.env` { #the-env-file } + +У вас может быть файл `.env` со следующим содержимым: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### Чтение настроек из `.env` { #read-settings-from-env } + +Затем обновите ваш `config.py` так: + +//// tab | Pydantic v2 + +{* ../../docs_src/settings/app03_an/config.py hl[9] *} + +/// tip | Совет + +Атрибут `model_config` используется только для конфигурации Pydantic. Подробнее см. Pydantic: Concepts: Configuration. + +/// + +//// + +//// tab | Pydantic v1 + +{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} + +/// tip | Совет + +Класс `Config` используется только для конфигурации Pydantic. Подробнее см. Pydantic Model Config. + +/// + +//// + +/// info | Информация + +В Pydantic версии 1 конфигурация задавалась во внутреннем классе `Config`, в Pydantic версии 2 — в атрибуте `model_config`. Этот атрибут принимает `dict`, и чтобы получить автозавершение и ошибки «на лету», вы можете импортировать и использовать `SettingsConfigDict` для описания этого `dict`. + +/// + +Здесь мы задаем параметр конфигурации `env_file` внутри вашего класса Pydantic `Settings` и устанавливаем значение равным имени файла dotenv, который хотим использовать. + +### Создание `Settings` только один раз с помощью `lru_cache` { #creating-the-settings-only-once-with-lru-cache } + +Чтение файла с диска обычно затратная (медленная) операция, поэтому, вероятно, вы захотите сделать это один раз и затем переиспользовать один и тот же объект настроек, а не читать файл при каждом запросе. + +Но каждый раз, когда мы делаем: + +```Python +Settings() +``` + +создается новый объект `Settings`, и при создании он снова считывает файл `.env`. + +Если бы функция зависимости была такой: + +```Python +def get_settings(): + return Settings() +``` + +мы бы создавали этот объект для каждого запроса и читали файл `.env` на каждый запрос. ⚠️ + +Но так как мы используем декоратор `@lru_cache` сверху, объект `Settings` будет создан только один раз — при первом вызове. ✔️ + +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} + +Затем при любых последующих вызовах `get_settings()` в зависимостях для следующих запросов, вместо выполнения внутреннего кода `get_settings()` и создания нового объекта `Settings`, будет возвращаться тот же объект, что был возвращен при первом вызове, снова и снова. + +#### Технические детали `lru_cache` { #lru-cache-technical-details } + +`@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`, что входит в стандартную библиотеку Python. Подробнее можно прочитать в документации Python по `@lru_cache`. + +## Итоги { #recap } + +Вы можете использовать Pydantic Settings для управления настройками и конфигурациями вашего приложения с полной мощью Pydantic‑моделей. + +* Используя зависимость, вы упрощаете тестирование. +* Можно использовать файлы `.env`. +* `@lru_cache` позволяет не читать файл dotenv снова и снова для каждого запроса, при этом давая возможность переопределять его во время тестирования. diff --git a/docs/ru/docs/advanced/sub-applications.md b/docs/ru/docs/advanced/sub-applications.md new file mode 100644 index 0000000000..3464f17040 --- /dev/null +++ b/docs/ru/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# Подприложения — Mounts (монтирование) { #sub-applications-mounts } + +Если вам нужны два независимых приложения FastAPI, каждое со своим собственным OpenAPI и собственными интерфейсами документации, вы можете иметь основное приложение и «смонтировать» одно (или несколько) подприложений. + +## Монтирование приложения **FastAPI** { #mounting-a-fastapi-application } + +«Монтирование» означает добавление полностью независимого приложения по конкретному пути; далее оно будет обрабатывать всё под этим путём, используя объявленные в подприложении _операции пути_. + +### Приложение верхнего уровня { #top-level-application } + +Сначала создайте основное, верхнего уровня, приложение **FastAPI** и его *операции пути*: + +{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} + +### Подприложение { #sub-application } + +Затем создайте подприложение и его *операции пути*. + +Это подприложение — обычное стандартное приложение FastAPI, но именно оно будет «смонтировано»: + +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} + +### Смонтируйте подприложение { #mount-the-sub-application } + +В вашем приложении верхнего уровня, `app`, смонтируйте подприложение `subapi`. + +В этом случае оно будет смонтировано по пути `/subapi`: + +{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} + +### Проверьте автоматическую документацию API { #check-the-automatic-api-docs } + +Теперь запустите команду `fastapi` с вашим файлом: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +И откройте документацию по адресу http://127.0.0.1:8000/docs. + +Вы увидите автоматическую документацию API для основного приложения, включающую только его собственные _операции пути_: + + + +Затем откройте документацию для подприложения по адресу http://127.0.0.1:8000/subapi/docs. + +Вы увидите автоматическую документацию API для подприложения, включающую только его собственные _операции пути_, все под корректным префиксом подпути `/subapi`: + + + +Если вы попробуете взаимодействовать с любым из двух интерфейсов, всё будет работать корректно, потому что браузер сможет обращаться к каждому конкретному приложению и подприложению. + +### Технические подробности: `root_path` { #technical-details-root-path } + +Когда вы монтируете подприложение, как описано выше, FastAPI позаботится о передаче пути монтирования для подприложения, используя механизм из спецификации ASGI под названием `root_path`. + +Таким образом подприложение будет знать, что для интерфейса документации нужно использовать этот префикс пути. + +У подприложения также могут быть свои собственные смонтированные подприложения, и всё будет работать корректно, потому что FastAPI автоматически обрабатывает все эти `root_path`. + +Вы узнаете больше о `root_path` и о том, как использовать его явно, в разделе [За прокси](behind-a-proxy.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/advanced/templates.md b/docs/ru/docs/advanced/templates.md new file mode 100644 index 0000000000..204e887605 --- /dev/null +++ b/docs/ru/docs/advanced/templates.md @@ -0,0 +1,126 @@ +# Шаблоны { #templates } + +Вы можете использовать любой шаблонизатор вместе с **FastAPI**. + +Часто выбирают Jinja2 — тот же, что используется во Flask и других инструментах. + +Есть утилиты для простой настройки, которые вы можете использовать прямо в своем приложении **FastAPI** (предоставляются Starlette). + +## Установка зависимостей { #install-dependencies } + +Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `jinja2`: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## Использование `Jinja2Templates` { #using-jinja2templates } + +- Импортируйте `Jinja2Templates`. +- Создайте объект `templates`, который сможете переиспользовать позже. +- Объявите параметр `Request` в *операции пути*, которая будет возвращать шаблон. +- Используйте созданный `templates`, чтобы отрендерить и вернуть `TemplateResponse`; передайте имя шаблона, объект `request` и словарь «context» с парами ключ-значение для использования внутри шаблона Jinja2. + +{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} + +/// note | Примечание + +До FastAPI 0.108.0, Starlette 0.29.0, `name` был первым параметром. + +Также раньше, в предыдущих версиях, объект `request` передавался как часть пар ключ-значение в контексте для Jinja2. + +/// + +/// tip | Совет + +Если указать `response_class=HTMLResponse`, интерфейс документации сможет определить, что ответ будет в формате HTML. + +/// + +/// note | Технические детали + +Можно также использовать `from starlette.templating import Jinja2Templates`. + +**FastAPI** предоставляет тот же `starlette.templating` как `fastapi.templating` просто для удобства разработчика. Но большинство доступных ответов приходят напрямую из Starlette. Так же и с `Request` и `StaticFiles`. + +/// + +## Написание шаблонов { #writing-templates } + +Затем вы можете создать шаблон в `templates/item.html`, например: + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### Значения контекста шаблона { #template-context-values } + +В HTML, который содержит: + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +...будет показан `id`, взятый из переданного вами «context» `dict`: + +```Python +{"id": id} +``` + +Например, для ID `42` это отрендерится как: + +```html +Item ID: 42 +``` + +### Аргументы `url_for` в шаблоне { #template-url-for-arguments } + +Вы также можете использовать `url_for()` внутри шаблона — он принимает те же аргументы, что использовались бы вашей *функцией-обработчиком пути*. + +Таким образом, фрагмент: + +{% raw %} + +```jinja + +``` + +{% endraw %} + +...сгенерирует ссылку на тот же URL, который обрабатывается *функцией-обработчиком пути* `read_item(id=id)`. + +Например, для ID `42` это отрендерится как: + +```html + +``` + +## Шаблоны и статические файлы { #templates-and-static-files } + +Вы также можете использовать `url_for()` внутри шаблона, например, с `StaticFiles`, которые вы монтировали с `name="static"`. + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +В этом примере будет создана ссылка на CSS-файл `static/styles.css` с помощью: + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +И, так как вы используете `StaticFiles`, этот CSS-файл будет автоматически «отдаваться» вашим приложением **FastAPI** по URL `/static/styles.css`. + +## Подробнее { #more-details } + +Больше подробностей, включая то, как тестировать шаблоны, смотрите в документации Starlette по шаблонам. diff --git a/docs/ru/docs/advanced/testing-dependencies.md b/docs/ru/docs/advanced/testing-dependencies.md new file mode 100644 index 0000000000..2846c5b9a3 --- /dev/null +++ b/docs/ru/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# Тестирование зависимостей с переопределениями { #testing-dependencies-with-overrides } + +## Переопределение зависимостей во время тестирования { #overriding-dependencies-during-testing } + +Есть сценарии, когда может понадобиться переопределить зависимость во время тестирования. + +Вы не хотите, чтобы исходная зависимость выполнялась (и любые её подзависимости тоже). + +Вместо этого вы хотите предоставить другую зависимость, которая будет использоваться только во время тестов (возможно, только в некоторых конкретных тестах) и будет возвращать значение, которое можно использовать везде, где использовалось значение исходной зависимости. + +### Варианты использования: внешний сервис { #use-cases-external-service } + +Пример: у вас есть внешний провайдер аутентификации, к которому нужно обращаться. + +Вы отправляете ему токен, а он возвращает аутентифицированного пользователя. + +Такой провайдер может брать плату за каждый запрос, и его вызов может занимать больше времени, чем использование фиксированного мок-пользователя для тестов. + +Вероятно, вы захотите протестировать внешний провайдер один раз, но не обязательно вызывать его для каждого запускаемого теста. + +В таком случае вы можете переопределить зависимость, которая обращается к этому провайдеру, и использовать собственную зависимость, возвращающую мок-пользователя, только для ваших тестов. + +### Используйте атрибут `app.dependency_overrides` { #use-the-app-dependency-overrides-attribute } + +Для таких случаев у вашего приложения **FastAPI** есть атрибут `app.dependency_overrides`, это простой `dict`. + +Чтобы переопределить зависимость для тестирования, укажите в качестве ключа исходную зависимость (функцию), а в качестве значения — ваше переопределение зависимости (другую функцию). + +Тогда **FastAPI** будет вызывать это переопределение вместо исходной зависимости. + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | Совет + +Вы можете задать переопределение для зависимости, используемой в любом месте вашего приложения **FastAPI**. + +Исходная зависимость может использоваться в функции-обработчике пути, в декораторе операции пути (когда вы не используете возвращаемое значение), в вызове `.include_router()` и т.д. + +FastAPI всё равно сможет её переопределить. + +/// + +Затем вы можете сбросить переопределения (удалить их), установив `app.dependency_overrides` в пустой `dict`: + +```Python +app.dependency_overrides = {} +``` + +/// tip | Совет + +Если вы хотите переопределять зависимость только во время некоторых тестов, задайте переопределение в начале теста (внутри функции теста) и сбросьте его в конце (в конце функции теста). + +/// diff --git a/docs/ru/docs/advanced/testing-events.md b/docs/ru/docs/advanced/testing-events.md new file mode 100644 index 0000000000..e0ec774399 --- /dev/null +++ b/docs/ru/docs/advanced/testing-events.md @@ -0,0 +1,12 @@ +# Тестирование событий: lifespan и startup - shutdown { #testing-events-lifespan-and-startup-shutdown } + +Если вам нужно, чтобы `lifespan` выполнялся в ваших тестах, вы можете использовать `TestClient` вместе с оператором `with`: + +{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *} + + +Вы можете узнать больше подробностей в статье [Запуск lifespan в тестах на официальном сайте документации Starlette.](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) + +Для устаревших событий `startup` и `shutdown` вы можете использовать `TestClient` следующим образом: + +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/ru/docs/advanced/testing-websockets.md b/docs/ru/docs/advanced/testing-websockets.md new file mode 100644 index 0000000000..e840a03f28 --- /dev/null +++ b/docs/ru/docs/advanced/testing-websockets.md @@ -0,0 +1,13 @@ +# Тестирование WebSocket { #testing-websockets } + +Вы можете использовать тот же `TestClient` для тестирования WebSocket. + +Для этого используйте `TestClient` с менеджером контекста `with`, подключаясь к WebSocket: + +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} + +/// note | Примечание + +Подробности смотрите в документации Starlette по тестированию WebSocket. + +/// diff --git a/docs/ru/docs/advanced/using-request-directly.md b/docs/ru/docs/advanced/using-request-directly.md new file mode 100644 index 0000000000..b922216105 --- /dev/null +++ b/docs/ru/docs/advanced/using-request-directly.md @@ -0,0 +1,56 @@ +# Прямое использование Request { #using-the-request-directly } + +До этого вы объявляли нужные части HTTP-запроса вместе с их типами. + +Извлекая данные из: + +* пути (как параметров), +* HTTP-заголовков, +* Cookie, +* и т.д. + +Тем самым **FastAPI** валидирует эти данные, преобразует их и автоматически генерирует документацию для вашего API. + +Но бывают ситуации, когда нужно обратиться к объекту `Request` напрямую. + +## Подробности об объекте `Request` { #details-about-the-request-object } + +Так как под капотом **FastAPI** — это **Starlette** с дополнительным слоем инструментов, вы можете при необходимости напрямую использовать объект `Request` из Starlette. + +Это также означает, что если вы получаете данные напрямую из объекта `Request` (например, читаете тело запроса), то они не будут валидироваться, конвертироваться или документироваться (с OpenAPI, для автоматического пользовательского интерфейса API) средствами FastAPI. + +При этом любой другой параметр, объявленный обычным образом (например, тело запроса с Pydantic-моделью), по-прежнему будет валидироваться, конвертироваться, аннотироваться и т.д. + +Но есть конкретные случаи, когда полезно получить объект `Request`. + +## Используйте объект `Request` напрямую { #use-the-request-object-directly } + +Представим, что вы хотите получить IP-адрес/хост клиента внутри вашей *функции-обработчика пути*. + +Для этого нужно обратиться к запросу напрямую. + +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} + +Если объявить параметр *функции-обработчика пути* с типом `Request`, **FastAPI** поймёт, что нужно передать объект `Request` в этот параметр. + +/// tip | Совет + +Обратите внимание, что в этом примере мы объявляем path-параметр вместе с параметром `Request`. + +Таким образом, path-параметр будет извлечён, валидирован, преобразован к указанному типу и задокументирован в OpenAPI. + +Точно так же вы можете объявлять любые другие параметры как обычно и, дополнительно, получать `Request`. + +/// + +## Документация по `Request` { #request-documentation } + +Подробнее об объекте `Request` на официальном сайте документации Starlette. + +/// note | Технические детали + +Вы также можете использовать `from starlette.requests import Request`. + +**FastAPI** предоставляет его напрямую для удобства разработчика, но сам объект приходит из Starlette. + +/// diff --git a/docs/ru/docs/advanced/websockets.md b/docs/ru/docs/advanced/websockets.md new file mode 100644 index 0000000000..f26185bea5 --- /dev/null +++ b/docs/ru/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# Веб-сокеты { #websockets } + +Вы можете использовать веб-сокеты в **FastAPI**. + +## Установка `websockets` { #install-websockets } + +Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и установили `websockets` (библиотека Python, упрощающая работу с протоколом "WebSocket"): + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## Клиент WebSockets { #websockets-client } + +### В продакшн { #in-production } + +В продакшн у вас, вероятно, есть фронтенд, созданный с помощью современного фреймворка вроде React, Vue.js или Angular. + +И для взаимодействия с бекендом по WebSocket вы, скорее всего, будете использовать инструменты вашего фронтенда. + +Также у вас может быть нативное мобильное приложение, которое напрямую, нативным кодом, взаимодействует с вашим WebSocket-бекендом. + +Либо у вас может быть любой другой способ взаимодействия с WebSocket-эндпоинтом. + +--- + +Но для этого примера мы воспользуемся очень простым HTML‑документом с небольшим JavaScript, всё внутри одной длинной строки. + +Конечно же, это неоптимально, и вы бы не использовали это в продакшн. + +В продакшн у вас был бы один из вариантов выше. + +Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код: + +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} + +## Создание `websocket` { #create-a-websocket } + +Создайте `websocket` в своем **FastAPI** приложении: + +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} + +/// note | Технические детали + +Вы также можете использовать `from starlette.websockets import WebSocket`. + +**FastAPI** напрямую предоставляет тот же самый `WebSocket` просто для удобства. На самом деле это `WebSocket` из Starlette. + +/// + +## Ожидание и отправка сообщений { #await-for-messages-and-send-messages } + +Через эндпоинт веб-сокета вы можете получать и отправлять сообщения. + +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} + +Вы можете получать и отправлять двоичные, текстовые и JSON данные. + +## Проверка в действии { #try-it } + +Если ваш файл называется `main.py`, то запустите приложение командой: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Откройте браузер по адресу http://127.0.0.1:8000. + +Вы увидите следующую простенькую страницу: + + + +Вы можете набирать сообщения в поле ввода и отправлять их: + + + +И ваше **FastAPI** приложение с веб-сокетами ответит: + + + +Вы можете отправлять и получать множество сообщений: + + + +И все они будут использовать одно и то же веб-сокет соединение. + +## Использование `Depends` и не только { #using-depends-and-others } + +Вы можете импортировать из `fastapi` и использовать в эндпоинте вебсокета: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +Они работают так же, как и в других FastAPI эндпоинтах/*операциях пути*: + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info | Примечание + +В веб-сокете вызывать `HTTPException` не имеет смысла. Вместо этого нужно использовать `WebSocketException`. + +Закрывающий статус код можно использовать из valid codes defined in the specification. + +/// + +### Веб-сокеты с зависимостями: проверка в действии { #try-the-websockets-with-dependencies } + +Если ваш файл называется `main.py`, то запустите приложение командой: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Откройте браузер по адресу http://127.0.0.1:8000. + +Там вы можете задать: + +* "Item ID", используемый в пути. +* "Token", используемый как query-параметр. + +/// tip | Подсказка + +Обратите внимание, что query-параметр `token` будет обработан в зависимости. + +/// + +Теперь вы можете подключиться к веб-сокету и начинать отправку и получение сообщений: + + + +## Обработка отключений и работа с несколькими клиентами { #handling-disconnections-and-multiple-clients } + +Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере: + +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} + +Чтобы воспроизвести пример: + +* Откройте приложение в нескольких вкладках браузера. +* Отправьте из них сообщения. +* Затем закройте одну из вкладок. + +Это вызовет исключение `WebSocketDisconnect`, и все остальные клиенты получат следующее сообщение: + +``` +Client #1596980209979 left the chat +``` + +/// tip | Подсказка + +Приложение выше - это всего лишь простой минимальный пример, демонстрирующий обработку и передачу сообщений нескольким веб-сокет соединениям. + +Но имейте в виду, что это будет работать только в одном процессе и только пока он активен, так как всё обрабатывается в простом списке в оперативной памяти. + +Если нужно что-то легко интегрируемое с FastAPI, но более надежное и с поддержкой Redis, PostgreSQL или другого, то можно воспользоваться encode/broadcaster. + +/// + +## Дополнительная информация { #more-info } + +Для более глубокого изучения темы воспользуйтесь документацией Starlette: + +* The `WebSocket` class. +* Class-based WebSocket handling. diff --git a/docs/ru/docs/advanced/wsgi.md b/docs/ru/docs/advanced/wsgi.md new file mode 100644 index 0000000000..1c5bf0a626 --- /dev/null +++ b/docs/ru/docs/advanced/wsgi.md @@ -0,0 +1,35 @@ +# Подключение WSGI — Flask, Django и другие { #including-wsgi-flask-django-others } + +Вы можете монтировать WSGI‑приложения, как вы видели в [Подприложения — Mounts](sub-applications.md){.internal-link target=_blank}, [За прокси‑сервером](behind-a-proxy.md){.internal-link target=_blank}. + +Для этого вы можете использовать `WSGIMiddleware` и обернуть им ваше WSGI‑приложение, например Flask, Django и т.д. + +## Использование `WSGIMiddleware` { #using-wsgimiddleware } + +Нужно импортировать `WSGIMiddleware`. + +Затем оберните WSGI‑приложение (например, Flask) в middleware (Промежуточный слой). + +После этого смонтируйте его на путь. + +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} + +## Проверьте { #check-it } + +Теперь каждый HTTP‑запрос по пути `/v1/` будет обрабатываться приложением Flask. + +А всё остальное будет обрабатываться **FastAPI**. + +Если вы запустите это и перейдёте по http://localhost:8000/v1/, вы увидите HTTP‑ответ от Flask: + +```txt +Hello, World from Flask! +``` + +А если вы перейдёте по http://localhost:8000/v2, вы увидите HTTP‑ответ от FastAPI: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/ru/docs/alternatives.md b/docs/ru/docs/alternatives.md index 413bf70b2d..17b54aad2f 100644 --- a/docs/ru/docs/alternatives.md +++ b/docs/ru/docs/alternatives.md @@ -1,104 +1,94 @@ -# Альтернативы, источники вдохновения и сравнения +# Альтернативы, источники вдохновения и сравнения { #alternatives-inspiration-and-comparisons } -Что вдохновило на создание **FastAPI**, сравнение его с альтернативами и чему он научился у них. +Что вдохновило **FastAPI**, сравнение с альтернативами и чему он у них научился. -## Введение +## Введение { #intro } -**FastAPI** не существовал бы, если б не было более ранних работ других людей. +**FastAPI** не существовал бы без предыдущих работ других людей. -Они создали большое количество инструментов, которые вдохновили меня на создание **FastAPI**. +Было создано множество инструментов, которые вдохновили на его появление. -Я всячески избегал создания нового фреймворка в течение нескольких лет. -Сначала я пытался собрать все нужные функции, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов. +Я несколько лет избегал создания нового фреймворка. Сначала пытался закрыть все возможности, которые сейчас предоставляет **FastAPI**, с помощью множества разных фреймворков, плагинов и инструментов. -Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти функции сразу. -Взять самые лучшие идеи из предыдущих инструментов и, используя новые возможности Python (которых не было до версии 3.6, то есть подсказки типов), объединить их. +Но в какой-то момент не осталось другого варианта, кроме как создать что-то, что включает все эти возможности, взяв лучшие идеи из прежних инструментов и совместив их максимально удачным образом, используя возможности языка, которых прежде не было (аннотации типов в Python 3.6+). -## Предшествующие инструменты +## Предшествующие инструменты { #previous-tools } -### Django +### Django { #django } -Это самый популярный Python-фреймворк, и он пользуется доверием. -Он используется для создания проектов типа Instagram. +Это самый популярный Python-фреймворк, ему широко доверяют. Он используется для построения систем вроде Instagram. -Django довольно тесно связан с реляционными базами данных (такими как MySQL или PostgreSQL), потому использовать NoSQL базы данных (например, Couchbase, MongoDB, Cassandra и т.п.) в качестве основного хранилища данных - непросто. +Он относительно тесно связан с реляционными базами данных (например, MySQL или PostgreSQL), поэтому использовать NoSQL-базу данных (например, Couchbase, MongoDB, Cassandra и т. п.) в качестве основного хранилища не очень просто. -Он был создан для генерации HTML-страниц на сервере, а не для создания API, используемых современными веб-интерфейсами (React, Vue.js, Angular и т.п.) или другими системами (например, IoT) взаимодействующими с сервером. +Он был создан для генерации HTML на бэкенде, а не для создания API, используемых современным фронтендом (например, React, Vue.js и Angular) или другими системами (например, устройствами IoT), которые с ним общаются. -### Django REST Framework +### Django REST Framework { #django-rest-framework } -Фреймворк Django REST был создан, как гибкий инструментарий для создания веб-API на основе Django. +Django REST Framework был создан как гибкий набор инструментов для построения веб-API поверх Django, чтобы улучшить его возможности в части API. -DRF использовался многими компаниями, включая Mozilla, Red Hat и Eventbrite. +Он используется многими компаниями, включая Mozilla, Red Hat и Eventbrite. -Это был один из первых примеров **автоматического документирования API** и это была одна из первых идей, вдохновивших на создание **FastAPI**. +Это был один из первых примеров **автоматической документации API**, и именно эта идея одной из первых вдохновила на «поиск» **FastAPI**. -/// note | "Заметка" +/// note | Заметка -Django REST Framework был создан Tom Christie. -Он же создал Starlette и Uvicorn, на которых основан **FastAPI**. +Django REST Framework был создан Томом Кристи. Он же создал Starlette и Uvicorn, на которых основан **FastAPI**. /// -/// check | "Идея для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Должно быть автоматическое создание документации API с пользовательским веб-интерфейсом. +Наличие пользовательского веб-интерфейса с автоматической документацией API. /// -### Flask +### Flask { #flask } -Flask - это "микрофреймворк", в нём нет интеграции с базами данных и многих других вещей, которые предустановлены в Django. +Flask — это «микрофреймворк», он не включает интеграции с базами данных и многие другие вещи, которые в Django идут «из коробки». -Его простота и гибкость дают широкие возможности, такие как использование баз данных NoSQL в качестве основной системы хранения данных. +Эта простота и гибкость позволяет, например, использовать NoSQL-базы в качестве основной системы хранения данных. -Он очень прост, его изучение интуитивно понятно, хотя в некоторых местах документация довольно техническая. +Он очень прост, его относительно легко интуитивно освоить, хотя местами документация довольно техническая. -Flask часто используется и для приложений, которым не нужна база данных, настройки прав доступа для пользователей и прочие из множества функций, предварительно встроенных в Django. -Хотя многие из этих функций могут быть добавлены с помощью плагинов. +Его также часто используют для приложений, которым не нужна база данных, управление пользователями или многие другие функции, предварительно встроенные в Django. Хотя многие из этих возможностей можно добавить плагинами. -Такое разделение на части и то, что это "микрофреймворк", который можно расширить, добавляя необходимые возможности, было ключевой особенностью, которую я хотел сохранить. +Такое разбиение на части и то, что это «микрофреймворк», который можно расширять ровно под нужды, — ключевая особенность, которую хотелось сохранить. -Простота Flask, показалась мне подходящей для создания API. -Но ещё нужно было найти "Django REST Framework" для Flask. +С учётом простоты Flask он казался хорошим вариантом для создания API. Следующим было найти «Django REST Framework» для Flask. -/// check | "Идеи для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Это будет микрофреймворк. К нему легко будет добавить необходимые инструменты и части. +Быть микро-фреймворком. Облегчить комбинирование необходимых инструментов и компонентов. -Должна быть простая и лёгкая в использовании система маршрутизации запросов. +Иметь простую и удобную систему маршрутизации. /// -### Requests +### Requests { #requests } -На самом деле **FastAPI** не является альтернативой **Requests**. -Их область применения очень разная. +**FastAPI** на самом деле не альтернатива **Requests**. Их области применения очень различны. -В принципе, можно использовать Requests *внутри* приложения FastAPI. +Обычно Requests используют даже внутри приложения FastAPI. -Но всё же я использовал в FastAPI некоторые идеи из Requests. +И всё же **FastAPI** во многом вдохновлялся Requests. -**Requests** - это библиотека для взаимодействия с API в качестве клиента, -в то время как **FastAPI** - это библиотека для *создания* API (то есть сервера). +**Requests** — это библиотека для взаимодействия с API (как клиент), а **FastAPI** — библиотека для создания API (как сервер). -Они, так или иначе, диаметрально противоположны и дополняют друг друга. +Они, в каком-то смысле, находятся на противоположных концах и дополняют друг друга. -Requests имеет очень простой и понятный дизайн, очень прост в использовании и имеет разумные значения по умолчанию. -И в то же время он очень мощный и настраиваемый. +Requests имеет очень простой и понятный дизайн, им очень легко пользоваться, есть разумные значения по умолчанию. И при этом он очень мощный и настраиваемый. -Вот почему на официальном сайте написано: +Именно поэтому на официальном сайте сказано: -> Requests - один из самых загружаемых пакетов Python всех времен +> Requests — один из самых загружаемых Python-пакетов всех времён - -Использовать его очень просто. Например, чтобы выполнить запрос `GET`, Вы бы написали: +Пользоваться им очень просто. Например, чтобы сделать запрос `GET`, вы бы написали: ```Python response = requests.get("http://example.com/some/url") ``` -Противоположная *операция пути* в FastAPI может выглядеть следующим образом: +Соответствующая в FastAPI API-операция пути могла бы выглядеть так: ```Python hl_lines="1" @app.get("/some/url") @@ -106,428 +96,390 @@ def read_url(): return {"message": "Hello World"} ``` -Глядите, как похоже `requests.get(...)` и `@app.get(...)`. +Посмотрите, насколько похожи `requests.get(...)` и `@app.get(...)`. -/// check | "Идеи для **FastAPI**" +/// check | Вдохновило **FastAPI** на -* Должен быть простой и понятный API. -* Нужно использовать названия HTTP-методов (операций) для упрощения понимания происходящего. -* Должны быть разумные настройки по умолчанию и широкие возможности их кастомизации. +* Иметь простой и понятный API. +* Использовать названия HTTP-методов (операций) напрямую, простым и интуитивным образом. +* Иметь разумные значения по умолчанию, но и мощные настройки. /// -### Swagger / OpenAPI +### Swagger / OpenAPI { #swagger-openapi } -Главной функцией, которую я хотел унаследовать от Django REST Framework, была автоматическая документация API. +Главной возможностью, которую хотелось взять из Django REST Framework, была автоматическая документация API. -Но потом я обнаружил, что существует стандарт документирования API, использующий JSON (или YAML, расширение JSON) под названием Swagger. +Затем я обнаружил, что есть стандарт для документирования API с использованием JSON (или YAML — расширения JSON), под названием Swagger. -И к нему уже был создан пользовательский веб-интерфейс. -Таким образом, возможность генерировать документацию Swagger для API позволила бы использовать этот интерфейс. +И уже существовал веб-интерфейс для Swagger API. Поэтому возможность генерировать документацию Swagger для API позволила бы автоматически использовать этот веб-интерфейс. В какой-то момент Swagger был передан Linux Foundation и переименован в OpenAPI. -Вот почему, когда говорят о версии 2.0, обычно говорят "Swagger", а для версии 3+ "OpenAPI". +Вот почему, говоря о версии 2.0, обычно говорят «Swagger», а о версии 3+ — «OpenAPI». -/// check | "Идеи для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Использовать открытые стандарты для спецификаций API вместо самодельных схем. +Использовать открытый стандарт для спецификаций API вместо самодельной схемы. -Совместимость с основанными на стандартах пользовательскими интерфейсами: +И интегрировать основанные на стандартах инструменты пользовательского интерфейса: * Swagger UI * ReDoc -Они были выбраны за популярность и стабильность. -Но сделав беглый поиск, Вы можете найти десятки альтернативных пользовательских интерфейсов для OpenAPI, которые Вы можете использовать с **FastAPI**. +Эти два инструмента выбраны за популярность и стабильность, но даже при беглом поиске можно найти десятки альтернативных интерфейсов для OpenAPI (которые можно использовать с **FastAPI**). /// -### REST фреймворки для Flask +### REST-фреймворки для Flask { #flask-rest-frameworks } -Существует несколько REST фреймворков для Flask, но потратив время и усилия на их изучение, я обнаружил, что многие из них не обновляются или заброшены и имеют нерешённые проблемы из-за которых они непригодны к использованию. +Существует несколько REST-фреймворков для Flask, но, вложив время и усилия в исследование, я обнаружил, что многие из них прекращены или заброшены, с несколькими нерешёнными Issue (тикет\обращение), из-за которых они непригодны. -### Marshmallow +### Marshmallow { #marshmallow } -Одной из основных функций, необходимых системам API, является "сериализация" данных, то есть преобразование данных из кода (Python) во что-то, что может быть отправлено по сети. -Например, превращение объекта содержащего данные из базы данных в объект JSON, конвертация объекта `datetime` в строку и т.п. +Одна из основных возможностей, нужных системам API, — «сериализация» данных, то есть преобразование данных из кода (Python) во что-то, что можно отправить по сети. Например, преобразование объекта с данными из базы в JSON-объект. Преобразование объектов `datetime` в строки и т. п. -Еще одна важная функция, необходимая API — проверка данных, позволяющая убедиться, что данные действительны и соответствуют заданным параметрам. -Как пример, можно указать, что ожидаются данные типа `int`, а не какая-то произвольная строка. -Это особенно полезно для входящих данных. +Ещё одна важная возможность, востребованная API, — валидация данных: убеждаться, что данные валидны с учётом заданных параметров. Например, что какое-то поле — `int`, а не произвольная строка. Это особенно полезно для входящих данных. -Без системы проверки данных Вам пришлось бы прописывать все проверки вручную. +Без системы валидации данных вам пришлось бы выполнять все проверки вручную в коде. -Именно для обеспечения этих функций и была создана Marshmallow. -Это отличная библиотека и я много раз пользовался ею раньше. +Именно для этих возможностей и был создан Marshmallow. Это отличная библиотека, я много ей пользовался раньше. -Но она была создана до того, как появились подсказки типов Python. -Итак, чтобы определить каждую схему, -Вам нужно использовать определенные утилиты и классы, предоставляемые Marshmallow. +Но она появилась до того, как в Python появились аннотации типов. Поэтому для определения каждой схемы нужно использовать специальные утилиты и классы, предоставляемые Marshmallow. -/// check | "Идея для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Использовать код программы для автоматического создания "схем", определяющих типы данных и их проверку. +Использовать код для автоматического определения «схем», задающих типы данных и их валидацию. /// -### Webargs +### Webargs { #webargs } -Другая немаловажная функция API - парсинг данных из входящих запросов. +Ещё одна важная возможность для API — парсинг данных из входящих HTTP-запросов. -Webargs - это инструмент, который был создан для этого и поддерживает несколько фреймворков, включая Flask. +Webargs — это инструмент, созданный для этого поверх нескольких фреймворков, включая Flask. -Для проверки данных он использует Marshmallow и создан теми же авторами. +Он использует Marshmallow для валидации данных. И создан теми же разработчиками. -Это превосходный инструмент и я тоже часто пользовался им до **FastAPI**. +Это отличный инструмент, и я тоже много им пользовался до появления **FastAPI**. -/// info | "Информация" +/// info | Информация -Webargs бы создан разработчиками Marshmallow. +Webargs был создан теми же разработчиками, что и Marshmallow. /// -/// check | "Идея для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Должна быть автоматическая проверка входных данных. +Автоматическую валидацию входящих данных HTTP-запроса. /// -### APISpec +### APISpec { #apispec } -Marshmallow и Webargs осуществляют проверку, анализ и сериализацию данных как плагины. +Marshmallow и Webargs предоставляют валидацию, парсинг и сериализацию как плагины. -Но документации API всё ещё не было. Тогда был создан APISpec. +Но документации всё ещё не было. Тогда появился APISpec. -Это плагин для множества фреймворков, в том числе и для Starlette. +Это плагин для многих фреймворков (есть плагин и для Starlette). -Он работает так - Вы записываете определение схем, используя формат YAML, внутри докстринга каждой функции, обрабатывающей маршрут. +Он работает так: вы пишете определение схемы в формате YAML внутри докстринга каждой функции, обрабатывающей маршрут. -Используя эти докстринги, он генерирует схему OpenAPI. +И он генерирует схемы OpenAPI. -Так это работает для Flask, Starlette, Responder и т.п. +Так это работает во Flask, Starlette, Responder и т. д. -Но теперь у нас возникает новая проблема - наличие постороннего микро-синтаксиса внутри кода Python (большие YAML). +Но у нас снова возникает проблема: появляется микро-синтаксис внутри строки Python (большой YAML). -Редактор кода не особо может помочь в такой парадигме. -А изменив какие-то параметры или схемы для Marshmallow можно забыть отредактировать докстринг с YAML и сгенерированная схема становится недействительной. +Редактор кода мало чем может помочь. И если мы изменим параметры или схемы Marshmallow и забудем также изменить YAML в докстринге, сгенерированная схема устареет. -/// info | "Информация" +/// info | Информация -APISpec тоже был создан авторами Marshmallow. +APISpec был создан теми же разработчиками, что и Marshmallow. /// -/// check | "Идея для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Необходима поддержка открытого стандарта для API - OpenAPI. +Поддержку открытого стандарта для API — OpenAPI. /// -### Flask-apispec +### Flask-apispec { #flask-apispec } -Это плагин для Flask, который связан с Webargs, Marshmallow и APISpec. +Это плагин для Flask, который связывает Webargs, Marshmallow и APISpec. -Он получает информацию от Webargs и Marshmallow, а затем использует APISpec для автоматического создания схемы OpenAPI. +Он использует информацию из Webargs и Marshmallow, чтобы автоматически генерировать схемы OpenAPI с помощью APISpec. -Это отличный, но крайне недооценённый инструмент. -Он должен быть более популярен, чем многие плагины для Flask. -Возможно, это связано с тем, что его документация слишком скудна и абстрактна. +Отличный и недооценённый инструмент. Он заслуживает большей популярности, чем многие плагины для Flask. Возможно, из-за слишком краткой и абстрактной документации. -Он избавил от необходимости писать чужеродный синтаксис YAML внутри докстрингов. +Это решило проблему необходимости писать YAML (ещё один синтаксис) в докстрингах Python. -Такое сочетание Flask, Flask-apispec, Marshmallow и Webargs было моим любимым стеком при построении бэкенда до появления **FastAPI**. +Комбинация Flask, Flask-apispec с Marshmallow и Webargs была моим любимым бэкенд-стеком до создания **FastAPI**. -Использование этого стека привело к созданию нескольких генераторов проектов. Я и некоторые другие команды до сих пор используем их: +Его использование привело к созданию нескольких full-stack генераторов на Flask. Это основные стеки, которые я (и несколько внешних команд) использовали до сих пор: * 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}. +И эти же full-stack генераторы стали основой для [Генераторов проектов **FastAPI**](project-generation.md){.internal-link target=_blank}. -/// info | "Информация" +/// info | Информация -Как ни странно, но Flask-apispec тоже создан авторами Marshmallow. +Flask-apispec был создан теми же разработчиками, что и Marshmallow. /// -/// check | "Идея для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Схема OpenAPI должна создаваться автоматически и использовать тот же код, который осуществляет сериализацию и проверку данных. +Автоматическую генерацию схемы OpenAPI из того же кода, который определяет сериализацию и валидацию. /// -### NestJSAngular) +### NestJSAngular) { #nestjs-and-angular } -Здесь даже не используется Python. NestJS - этот фреймворк написанный на JavaScript (TypeScript), основанный на NodeJS и вдохновлённый Angular. +Это даже не Python. NestJS — это JavaScript/TypeScript-фреймворк на NodeJS, вдохновлённый Angular. -Он позволяет получить нечто похожее на то, что можно сделать с помощью Flask-apispec. +Он достигает чего-то отчасти похожего на то, что можно сделать с Flask-apispec. -В него встроена система внедрения зависимостей, ещё одна идея взятая от Angular. -Однако требуется предварительная регистрация "внедрений" (как и во всех других известных мне системах внедрения зависимостей), что увеличивает количество и повторяемость кода. +В нём встроена система внедрения зависимостей, вдохновлённая Angular 2. Требуется предварительная регистрация «инжектируемых» компонентов (как и во всех известных мне системах внедрения зависимостей), что добавляет многословности и повторяемости кода. -Так как параметры в нём описываются с помощью типов TypeScript (аналогично подсказкам типов в Python), поддержка редактора работает довольно хорошо. +Поскольку параметры описываются с помощью типов TypeScript (аналог аннотаций типов в Python), поддержка редактора весьма хороша. -Но поскольку данные из TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на подсказки типов для определения проверки данных, сериализации и документации. -Из-за этого и некоторых дизайнерских решений, для валидации, сериализации и автоматической генерации схем, приходится во многих местах добавлять декораторы. -Таким образом, это становится довольно многословным. +Но так как данные о типах TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на типы для одновременного определения валидации, сериализации и документации. Из‑за этого и некоторых проектных решений для получения валидации, сериализации и автоматической генерации схем приходится добавлять декораторы во многих местах. В итоге это становится довольно многословным. -Кроме того, он не очень хорошо справляется с вложенными моделями. -Если в запросе имеется объект JSON, внутренние поля которого, в свою очередь, являются вложенными объектами JSON, это не может быть должным образом задокументировано и проверено. +Он плохо справляется с вложенными моделями. Если JSON-тело запроса — это объект JSON, содержащий внутренние поля, которые сами являются вложенными объектами JSON, это нельзя как следует задокументировать и провалидировать. -/// check | "Идеи для **FastAPI** " +/// check | Вдохновило **FastAPI** на -Нужно использовать подсказки типов, чтоб воспользоваться поддержкой редактора кода. +Использовать типы Python для отличной поддержки в редакторе кода. -Нужна мощная система внедрения зависимостей. Необходим способ для уменьшения повторов кода. +Иметь мощную систему внедрения зависимостей. Найти способ минимизировать повторение кода. /// -### Sanic +### Sanic { #sanic } -Sanic был одним из первых чрезвычайно быстрых Python-фреймворков основанных на `asyncio`. -Он был сделан очень похожим на Flask. +Это был один из первых чрезвычайно быстрых Python-фреймворков на основе `asyncio`. Он был сделан очень похожим на Flask. -/// note | "Технические детали" +/// note | Технические детали -В нём использован `uvloop` вместо стандартного цикла событий `asyncio`, что и сделало его таким быстрым. +Он использовал `uvloop` вместо стандартного цикла `asyncio` в Python. Это и сделало его таким быстрым. -Он явно вдохновил создателей Uvicorn и Starlette, которые в настоящее время быстрее Sanic в открытых бенчмарках. +Он явно вдохновил Uvicorn и Starlette, которые сейчас быстрее Sanic в открытых бенчмарках. /// -/// check | "Идеи для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Должна быть сумасшедшая производительность. +Поиск способа достичь сумасшедшей производительности. -Для этого **FastAPI** основан на Starlette, самом быстром из доступных фреймворков (по замерам незаинтересованных лиц). +Именно поэтому **FastAPI** основан на Starlette, так как это самый быстрый доступный фреймворк (по данным сторонних бенчмарков). /// -### Falcon +### Falcon { #falcon } -Falcon - ещё один высокопроизводительный Python-фреймворк. -В нём минимум функций и он создан, чтоб быть основой для других фреймворков, например, Hug. +Falcon — ещё один высокопроизводительный Python-фреймворк, он минималистичен и служит основой для других фреймворков, таких как Hug. -Функции в нём получают два параметра - "запрос к серверу" и "ответ сервера". -Затем Вы "читаете" часть запроса и "пишите" часть ответа. -Из-за такой конструкции невозможно объявить параметры запроса и тела сообщения со стандартными подсказками типов Python в качестве параметров функции. +Он спроектирован так, что функции получают два параметра: «request» и «response». Затем вы «читаете» части из запроса и «пишете» части в ответ. Из‑за такого дизайна невозможно объявить параметры запроса и тело запроса стандартными аннотациями типов Python как параметры функции. -Таким образом, и валидацию данных, и их сериализацию, и документацию нужно прописывать вручную. -Либо эти функции должны быть встроены во фреймворк, сконструированный поверх Falcon, как в Hug. -Такая же особенность присутствует и в других фреймворках, вдохновлённых идеей Falcon, использовать только один объект запроса и один объект ответа. +Поэтому валидация данных, сериализация и документация должны выполняться в коде вручную, не автоматически. Либо должны быть реализованы во фреймворке поверх Falcon, как в Hug. Та же особенность есть и в других фреймворках, вдохновлённых дизайном Falcon — с одним объектом запроса и одним объектом ответа в параметрах. -/// check | "Идея для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Найдите способы добиться отличной производительности. +Поиск способов получить отличную производительность. -Объявлять параметры `ответа сервера` в функциях, как в Hug. +Вместе с Hug (так как Hug основан на Falcon) вдохновило **FastAPI** объявлять параметр `response` в функциях. -Хотя в FastAPI это необязательно и используется в основном для установки заголовков, куки и альтернативных кодов состояния. +Хотя в FastAPI это необязательно, и используется в основном для установки HTTP-заголовков, cookie и альтернативных статус-кодов. /// -### Molten +### Molten { #molten } -Molten мне попался на начальной стадии написания **FastAPI**. В нём были похожие идеи: +Я обнаружил Molten на ранних этапах создания **FastAPI**. И у него были очень похожие идеи: -* Использование подсказок типов. -* Валидация и документация исходя из этих подсказок. +* Основан на аннотациях типов Python. +* Валидация и документация из этих типов. * Система внедрения зависимостей. -В нём не используются сторонние библиотеки (такие, как Pydantic) для валидации, сериализации и документации. -Поэтому переиспользовать эти определения типов непросто. +Он не использует стороннюю библиотеку для валидации, сериализации и документации, такую как Pydantic, — у него своя. Поэтому такие определения типов данных будет сложнее переиспользовать. -Также требуется более подробная конфигурация и используется стандарт WSGI, который не предназначен для использования с высокопроизводительными инструментами, такими как Uvicorn, Starlette и Sanic, в отличие от ASGI. +Требуются более многословные конфигурации. И так как он основан на WSGI (вместо ASGI), он не предназначен для использования преимуществ высокой производительности инструментов вроде Uvicorn, Starlette и Sanic. -Его система внедрения зависимостей требует предварительной регистрации, и зависимости определяются, как объявления типов. -Из-за этого невозможно объявить более одного "компонента" (зависимости), который предоставляет определенный тип. +Система внедрения зависимостей требует предварительной регистрации зависимостей, а зависимости разрешаются по объявленным типам. Поэтому невозможно объявить более одного «компонента», предоставляющего определённый тип. -Маршруты объявляются в единственном месте с использованием функций, объявленных в других местах (вместо использования декораторов, в которые могут быть обёрнуты функции, обрабатывающие конкретные ресурсы). -Это больше похоже на Django, чем на Flask и Starlette. -Он разделяет в коде вещи, которые довольно тесно связаны. +Маршруты объявляются в одном месте, используя функции, объявленные в других местах (вместо декораторов, которые можно разместить прямо над функцией, обрабатывающей эндпоинт). Это ближе к тому, как это делает Django, чем Flask (и Starlette). Это разделяет в коде вещи, которые довольно тесно связаны. -/// check | "Идея для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Определить дополнительные проверки типов данных, используя значения атрибутов модели "по умолчанию". -Это улучшает помощь редактора и раньше это не было доступно в Pydantic. +Определять дополнительные проверки типов данных, используя значение «по умолчанию» атрибутов модели. Это улучшает поддержку в редакторе кода, и раньше этого не было в Pydantic. -Фактически это подтолкнуло на обновление Pydantic для поддержки одинакового стиля проверок (теперь этот функционал уже доступен в Pydantic). +Фактически это вдохновило на обновление частей Pydantic, чтобы поддерживать такой же стиль объявления валидации (вся эта функциональность теперь уже есть в Pydantic). /// -### Hug +### Hug { #hug } -Hug был одним из первых фреймворков, реализовавших объявление параметров API с использованием подсказок типов Python. -Эта отличная идея была использована и другими инструментами. +Hug был одним из первых фреймворков, реализовавших объявление типов параметров API с использованием аннотаций типов Python. Это была отличная идея, которая вдохновила и другие инструменты. -При объявлении параметров вместо стандартных типов Python использовались собственные типы, но всё же это был огромный шаг вперед. +Он использовал собственные типы в объявлениях вместо стандартных типов Python, но это всё равно был огромный шаг вперёд. -Это также был один из первых фреймворков, генерировавших полную API-схему в формате JSON. +Он также был одним из первых фреймворков, генерировавших собственную схему, описывающую весь API в JSON. -Данная схема не придерживалась стандартов вроде OpenAPI и JSON Schema. -Поэтому было бы непросто совместить её с другими инструментами, такими как Swagger UI. -Но опять же, это была очень инновационная идея. +Он не был основан на стандартах вроде OpenAPI и JSON Schema. Поэтому интегрировать его с другими инструментами, такими как Swagger UI, было бы непросто. Но, опять же, это была очень инновационная идея. -Ещё у него есть интересная и необычная функция: используя один и тот же фреймворк можно создавать и API, и CLI. +У него есть интересная и необычная особенность: с помощью одного и того же фреймворка можно создавать и API, и CLI. -Поскольку он основан на WSGI, старом стандарте для синхронных веб-фреймворков, он не может работать с веб-сокетами и другими модными штуками, но всё равно обладает высокой производительностью. +Так как он основан на предыдущем стандарте для синхронных веб-фреймворков Python (WSGI), он не может работать с WebSocket и прочим, хотя также демонстрирует высокую производительность. -/// info | "Информация" +/// info | Информация -Hug создан Timothy Crosley, автором `isort`, отличного инструмента для автоматической сортировки импортов в Python-файлах. +Hug был создан Тимоти Кросли, тем же автором `isort`, отличного инструмента для автоматической сортировки импортов в файлах Python. /// -/// check | "Идеи для **FastAPI**" +/// check | Идеи, вдохновившие **FastAPI** -Hug повлиял на создание некоторых частей APIStar и был одним из инструментов, которые я счел наиболее многообещающими, наряду с APIStar. +Hug вдохновил части APIStar и был одним из наиболее многообещающих инструментов, которые я нашёл, наряду с APIStar. -Hug натолкнул на мысли использовать в **FastAPI** подсказки типов Python для автоматического создания схемы, определяющей API и его параметры. +Hug помог вдохновить **FastAPI** использовать аннотации типов Python для объявления параметров и автоматически генерировать схему, определяющую API. -Hug вдохновил **FastAPI** объявить параметр `ответа` в функциях для установки заголовков и куки. +Hug вдохновил **FastAPI** объявлять параметр `response` в функциях для установки HTTP-заголовков и cookie. /// -### APIStar (<= 0.5) +### APIStar (<= 0.5) { #apistar-0-5 } -Непосредственно перед тем, как принять решение о создании **FastAPI**, я обнаружил **APIStar**. -В нем было почти все, что я искал и у него был отличный дизайн. +Прямо перед решением строить **FastAPI** я нашёл сервер **APIStar**. В нём было почти всё, что я искал, и отличный дизайн. -Это была одна из первых реализаций фреймворка, использующего подсказки типов для объявления параметров и запросов, которые я когда-либо видел (до NestJS и Molten). -Я нашёл его примерно в то же время, что и Hug, но APIStar использовал стандарт OpenAPI. +Это была одна из первых реализаций фреймворка, использующего аннотации типов Python для объявления параметров и запросов (до NestJS и Molten), которые я видел. Я обнаружил его примерно в то же время, что и Hug. Но APIStar использовал стандарт OpenAPI. -В нём были автоматические проверка и сериализация данных и генерация схемы OpenAPI основанные на подсказках типов в нескольких местах. +В нём были автоматические валидация данных, сериализация данных и генерация схемы OpenAPI на основе тех же аннотаций типов в нескольких местах. -При определении схемы тела сообщения не использовались подсказки типов, как в Pydantic, это больше похоже на Marshmallow, поэтому помощь редактора была недостаточно хорошей, но всё же APIStar был лучшим доступным вариантом. +Определение схемы тела запроса не использовало те же аннотации типов Python, как в Pydantic, — это было ближе к Marshmallow, поэтому поддержка редактора была бы хуже, но всё равно APIStar оставался лучшим доступным вариантом. -На тот момент у него были лучшие показатели производительности (проигрывающие только Starlette). +На тот момент у него были лучшие показатели в бенчмарках (его превосходил только Starlette). -Изначально у него не было автоматической документации API для веб-интерфейса, но я знал, что могу добавить к нему Swagger UI. +Сначала у него не было веб‑UI для автоматической документации API, но я знал, что могу добавить к нему Swagger UI. -В APIStar была система внедрения зависимостей, которая тоже требовала предварительную регистрацию компонентов, как и ранее описанные инструменты. -Но, тем не менее, это была отличная штука. +У него была система внедрения зависимостей. Она требовала предварительной регистрации компонентов, как и другие инструменты, обсуждавшиеся выше. Но всё же это была отличная возможность. -Я не смог использовать его в полноценном проекте, так как были проблемы со встраиванием функций безопасности в схему OpenAPI, из-за которых невозможно было встроить все функции, применяемые в генераторах проектов на основе Flask-apispec. -Я добавил в свой список задач создание пул-реквеста, добавляющего эту функциональность. +Мне так и не удалось использовать его в полном проекте, поскольку не было интеграции с системой безопасности, поэтому я не мог заменить все возможности, которые имел с full-stack генераторами на основе Flask-apispec. В моём бэклоге было создать пулл-реквест (запрос на изменение), добавляющий эту функциональность. -В дальнейшем фокус проекта сместился. +Затем фокус проекта сместился. -Это больше не был API-фреймворк, так как автор сосредоточился на Starlette. +Это перестал быть веб-фреймворк для API, так как автору нужно было сосредоточиться на Starlette. -Ныне APIStar - это набор инструментов для проверки спецификаций OpenAPI. +Сейчас APIStar — это набор инструментов для валидации спецификаций OpenAPI, а не веб-фреймворк. -/// info | "Информация" +/// info | Информация -APIStar был создан Tom Christie. Тот самый парень, который создал: +APIStar был создан Томом Кристи. Тем самым человеком, который создал: * Django REST Framework * Starlette (на котором основан **FastAPI**) -* Uvicorn (используемый в Starlette и **FastAPI**) +* Uvicorn (используется Starlette и **FastAPI**) /// -/// check | "Идеи для **FastAPI**" +/// check | Вдохновило **FastAPI** на -Воплощение. +Существование. -Мне казалось блестящей идеей объявлять множество функций (проверка данных, сериализация, документация) с помощью одних и тех же типов Python, которые при этом обеспечивают ещё и помощь редактора кода. +Идея объявлять сразу несколько вещей (валидацию данных, сериализацию и документацию) с помощью одних и тех же типов Python, которые одновременно обеспечивают отличную поддержку в редакторе кода, показалась мне блестящей. -После долгих поисков среди похожих друг на друга фреймворков и сравнения их различий, APIStar стал самым лучшим выбором. +После долгих поисков похожего фреймворка и тестирования множества альтернатив APIStar был лучшим доступным вариантом. -Но APIStar перестал быть фреймворком для создания веб-сервера, зато появился Starlette, новая и лучшая основа для построения подобных систем. -Это была последняя капля, сподвигнувшая на создание **FastAPI**. +Затем APIStar перестал существовать как сервер, а был создан Starlette — новая и лучшая основа для такой системы. Это стало окончательным вдохновением для создания **FastAPI**. -Я считаю **FastAPI** "духовным преемником" APIStar, улучившим его возможности благодаря урокам, извлечённым из всех упомянутых выше инструментов. +Я считаю **FastAPI** «духовным преемником» APIStar, который улучшает и расширяет возможности, систему типов и другие части, опираясь на уроки от всех этих предыдущих инструментов. /// -## Что используется в **FastAPI** +## Что используется в **FastAPI** { #used-by-fastapi } -### Pydantic +### Pydantic { #pydantic } -Pydantic - это библиотека для валидации данных, сериализации и документирования (используя JSON Schema), основываясь на подсказках типов Python, что делает его чрезвычайно интуитивным. +Pydantic — это библиотека для определения валидации данных, сериализации и документации (с использованием JSON Schema) на основе аннотаций типов Python. -Его можно сравнить с Marshmallow, хотя в бенчмарках Pydantic быстрее, чем Marshmallow. -И он основан на тех же подсказках типов, которые отлично поддерживаются редакторами кода. +Благодаря этому он чрезвычайно интуитивен. -/// check | "**FastAPI** использует Pydantic" +Его можно сравнить с Marshmallow. Хотя в бенчмарках он быстрее Marshmallow. И поскольку он основан на тех же аннотациях типов Python, поддержка в редакторе кода отличная. -Для проверки данных, сериализации данных и автоматической документации моделей (на основе JSON Schema). +/// check | **FastAPI** использует его для -Затем **FastAPI** берёт эти схемы JSON и помещает их в схему OpenAPI, не касаясь других вещей, которые он делает. +Обработки всей валидации данных, сериализации данных и автоматической документации моделей (на основе JSON Schema). + +Затем **FastAPI** берёт эти данные JSON Schema и помещает их в OpenAPI, помимо всех прочих функций. /// -### Starlette +### Starlette { #starlette } -Starlette - это легковесный ASGI фреймворк/набор инструментов, который идеален для построения высокопроизводительных асинхронных сервисов. +Starlette — это лёгкий ASGI фреймворк/набор инструментов, идеально подходящий для создания высокопроизводительных asyncio‑сервисов. -Starlette очень простой и интуитивный. -Он разработан таким образом, чтобы быть легко расширяемым и иметь модульные компоненты. +Он очень простой и интуитивный. Спроектирован так, чтобы его было легко расширять, и чтобы компоненты были модульными. В нём есть: * Впечатляющая производительность. -* Поддержка веб-сокетов. -* Фоновые задачи. -* Обработка событий при старте и финише приложения. -* Тестовый клиент на основе HTTPX. -* Поддержка CORS, сжатие GZip, статические файлы, потоковая передача данных. -* Поддержка сессий и куки. +* Поддержка WebSocket. +* Фоновые задачи, выполняемые в том же процессе. +* События запуска и завершения. +* Тестовый клиент на базе HTTPX. +* CORS, GZip, статические файлы, потоковые ответы. +* Поддержка сессий и cookie. * 100% покрытие тестами. -* 100% аннотированный код. -* Несколько жёстких зависимостей. +* 100% кодовой базы с аннотациями типов. +* Мало жёстких зависимостей. -В настоящее время Starlette показывает самую высокую скорость среди Python-фреймворков в тестовых замерах. -Быстрее только Uvicorn, который является сервером, а не фреймворком. +В настоящее время Starlette — самый быстрый из протестированных Python-фреймворков. Его превосходит только Uvicorn, который не фреймворк, а сервер. -Starlette обеспечивает весь функционал микрофреймворка, но не предоставляет автоматическую валидацию данных, сериализацию и документацию. +Starlette предоставляет весь базовый функционал веб-микрофреймворка. -**FastAPI** добавляет эти функции используя подсказки типов Python и Pydantic. -Ещё **FastAPI** добавляет систему внедрения зависимостей, утилиты безопасности, генерацию схемы OpenAPI и т.д. +Но он не предоставляет автоматическую валидацию данных, сериализацию или документацию. -/// note | "Технические детали" +Это одна из главных вещей, которые **FastAPI** добавляет поверх, всё на основе аннотаций типов Python (с использованием Pydantic). Плюс система внедрения зависимостей, утилиты безопасности, генерация схемы OpenAPI и т. д. -ASGI - это новый "стандарт" разработанный участниками команды Django. -Он пока что не является "стандартом в Python" (то есть принятым PEP), но процесс принятия запущен. +/// note | Технические детали -Тем не менее он уже используется в качестве "стандарта" несколькими инструментами. -Это значительно улучшает совместимость, поскольку Вы можете переключиться с Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или Вы можете добавить ASGI-совместимые инструменты, такие как `python-socketio`. +ASGI — это новый «стандарт», разрабатываемый участниками core-команды Django. Он всё ещё не является «стандартом Python» (PEP), хотя процесс идёт. + +Тем не менее его уже используют как «стандарт» несколько инструментов. Это сильно улучшает совместимость: вы можете заменить Uvicorn на любой другой ASGI-сервер (например, Daphne или Hypercorn) или добавить совместимые с ASGI инструменты, такие как `python-socketio`. /// -/// check | "**FastAPI** использует Starlette" +/// check | **FastAPI** использует его для -В качестве ядра веб-сервиса для обработки запросов, добавив некоторые функции сверху. +Обработки всех основных веб-частей. Добавляя возможности поверх. -Класс `FastAPI` наследуется напрямую от класса `Starlette`. +Класс `FastAPI` напрямую наследуется от класса `Starlette`. -Таким образом, всё что Вы могли делать со Starlette, Вы можете делать с **FastAPI**, по сути это прокачанный Starlette. +Так что всё, что вы можете сделать со Starlette, вы можете сделать напрямую с **FastAPI**, по сути это «Starlette на стероидах». /// -### Uvicorn +### Uvicorn { #uvicorn } -Uvicorn - это молниеносный ASGI-сервер, построенный на uvloop и httptools. +Uvicorn — молниеносный ASGI-сервер, построенный на uvloop и httptools. -Uvicorn является сервером, а не фреймворком. -Например, он не предоставляет инструментов для маршрутизации запросов по ресурсам. -Для этого нужна надстройка, такая как Starlette (или **FastAPI**). +Это не веб-фреймворк, а сервер. Например, он не предоставляет инструменты для маршрутизации по путям. Это предоставляет сверху фреймворк, такой как Starlette (или **FastAPI**). -Он рекомендуется в качестве сервера для Starlette и **FastAPI**. +Это рекомендуемый сервер для Starlette и **FastAPI**. -/// check | "**FastAPI** рекомендует его" +/// check | **FastAPI** рекомендует его как -Как основной сервер для запуска приложения **FastAPI**. +Основной веб-сервер для запуска приложений **FastAPI**. -Вы можете объединить его с Gunicorn, чтобы иметь асинхронный многопроцессный сервер. +Также вы можете использовать опцию командной строки `--workers`, чтобы получить асинхронный многопроцессный сервер. -Узнать больше деталей можно в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}. +Подробнее см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}. /// -## Тестовые замеры и скорость +## Бенчмарки и скорость { #benchmarks-and-speed } -Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, ознакомьтесь с разделом [Тестовые замеры](benchmarks.md){.internal-link target=_blank}. +Чтобы понять, сравнить и увидеть разницу между Uvicorn, Starlette и FastAPI, см. раздел о [Бенчмарках](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/async.md b/docs/ru/docs/async.md index 6c5d982df3..15d4e108ab 100644 --- a/docs/ru/docs/async.md +++ b/docs/ru/docs/async.md @@ -1,18 +1,18 @@ -# Конкурентность и async / await +# Конкурентность и async / await { #concurrency-and-async-await } -Здесь приведена подробная информация об использовании синтаксиса `async def` при написании *функций обработки пути*, а также рассмотрены основы асинхронного программирования, конкурентности и параллелизма. +Подробности о синтаксисе `async def` для *функций-обработчиков пути* и немного фона об асинхронном коде, конкурентности и параллелизме. -## Нет времени? +## Нет времени? { #in-a-hurry } -TL;DR: +TL;DR: -Допустим, вы используете сторонюю библиотеку, которая требует вызова с ключевым словом `await`: +Если вы используете сторонние библиотеки, которые нужно вызывать с `await`, например: ```Python results = await some_library() ``` -В этом случае *функции обработки пути* необходимо объявлять с использованием синтаксиса `async def`: +Тогда объявляйте *функции-обработчики пути* с `async def`, например: ```Python hl_lines="2" @app.get('/') @@ -21,18 +21,15 @@ async def read_results(): return results ``` -/// note +/// note | Примечание -`await` можно использовать только внутри функций, объявленных с использованием `async def`. +`await` можно использовать только внутри функций, объявленных с `async def`. /// --- -Если вы обращаетесь к сторонней библиотеке, которая с чем-то взаимодействует -(с базой данных, API, файловой системой и т. д.), и не имеет поддержки синтаксиса `await` -(что относится сейчас к большинству библиотек для работы с базами данных), то -объявляйте *функции обработки пути* обычным образом с помощью `def`, например: +Если вы используете стороннюю библиотеку, которая взаимодействует с чем-то (база данных, API, файловая система и т.д.) и не поддерживает использование `await` (сейчас это относится к большинству библиотек для БД), тогда объявляйте *функции-обработчики пути* как обычно, просто с `def`, например: ```Python hl_lines="2" @app.get('/') @@ -43,310 +40,283 @@ def results(): --- -Если вашему приложению (странным образом) не нужно ни с чем взаимодействовать и, соответственно, -ожидать ответа, используйте `async def`. +Если вашему приложению (по какой-то причине) не нужно ни с чем взаимодействовать и ждать ответа, используйте `async def`, даже если внутри не нужен `await`. --- -Если вы не уверены, используйте обычный синтаксис `def`. +Если вы просто не уверены, используйте обычный `def`. --- -**Примечание**: при необходимости можно смешивать `def` и `async def` в *функциях обработки пути* -и использовать в каждом случае наиболее подходящий синтаксис. А FastAPI сделает с этим всё, что нужно. +**Примечание**: вы можете смешивать `def` и `async def` в *функциях-обработчиках пути* столько, сколько нужно, и объявлять каждую так, как лучше для вашего случая. FastAPI сделает с ними всё как надо. -В любом из описанных случаев FastAPI работает асинхронно и очень быстро. +В любом из случаев выше FastAPI всё равно работает асинхронно и очень быстро. -Однако придерживаясь указанных советов, можно получить дополнительную оптимизацию производительности. +Но следуя этим шагам, он сможет выполнить некоторые оптимизации производительности. -## Технические подробности +## Технические подробности { #technical-details } -Современные версии Python поддерживают разработку так называемого **"асинхронного кода"** посредством написания **"сопрограмм"** с использованием синтаксиса **`async` и `await`**. +Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**. -Ниже разберём эту фразу по частям: +Разберём эту фразу по частям в разделах ниже: * **Асинхронный код** * **`async` и `await`** * **Сопрограммы** -## Асинхронный код +## Асинхронный код { #asynchronous-code } -Асинхронный код означает, что в языке 💬 есть возможность сообщить машине / программе 🤖, -что в определённой точке кода ей 🤖 нужно будет ожидать завершения выполнения *чего-то ещё* в другом месте. Допустим это *что-то ещё* называется "медленный файл" 📝. +Асинхронный код значит, что в языке 💬 есть способ сказать компьютеру/программе 🤖, что в некоторый момент кода ему 🤖 придётся подождать, пока *что-то ещё* где-то в другом месте завершится. Назовём это *что-то ещё* «медленный файл» 📝. -И пока мы ждём завершения работы с "медленным файлом" 📝, компьютер может переключиться для выполнения других задач. +И пока мы ждём завершения работы с «медленныи файлом» 📝, компьютер может заняться другой работой. -Но при каждой возможности компьютер / программа 🤖 будет возвращаться обратно. Например, если он 🤖 опять окажется в режиме ожидания, или когда закончит всю работу. В этом случае компьютер 🤖 проверяет, не завершена ли какая-нибудь из текущих задач. +Затем компьютер/программа 🤖 будет возвращаться каждый раз, когда появится возможность (пока снова где-то идёт ожидание), или когда 🤖 завершит всю текущую работу. И он 🤖 проверит, не завершилась ли какая-либо из задач, которых он ждал, и сделает то, что нужно. -Потом он 🤖 берёт первую выполненную задачу (допустим, наш "медленный файл" 📝) и продолжает работу, производя с ней необходимые действия. +Далее он 🤖 возьмёт первую завершившуюся задачу (скажем, наш «медленный файл» 📝) и продолжит делать с ней то, что требуется. -Вышеупомянутое "что-то ещё", завершения которого приходится ожидать, обычно относится к достаточно "медленным" операциям I/O (по сравнению со скоростью работы процессора и оперативной памяти), например: +Это «ожидание чего-то ещё» обычно относится к операциям I/O, которые относительно «медленные» (по сравнению со скоростью процессора и оперативной памяти), например ожидание: -* отправка данных от клиента по сети -* получение клиентом данных, отправленных вашей программой по сети -* чтение системой содержимого файла с диска и передача этих данных программе -* запись на диск данных, которые программа передала системе -* обращение к удалённому API -* ожидание завершения операции с базой данных -* получение результатов запроса к базе данных -* и т. д. +* отправки данных клиентом по сети +* получения клиентом данных, отправленных вашей программой по сети +* чтения системой содержимого файла на диске и передачи этих данных вашей программе +* записи на диск содержимого, которое ваша программа передала системе +* операции удалённого API +* завершения операции базы данных +* возврата результатов запроса к базе данных +* и т.д. -Поскольку в основном время тратится на ожидание выполнения операций I/O, -их обычно называют операциями, ограниченными скоростью ввода-вывода. +Поскольку основное время выполнения уходит на ожидание операций I/O, их называют операциями, «ограниченными вводом-выводом» (I/O bound). -Код называют "асинхронным", потому что компьютеру / программе не требуется "синхронизироваться" с медленной задачей и, -будучи в простое, ожидать момента её завершения, с тем чтобы забрать результат и продолжить работу. +Это называется «асинхронным», потому что компьютеру/программе не нужно «синхронизироваться» с медленной задачей, простаивая и выжидая точный момент её завершения, чтобы забрать результат и продолжить работу. -Вместо этого в "асинхронной" системе завершённая задача может немного подождать (буквально несколько микросекунд), -пока компьютер / программа занимается другими важными вещами, с тем чтобы потом вернуться, -забрать результаты выполнения и начать их обрабатывать. +Вместо этого, в «асинхронной» системе, уже завершившаяся задача может немного подождать (несколько микросекунд) в очереди, пока компьютер/программа завершит то, чем занимался, и затем вернётся, чтобы забрать результаты и продолжить работу с ними. -"Синхронное" исполнение (в противовес "асинхронному") также называют "последовательным", -потому что компьютер / программа последовательно выполняет все требуемые шаги перед тем, как перейти к следующей задаче, -даже если в процессе приходится ждать. +Для «синхронного» (в противоположность «асинхронному») исполнения часто используют термин «последовательный», потому что компьютер/программа выполняет все шаги по порядку, прежде чем переключиться на другую задачу, даже если эти шаги включают ожидание. -### Конкурентность и бургеры +### Конкурентность и бургеры { #concurrency-and-burgers } -Тот **асинхронный** код, о котором идёт речь выше, иногда называют **"конкурентностью"**. Она отличается от **"параллелизма"**. +Та идея **асинхронного** кода, описанная выше, иногда также называется **«конкурентностью»**. Она отличается от **«параллелизма»**. -Да, **конкурентность** и **параллелизм** подразумевают, что разные вещи происходят примерно в одно время. +И **конкурентность**, и **параллелизм** относятся к «разным вещам, происходящим примерно одновременно». -Но внутреннее устройство **конкурентности** и **параллелизма** довольно разное. +Но различия между *конкурентностью* и *параллелизмом* довольно существенные. -Чтобы это понять, представьте такую картину: +Чтобы их увидеть, представьте следующую историю про бургеры: -### Конкурентные бургеры +### Конкурентные бургеры { #concurrent-burgers } - +Вы идёте со своей возлюбленной за фастфудом, вы стоите в очереди, пока кассир принимает заказы у людей перед вами. 😍 -Вы идёте со своей возлюбленной 😍 в фастфуд 🍔 и становитесь в очередь, в это время кассир 💁 принимает заказы у посетителей перед вами. + -Когда наконец подходит очередь, вы заказываете парочку самых вкусных и навороченных бургеров 🍔, один для своей возлюбленной 😍, а другой себе. +Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя. 🍔🍔 -Отдаёте деньги 💸. + -Кассир 💁 что-то говорит поварам на кухне 👨‍🍳, теперь они знают, какие бургеры нужно будет приготовить 🍔 -(но пока они заняты бургерами предыдущих клиентов). +Кассир говорит что-то повару на кухне, чтобы они знали, что нужно приготовить ваши бургеры (хотя сейчас они готовят бургеры для предыдущих клиентов). -Кассир 💁 отдаёт вам чек с номером заказа. + -В ожидании еды вы идёте со своей возлюбленной 😍 выбрать столик, садитесь и довольно продолжительное время общаетесь 😍 -(поскольку ваши бургеры самые навороченные, готовятся они не так быстро ✨🍔✨). +Вы платите. 💸 -Сидя за столиком с возлюбленной 😍 в ожидании бургеров 🍔, вы отлично проводите время, -восхищаясь её великолепием, красотой и умом ✨😍✨. +Кассир выдаёт вам номер вашей очереди. -Всё ещё ожидая заказ и болтая со своей возлюбленной 😍, время от времени вы проверяете, -какой номер горит над прилавком, и не подошла ли уже ваша очередь. + -И вот наконец настаёт этот момент, и вы идёте к стойке, чтобы забрать бургеры 🍔 и вернуться за столик. +Пока вы ждёте, вы вместе со своей возлюбленной идёте и выбираете столик, садитесь и долго болтаете (ваши бургеры очень «навороченные», поэтому им нужно время на приготовление). -Вы со своей возлюбленной 😍 едите бургеры 🍔 и отлично проводите время ✨. +Сидя за столиком со своей возлюбленной в ожидании бургеров, вы можете провести это время, восхищаясь тем, какая она классная, милая и умная ✨😍✨. + + + +Пока вы ждёте и разговариваете, время от времени вы поглядываете на номер на табло, чтобы понять, не подошла ли уже ваша очередь. + +И вот в какой-то момент ваша очередь наступает. Вы подходите к стойке, забираете свои бургеры и возвращаетесь к столику. + + + +Вы со своей возлюбленной едите бургеры и отлично проводите время. ✨ + + + +/// info | Информация + +Прекрасные иллюстрации от Ketrina Thompson. 🎨 + +/// --- -А теперь представьте, что в этой небольшой истории вы компьютер / программа 🤖. +Представьте, что в этой истории вы — компьютер/программа 🤖. -В очереди вы просто глазеете по сторонам 😴, ждёте и ничего особо "продуктивного" не делаете. -Но очередь движется довольно быстро, поскольку кассир 💁 только принимает заказы (а не занимается приготовлением еды), так что ничего страшного. +Пока вы стоите в очереди, вы просто бездействуете 😴, ждёте своей очереди и не делаете ничего особо «продуктивного». Но очередь движется быстро, потому что кассир только принимает заказы (а не готовит их), так что это нормально. -Когда подходит очередь вы наконец предпринимаете "продуктивные" действия 🤓: просматриваете меню, выбираете в нём что-то, узнаёте, что хочет ваша возлюбленная 😍, собираетесь оплатить 💸, смотрите, какую достали карту, проверяете, чтобы с вас списали верную сумму, и что в заказе всё верно и т. д. +Когда приходит ваша очередь, вы выполняете действительно «продуктивную» работу: просматриваете меню, решаете, чего хотите, учитываете выбор своей возлюбленной, платите, проверяете, что дали правильную купюру/карту, что сумма списана корректно, что в заказе верные позиции и т.д. -И хотя вы всё ещё не получили бургеры 🍔, ваша работа с кассиром 💁 ставится "на паузу" ⏸, -поскольку теперь нужно ждать 🕙, когда заказ приготовят. +Но затем, хотя у вас ещё нет бургеров, ваша «работа» с кассиром поставлена «на паузу» ⏸, потому что нужно подождать 🕙, пока бургеры будут готовы. -Но отойдя с номерком от прилавка, вы садитесь за столик и можете переключить 🔀 внимание -на свою возлюбленную 😍 и "работать" ⏯ 🤓 уже над этим. И вот вы снова очень -"продуктивны" 🤓, мило болтаете вдвоём и всё такое 😍. +Но, отойдя от стойки и сев за столик с номерком, вы можете переключить 🔀 внимание на свою возлюбленную и «поработать» ⏯ 🤓 над этим. Снова очень «продуктивно» — флирт с вашей возлюбленной 😍. -В какой-то момент кассир 💁 поместит на табло ваш номер, подразумевая, что бургеры готовы 🍔, но вы не станете подскакивать как умалишённый, лишь только увидев на экране свою очередь. Вы уверены, что ваши бургеры 🍔 никто не утащит, ведь у вас свой номерок, а у других свой. +Потом кассир 💁 «говорит»: «Я закончил делать бургеры», — выводя ваш номер на табло, но вы не подпрыгиваете как сумасшедший в ту же секунду, как только номер сменился на ваш. Вы знаете, что ваши бургеры никто не украдёт, потому что у вас есть номер вашей очереди, а у других — их. -Поэтому вы подождёте, пока возлюбленная 😍 закончит рассказывать историю (закончите текущую работу ⏯ / задачу в обработке 🤓), -и мило улыбнувшись, скажете, что идёте забирать заказ ⏸. +Поэтому вы дожидаетесь, пока ваша возлюбленная закончит историю (завершится текущая работа ⏯ / выполняемая задача 🤓), мягко улыбаетесь и говорите, что идёте за бургерами ⏸. -И вот вы подходите к стойке 🔀, к первоначальной задаче, которая уже завершена ⏯, берёте бургеры 🍔, говорите спасибо и относите заказ за столик. На этом заканчивается этап / задача взаимодействия с кассой ⏹. -В свою очередь порождается задача "поедание бургеров" 🔀 ⏯, но предыдущая ("получение бургеров") завершена ⏹. +Затем вы идёте к стойке 🔀, к исходной задаче, которая теперь завершена ⏯, забираете бургеры, благодарите и несёте их к столику. На этом шаг/задача взаимодействия со стойкой завершён ⏹. Это, в свою очередь, создаёт новую задачу — «есть бургеры» 🔀 ⏯, но предыдущая «получить бургеры» — завершена ⏹. -### Параллельные бургеры +### Параллельные бургеры { #parallel-burgers } -Теперь представим, что вместо бургерной "Конкурентные бургеры" вы решили сходить в "Параллельные бургеры". +Теперь представим, что это не «Конкурентные бургеры», а «Параллельные бургеры». -И вот вы идёте со своей возлюбленной 😍 отведать параллельного фастфуда 🍔. +Вы идёте со своей возлюбленной за параллельным фастфудом. -Вы становитесь в очередь пока несколько (пусть будет 8) кассиров, которые по совместительству ещё и повары 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳, принимают заказы у посетителей перед вами. +Вы стоите в очереди, пока несколько (скажем, 8) кассиров, которые одновременно являются поварами, принимают заказы у людей перед вами. -При этом клиенты не отходят от стойки и ждут 🕙 получения еды, поскольку каждый -из 8 кассиров идёт на кухню готовить бургеры 🍔, а только потом принимает следующий заказ. +Все перед вами ждут, пока их бургеры будут готовы, не отходя от стойки, потому что каждый из 8 кассиров сразу идёт готовить бургер перед тем, как принять следующий заказ. -Наконец настаёт ваша очередь, и вы просите два самых навороченных бургера 🍔, один для дамы сердца 😍, а другой себе. + -Ни о чём не жалея, расплачиваетесь 💸. +Наконец ваша очередь: вы заказываете 2 очень «навороченных» бургера — для вашей возлюбленной и для себя. -И кассир уходит на кухню 👨‍🍳. +Вы платите 💸. -Вам приходится ждать перед стойкой 🕙, чтобы никто по случайности не забрал ваши бургеры 🍔, ведь никаких номерков у вас нет. + -Поскольку вы с возлюбленной 😍 хотите получить заказ вовремя 🕙, и следите за тем, чтобы никто не вклинился в очередь, -у вас не получается уделять должного внимание своей даме сердца 😞. +Кассир уходит на кухню. -Это "синхронная" работа, вы "синхронизированы" с кассиром/поваром 👨‍🍳. Приходится ждать 🕙 у стойки, -когда кассир/повар 👨‍🍳 закончит делать бургеры 🍔 и вручит вам заказ, иначе его случайно может забрать кто-то другой. +Вы ждёте, стоя у стойки 🕙, чтобы никто не забрал ваши бургеры раньше вас, так как никаких номерков нет. -Наконец кассир/повар 👨‍🍳 возвращается с бургерами 🍔 после невыносимо долгого ожидания 🕙 за стойкой. + -Вы скорее забираете заказ 🍔 и идёте с возлюбленной 😍 за столик. +Так как вы со своей возлюбленной заняты тем, чтобы никто не встал перед вами и не забрал ваши бургеры, как только они появятся, вы не можете уделить внимание своей возлюбленной. 😞 -Там вы просто едите эти бургеры, и на этом всё 🍔 ⏹. +Это «синхронная» работа, вы «синхронизированы» с кассиром/поваром 👨‍🍳. Вам нужно ждать 🕙 и находиться там в точный момент, когда кассир/повар 👨‍🍳 закончит бургеры и вручит их вам, иначе их может забрать кто-то другой. -Вам не особо удалось пообщаться, потому что большую часть времени 🕙 пришлось провести у кассы 😞. + + +Затем ваш кассир/повар 👨‍🍳 наконец возвращается с вашими бургерами, после долгого ожидания 🕙 у стойки. + + + +Вы берёте бургеры и идёте со своей возлюбленной к столику. + +Вы просто их съедаете — и всё. ⏹ + + + +Разговоров и флирта было немного, потому что большую часть времени вы ждали 🕙 у стойки. 😞 + +/// info | Информация + +Прекрасные иллюстрации от Ketrina Thompson. 🎨 + +/// --- -В описанном сценарии вы компьютер / программа 🤖 с двумя исполнителями (вы и ваша возлюбленная 😍), -на протяжении долгого времени 🕙 вы оба уделяете всё внимание ⏯ задаче "ждать на кассе". +В этом сценарии «параллельных бургеров» вы — компьютер/программа 🤖 с двумя процессорами (вы и ваша возлюбленная), оба ждут 🕙 и уделяют внимание ⏯ тому, чтобы «ждать у стойки» 🕙 долгое время. -В этом ресторане быстрого питания 8 исполнителей (кассиров/поваров) 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳. -Хотя в бургерной конкурентного типа было всего два (один кассир и один повар) 💁 👨‍🍳. +В ресторане 8 процессоров (кассиров/поваров). Тогда как в «конкурентных бургерах» могло быть только 2 (один кассир и один повар). -Несмотря на обилие работников, опыт в итоге получился не из лучших 😞. +И всё же финальный опыт — не самый лучший. 😞 --- -Так бы выглядел аналог истории про бургерную 🍔 в "параллельном" мире. +Это была параллельная версия истории про бургеры. 🍔 -Вот более реалистичный пример. Представьте себе банк. +Для более «жизненного» примера представьте банк. -До недавних пор в большинстве банков было несколько кассиров 👨‍💼👨‍💼👨‍💼👨‍💼 и длинные очереди 🕙🕙🕙🕙🕙🕙🕙🕙. +До недавнего времени в большинстве банков было несколько кассиров 👨‍💼👨‍💼👨‍💼👨‍💼 и длинная очередь 🕙🕙🕙🕙🕙🕙🕙🕙. -Каждый кассир обслуживал одного клиента, потом следующего 👨‍💼⏯. +Все кассиры делают всю работу с одним клиентом за другим 👨‍💼⏯. -Нужно было долгое время 🕙 стоять перед окошком вместе со всеми, иначе пропустишь свою очередь. +И вам приходится долго 🕙 стоять в очереди, иначе вы потеряете свою очередь. -Сомневаюсь, что у вас бы возникло желание прийти с возлюбленной 😍 в банк 🏦 оплачивать налоги. +Вы вряд ли захотите взять свою возлюбленную 😍 с собой, чтобы заняться делами в банке 🏦. -### Выводы о бургерах +### Вывод про бургеры { #burger-conclusion } -В нашей истории про поход в фастфуд за бургерами приходится много ждать 🕙, -поэтому имеет смысл организовать конкурентную систему ⏸🔀⏯. +В этом сценарии «фастфуда с вашей возлюбленной», так как много ожидания 🕙, гораздо логичнее иметь конкурентную систему ⏸🔀⏯. -И то же самое с большинством веб-приложений. +Так обстоит дело и с большинством веб-приложений. -Пользователей очень много, но ваш сервер всё равно вынужден ждать 🕙 запросы по их слабому интернет-соединению. +Очень много пользователей, но ваш сервер ждёт 🕙, пока их не самое хорошее соединение отправит их запросы. -Потом снова ждать 🕙, пока вернётся ответ. +А затем снова ждёт 🕙, пока отправятся ответы. - -Это ожидание 🕙 измеряется микросекундами, но если всё сложить, то набегает довольно много времени. +Это «ожидание» 🕙 измеряется микросекундами, но если всё сложить, то в сумме получается много ожидания. -Вот почему есть смысл использовать асинхронное ⏸🔀⏯ программирование при построении веб-API. +Вот почему асинхронный ⏸🔀⏯ код очень уместен для веб-API. -Большинство популярных фреймворков (включая Flask и Django) создавались -до появления в Python новых возможностей асинхронного программирования. Поэтому -их можно разворачивать с поддержкой параллельного исполнения или асинхронного -программирования старого типа, которое не настолько эффективно. +Именно такая асинхронность сделала NodeJS популярным (хотя NodeJS — не параллельный), и это сильная сторона Go как языка программирования. -При том, что основная спецификация асинхронного взаимодействия Python с веб-сервером -(ASGI) -была разработана командой Django для внедрения поддержки веб-сокетов. +Того же уровня производительности вы получаете с **FastAPI**. -Именно асинхронность сделала NodeJS таким популярным (несмотря на то, что он не параллельный), -и в этом преимущество Go как языка программирования. +А так как можно одновременно использовать параллелизм и асинхронность, вы получаете производительность выше, чем у большинства протестированных фреймворков на NodeJS и на уровне Go, который — компилируемый язык, ближе к C (всё благодаря Starlette). -И тот же уровень производительности даёт **FastAPI**. +### Конкурентность лучше параллелизма? { #is-concurrency-better-than-parallelism } -Поскольку можно использовать преимущества параллелизма и асинхронности вместе, -вы получаете производительность лучше, чем у большинства протестированных NodeJS фреймворков -и на уровне с Go, который является компилируемым языком близким к C (всё благодаря Starlette). +Нет! Мораль истории не в этом. -### Получается, конкурентность лучше параллелизма? +Конкурентность отличается от параллелизма. И она лучше в **конкретных** сценариях, где много ожидания. Поэтому при разработке веб-приложений она обычно намного лучше параллелизма. Но не во всём. -Нет! Мораль истории совсем не в этом. +Чтобы уравновесить это, представьте такую короткую историю: -Конкурентность отличается от параллелизма. Она лучше в **конкретных** случаях, где много времени приходится на ожидание. -Вот почему она зачастую лучше параллелизма при разработке веб-приложений. Но это не значит, что конкурентность лучше в любых сценариях. - -Давайте посмотрим с другой стороны, представьте такую картину: - -> Вам нужно убраться в большом грязном доме. +> Вам нужно убрать большой грязный дом. *Да, это вся история*. --- -Тут не нужно нигде ждать 🕙, просто есть куча работы в разных частях дома. +Здесь нигде нет ожидания 🕙, просто очень много работы в разных местах дома. -Можно организовать очередь как в примере с бургерами, сначала гостиная, потом кухня, -но это ни на что не повлияет, поскольку вы нигде не ждёте 🕙, а просто трёте да моете. +Можно организовать «очереди» как в примере с бургерами — сначала гостиная, потом кухня, — но так как вы ничего не ждёте 🕙, а просто убираете и убираете, очереди ни на что не повлияют. -И понадобится одинаковое количество времени с очередью (конкурентностью) и без неё, -и работы будет сделано тоже одинаковое количество. +На завершение уйдёт одинаковое время — с очередями (конкурентностью) и без них — и объём выполненной работы будет одинаковым. -Однако в случае, если бы вы могли привести 8 бывших кассиров/поваров, а ныне уборщиков 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳, -и каждый из них (вместе с вами) взялся бы за свой участок дома, -с такой помощью вы бы закончили намного быстрее, делая всю работу **параллельно**. +Но в этом случае, если бы вы могли привести 8 бывших кассиров/поваров, а теперь — уборщиков, и каждый из них (плюс вы) взял бы свою зону дома для уборки, вы могли бы сделать всю работу **параллельно**, с дополнительной помощью, и завершить гораздо быстрее. -В описанном сценарии каждый уборщик (включая вас) был бы исполнителем, занятым на своём участке работы. +В этом сценарии каждый уборщик (включая вас) был бы процессором, выполняющим свою часть работы. -И поскольку большую часть времени выполнения занимает реальная работа (а не ожидание), -а работу в компьютере делает ЦП, -такие задачи называют ограниченными производительностью процессора. +И так как основное время выполнения уходит на реальную работу (а не ожидание), а работу в компьютере выполняет CPU, такие задачи называют «ограниченными процессором» (CPU bound). --- -Ограничение по процессору проявляется в операциях, где требуется выполнять сложные математические вычисления. +Типичные примеры CPU-bound операций — те, которые требуют сложной математической обработки. Например: -* Обработка **звука** или **изображений**. -* **Компьютерное зрение**: изображение состоит из миллионов пикселей, в каждом пикселе 3 составляющих цвета, -обработка обычно требует проведения расчётов по всем пикселям сразу. -* **Машинное обучение**: здесь обычно требуется умножение "матриц" и "векторов". -Представьте гигантскую таблицу с числами в Экселе, и все их надо одновременно перемножить. -* **Глубокое обучение**: это область *машинного обучения*, поэтому сюда подходит то же описание. -Просто у вас будет не одна таблица в Экселе, а множество. В ряде случаев используется -специальный процессор для создания и / или использования построенных таким образом моделей. +* Обработка **аудио** или **изображений**. +* **Компьютерное зрение**: изображение состоит из миллионов пикселей, каждый пиксель имеет 3 значения/цвета; обычно требуется вычислить что-то для всех этих пикселей одновременно. +* **Машинное обучение**: обычно требует множества умножений «матриц» и «векторов». Представьте огромную таблицу с числами и умножение всех этих чисел «одновременно». +* **Глубокое обучение**: это подполе Машинного обучения, так что всё вышесказанное применимо. Просто это не одна таблица чисел, а их огромный набор, и во многих случаях вы используете специальный процессор, чтобы строить и/или использовать такие модели. -### Конкурентность + параллелизм: Веб + машинное обучение +### Конкурентность + параллелизм: Веб + Машинное обучение { #concurrency-parallelism-web-machine-learning } -**FastAPI** предоставляет возможности конкуретного программирования, -которое очень распространено в веб-разработке (именно этим славится NodeJS). +С **FastAPI** вы можете использовать преимущества конкурентности, что очень распространено в веб-разработке (это та же основная «фишка» NodeJS). -Кроме того вы сможете использовать все преимущества параллелизма и -многопроцессорности (когда несколько процессов работают параллельно), -если рабочая нагрузка предполагает **ограничение по процессору**, -как, например, в системах машинного обучения. +Но вы также можете использовать выгоды параллелизма и многопроцессности (когда несколько процессов работают параллельно) для рабочих нагрузок, **ограниченных процессором** (CPU bound), как в системах Машинного обучения. -Необходимо также отметить, что Python является главным языком в области -**дата-сайенс**, -машинного обучения и, особенно, глубокого обучения. Всё это делает FastAPI -отличным вариантом (среди многих других) для разработки веб-API и приложений -в области дата-сайенс / машинного обучения. +Плюс к этому простой факт, что Python — основной язык для **Data Science**, Машинного обучения и особенно Глубокого обучения, делает FastAPI очень хорошим выбором для веб-API и приложений в области Data Science / Машинного обучения (среди многих других). -Как добиться такого параллелизма в эксплуатации описано в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}. +Как добиться такого параллелизма в продакшн, см. раздел [Развёртывание](deployment/index.md){.internal-link target=_blank}. -## `async` и `await` +## `async` и `await` { #async-and-await } -В современных версиях Python разработка асинхронного кода реализована очень интуитивно. -Он выглядит как обычный "последовательный" код и самостоятельно выполняет "ожидание", когда это необходимо. +В современных версиях Python есть очень интуитивный способ определять асинхронный код. Это делает его похожим на обычный «последовательный» код, а «ожидание» выполняется за вас в нужные моменты. -Если некая операция требует ожидания перед тем, как вернуть результат, и -поддерживает современные возможности Python, код можно написать следующим образом: +Когда есть операция, которой нужно подождать перед тем, как вернуть результат, и она поддерживает эти новые возможности Python, вы можете написать так: ```Python burgers = await get_burgers(2) ``` -Главное здесь слово `await`. Оно сообщает интерпретатору, что необходимо дождаться ⏸ -пока `get_burgers(2)` закончит свои дела 🕙, и только после этого сохранить результат в `burgers`. -Зная это, Python может пока переключиться на выполнение других задач 🔀 ⏯ -(например получение следующего запроса). +Ключ здесь — `await`. Он говорит Python, что нужно подождать ⏸, пока `get_burgers(2)` закончит своё дело 🕙, прежде чем сохранять результат в `burgers`. Благодаря этому Python будет знать, что за это время можно заняться чем-то ещё 🔀 ⏯ (например, принять другой запрос). -Чтобы ключевое слово `await` сработало, оно должно находиться внутри функции, -которая поддерживает асинхронность. Для этого вам просто нужно объявить её как `async def`: +Чтобы `await` работал, он должен находиться внутри функции, которая поддерживает такую асинхронность. Для этого просто объявите её с `async def`: ```Python hl_lines="1" async def get_burgers(number: int): - # Готовим бургеры по специальному асинхронному рецепту + # Сделать что-то асинхронное, чтобы приготовить бургеры return burgers ``` @@ -355,26 +325,22 @@ async def get_burgers(number: int): ```Python hl_lines="2" # Это не асинхронный код def get_sequential_burgers(number: int): - # Готовим бургеры последовательно по шагам + # Сделать что-то последовательное, чтобы приготовить бургеры return burgers ``` -Объявление `async def` указывает интерпретатору, что внутри этой функции -следует ожидать выражений `await`, и что можно поставить выполнение такой функции на "паузу" ⏸ и -переключиться на другие задачи 🔀, с тем чтобы вернуться сюда позже. +С `async def` Python знает, что внутри этой функции нужно учитывать выражения `await` и что выполнение такой функции можно «приостанавливать» ⏸ и идти делать что-то ещё 🔀, чтобы потом вернуться. -Если вы хотите вызвать функцию с `async def`, вам нужно "ожидать" её. -Поэтому такое не сработает: +Когда вы хотите вызвать функцию, объявленную с `async def`, нужно её «ожидать». Поэтому вот так не сработает: ```Python -# Это не заработает, поскольку get_burgers объявлена с использованием async def +# Это не сработает, потому что get_burgers определена с: async def burgers = get_burgers(2) ``` --- -Если сторонняя библиотека требует вызывать её с ключевым словом `await`, -необходимо писать *функции обработки пути* с использованием `async def`, например: +Итак, если вы используете библиотеку, которую можно вызывать с `await`, вам нужно создать *функцию-обработчик пути*, которая её использует, с `async def`, например: ```Python hl_lines="2-3" @app.get('/burgers') @@ -383,129 +349,96 @@ async def read_burgers(): return burgers ``` -### Технические подробности +### Более технические подробности { #more-technical-details } -Как вы могли заметить, `await` может применяться только в функциях, объявленных с использованием `async def`. +Вы могли заметить, что `await` можно использовать только внутри функций, определённых с `async def`. - -Но выполнение такой функции необходимо "ожидать" с помощью `await`. -Это означает, что её можно вызвать только из другой функции, которая тоже объявлена с `async def`. +Но при этом функции, определённые с `async def`, нужно «ожидать». Значит, функции с `async def` тоже можно вызывать только из функций, определённых с `async def`. -Но как же тогда появилась первая курица? В смысле... как нам вызвать первую асинхронную функцию? +Так что же с «яйцом и курицей» — как вызвать первую `async` функцию? -При работе с **FastAPI** просто не думайте об этом, потому что "первой" функцией является ваша *функция обработки пути*, -и дальше с этим разберётся FastAPI. +Если вы работаете с **FastAPI**, вам не о чем беспокоиться, потому что этой «первой» функцией будет ваша *функция-обработчик пути*, а FastAPI знает, как сделать всё правильно. -Кроме того, если хотите, вы можете использовать синтаксис `async` / `await` и без FastAPI. +Но если вы хотите использовать `async` / `await` без FastAPI, вы тоже можете это сделать. -### Пишите свой асинхронный код +### Пишите свой асинхронный код { #write-your-own-async-code } -Starlette (и **FastAPI**) основаны на AnyIO, что делает их совместимыми как со стандартной библиотекой asyncio в Python, так и с Trio. +Starlette (и **FastAPI**) основаны на AnyIO, что делает их совместимыми и со стандартной библиотекой Python asyncio, и с Trio. -В частности, вы можете напрямую использовать AnyIO в тех проектах, где требуется более сложная логика работы с конкурентностью. +В частности, вы можете напрямую использовать AnyIO для продвинутых сценариев конкурентности, где в вашем коде нужны более сложные паттерны. -Даже если вы не используете FastAPI, вы можете писать асинхронные приложения с помощью AnyIO, чтобы они были максимально совместимыми и получали его преимущества (например *структурную конкурентность*). +И даже если вы не используете FastAPI, вы можете писать свои асинхронные приложения с AnyIO, чтобы они были максимально совместимыми и получали его преимущества (например, *структурную конкурентность*). -### Другие виды асинхронного программирования +Я создал ещё одну библиотеку поверх AnyIO, тонкий слой, чтобы немного улучшить аннотации типов и получить более качественное **автозавершение**, **ошибки прямо в редакторе** и т.д. Там также есть дружелюбное введение и руководство, чтобы помочь вам **понять** и писать **свой собственный асинхронный код**: Asyncer. Она особенно полезна, если вам нужно **комбинировать асинхронный код с обычным** (блокирующим/синхронным) кодом. -Стиль написания кода с `async` и `await` появился в языке Python относительно недавно. +### Другие формы асинхронного кода { #other-forms-of-asynchronous-code } -Но он сильно облегчает работу с асинхронным кодом. +Такой стиль использования `async` и `await` относительно новый в языке. -Ровно такой же синтаксис (ну или почти такой же) недавно был включён в современные версии JavaScript (в браузере и NodeJS). +Но он сильно упрощает работу с асинхронным кодом. -До этого поддержка асинхронного кода была реализована намного сложнее, и его было труднее воспринимать. +Такой же (или почти такой же) синтаксис недавно появился в современных версиях JavaScript (в браузере и NodeJS). -В предыдущих версиях Python для этого использовались потоки или Gevent. Но такой код намного сложнее понимать, отлаживать и мысленно представлять. +До этого работа с асинхронным кодом была заметно сложнее и труднее для понимания. -Что касается JavaScript (в браузере и NodeJS), раньше там использовали для этой цели -"обратные вызовы". Что выливалось в -ад обратных вызовов. +В предыдущих версиях Python можно было использовать потоки или Gevent. Но такой код гораздо сложнее понимать, отлаживать и держать в голове. -## Сопрограммы +В прежних версиях NodeJS/браузерного JavaScript вы бы использовали «callbacks» (обратные вызовы), что приводит к «callback hell» (ад обратных вызовов). -**Корути́на** (или же сопрограмма) — это крутое словечко для именования той сущности, -которую возвращает функция `async def`. Python знает, что её можно запустить, как и обычную функцию, -но кроме того сопрограмму можно поставить на паузу ⏸ в том месте, где встретится слово `await`. +## Сопрограммы { #coroutines } -Всю функциональность асинхронного программирования с использованием `async` и `await` -часто обобщают словом "корутины". Они аналогичны "горутинам", ключевой особенности -языка Go. +**Сопрограмма** (coroutine) — это просто «навороченное» слово для того, что возвращает функция `async def`. Python знает, что это похоже на функцию: её можно запустить, она когда-нибудь завершится, но её выполнение может приостанавливаться ⏸ внутри, когда встречается `await`. -## Заключение +Часто всю функциональность использования асинхронного кода с `async` и `await` кратко называют «сопрограммами». Это сопоставимо с ключевой особенностью Go — «goroutines». -В самом начале была такая фраза: +## Заключение { #conclusion } -> Современные версии Python поддерживают разработку так называемого -**"асинхронного кода"** посредством написания **"сопрограмм"** с использованием -синтаксиса **`async` и `await`**. +Вернёмся к той же фразе: -Теперь всё должно звучать понятнее. ✨ +> Современные версии Python поддерживают **«асинхронный код»** с помощью **«сопрограмм»** (coroutines) и синтаксиса **`async` и `await`**. -На этом основана работа FastAPI (посредством Starlette), и именно это -обеспечивает его высокую производительность. +Теперь это должно звучать понятнее. ✨ -## Очень технические подробности +Именно это «движет» FastAPI (через Starlette) и обеспечивает столь впечатляющую производительность. -/// warning +## Очень технические подробности { #very-technical-details } -Этот раздел читать не обязательно. +/// warning | Предупреждение -Здесь приводятся подробности внутреннего устройства **FastAPI**. +Скорее всего, этот раздел можно пропустить. -Но если вы обладаете техническими знаниями (корутины, потоки, блокировка и т. д.) -и вам интересно, как FastAPI обрабатывает `async def` в отличие от обычных `def`, -читайте дальше. +Здесь — очень технические подробности о том, как **FastAPI** работает «под капотом». + +Если у вас есть достаточно технических знаний (сопрограммы, потоки, блокировки и т.д.) и вам интересно, как FastAPI обрабатывает `async def` по сравнению с обычным `def`, — вперёд. /// -### Функции обработки пути +### Функции-обработчики пути { #path-operation-functions } -Когда вы объявляете *функцию обработки пути* обычным образом с ключевым словом `def` -вместо `async def`, FastAPI ожидает её выполнения, запустив функцию во внешнем -пуле потоков, а не напрямую (это бы заблокировало сервер). +Когда вы объявляете *функцию-обработчик пути* обычным `def` вместо `async def`, она запускается во внешнем пуле потоков, который затем «ожидается», вместо прямого вызова (прямой вызов заблокировал бы сервер). -Если ранее вы использовали другой асинхронный фреймворк, который работает иначе, -и привыкли объявлять простые вычислительные *функции* через `def` ради -незначительного прироста скорости (порядка 100 наносекунд), обратите внимание, -что с **FastAPI** вы получите противоположный эффект. В таком случае больше подходит -`async def`, если только *функция обработки пути* не использует код, приводящий -к блокировке I/O. - +Если вы пришли из другого async-фреймворка, который работает иначе, и привыкли объявлять тривиальные *функции-обработчики пути*, выполняющие только вычисления, через простой `def` ради крошечной выгоды в производительности (около 100 наносекунд), обратите внимание: в **FastAPI** эффект будет противоположным. В таких случаях лучше использовать `async def`, если только ваши *функции-обработчики пути* не используют код, выполняющий блокирующий I/O. - -Но в любом случае велика вероятность, что **FastAPI** [окажется быстрее](index.md#_11){.internal-link target=_blank} -другого фреймворка (или хотя бы на уровне с ним). +Тем не менее, в обоих случаях велика вероятность, что **FastAPI** [всё равно будет быстрее](index.md#performance){.internal-link target=_blank} (или как минимум сопоставим) с вашим предыдущим фреймворком. -### Зависимости +### Зависимости { #dependencies } -То же относится к зависимостям. Если это обычная функция `def`, а не `async def`, -она запускается во внешнем пуле потоков. +То же относится к [зависимостям](tutorial/dependencies/index.md){.internal-link target=_blank}. Если зависимость — это обычная функция `def`, а не `async def`, она запускается во внешнем пуле потоков. -### Подзависимости +### Подзависимости { #sub-dependencies } -Вы можете объявить множество ссылающихся друг на друга зависимостей и подзависимостей -(в виде параметров при определении функции). Какие-то будут созданы с помощью `async def`, -другие обычным образом через `def`, и такая схема вполне работоспособна. Функции, -объявленные с помощью `def` будут запускаться на внешнем потоке (из пула), -а не с помощью `await`. +У вас может быть несколько зависимостей и [подзависимостей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, которые требуют друг друга (в виде параметров определений функций): часть из них может быть объявлена с `async def`, а часть — обычным `def`. Всё будет работать, а те, что объявлены обычным `def`, будут вызываться во внешнем потоке (из пула), а не «ожидаться». -### Другие служебные функции +### Другие служебные функции { #other-utility-functions } -Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять -с использованием `def` или `async def`. FastAPI не будет влиять на то, как вы -их запускаете. +Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять обычным `def` или `async def`, и FastAPI не будет влиять на то, как вы их вызываете. -Этим они отличаются от функций, которые FastAPI вызывает самостоятельно: -*функции обработки пути* и зависимости. +В отличие от функций, которые FastAPI вызывает за вас: *функции-обработчики пути* и зависимости. -Если служебная функция объявлена с помощью `def`, она будет вызвана напрямую -(как вы и написали в коде), а не в отдельном потоке. Если же она объявлена с -помощью `async def`, её вызов должен осуществляться с ожиданием через `await`. +Если служебная функция — обычная функция с `def`, она будет вызвана напрямую (как вы и пишете в коде), не в пуле потоков; если функция объявлена с `async def`, тогда при её вызове в вашем коде вы должны использовать `await`. --- - -Ещё раз повторим, что все эти технические подробности полезны, только если вы специально их искали. +Снова: это очень технические подробности, полезные, вероятно, только если вы целенаправленно их ищете. -В противном случае просто ознакомьтесь с основными принципами в разделе выше: Нет времени?. +Иначе вам достаточно руководствоваться рекомендациями из раздела выше: Нет времени?. diff --git a/docs/ru/docs/benchmarks.md b/docs/ru/docs/benchmarks.md index 259dca8e67..612b39f708 100644 --- a/docs/ru/docs/benchmarks.md +++ b/docs/ru/docs/benchmarks.md @@ -1,37 +1,34 @@ -# Замеры производительности +# Бенчмарки (тесты производительности) { #benchmarks } -Независимые тесты производительности приложений от TechEmpower показывают, что **FastAPI** под управлением Uvicorn один из самых быстрых Python-фреймворков и уступает только Starlette и Uvicorn (которые используются в FastAPI). (*) +Независимые бенчмарки TechEmpower показывают, что приложения **FastAPI** под управлением Uvicorn — одни из самых быстрых Python‑фреймворков, уступающие только Starlette и самому Uvicorn (используются внутри FastAPI). -Но при просмотре и сравнении замеров производительности следует иметь в виду нижеописанное. +Но при просмотре бенчмарков и сравнений следует иметь в виду следующее. -## Замеры производительности и скорости +## Бенчмарки и скорость { #benchmarks-and-speed } -В подобных тестах часто можно увидеть, что инструменты разного типа сравнивают друг с другом, как аналогичные. +При проверке бенчмарков часто можно увидеть, что инструменты разных типов сравнивают как эквивалентные. -В частности, сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов). +В частности, часто сравнивают вместе Uvicorn, Starlette и FastAPI (среди многих других инструментов). -Чем проще проблема, которую решает инструмент, тем выше его производительность. И большинство тестов не проверяют дополнительные функции, предоставляемые инструментом. +Чем проще задача, которую решает инструмент, тем выше его производительность. И большинство бенчмарков не тестируют дополнительные возможности, предоставляемые инструментом. -Иерархия инструментов имеет следующий вид: +Иерархия выглядит так: * **Uvicorn**: ASGI-сервер - * **Starlette** (использует Uvicorn): веб-микрофреймворк - * **FastAPI** (использует Starlette): API-микрофреймворк с дополнительными функциями для создания API, с валидацией данных и т.д. + * **Starlette**: (использует Uvicorn) веб-микрофреймворк + * **FastAPI**: (использует Starlette) API-микрофреймворк с рядом дополнительных возможностей для создания API, включая валидацию данных и т. п. * **Uvicorn**: - * Будет иметь наилучшую производительность, так как не имеет большого количества дополнительного кода, кроме самого сервера. - * Вы не будете писать приложение на Uvicorn напрямую. Это означало бы, что Ваш код должен включать как минимум весь - код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки. - * Uvicorn подлежит сравнению с Daphne, Hypercorn, uWSGI и другими веб-серверами. - + * Будет иметь наилучшую производительность, так как помимо самого сервера у него немного дополнительного кода. + * Вы не будете писать приложение непосредственно на Uvicorn. Это означало бы, что Ваш код должен включать как минимум весь код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки. + * Если Вы сравниваете Uvicorn, сравнивайте его с Daphne, Hypercorn, uWSGI и т. д. — серверами приложений. * **Starlette**: - * Будет уступать Uvicorn по производительности. Фактически Starlette управляется Uvicorn и из-за выполнения большего количества кода он не может быть быстрее, чем Uvicorn. - * Зато он предоставляет Вам инструменты для создания простых веб-приложений с обработкой маршрутов URL и т.д. - * Starlette следует сравнивать с Sanic, Flask, Django и другими веб-фреймворками (или микрофреймворками). - + * Будет на следующем месте по производительности после Uvicorn. Фактически Starlette запускается под управлением Uvicorn, поэтому он может быть только «медленнее» Uvicorn из‑за выполнения большего объёма кода. + * Зато он предоставляет Вам инструменты для создания простых веб‑приложений с маршрутизацией по путям и т. п. + * Если Вы сравниваете 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 и им подобные. + * Точно так же, как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует 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 deleted file mode 100644 index c4370f9bbf..0000000000 --- a/docs/ru/docs/contributing.md +++ /dev/null @@ -1,496 +0,0 @@ -# Участие в разработке фреймворка - -Возможно, для начала Вам стоит ознакомиться с основными способами [помочь FastAPI или получить помощь](help-fastapi.md){.internal-link target=_blank}. - -## Разработка - -Если Вы уже склонировали репозиторий и знаете, что Вам нужно более глубокое погружение в код фреймворка, то здесь представлены некоторые инструкции по настройке виртуального окружения. - -### Виртуальное окружение с помощью `venv` - -Находясь в нужной директории, Вы можете создать виртуальное окружение при помощи Python модуля `venv`. - -
- -```console -$ python -m venv env -``` - -
- -Эта команда создаст директорию `./env/` с бинарными (двоичными) файлами Python, а затем Вы сможете скачивать и устанавливать необходимые библиотеки в изолированное виртуальное окружение. - -### Активация виртуального окружения - -Активируйте виртуально окружение командой: - -//// tab | Linux, macOS - -
- -```console -$ source ./env/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .\env\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Если Вы пользуетесь Bash для Windows (например: Git Bash): - -
- -```console -$ source ./env/Scripts/activate -``` - -
- -//// - -Проверьте, что всё сработало: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -//// tab | 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/cloud.md b/docs/ru/docs/deployment/cloud.md new file mode 100644 index 0000000000..a400d18434 --- /dev/null +++ b/docs/ru/docs/deployment/cloud.md @@ -0,0 +1,16 @@ +# Развертывание FastAPI у облачных провайдеров { #deploy-fastapi-on-cloud-providers } + +Вы можете использовать практически любого облачного провайдера, чтобы развернуть свое приложение на FastAPI. + +В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI на их платформе. + +## Облачные провайдеры — спонсоры { #cloud-providers-sponsors } + +Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ — это обеспечивает непрерывное и здоровое развитие FastAPI и его экосистемы. + +И это показывает их искреннюю приверженность FastAPI и его сообществу (вам): они не только хотят предоставить вам хороший сервис, но и стремятся гарантировать, что у вас будет хороший и стабильный фреймворк — FastAPI. 🙇 + +Возможно, вы захотите попробовать их сервисы и воспользоваться их руководствами: + +* Render +* Railway diff --git a/docs/ru/docs/deployment/concepts.md b/docs/ru/docs/deployment/concepts.md index c410257909..207d1604d7 100644 --- a/docs/ru/docs/deployment/concepts.md +++ b/docs/ru/docs/deployment/concepts.md @@ -1,323 +1,321 @@ -# Концепции развёртывания +# Концепции развёртывания { #deployments-concepts } -Существует несколько концепций, применяемых для развёртывания приложений **FastAPI**, равно как и для любых других типов веб-приложений, среди которых вы можете выбрать **наиболее подходящий** способ. +При развёртывании приложения **FastAPI** (и вообще любого веб‑API) есть несколько концепций, о которых стоит думать — с их помощью можно выбрать **наиболее подходящий** способ **развёртывания вашего приложения**. -Самые важные из них: +Некоторые из важных концепций: -* Использование более безопасного протокола HTTPS -* Настройки запуска приложения -* Перезагрузка приложения -* Запуск нескольких экземпляров приложения -* Управление памятью -* Использование перечисленных функций перед запуском приложения. +* Безопасность — HTTPS +* Запуск при старте +* Перезапуски +* Репликация (количество запущенных процессов) +* Память +* Предварительные шаги перед запуском -Рассмотрим ниже влияние каждого из них на процесс **развёртывания**. +Посмотрим, как они влияют на **развёртывания**. -Наша конечная цель - **обслуживать клиентов вашего API безопасно** и **бесперебойно**, с максимально эффективным использованием **вычислительных ресурсов** (например, удалённых серверов/виртуальных машин). 🚀 +В конечном итоге цель — **обслуживать клиентов вашего API** безопасно, **избегать перебоев** и максимально эффективно использовать **вычислительные ресурсы** (например, удалённые серверы/виртуальные машины). 🚀 -Здесь я немного расскажу Вам об этих **концепциях** и надеюсь, что у вас сложится **интуитивное понимание**, какой способ выбрать при развертывании вашего API в различных окружениях, возможно, даже **ещё не существующих**. +Здесь я немного расскажу о этих **концепциях**, чтобы у вас появилась **интуиция**, как развёртывать ваш API в разных окружениях, возможно даже в **будущих**, которых ещё не существует. -Ознакомившись с этими концепциями, вы сможете **оценить и выбрать** лучший способ развёртывании **Вашего API**. +Учитывая эти концепции, вы сможете **оценить и спроектировать** лучший способ развёртывания **своих API**. -В последующих главах я предоставлю Вам **конкретные рецепты** развёртывания приложения FastAPI. +В следующих главах я дам более **конкретные рецепты** по развёртыванию приложений FastAPI. -А сейчас давайте остановимся на важных **идеях этих концепций**. Эти идеи можно также применить и к другим типам веб-приложений. 💡 +А пока давайте разберём важные **идеи**. Эти концепции применимы и к другим типам веб‑API. 💡 -## Использование более безопасного протокола HTTPS +## Безопасность — HTTPS { #security-https } -В [предыдущей главе об HTTPS](https.md){.internal-link target=_blank} мы рассмотрели, как HTTPS обеспечивает шифрование для вашего API. +В [предыдущей главе про HTTPS](https.md){.internal-link target=_blank} мы разобрались, как HTTPS обеспечивает шифрование для вашего API. -Также мы заметили, что обычно для работы с HTTPS вашему приложению нужен **дополнительный** компонент - **прокси-сервер завершения работы TLS**. +Также мы увидели, что HTTPS обычно обеспечивает компонент, **внешний** по отношению к серверу вашего приложения — **TLS Termination Proxy**. -И если прокси-сервер не умеет сам **обновлять сертификаты HTTPS**, то нужен ещё один компонент для этого действия. +И должен быть компонент, отвечающий за **обновление HTTPS‑сертификатов** — это может быть тот же самый компонент или отдельный. -### Примеры инструментов для работы с HTTPS +### Примеры инструментов для HTTPS { #example-tools-for-https } -Вот некоторые инструменты, которые вы можете применять как прокси-серверы: +Некоторые инструменты, которые можно использовать как TLS Termination Proxy: * Traefik - * С автоматическим обновлением сертификатов ✨ + * Автоматически обновляет сертификаты ✨ * Caddy - * С автоматическим обновлением сертификатов ✨ + * Автоматически обновляет сертификаты ✨ * Nginx - * С дополнительным компонентом типа Certbot для обновления сертификатов + * С внешним компонентом (например, Certbot) для обновления сертификатов * HAProxy - * С дополнительным компонентом типа Certbot для обновления сертификатов -* Kubernetes с Ingress Controller похожим на Nginx - * С дополнительным компонентом типа cert-manager для обновления сертификатов -* Использование услуг облачного провайдера (читайте ниже 👇) + * С внешним компонентом (например, Certbot) для обновления сертификатов +* Kubernetes с Ingress Controller (например, Nginx) + * С внешним компонентом (например, cert-manager) для обновления сертификатов +* Обрабатывается внутри облачного провайдера как часть его услуг (см. ниже 👇) -В последнем варианте вы можете воспользоваться услугами **облачного сервиса**, который сделает большую часть работы, включая настройку HTTPS. Это может наложить дополнительные ограничения или потребовать дополнительную плату и т.п. Зато Вам не понадобится самостоятельно заниматься настройками прокси-сервера. +Другой вариант — использовать **облачный сервис**, который возьмёт на себя больше задач, включая настройку HTTPS. Там могут быть ограничения или дополнительная стоимость и т.п., но в таком случае вам не придётся самим настраивать TLS Termination Proxy. -В дальнейшем я покажу Вам некоторые конкретные примеры их применения. +В следующих главах я покажу конкретные примеры. --- -Следующие концепции рассматривают применение программы, запускающей Ваш API (такой как Uvicorn). +Далее рассмотрим концепции, связанные с программой, которая запускает ваш реальный API (например, Uvicorn). -## Программа и процесс +## Программа и процесс { #program-and-process } -Мы часто будем встречать слова **процесс** и **программа**, потому следует уяснить отличия между ними. +Мы часто будем говорить о работающем "**процессе**", поэтому полезно чётко понимать, что это значит и чем отличается от "**программы**". -### Что такое программа +### Что такое программа { #what-is-a-program } -Термином **программа** обычно описывают множество вещей: +Словом **программа** обычно называют разные вещи: -* **Код**, который вы написали, в нашем случае **Python-файлы**. -* **Файл**, который может быть **исполнен** операционной системой, например `python`, `python.exe` или `uvicorn`. -* Конкретная программа, **запущенная** операционной системой и использующая центральный процессор и память. В таком случае это также называется **процесс**. +* **Код**, который вы пишете, то есть **Python‑файлы**. +* **Файл**, который может быть **запущен** операционной системой, например: `python`, `python.exe` или `uvicorn`. +* Конкретную программу в момент, когда она **работает** в операционной системе, используя CPU и память. Это также называют **процессом**. -### Что такое процесс +### Что такое процесс { #what-is-a-process } -Термин **процесс** имеет более узкое толкование, подразумевая что-то, запущенное операционной системой (как в последнем пункте из вышестоящего абзаца): +Слово **процесс** обычно используют более конкретно — только для того, что реально выполняется в операционной системе (как в последнем пункте выше): -* Конкретная программа, **запущенная** операционной системой. - * Это не имеет отношения к какому-либо файлу или коду, но нечто **определённое**, управляемое и **выполняемое** операционной системой. -* Любая программа, любой код, **могут делать что-то** только когда они **выполняются**. То есть, когда являются **работающим процессом**. -* Процесс может быть **прерван** (или "убит") Вами или вашей операционной системой. В результате чего он перестанет исполняться и **не будет продолжать делать что-либо**. -* Каждое приложение, которое вы запустили на своём компьютере, каждая программа, каждое "окно" запускает какой-то процесс. И обычно на включенном компьютере **одновременно** запущено множество процессов. -* И **одна программа** может запустить **несколько параллельных процессов**. +* Конкретная программа в момент, когда она **запущена** в операционной системе. + * Речь не о файле и не о коде, а **конкретно** о том, что **исполняется** и управляется операционной системой. +* Любая программа, любой код **могут что‑то делать** только когда **исполняются**, то есть когда есть **работающий процесс**. +* Процесс можно **завершить** (или «убить») вами или операционной системой. В этот момент он перестаёт выполняться и **больше ничего делать не может**. +* У каждого запущенного приложения на вашем компьютере есть свой процесс; у каждой программы, у каждого окна и т.д. Обычно одновременно **работает много процессов**, пока компьютер включён. +* Могут **одновременно** работать **несколько процессов** одной и той же **программы**. -Если вы заглянете в "диспетчер задач" или "системный монитор" (или аналогичные инструменты) вашей операционной системы, то увидите множество работающих процессов. +Если вы посмотрите «диспетчер задач» или «системный монитор» (или аналогичные инструменты) в вашей операционной системе, то увидите множество работающих процессов. -Вполне вероятно, что вы увидите несколько процессов с одним и тем же названием браузерной программы (Firefox, Chrome, Edge и т. Д.). Обычно браузеры запускают один процесс на вкладку и вдобавок некоторые дополнительные процессы. +Например, вы, скорее всего, увидите несколько процессов одного и того же браузера (Firefox, Chrome, Edge и т.д.). Обычно браузеры запускают один процесс на вкладку плюс дополнительные процессы. --- -Теперь, когда нам известна разница между **процессом** и **программой**, давайте продолжим обсуждение развёртывания. +Теперь, когда мы понимаем разницу между **процессом** и **программой**, продолжим разговор о развёртываниях. -## Настройки запуска приложения +## Запуск при старте { #running-on-startup } -В большинстве случаев когда вы создаёте веб-приложение, то желаете, чтоб оно **работало постоянно** и непрерывно, предоставляя клиентам доступ в любое время. Хотя иногда у вас могут быть причины, чтоб оно запускалось только при определённых условиях. +В большинстве случаев, создавая веб‑API, вы хотите, чтобы он **работал постоянно**, без перерывов, чтобы клиенты всегда могли к нему обратиться. Разве что у вас есть особые причины запускать его только при определённых условиях, но обычно вы хотите, чтобы он был постоянно запущен и **доступен**. -### Удалённый сервер +### На удалённом сервере { #in-a-remote-server } -Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самое простое, что можно сделать, запустить Uvicorn (или его аналог) вручную, как вы делаете при локальной разработке. +Когда вы настраиваете удалённый сервер (облачный сервер, виртуальную машину и т.п.), самый простой вариант — вручную использовать `fastapi run` (он использует Uvicorn) или что‑то похожее, как вы делаете при локальной разработке. -Это рабочий способ и он полезен **во время разработки**. +Это будет работать и полезно **во время разработки**. -Но если вы потеряете соединение с сервером, то не сможете отслеживать - работает ли всё ещё **запущенный Вами процесс**. +Но если соединение с сервером прервётся, **запущенный процесс**, скорее всего, завершится. -И если сервер перезагрузится (например, после обновления или каких-то действий облачного провайдера), вы скорее всего **этого не заметите**, чтобы снова запустить процесс вручную. Вследствие этого Ваш API останется мёртвым. 😱 +А если сервер перезагрузится (например, после обновлений или миграций у облачного провайдера), вы, вероятно, **даже не заметите этого**. Из‑за этого вы не узнаете, что нужно вручную перезапустить процесс — и ваш API просто будет «мёртв». 😱 -### Автоматический запуск программ +### Автоматический запуск при старте { #run-automatically-on-startup } -Вероятно вы захотите, чтоб Ваша серверная программа (такая, как Uvicorn) стартовала автоматически при включении сервера, без **человеческого вмешательства** и всегда могла управлять Вашим API (так как Uvicorn запускает приложение FastAPI). +Как правило, вы захотите, чтобы серверная программа (например, Uvicorn) запускалась автоматически при старте сервера и без **участия человека**, чтобы всегда был процесс, запущенный с вашим API (например, Uvicorn, запускающий ваше приложение FastAPI). -### Отдельная программа +### Отдельная программа { #separate-program } -Для этого у обычно используют отдельную программу, которая следит за тем, чтобы Ваши приложения запускались при включении сервера. Такой подход гарантирует, что другие компоненты или приложения также будут запущены, например, база данных +Чтобы этого добиться, обычно используют **отдельную программу**, которая гарантирует запуск вашего приложения при старте. Во многих случаях она также запускает и другие компоненты/приложения, например базу данных. -### Примеры инструментов, управляющих запуском программ +### Примеры инструментов для запуска при старте { #example-tools-to-run-at-startup } -Вот несколько примеров, которые могут справиться с такой задачей: +Примеры инструментов, которые могут с этим справиться: * Docker * Kubernetes * Docker Compose -* Docker в режиме Swarm +* Docker в режиме Swarm (Swarm Mode) * Systemd * Supervisor -* Использование услуг облачного провайдера +* Обработка внутри облачного провайдера как часть его услуг * Прочие... -Я покажу Вам некоторые примеры их использования в следующих главах. +Более конкретные примеры будут в следующих главах. -## Перезапуск +## Перезапуски { #restarts } -Вы, вероятно, также захотите, чтоб ваше приложение **перезапускалось**, если в нём произошёл сбой. +Подобно тому как вы обеспечиваете запуск приложения при старте, вы, вероятно, захотите обеспечить его **перезапуск** после сбоев. -### Мы ошибаемся +### Мы ошибаемся { #we-make-mistakes } -Все люди совершают **ошибки**. Программное обеспечение почти *всегда* содержит **баги** спрятавшиеся в разных местах. 🐛 +Мы, люди, постоянно совершаем **ошибки**. В программном обеспечении почти всегда есть **баги**, скрытые в разных местах. 🐛 -И мы, будучи разработчиками, продолжаем улучшать код, когда обнаруживаем в нём баги или добавляем новый функционал (возможно, добавляя при этом баги 😅). +И мы, как разработчики, продолжаем улучшать код — находим баги и добавляем новые возможности (иногда добавляя новые баги 😅). -### Небольшие ошибки обрабатываются автоматически +### Небольшие ошибки обрабатываются автоматически { #small-errors-automatically-handled } -Когда вы создаёте свои API на основе FastAPI и допускаете в коде ошибку, то FastAPI обычно остановит её распространение внутри одного запроса, при обработке которого она возникла. 🛡 +Создавая веб‑API с FastAPI, если в нашем коде возникает ошибка, FastAPI обычно «локализует» её в пределах одного запроса, который эту ошибку вызвал. 🛡 -Клиент получит ошибку **500 Internal Server Error** в ответ на свой запрос, но приложение не сломается и будет продолжать работать с последующими запросами. +Клиент получит **500 Internal Server Error** для этого запроса, но приложение продолжит работать для последующих запросов, а не «упадёт» целиком. -### Большие ошибки - Падение приложений +### Большие ошибки — падения { #bigger-errors-crashes } -Тем не менее, может случиться так, что ошибка вызовет **сбой всего приложения** или даже сбой в Uvicorn, а то и в самом Python. 💥 +Тем не менее возможны случаи, когда код **роняет всё приложение**, приводя к сбою Uvicorn и Python. 💥 -Но мы всё ещё хотим, чтобы приложение **продолжало работать** несмотря на эту единственную ошибку, обрабатывая, как минимум, запросы к *операциям пути* не имеющим ошибок. +И вы, скорее всего, не захотите, чтобы приложение оставалось «мёртвым» из‑за ошибки в одном месте — вы захотите, чтобы оно **продолжало работать** хотя бы для *операций пути*, которые не сломаны. -### Перезапуск после падения +### Перезапуск после падения { #restart-after-crash } -Для случаев, когда ошибки приводят к сбою в запущенном **процессе**, Вам понадобится добавить компонент, который **перезапустит** процесс хотя бы пару раз... +В случаях действительно серьёзных ошибок, которые роняют работающий **процесс**, вам понадобится внешний компонент, отвечающий за **перезапуск** процесса, как минимум пару раз... -/// tip | "Заметка" +/// tip | Совет -... Если приложение падает сразу же после запуска, вероятно бесполезно его бесконечно перезапускать. Но полагаю, вы заметите такое поведение во время разработки или, по крайней мере, сразу после развёртывания. +...Хотя если приложение **падает сразу же**, вероятно, нет смысла перезапускать его бесконечно. Но такие случаи вы, скорее всего, заметите во время разработки или как минимум сразу после развёртывания. -Так что давайте сосредоточимся на конкретных случаях, когда приложение может полностью выйти из строя, но всё ещё есть смысл его запустить заново. +Давайте сосредоточимся на основных сценариях, когда в каких‑то конкретных ситуациях **в будущем** приложение может падать целиком, и при этом имеет смысл его перезапускать. /// -Возможно вы захотите, чтоб был некий **внешний компонент**, ответственный за перезапуск вашего приложения даже если уже не работает Uvicorn или Python. То есть ничего из того, что написано в вашем коде внутри приложения, не может быть выполнено в принципе. +Скорее всего, вы захотите, чтобы перезапуском вашего приложения занимался **внешний компонент**, потому что к тому моменту Uvicorn и Python уже упали, и внутри того же кода вашего приложения сделать уже ничего нельзя. -### Примеры инструментов для автоматического перезапуска +### Примеры инструментов для автоматического перезапуска { #example-tools-to-restart-automatically } -В большинстве случаев инструменты **запускающие программы при старте сервера** умеют **перезапускать** эти программы. +В большинстве случаев тот же инструмент, который **запускает программу при старте**, умеет обрабатывать и автоматические **перезапуски**. -В качестве примера можно взять те же: +Например, это может быть: * Docker * Kubernetes * Docker Compose -* Docker в режиме Swarm +* Docker в режиме Swarm (Swarm Mode) * Systemd * Supervisor -* Использование услуг облачного провайдера +* Обработка внутри облачного провайдера как часть его услуг * Прочие... -## Запуск нескольких экземпляров приложения (Репликация) - Процессы и память +## Репликация — процессы и память { #replication-processes-and-memory } -Приложение FastAPI, управляемое серверной программой (такой как Uvicorn), запускается как **один процесс** и может обслуживать множество клиентов одновременно. +В приложении FastAPI, используя серверную программу (например, команду `fastapi`, которая запускает Uvicorn), запуск в **одном процессе** уже позволяет обслуживать нескольких клиентов одновременно. -Но часто Вам может понадобиться несколько одновременно работающих одинаковых процессов. +Но во многих случаях вы захотите одновременно запустить несколько процессов‑воркеров. -### Множество процессов - Воркеры (Workers) +### Несколько процессов — Воркеры { #multiple-processes-workers } -Если количество Ваших клиентов больше, чем может обслужить один процесс (допустим, что виртуальная машина не слишком мощная), но при этом Вам доступно **несколько ядер процессора**, то вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределить запросы между этими процессами. +Если клиентов больше, чем способен обслужить один процесс (например, если виртуальная машина не слишком мощная), и на сервере есть **несколько ядер CPU**, вы можете запустить **несколько процессов** одного и того же приложения параллельно и распределять запросы между ними. -**Несколько запущенных процессов** одной и той же API-программы часто называют **воркерами**. +Когда вы запускаете **несколько процессов** одной и той же программы API, их обычно называют **воркерами**. -### Процессы и порты́ +### Процессы‑воркеры и порты { #worker-processes-and-ports } -Помните ли Вы, как на странице [Об HTTPS](https.md){.internal-link target=_blank} мы обсуждали, что на сервере только один процесс может слушать одну комбинацию IP-адреса и порта? +Помните из раздела [Об HTTPS](https.md){.internal-link target=_blank}, что на сервере только один процесс может слушать конкретную комбинацию порта и IP‑адреса? -С тех пор ничего не изменилось. +Это по‑прежнему так. -Соответственно, чтобы иметь возможность работать с **несколькими процессами** одновременно, должен быть **один процесс, прослушивающий порт** и затем каким-либо образом передающий данные каждому рабочему процессу. +Поэтому, чтобы одновременно работало **несколько процессов**, должен быть **один процесс, слушающий порт**, который затем каким‑то образом передаёт коммуникацию каждому воркер‑процессу. -### У каждого процесса своя память +### Память на процесс { #memory-per-process } -Работающая программа загружает в память данные, необходимые для её работы, например, переменные содержащие модели машинного обучения или большие файлы. Каждая переменная **потребляет некоторое количество оперативной памяти (RAM)** сервера. +Когда программа загружает что‑то в память (например, модель машинного обучения в переменную или содержимое большого файла в переменную), всё это **потребляет часть памяти (RAM)** сервера. -Обычно процессы **не делятся памятью друг с другом**. Сие означает, что каждый работающий процесс имеет свои данные, переменные и свой кусок памяти. И если для выполнения вашего кода процессу нужно много памяти, то **каждый такой же процесс** запущенный дополнительно, потребует такого же количества памяти. +И разные процессы обычно **не делят память**. Это значит, что у каждого процесса свои переменные и своя память. Если ваш код потребляет много памяти, то **каждый процесс** будет потреблять сопоставимый объём памяти. -### Память сервера +### Память сервера { #server-memory } -Допустим, что Ваш код загружает модель машинного обучения **размером 1 ГБ**. Когда вы запустите своё API как один процесс, он займёт в оперативной памяти не менее 1 ГБ. А если вы запустите **4 таких же процесса** (4 воркера), то каждый из них займёт 1 ГБ оперативной памяти. В результате вашему API потребуется **4 ГБ оперативной памяти (RAM)**. +Например, если ваш код загружает модель Машинного обучения размером **1 ГБ**, то при запуске одного процесса с вашим API он будет использовать как минимум 1 ГБ RAM. А если вы запустите **4 процесса** (4 воркера), каждый процесс будет использовать 1 ГБ RAM. Всего ваш API будет потреблять **4 ГБ RAM**. -И если Ваш удалённый сервер или виртуальная машина располагает только 3 ГБ памяти, то попытка загрузить в неё 4 ГБ данных вызовет проблемы. 🚨 +И если у вашего удалённого сервера или виртуальной машины только 3 ГБ RAM, попытка загрузить более 4 ГБ вызовет проблемы. 🚨 -### Множество процессов - Пример +### Несколько процессов — пример { #multiple-processes-an-example } -В этом примере **менеджер процессов** запустит и будет управлять двумя **воркерами**. +В этом примере есть **процесс‑менеджер**, который запускает и контролирует два **процесса‑воркера**. -Менеджер процессов будет слушать определённый **сокет** (IP:порт) и передавать данные работающим процессам. +Процесс‑менеджер, вероятно, будет тем, кто слушает **порт** на IP. И он будет передавать всю коммуникацию воркер‑процессам. -Каждый из этих процессов будет запускать ваше приложение для обработки полученного **запроса** и возвращения вычисленного **ответа** и они будут использовать оперативную память. +Эти воркеры будут запускать ваше приложение, выполнять основные вычисления для получения **запроса** и возврата **ответа**, и загружать всё, что вы кладёте в переменные, в RAM. - + -Безусловно, на этом же сервере будут работать и **другие процессы**, которые не относятся к вашему приложению. +Конечно, на той же машине помимо вашего приложения, скорее всего, будут работать и **другие процессы**. -Интересная деталь заключается в том, что процент **использования центрального процессора (CPU)** каждым процессом может сильно меняться с течением времени, но объём занимаемой **оперативной памяти (RAM)** остаётся относительно **стабильным**. +Интересная деталь: процент **использования CPU** каждым процессом со временем может сильно **меняться**, но **память (RAM)** обычно остаётся более‑менее **стабильной**. -Если у вас есть API, который каждый раз выполняет сопоставимый объем вычислений, и у вас много клиентов, то **загрузка процессора**, вероятно, *также будет стабильной* (вместо того, чтобы постоянно быстро увеличиваться и уменьшаться). +Если у вас API, который каждый раз выполняет сопоставимый объём вычислений, и у вас много клиентов, то **загрузка процессора**, вероятно, *тоже будет стабильной* (вместо того, чтобы быстро и постоянно «скакать»). -### Примеры стратегий и инструментов для запуска нескольких экземпляров приложения +### Примеры инструментов и стратегий репликации { #examples-of-replication-tools-and-strategies } -Существует несколько подходов для достижения целей репликации и я расскажу Вам больше о конкретных стратегиях в следующих главах, например, когда речь пойдет о Docker и контейнерах. +Есть несколько подходов для достижения этого, и я расскажу больше о конкретных стратегиях в следующих главах, например, говоря о Docker и контейнерах. -Основное ограничение при этом - только **один** компонент может работать с определённым **портом публичного IP**. И должен быть способ **передачи** данных между этим компонентом и копиями **процессов/воркеров**. +Главное ограничение: должен быть **один** компонент, который обрабатывает **порт** на **публичном IP**. И у него должен быть способ **передавать** коммуникацию реплицированным **процессам/воркерам**. -Вот некоторые возможные комбинации и стратегии: +Некоторые возможные комбинации и стратегии: -* **Gunicorn** управляющий **воркерами Uvicorn** - * Gunicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **множества работающих процессов Uvicorn**. -* **Uvicorn** управляющий **воркерами Uvicorn** - * Один процесс Uvicorn будет выступать как **менеджер процессов**, прослушивая **IP:port**. Он будет запускать **множество работающих процессов Uvicorn**. -* **Kubernetes** и аналогичные **контейнерные системы** - * Какой-то компонент в **Kubernetes** будет слушать **IP:port**. Необходимое количество запущенных экземпляров приложения будет осуществляться посредством запуска **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**. -* **Облачные сервисы**, которые позаботятся обо всём за Вас - * Возможно, что облачный сервис умеет **управлять запуском дополнительных экземпляров приложения**. Вероятно, он потребует, чтоб вы указали - какой **процесс** или **образ** следует клонировать. Скорее всего, вы укажете **один процесс Uvicorn** и облачный сервис будет запускать его копии при необходимости. +* **Uvicorn** с `--workers` + * Один **процесс‑менеджер** Uvicorn будет слушать **IP** и **порт** и запускать **несколько процессов‑воркеров Uvicorn**. +* **Kubernetes** и другие распределённые **контейнерные системы** + * Некий компонент на уровне **Kubernetes** будет слушать **IP** и **порт**. Репликация достигается с помощью **нескольких контейнеров**, в каждом из которых работает **один процесс Uvicorn**. +* **Облачные сервисы**, которые берут это на себя + * Облачный сервис, скорее всего, **возьмёт репликацию на себя**. Он, возможно, позволит указать **процесс для запуска** или **образ контейнера**. В любом случае это, скорее всего, будет **один процесс Uvicorn**, а сервис займётся его репликацией. -/// tip | "Заметка" +/// tip | Совет -Если вы не знаете, что такое **контейнеры**, Docker или Kubernetes, не переживайте. +Не беспокойтесь, если некоторые пункты про **контейнеры**, Docker или Kubernetes пока кажутся неочевидными. -Я поведаю Вам о контейнерах, образах, Docker, Kubernetes и т.п. в главе: [FastAPI внутри контейнеров - Docker](docker.md){.internal-link target=_blank}. +Я расскажу больше про образы контейнеров, Docker, Kubernetes и т.п. в следующей главе: [FastAPI внутри контейнеров — Docker](docker.md){.internal-link target=_blank}. /// -## Шаги, предшествующие запуску +## Предварительные шаги перед запуском { #previous-steps-before-starting } -Часто бывает, что Вам необходимо произвести какие-то подготовительные шаги **перед запуском** своего приложения. +Во многих случаях вы захотите выполнить некоторые шаги **перед запуском** приложения. Например, запустить **миграции базы данных**. -Но в большинстве случаев такие действия достаточно произвести **однократно**. +Но чаще всего эти шаги нужно выполнять только **один раз**. -Поэтому Вам нужен будет **один процесс**, выполняющий эти **подготовительные шаги** до запуска приложения. +Поэтому вы захотите иметь **один процесс**, который выполнит эти **предварительные шаги**, прежде чем запускать приложение. -Также Вам нужно будет убедиться, что этот процесс выполнил подготовительные шаги *даже* если впоследствии вы запустите **несколько процессов** (несколько воркеров) самого приложения. Если бы эти шаги выполнялись в каждом **клонированном процессе**, они бы **дублировали** работу, пытаясь выполнить её **параллельно**. И если бы эта работа была бы чем-то деликатным, вроде миграции базы данных, то это может вызвать конфликты между ними. +И вам нужно будет убедиться, что это делает один процесс **даже** если потом вы запускаете **несколько процессов** (несколько воркеров) самого приложения. Если эти шаги выполнят **несколько процессов**, они **дублируют** работу, запустив её **параллельно**, и, если речь о чём‑то деликатном (например, миграции БД), это может вызвать конфликты. -Безусловно, возможны случаи, когда нет проблем при выполнении предварительной подготовки параллельно или несколько раз. Тогда Вам повезло, работать с ними намного проще. +Конечно, бывают случаи, когда нет проблем, если предварительные шаги выполняются несколько раз — тогда всё проще. -/// tip | "Заметка" +/// tip | Совет -Имейте в виду, что в некоторых случаях запуск вашего приложения **может не требовать каких-либо предварительных шагов вовсе**. +Также учтите, что в зависимости от вашей схемы развёртывания в некоторых случаях **предварительные шаги могут вовсе не требоваться**. -Что ж, тогда Вам не нужно беспокоиться об этом. 🤷 +Тогда об этом можно не беспокоиться. 🤷 /// -### Примеры стратегий запуска предварительных шагов +### Примеры стратегий для предварительных шагов { #examples-of-previous-steps-strategies } -Существует **сильная зависимость** от того, как вы **развёртываете свою систему**, запускаете программы, обрабатываете перезапуски и т.д. +Это будет **сильно зависеть** от того, как вы **развёртываете систему**, как запускаете программы, обрабатываете перезапуски и т.д. -Вот некоторые возможные идеи: +Некоторые возможные идеи: -* При использовании Kubernetes нужно предусмотреть "инициализирующий контейнер", запускаемый до контейнера с приложением. -* Bash-скрипт, выполняющий предварительные шаги, а затем запускающий приложение. - * При этом Вам всё ещё нужно найти способ - как запускать/перезапускать *такой* bash-скрипт, обнаруживать ошибки и т.п. +* «Init Container» в Kubernetes, который запускается перед контейнером с приложением +* Bash‑скрипт, который выполняет предварительные шаги, а затем запускает приложение + * При этом всё равно нужен способ запускать/перезапускать *этот* bash‑скрипт, обнаруживать ошибки и т.п. -/// tip | "Заметка" +/// tip | Совет -Я приведу Вам больше конкретных примеров работы с контейнерами в главе: [FastAPI внутри контейнеров - Docker](docker.md){.internal-link target=_blank}. +Я приведу более конкретные примеры с контейнерами в следующей главе: [FastAPI внутри контейнеров — Docker](docker.md){.internal-link target=_blank}. /// -## Утилизация ресурсов +## Использование ресурсов { #resource-utilization } -Ваш сервер располагает ресурсами, которые Ваши программы могут потреблять или **утилизировать**, а именно - время работы центрального процессора и объём оперативной памяти. +Ваш сервер(а) — это **ресурс**, который ваши программы могут потреблять или **использовать**: время вычислений на CPU и доступную оперативную память (RAM). -Как много системных ресурсов вы предполагаете потребить/утилизировать? Если не задумываться, то можно ответить - "немного", но на самом деле Вы, вероятно, захотите использовать **максимально возможное количество**. +Какую долю системных ресурсов вы хотите потреблять/использовать? Можно подумать «немного», но на практике вы, скорее всего, захотите потреблять **максимум без падений**. -Если вы платите за содержание трёх серверов, но используете лишь малую часть системных ресурсов каждого из них, то вы **выбрасываете деньги на ветер**, а также **впустую тратите электроэнергию** и т.п. +Если вы платите за 3 сервера, но используете лишь малую часть их RAM и CPU, вы, вероятно, **тратите деньги впустую** 💸 и **электроэнергию серверов** 🌎 и т.п. -В таком случае было бы лучше обойтись двумя серверами, но более полно утилизировать их ресурсы (центральный процессор, оперативную память, жёсткий диск, сети передачи данных и т.д). +В таком случае лучше иметь 2 сервера и использовать более высокий процент их ресурсов (CPU, память, диск, сетевую полосу и т.д.). -С другой стороны, если вы располагаете только двумя серверами и используете **на 100% их процессоры и память**, но какой-либо процесс запросит дополнительную память, то операционная система сервера будет использовать жёсткий диск для расширения оперативной памяти (а диск работает в тысячи раз медленнее), а то вовсе **упадёт**. Или если какому-то процессу понадобится произвести вычисления, то ему придётся подождать, пока процессор освободится. +С другой стороны, если у вас 2 сервера и вы используете **100% их CPU и RAM**, в какой‑то момент один процесс попросит больше памяти, и сервер начнёт использовать диск как «память» (что в тысячи раз медленнее) или даже **упадёт**. Или процессу понадобятся вычисления, но ему придётся ждать освобождения CPU. -В такой ситуации лучше подключить **ещё один сервер** и перераспределить процессы между серверами, чтоб всем **хватало памяти и процессорного времени**. +В таком случае лучше добавить **ещё один сервер** и запустить часть процессов на нём, чтобы у всех было **достаточно RAM и времени CPU**. -Также есть вероятность, что по какой-то причине возник **всплеск** запросов к вашему API. Возможно, это был вирус, боты или другие сервисы начали пользоваться им. И для таких происшествий вы можете захотеть иметь дополнительные ресурсы. +Также возможен **всплеск** использования вашего API: он мог «взорваться» по популярности, или какие‑то сервисы/боты начали его активно использовать. На такие случаи стоит иметь запас ресурсов. -При настройке логики развёртываний, вы можете указать **целевое значение** утилизации ресурсов, допустим, **от 50% до 90%**. Обычно эти метрики и используют. +Можно задать **целевое значение**, например **между 50% и 90%** использования ресурсов. Скорее всего, именно эти вещи вы будете измерять и на их основе настраивать развёртывание. -Вы можете использовать простые инструменты, такие как `htop`, для отслеживания загрузки центрального процессора и оперативной памяти сервера, в том числе каждым процессом. Или более сложные системы мониторинга нескольких серверов. +Можно использовать простые инструменты вроде `htop`, чтобы смотреть загрузку CPU и RAM на сервере или по процессам. Или более сложные распределённые системы мониторинга. -## Резюме +## Резюме { #recap } -Вы прочитали некоторые из основных концепций, которые необходимо иметь в виду при принятии решения о развертывании приложений: +Здесь вы прочитали о некоторых основных концепциях, которые, вероятно, стоит учитывать при выборе способа развёртывания приложения: -* Использование более безопасного протокола HTTPS -* Настройки запуска приложения -* Перезагрузка приложения -* Запуск нескольких экземпляров приложения -* Управление памятью -* Использование перечисленных функций перед запуском приложения. +* Безопасность — HTTPS +* Запуск при старте +* Перезапуски +* Репликация (количество запущенных процессов) +* Память +* Предварительные шаги перед запуском -Осознание этих идей и того, как их применять, должно дать Вам интуитивное понимание, необходимое для принятия решений при настройке развертываний. 🤓 +Понимание этих идей и того, как их применять, даст вам интуицию, необходимую для принятия решений при настройке и доработке ваших развёртываний. 🤓 -В следующих разделах я приведу более конкретные примеры возможных стратегий, которым вы можете следовать. 🚀 +В следующих разделах я приведу более конкретные примеры возможных стратегий. 🚀 diff --git a/docs/ru/docs/deployment/docker.md b/docs/ru/docs/deployment/docker.md index 9eef5c4d2a..3937b01654 100644 --- a/docs/ru/docs/deployment/docker.md +++ b/docs/ru/docs/deployment/docker.md @@ -1,17 +1,17 @@ -# FastAPI и Docker-контейнеры +# FastAPI в контейнерах — Docker { #fastapi-in-containers-docker } -При развёртывании приложений FastAPI, часто начинают с создания **образа контейнера на основе Linux**. Обычно для этого используют **Docker**. Затем можно развернуть такой контейнер на сервере одним из нескольких способов. +При развёртывании приложений FastAPI распространённый подход — собирать **образ контейнера на Linux**. Обычно это делают с помощью **Docker**. Затем такой образ контейнера можно развернуть несколькими способами. -Использование контейнеров на основе Linux имеет ряд преимуществ, включая **безопасность**, **воспроизводимость**, **простоту** и прочие. +Использование Linux-контейнеров даёт ряд преимуществ: **безопасность**, **воспроизводимость**, **простоту** и другие. -/// tip | "Подсказка" +/// tip | Подсказка -Торопитесь или уже знакомы с этой технологией? Перепрыгните на раздел [Создать Docker-образ для FastAPI 👇](#docker-fastapi) +Нет времени и вы уже знакомы с этим? Перейдите к [`Dockerfile` ниже 👇](#build-a-docker-image-for-fastapi). ///
-Развернуть Dockerfile 👀 +Предпросмотр Dockerfile 👀 ```Dockerfile FROM python:3.9 @@ -24,130 +24,125 @@ 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"] +CMD ["fastapi", "run", "app/main.py", "--port", "80"] -# Если используете прокси-сервер, такой как Nginx или Traefik, добавьте --proxy-headers -# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] +# Если запускаете за прокси, например Nginx или Traefik, добавьте --proxy-headers +# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"] ```
-## Что такое "контейнер" +## Что такое контейнер { #what-is-a-container } -Контейнеризация - это **легковесный** способ упаковать приложение, включая все его зависимости и необходимые файлы, чтобы изолировать его от других контейнеров (других приложений и компонентов) работающих на этой же системе. +Контейнеры (в основном Linux-контейнеры) — это очень **легковесный** способ упаковать приложения вместе со всеми их зависимостями и необходимыми файлами, изолировав их от других контейнеров (других приложений или компонентов) в той же системе. -Контейнеры, основанные на Linux, запускаются используя ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это значит, что они очень легковесные (по сравнению с полноценными виртуальными машинами, полностью эмулирующими работу операционной системы). +Linux-контейнеры запускаются, используя то же ядро Linux хоста (машины, виртуальной машины, облачного сервера и т.п.). Это означает, что они очень легковесные (по сравнению с полноценными виртуальными машинами, эмулирующими целую операционную систему). -Благодаря этому, контейнеры потребляют **малое количество ресурсов**, сравнимое с процессом запущенным напрямую (виртуальная машина потребует гораздо больше ресурсов). +Таким образом, контейнеры потребляют **малое количество ресурсов**, сопоставимое с запуском процессов напрямую (виртуальная машина потребовала бы намного больше ресурсов). -Контейнеры также имеют собственные запущенные **изолированные** процессы (но часто только один процесс), файловую систему и сеть, что упрощает развёртывание, разработку, управление доступом и т.п. +У контейнеров также есть собственные **изолированные** выполняемые процессы (обычно всего один процесс), файловая система и сеть, что упрощает развёртывание, безопасность, разработку и т.д. -## Что такое "образ контейнера" +## Что такое образ контейнера { #what-is-a-container-image } -Для запуска **контейнера** нужен **образ контейнера**. +**Контейнер** запускается из **образа контейнера**. -Образ контейнера - это **замороженная** версия всех файлов, переменных окружения, программ и команд по умолчанию, необходимых для работы приложения. **Замороженный** - означает, что **образ** не запущен и не выполняется, это всего лишь упакованные вместе файлы и метаданные. +Образ контейнера — это **статическая** версия всех файлов, переменных окружения и команды/программы по умолчанию, которые должны присутствовать в контейнере. Здесь **статическая** означает, что **образ** не запущен, он не выполняется — это только упакованные файлы и метаданные. -В отличие от **образа контейнера**, хранящего неизменное содержимое, под термином **контейнер** подразумевают запущенный образ, то есть объёкт, который **исполняется**. +В противоположность «**образу контейнера**» (хранящему статическое содержимое), «**контейнер**» обычно означает запущенный экземпляр, то, что **выполняется**. -Когда **контейнер** запущен (на основании **образа**), он может создавать и изменять файлы, переменные окружения и т.д. Эти изменения будут существовать только внутри контейнера, но не будут сохраняться в образе контейнера (не будут сохранены на диск). +Когда **контейнер** запущен (на основе **образа контейнера**), он может создавать или изменять файлы, переменные окружения и т.д.. Эти изменения существуют только внутри контейнера и не сохраняются в исходном образе контейнера (не записываются на диск). -Образ контейнера можно сравнить с файлом, содержащем **программу**, например, как файл `main.py`. +Образ контейнера можно сравнить с **файлами программы**, например `python` и каким-то файлом `main.py`. -И **контейнер** (в отличие от **образа**) - это на самом деле выполняемый экземпляр образа, примерно как **процесс**. По факту, контейнер запущен только когда запущены его процессы (чаще, всего один процесс) и остановлен, когда запущенных процессов нет. +А сам **контейнер** (в отличие от **образа контейнера**) — это фактически запущенный экземпляр образа, сопоставимый с **процессом**. По сути, контейнер работает только тогда, когда в нём есть **запущенный процесс** (и обычно это один процесс). Контейнер останавливается, когда в нём не остаётся запущенных процессов. -## Образы контейнеров +## Образы контейнеров { #container-images } -Docker является одним оз основных инструментов для создания **образов** и **контейнеров** и управления ими. +Docker — один из основных инструментов для создания и управления **образами контейнеров** и **контейнерами**. -Существует общедоступный Docker Hub с подготовленными **официальными образами** многих инструментов, окружений, баз данных и приложений. +Существует публичный Docker Hub с готовыми **официальными образами** для многих инструментов, окружений, баз данных и приложений. -К примеру, есть официальный образ Python. +Например, есть официальный образ Python. -Также там представлены и другие полезные образы, такие как базы данных: +А также множество образов для разных вещей, например баз данных: * PostgreSQL * MySQL * MongoDB -* Redis +* Redis, и т.д. -и т.п. +Используя готовые образы, очень легко **комбинировать** разные инструменты и использовать их. Например, чтобы попробовать новую базу данных. В большинстве случаев можно воспользоваться **официальными образами** и просто настроить их через переменные окружения. -Использование подготовленных образов значительно упрощает **комбинирование** и использование разных инструментов. Например, вы можете попытаться использовать новую базу данных. В большинстве случаев можно использовать **официальный образ** и всего лишь указать переменные окружения. +Таким образом, во многих случаях вы можете изучить контейнеры и Docker и переиспользовать эти знания с множеством различных инструментов и компонентов. -Таким образом, вы можете изучить, что такое контейнеризация и Docker, и использовать полученные знания с разными инструментами и компонентами. +Например, вы можете запустить **несколько контейнеров**: с базой данных, Python-приложением, веб-сервером с фронтендом на React и связать их через внутреннюю сеть. -Так, вы можете запустить одновременно **множество контейнеров** с базой данных, Python-приложением, веб-сервером, React-приложением и соединить их вместе через внутреннюю сеть. +Все системы управления контейнерами (такие как Docker или Kubernetes) имеют интегрированные возможности для такого сетевого взаимодействия. -Все системы управления контейнерами (такие, как Docker или Kubernetes) имеют встроенные возможности для организации такого сетевого взаимодействия. +## Контейнеры и процессы { #containers-and-processes } -## Контейнеры и процессы +**Образ контейнера** обычно включает в свои метаданные программу или команду по умолчанию, которую следует запускать при старте **контейнера**, а также параметры, передаваемые этой программе. Это очень похоже на запуск команды в терминале. -Обычно **образ контейнера** содержит метаданные предустановленной программы или команду, которую следует выполнить при запуске **контейнера**. Также он может содержать параметры, передаваемые предустановленной программе. Похоже на то, как если бы вы запускали такую программу через терминал. +Когда **контейнер** стартует, он выполняет указанную команду/программу (хотя вы можете переопределить это и запустить другую команду/программу). -Когда **контейнер** запущен, он будет выполнять прописанные в нём команды и программы. Но вы можете изменить его так, чтоб он выполнял другие команды и программы. +Контейнер работает до тех пор, пока работает его **главный процесс** (команда или программа). -Контейнер буде работать до тех пор, пока выполняется его **главный процесс** (команда или программа). +Обычно в контейнере есть **один процесс**, но главный процесс может запускать подпроцессы, и тогда в том же контейнере будет **несколько процессов**. -В контейнере обычно выполняется **только один процесс**, но от его имени можно запустить другие процессы, тогда в этом же в контейнере будет выполняться **множество процессов**. +Нельзя иметь работающий контейнер без **хотя бы одного запущенного процесса**. Если главный процесс останавливается, контейнер останавливается. -Контейнер не считается запущенным, если в нём **не выполняется хотя бы один процесс**. Если главный процесс остановлен, значит и контейнер остановлен. +## Создать Docker-образ для FastAPI { #build-a-docker-image-for-fastapi } -## Создать Docker-образ для FastAPI +Итак, давайте что-нибудь соберём! 🚀 -Что ж, давайте ужё создадим что-нибудь! 🚀 +Я покажу, как собрать **Docker-образ** для FastAPI **с нуля** на основе **официального образа Python**. -Я покажу Вам, как собирать **Docker-образ** для FastAPI **с нуля**, основываясь на **официальном образе Python**. +Именно так стоит делать в **большинстве случаев**, например: -Такой подход сгодится для **большинства случаев**, например: +* При использовании **Kubernetes** или похожих инструментов +* При запуске на **Raspberry Pi** +* При использовании облачного сервиса, который запускает для вас образ контейнера и т.п. -* Использование с **Kubernetes** или аналогичным инструментом -* Запуск в **Raspberry Pi** -* Использование в облачных сервисах, запускающих образы контейнеров для вас и т.п. +### Зависимости пакетов { #package-requirements } -### Установить зависимости +Обычно **зависимости** вашего приложения описаны в каком-то файле. -Обычно вашему приложению необходимы **дополнительные библиотеки**, список которых находится в отдельном файле. +Конкретный формат зависит в основном от инструмента, которым вы **устанавливаете** эти зависимости. -На название и содержание такого файла влияет выбранный Вами инструмент **установки** этих библиотек (зависимостей). +Чаще всего используется файл `requirements.txt` с именами пакетов и их версиями по одному на строку. -Чаще всего это простой файл `requirements.txt` с построчным перечислением библиотек и их версий. +Разумеется, вы будете придерживаться тех же идей, что описаны здесь: [О версиях FastAPI](versions.md){.internal-link target=_blank}, чтобы задать диапазоны версий. -При этом Вы, для выбора версий, будете использовать те же идеи, что упомянуты на странице [О версиях FastAPI](versions.md){.internal-link target=_blank}. - -Ваш файл `requirements.txt` может выглядеть как-то так: +Например, ваш `requirements.txt` может выглядеть так: ``` -fastapi>=0.68.0,<0.69.0 -pydantic>=1.8.0,<2.0.0 -uvicorn>=0.15.0,<0.16.0 +fastapi[standard]>=0.113.0,<0.114.0 +pydantic>=2.7.0,<3.0.0 ``` -Устанавливать зависимости проще всего с помощью `pip`: +И обычно вы установите эти зависимости командой `pip`, например:
```console $ pip install -r requirements.txt ---> 100% -Successfully installed fastapi pydantic uvicorn +Successfully installed fastapi pydantic ```
-/// info | "Информация" +/// info | Информация -Существуют и другие инструменты управления зависимостями. - -В этом же разделе, но позже, я покажу вам пример использования Poetry. 👇 +Существуют и другие форматы и инструменты для описания и установки зависимостей. /// -### Создать приложение **FastAPI** +### Создать код **FastAPI** { #create-the-fastapi-code } * Создайте директорию `app` и перейдите в неё. * Создайте пустой файл `__init__.py`. -* Создайте файл `main.py` и заполните его: +* Создайте файл `main.py` со следующим содержимым: ```Python from typing import Union @@ -167,77 +162,109 @@ def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -### Dockerfile +### Dockerfile { #dockerfile } -В этой же директории создайте файл `Dockerfile` и заполните его: +Теперь в той же директории проекта создайте файл `Dockerfile`: ```{ .dockerfile .annotate } -# (1) +# (1)! FROM python:3.9 -# (2) +# (2)! WORKDIR /code -# (3) +# (3)! COPY ./requirements.txt /code/requirements.txt -# (4) +# (4)! RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -# (5) +# (5)! COPY ./app /code/app -# (6) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +# (6)! +CMD ["fastapi", "run", "app/main.py", "--port", "80"] ``` -1. Начните с официального образа Python, который будет основой для образа приложения. +1. Начинаем с официального базового образа Python. -2. Укажите, что в дальнейшем команды запускаемые в контейнере, будут выполняться в директории `/code`. +2. Устанавливаем текущую рабочую директорию в `/code`. - Инструкция создаст эту директорию внутри контейнера и мы поместим в неё файл `requirements.txt` и директорию `app`. + Здесь мы разместим файл `requirements.txt` и директорию `app`. -3. Скопируете файл с зависимостями из текущей директории в `/code`. +3. Копируем файл с зависимостями в директорию `/code`. - Сначала копируйте **только** файл с зависимостями. + Сначала копируйте **только** файл с зависимостями, не остальной код. - Этот файл **изменяется довольно редко**, Docker ищет изменения при постройке образа и если не находит, то использует **кэш**, в котором хранятся предыдущие версии сборки образа. + Так как этот файл **меняется нечасто**, Docker определит это и использует **кэш** на этом шаге, что позволит использовать кэш и на следующем шаге. -4. Установите библиотеки перечисленные в файле с зависимостями. +4. Устанавливаем зависимости из файла с требованиями. - Опция `--no-cache-dir` указывает `pip` не сохранять загружаемые библиотеки на локальной машине для использования их в случае повторной загрузки. В контейнере, в случае пересборки этого шага, они всё равно будут удалены. + Опция `--no-cache-dir` указывает `pip` не сохранять загруженные пакеты локально, т.к. это нужно только если `pip` будет запускаться снова для установки тех же пакетов, а при работе с контейнерами это обычно не требуется. /// note | Заметка - Опция `--no-cache-dir` нужна только для `pip`, она никак не влияет на Docker или контейнеры. + `--no-cache-dir` относится только к `pip` и не имеет отношения к Docker или контейнерам. /// - Опция `--upgrade` указывает `pip` обновить библиотеки, емли они уже установлены. + Опция `--upgrade` указывает `pip` обновлять пакеты, если они уже установлены. - Ка и в предыдущем шаге с копированием файла, этот шаг также будет использовать **кэш Docker** в случае отсутствия изменений. + Поскольку предыдущий шаг с копированием файла может быть обработан **кэшем Docker**, этот шаг также **использует кэш Docker**, когда это возможно. - Использование кэша, особенно на этом шаге, позволит вам **сэкономить** кучу времени при повторной сборке образа, так как зависимости будут сохранены в кеше, а не **загружаться и устанавливаться каждый раз**. + Использование кэша на этом шаге **сэкономит** вам много **времени** при повторных сборках образа во время разработки, вместо того чтобы **загружать и устанавливать** все зависимости **каждый раз**. -5. Скопируйте директорию `./app` внутрь директории `/code` (в контейнере). +5. Копируем директорию `./app` внутрь директории `/code`. - Так как в этой директории расположен код, который **часто изменяется**, то использование **кэша** на этом шаге будет наименее эффективно, а значит лучше поместить этот шаг **ближе к концу** `Dockerfile`, дабы не терять выгоду от оптимизации предыдущих шагов. + Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шагa или **последующих шагов**. -6. Укажите **команду**, запускающую сервер `uvicorn`. + Поэтому важно разместить этот шаг **ближе к концу** `Dockerfile`, чтобы оптимизировать время сборки образа контейнера. - `CMD` принимает список строк, разделённых запятыми, но при выполнении объединит их через пробел, собрав из них одну команду, которую вы могли бы написать в терминале. +6. Указываем **команду** для запуска `fastapi run`, под капотом используется Uvicorn. - Эта команда будет выполнена в **текущей рабочей директории**, а именно в директории `/code`, которая указана в команде `WORKDIR /code`. + `CMD` принимает список строк, каждая из которых — это то, что вы бы ввели в командной строке, разделяя пробелами. - Так как команда выполняется внутри директории `/code`, в которую мы поместили папку `./app` с приложением, то **Uvicorn** сможет найти и **импортировать** объект `app` из файла `app.main`. + Эта команда будет выполнена из **текущей рабочей директории**, той самой `/code`, которую вы задали выше `WORKDIR /code`. -/// tip | "Подсказка" +/// tip | Подсказка -Если ткнёте на кружок с плюсом, то увидите пояснения. 👆 +Посмотрите, что делает каждая строка, кликнув по номеру рядом со строкой. 👆 /// -На данном этапе структура проекта должны выглядеть так: +/// warning | Предупреждение + +Всегда используйте **exec-форму** инструкции `CMD`, как описано ниже. + +/// + +#### Используйте `CMD` — exec-форма { #use-cmd-exec-form } + +Инструкцию Docker `CMD` можно писать в двух формах: + +✅ **Exec**-форма: + +```Dockerfile +# ✅ Делайте так +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +⛔️ **Shell**-форма: + +```Dockerfile +# ⛔️ Не делайте так +CMD fastapi run app/main.py --port 80 +``` + +Обязательно используйте **exec**-форму, чтобы FastAPI мог корректно завершаться и чтобы срабатывали [события lifespan](../advanced/events.md){.internal-link target=_blank}. + +Подробнее об этом читайте в документации Docker о shell- и exec-формах. + +Это особенно заметно при использовании `docker compose`. См. раздел FAQ Docker Compose с техническими подробностями: Почему мои сервисы пересоздаются или останавливаются 10 секунд?. + +#### Структура директорий { #directory-structure } + +Теперь у вас должна быть такая структура: ``` . @@ -248,53 +275,52 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] └── requirements.txt ``` -#### Использование прокси-сервера +#### За прокси-сервером TLS терминации { #behind-a-tls-termination-proxy } -Если вы запускаете контейнер за прокси-сервером завершения TLS (балансирующего нагрузку), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`, которая укажет Uvicorn, что он работает позади прокси-сервера и может доверять заголовкам отправляемым им. +Если вы запускаете контейнер за прокси-сервером завершения TLS (балансировщиком нагрузки), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`. Это сообщит Uvicorn (через FastAPI CLI), что приложение работает за HTTPS и можно доверять соответствующим заголовкам. ```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] ``` -#### Кэш Docker'а +#### Кэш Docker { #docker-cache } -В нашем `Dockerfile` использована полезная хитрость, когда сначала копируется **только файл с зависимостями**, а не вся папка с кодом приложения. +В этом `Dockerfile` есть важная хитрость: мы сначала копируем **только файл с зависимостями**, а не весь код. Вот зачем. ```Dockerfile COPY ./requirements.txt /code/requirements.txt ``` -Docker и подобные ему инструменты **создают** образы контейнеров **пошагово**, добавляя **один слой над другим**, начиная с первой строки `Dockerfile` и добавляя файлы, создаваемые при выполнении каждой инструкции из `Dockerfile`. +Docker и подобные инструменты **строят** образы контейнеров **инкрементально**, добавляя **слой за слоем**, начиная с первой строки `Dockerfile` и добавляя любые файлы, создаваемые каждой инструкцией `Dockerfile`. -При создании образа используется **внутренний кэш** и если в файлах нет изменений с момента последней сборки образа, то будет **переиспользован** ранее созданный слой образа, а не повторное копирование файлов и создание слоя с нуля. -Заметьте, что так как слой следующего шага зависит от слоя предыдущего, то изменения внесённые в промежуточный слой, также повлияют на последующие. +Docker и подобные инструменты также используют **внутренний кэш** при сборке образа: если файл не изменился с момента предыдущей сборки, будет **переиспользован слой**, созданный в прошлый раз, вместо повторного копирования файла и создания нового слоя с нуля. -Избегание копирования файлов не обязательно улучшит ситуацию, но использование кэша на одном шаге, позволит **использовать кэш и на следующих шагах**. Например, можно использовать кэш при установке зависимостей: +Само по себе избегание копирования всех файлов не всегда даёт много, но благодаря использованию кэша на этом шаге Docker сможет **использовать кэш и на следующем шаге**. Например, на шаге установки зависимостей: ```Dockerfile RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt ``` -Файл со списком зависимостей **изменяется довольно редко**. Так что выполнив команду копирования только этого файла, Docker сможет **использовать кэш** на этом шаге. +Файл с зависимостями **меняется нечасто**. Поэтому, копируя только его, Docker сможет **использовать кэш** для этого шага. -А затем **использовать кэш и на следующем шаге**, загружающем и устанавливающем зависимости. И вот тут-то мы и **сэкономим много времени**. ✨ ...а не будем томиться в тягостном ожидании. 😪😆 +А затем Docker сможет **использовать кэш и на следующем шаге**, где скачиваются и устанавливаются зависимости. Здесь мы как раз **экономим много времени**. ✨ ...и не скучаем в ожидании. 😪😆 -Для загрузки и установки необходимых библиотек **может понадобиться несколько минут**, но использование **кэша** занимает несколько **секунд** максимум. +Скачивание и установка зависимостей **может занять минуты**, но использование **кэша** — **секунды**. -И так как во время разработки вы будете часто пересобирать контейнер для проверки работоспособности внесённых изменений, то сэкономленные минуты сложатся в часы, а то и дни. +Поскольку во время разработки вы будете пересобирать образ снова и снова, чтобы проверить изменения в коде, суммарно это сэкономит немало времени. -Так как папка с кодом приложения **изменяется чаще всего**, то мы расположили её в конце `Dockerfile`, ведь после внесённых в код изменений кэш не будет использован на этом и следующих шагах. +Затем, ближе к концу `Dockerfile`, мы копируем весь код. Так как он **меняется чаще всего**, мы ставим этот шаг в конец, потому что почти всегда всё, что после него, уже не сможет использовать кэш. ```Dockerfile COPY ./app /code/app ``` -### Создать Docker-образ +### Собрать Docker-образ { #build-the-docker-image } -Теперь, когда все файлы на своих местах, давайте создадим образ контейнера. +Теперь, когда все файлы на месте, соберём образ контейнера. -* Перейдите в директорию проекта (в ту, где расположены `Dockerfile` и папка `app` с приложением). -* Создай образ приложения FastAPI: +* Перейдите в директорию проекта (где ваш `Dockerfile` и директория `app`). +* Соберите образ FastAPI:
@@ -306,17 +332,17 @@ $ docker build -t myimage .
-/// tip | "Подсказка" +/// tip | Подсказка -Обратите внимание, что в конце написана точка - `.`, это то же самое что и `./`, тем самым мы указываем Docker директорию, из которой нужно выполнять сборку образа контейнера. +Обратите внимание на точку `.` в конце — это то же самое, что `./`. Так мы указываем Docker, из какой директории собирать образ контейнера. -В данном случае это та же самая директория (`.`). +В данном случае это текущая директория (`.`). /// -### Запуск Docker-контейнера +### Запустить Docker-контейнер { #start-the-docker-container } -* Запустите контейнер, основанный на вашем образе: +* Запустите контейнер на основе вашего образа:
@@ -326,35 +352,35 @@ $ docker run -d --name mycontainer -p 80:80 myimage
-## Проверка +## Проверка { #check-it } -Вы можете проверить, что Ваш Docker-контейнер работает перейдя по ссылке: http://192.168.99.100/items/5?q=somequery или http://127.0.0.1/items/5?q=somequery (или похожей, которую использует Ваш Docker-хост). +Проверьте работу по адресу вашего Docker-хоста, например: http://192.168.99.100/items/5?q=somequery или http://127.0.0.1/items/5?q=somequery (или аналогичный URL вашего Docker-хоста). -Там вы увидите: +Вы увидите что-то вроде: ```JSON {"item_id": 5, "q": "somequery"} ``` -## Интерактивная документация API +## Интерактивная документация API { #interactive-api-docs } -Теперь перейдите по ссылке http://192.168.99.100/docs или http://127.0.0.1/docs (или похожей, которую использует Ваш Docker-хост). +Теперь зайдите на http://192.168.99.100/docs или http://127.0.0.1/docs (или аналогичный URL вашего Docker-хоста). -Здесь вы увидите автоматическую интерактивную документацию API (предоставляемую Swagger UI): +Вы увидите автоматическую интерактивную документацию API (на базе Swagger UI): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -## Альтернативная документация API +## Альтернативная документация API { #alternative-api-docs } -Также вы можете перейти по ссылке http://192.168.99.100/redoc or http://127.0.0.1/redoc (или похожей, которую использует Ваш Docker-хост). +Также можно открыть http://192.168.99.100/redoc или http://127.0.0.1/redoc (или аналогичный URL вашего Docker-хоста). -Здесь вы увидите альтернативную автоматическую документацию API (предоставляемую ReDoc): +Вы увидите альтернативную автоматическую документацию (на базе ReDoc): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Создание Docker-образа на основе однофайлового приложения FastAPI +## Собрать Docker-образ для однофайлового FastAPI { #build-a-docker-image-with-a-single-file-fastapi } -Если ваше приложение FastAPI помещено в один файл, например, `main.py` и структура Ваших файлов похожа на эту: +Если ваше приложение FastAPI — один файл, например `main.py` без директории `./app`, структура файлов может быть такой: ``` . @@ -363,7 +389,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage └── requirements.txt ``` -Вам нужно изменить в `Dockerfile` соответствующие пути копирования файлов: +Тогда в `Dockerfile` нужно изменить пути копирования: ```{ .dockerfile .annotate hl_lines="10 13" } FROM python:3.9 @@ -374,360 +400,221 @@ COPY ./requirements.txt /code/requirements.txt RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -# (1) +# (1)! COPY ./main.py /code/ -# (2) -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +# (2)! +CMD ["fastapi", "run", "main.py", "--port", "80"] ``` -1. Скопируйте непосредственно файл `main.py` в директорию `/code` (не указывайте `./app`). +1. Копируем файл `main.py` напрямую в `/code` (без директории `./app`). -2. При запуске Uvicorn укажите ему, что объект `app` нужно импортировать из файла `main` (вместо импортирования из `app.main`). +2. Используем `fastapi run` для запуска приложения из одного файла `main.py`. -Настройте Uvicorn на использование `main` вместо `app.main` для импорта объекта `app`. +Когда вы передаёте файл в `fastapi run`, он автоматически определит, что это одиночный файл, а не часть пакета, и поймёт, как его импортировать и запустить ваше FastAPI-приложение. 😎 -## Концепции развёртывания +## Концепции развертывания { #deployment-concepts } -Давайте вспомним о [Концепциях развёртывания](concepts.md){.internal-link target=_blank} и применим их к контейнерам. +Ещё раз рассмотрим [концепции развертывания](concepts.md){.internal-link target=_blank} применительно к контейнерам. -Контейнеры - это, в основном, инструмент упрощающий **сборку и развёртывание** приложения и они не обязывают к применению какой-то определённой **концепции развёртывания**, а значит мы можем выбирать нужную стратегию. +Контейнеры главным образом упрощают **сборку и развёртывание** приложения, но не навязывают конкретный подход к этим **концепциям развертывания**, и существует несколько стратегий. -**Хорошая новость** в том, что независимо от выбранной стратегии, мы всё равно можем покрыть все концепции развёртывания. 🎉 +**Хорошая новость** в том, что при любой стратегии есть способ охватить все концепции развертывания. 🎉 -Рассмотрим эти **концепции развёртывания** применительно к контейнерам: +Рассмотрим эти **концепции развертывания** в терминах контейнеров: -* Использование более безопасного протокола HTTPS -* Настройки запуска приложения -* Перезагрузка приложения -* Запуск нескольких экземпляров приложения -* Управление памятью -* Использование перечисленных функций перед запуском приложения +* HTTPS +* Запуск при старте +* Перезапуски +* Репликация (количество запущенных процессов) +* Память +* Предварительные шаги перед запуском -## Использование более безопасного протокола HTTPS +## HTTPS { #https } -Если мы определимся, что **образ контейнера** будет содержать только приложение FastAPI, то работу с HTTPS можно организовать **снаружи** контейнера при помощи другого инструмента. +Если мы рассматриваем только **образ контейнера** для приложения FastAPI (и далее запущенный **контейнер**), то HTTPS обычно обрабатывается **внешним** инструментом. -Это может быть другой контейнер, в котором есть, например, Traefik, работающий с **HTTPS** и **самостоятельно** обновляющий **сертификаты**. +Это может быть другой контейнер, например с Traefik, который берёт на себя **HTTPS** и **автоматическое** получение **сертификатов**. -/// tip | "Подсказка" +/// tip | Подсказка -Traefik совместим с Docker, Kubernetes и им подобными инструментами. Он очень прост в установке и настройке использования HTTPS для Ваших контейнеров. +У Traefik есть интеграции с Docker, Kubernetes и другими, поэтому очень легко настроить и сконфигурировать HTTPS для ваших контейнеров. /// -В качестве альтернативы, работу с HTTPS можно доверить облачному провайдеру, если он предоставляет такую услугу. +В качестве альтернативы HTTPS может быть реализован как сервис облачного провайдера (при этом приложение всё равно работает в контейнере). -## Настройки запуска и перезагрузки приложения +## Запуск при старте и перезапуски { #running-on-startup-and-restarts } -Обычно **запуском контейнера с приложением** занимается какой-то отдельный инструмент. +Обычно есть другой инструмент, отвечающий за **запуск и работу** вашего контейнера. -Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный провайдер** и т.п. +Это может быть сам **Docker**, **Docker Compose**, **Kubernetes**, **облачный сервис** и т.п. -В большинстве случаев это простейшие настройки запуска и перезагрузки приложения (при падении). Например, команде запуска Docker-контейнера можно добавить опцию `--restart`. +В большинстве (или во всех) случаев есть простая опция, чтобы включить запуск контейнера при старте системы и перезапуски при сбоях. Например, в Docker это опция командной строки `--restart`. -Управление запуском и перезагрузкой приложений без использования контейнеров - весьма затруднительно. Но при **работе с контейнерами** - это всего лишь функционал доступный по умолчанию. ✨ +Без контейнеров обеспечить запуск при старте и перезапуски может быть сложно. Но при **работе с контейнерами** в большинстве случаев этот функционал доступен по умолчанию. ✨ -## Запуск нескольких экземпляров приложения - Указание количества процессов +## Репликация — количество процессов { #replication-number-of-processes } -Если у вас есть кластер машин под управлением **Kubernetes**, Docker Swarm Mode, Nomad или аналогичной сложной системой оркестрации контейнеров, скорее всего, вместо использования менеджера процессов (типа Gunicorn и его воркеры) в каждом контейнере, вы захотите **управлять количеством запущенных экземпляров приложения** на **уровне кластера**. +Если у вас есть кластер машин с **Kubernetes**, Docker Swarm Mode, Nomad или другой похожей системой для управления распределёнными контейнерами на нескольких машинах, скорее всего вы будете **управлять репликацией** на **уровне кластера**, а не использовать **менеджер процессов** (например, Uvicorn с воркерами) в каждом контейнере. -В любую из этих систем управления контейнерами обычно встроен способ управления **количеством запущенных контейнеров** для распределения **нагрузки** от входящих запросов на **уровне кластера**. +Одна из таких систем управления распределёнными контейнерами, как Kubernetes, обычно имеет встроенный способ управлять **репликацией контейнеров**, поддерживая **балансировку нагрузки** для входящих запросов — всё это на **уровне кластера**. -В такой ситуации Вы, вероятно, захотите создать **образ Docker**, как [описано выше](#dockerfile), с установленными зависимостями и запускающий **один процесс Uvicorn** вместо того, чтобы запускать Gunicorn управляющий несколькими воркерами Uvicorn. +В таких случаях вы, скорее всего, захотите собрать **Docker-образ с нуля**, как [описано выше](#dockerfile), установить зависимости и запускать **один процесс Uvicorn** вместо множества воркеров Uvicorn. -### Балансировщик нагрузки +### Балансировщик нагрузки { #load-balancer } -Обычно при использовании контейнеров один компонент **прослушивает главный порт**. Это может быть контейнер содержащий **прокси-сервер завершения работы TLS** для работы с **HTTPS** или что-то подобное. +При использовании контейнеров обычно есть компонент, **слушающий главный порт**. Это может быть другой контейнер — **прокси завершения TLS** для обработки **HTTPS** или похожий инструмент. -Поскольку этот компонент **принимает запросы** и равномерно **распределяет** их между компонентами, его также называют **балансировщиком нагрузки**. +Поскольку этот компонент принимает **нагрузку** запросов и распределяет её между воркерами **сбалансированно**, его часто называют **балансировщиком нагрузки**. -/// tip | "Подсказка" +/// tip | Подсказка -**Прокси-сервер завершения работы TLS** одновременно может быть **балансировщиком нагрузки**. +Тот же компонент **прокси завершения TLS**, который обрабатывает HTTPS, скорее всего также будет **балансировщиком нагрузки**. /// -Система оркестрации, которую вы используете для запуска и управления контейнерами, имеет встроенный инструмент **сетевого взаимодействия** (например, для передачи HTTP-запросов) между контейнерами с Вашими приложениями и **балансировщиком нагрузки** (который также может быть **прокси-сервером**). +При работе с контейнерами система, которую вы используете для запуска и управления ими, уже имеет внутренние средства для передачи **сетевого взаимодействия** (например, HTTP-запросов) от **балансировщика нагрузки** (который также может быть **прокси завершения TLS**) к контейнеру(-ам) с вашим приложением. -### Один балансировщик - Множество контейнеров +### Один балансировщик — несколько контейнеров-воркеров { #one-load-balancer-multiple-worker-containers } -При работе с **Kubernetes** или аналогичными системами оркестрации использование их внутренней сети позволяет иметь один **балансировщик нагрузки**, который прослушивает **главный** порт и передаёт запросы **множеству запущенных контейнеров** с Вашими приложениями. +При работе с **Kubernetes** или похожими системами управления распределёнными контейнерами их внутренние механизмы сети позволяют одному **балансировщику нагрузки**, слушающему главный **порт**, передавать запросы в **несколько контейнеров**, где запущено ваше приложение. -В каждом из контейнеров обычно работает **только один процесс** (например, процесс Uvicorn управляющий Вашим приложением FastAPI). Контейнеры могут быть **идентичными**, запущенными на основе одного и того же образа, но у каждого будут свои отдельные процесс, память и т.п. Таким образом мы получаем преимущества **распараллеливания** работы по **разным ядрам** процессора или даже **разным машинам**. +Каждый такой контейнер с вашим приложением обычно имеет **только один процесс** (например, процесс Uvicorn с вашим приложением FastAPI). Все они — **одинаковые контейнеры**, запускающие одно и то же, но у каждого свой процесс, память и т.п. Так вы используете **параллелизм** по **разным ядрам** CPU или даже **разным машинам**. -Система управления контейнерами с **балансировщиком нагрузки** будет **распределять запросы** к контейнерам с приложениями **по очереди**. То есть каждый запрос будет обработан одним из множества **одинаковых контейнеров** с одним и тем же приложением. +Система распределённых контейнеров с **балансировщиком нагрузки** будет **распределять запросы** между контейнерами с вашим приложением **по очереди**. То есть каждый запрос может обрабатываться одним из нескольких **реплицированных контейнеров**. -**Балансировщик нагрузки** может обрабатывать запросы к *разным* приложениям, расположенным в вашем кластере (например, если у них разные домены или префиксы пути) и передавать запросы нужному контейнеру с требуемым приложением. +Обычно такой **балансировщик нагрузки** может также обрабатывать запросы к *другим* приложениям в вашем кластере (например, к другому домену или под другим префиксом пути URL) и направлять их к нужным контейнерам этого *другого* приложения. -### Один процесс на контейнер +### Один процесс на контейнер { #one-process-per-container } -В этом варианте **в одном контейнере будет запущен только один процесс (Uvicorn)**, а управление изменением количества запущенных копий приложения происходит на уровне кластера. +В таком сценарии, скорее всего, вы захотите иметь **один (Uvicorn) процесс на контейнер**, так как репликация уже управляется на уровне кластера. -Здесь **не нужен** менеджер процессов типа Gunicorn, управляющий процессами Uvicorn, или же Uvicorn, управляющий другими процессами Uvicorn. Достаточно **только одного процесса Uvicorn** на контейнер (но запуск нескольких процессов не запрещён). +Поэтому в контейнере **не нужно** поднимать несколько воркеров, например через опцию командной строки `--workers`. Нужен **один процесс Uvicorn** на контейнер (но, возможно, несколько контейнеров). -Использование менеджера процессов (Gunicorn или Uvicorn) внутри контейнера только добавляет **излишнее усложнение**, так как управление следует осуществлять системой оркестрации. +Наличие отдельного менеджера процессов внутри контейнера (как при нескольких воркерах) только добавит **лишнюю сложность**, которую, вероятно, уже берёт на себя ваша кластерная система. -### Множество процессов внутри контейнера для особых случаев +### Контейнеры с несколькими процессами и особые случаи { #containers-with-multiple-processes-and-special-cases } -Безусловно, бывают **особые случаи**, когда может понадобиться внутри контейнера запускать **менеджер процессов Gunicorn**, управляющий несколькими **процессами Uvicorn**. +Конечно, есть **особые случаи**, когда может понадобиться **контейнер** с несколькими **воркерами Uvicorn** внутри. -Для таких случаев вы можете использовать **официальный Docker-образ** (прим. пер: - *здесь и далее на этой странице, если вы встретите сочетание "официальный Docker-образ" без уточнений, то автор имеет в виду именно предоставляемый им образ*), где в качестве менеджера процессов используется **Gunicorn**, запускающий несколько **процессов Uvicorn** и некоторые настройки по умолчанию, автоматически устанавливающие количество запущенных процессов в зависимости от количества ядер вашего процессора. Я расскажу вам об этом подробнее тут: [Официальный Docker-образ со встроенными Gunicorn и Uvicorn](#docker-gunicorn-uvicorn). +В таких случаях вы можете использовать опцию командной строки `--workers`, чтобы указать нужное количество воркеров: -Некоторые примеры подобных случаев: +```{ .dockerfile .annotate } +FROM python:3.9 -#### Простое приложение +WORKDIR /code -Вы можете использовать менеджер процессов внутри контейнера, если ваше приложение **настолько простое**, что у вас нет необходимости (по крайней мере, пока нет) в тщательных настройках количества процессов и вам достаточно имеющихся настроек по умолчанию (если используется официальный Docker-образ) для запуска приложения **только на одном сервере**, а не в кластере. +COPY ./requirements.txt /code/requirements.txt -#### Docker Compose +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -С помощью **Docker Compose** можно разворачивать несколько контейнеров на **одном сервере** (не кластере), но при это у вас не будет простого способа управления количеством запущенных контейнеров с одновременным сохранением общей сети и **балансировки нагрузки**. +COPY ./app /code/app -В этом случае можно использовать **менеджер процессов**, управляющий **несколькими процессами**, внутри **одного контейнера**. +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` -#### Prometheus и прочие причины +1. Здесь мы используем опцию `--workers`, чтобы установить число воркеров равным 4. -У вас могут быть и **другие причины**, когда использование **множества процессов** внутри **одного контейнера** будет проще, нежели запуск **нескольких контейнеров** с **единственным процессом** в каждом из них. +Примеры, когда это может быть уместно: -Например (в зависимости от конфигурации), у вас могут быть инструменты подобные экспортёру Prometheus, которые должны иметь доступ к **каждому запросу** приходящему в контейнер. +#### Простое приложение { #a-simple-app } -Если у вас будет **несколько контейнеров**, то Prometheus, по умолчанию, **при сборе метрик** получит их **только с одного контейнера**, который обрабатывает конкретный запрос, вместо **сбора метрик** со всех работающих контейнеров. +Вам может понадобиться менеджер процессов в контейнере, если приложение **достаточно простое**, чтобы запускаться на **одном сервере**, а не в кластере. -В таком случае может быть проще иметь **один контейнер** со **множеством процессов**, с нужным инструментом (таким как экспортёр Prometheus) в этом же контейнере и собирающем метрики со всех внутренних процессов этого контейнера. +#### Docker Compose { #docker-compose } + +Вы можете развёртывать на **одном сервере** (не кластере) с **Docker Compose**, и у вас не будет простого способа управлять репликацией контейнеров (в Docker Compose), сохраняя общую сеть и **балансировку нагрузки**. + +Тогда вы можете захотеть **один контейнер** с **менеджером процессов**, который запускает **несколько воркеров** внутри. --- -Самое главное - **ни одно** из перечисленных правил не является **высеченным на камне** и вы не обязаны слепо их повторять. вы можете использовать эти идеи при **рассмотрении вашего конкретного случая** и самостоятельно решать, какая из концепции подходит лучше: +Главное — **ни одно** из этих правил не является **строго обязательным**. Используйте эти идеи, чтобы **оценить свой конкретный случай** и решить, какой подход лучше для вашей системы, учитывая: -* Использование более безопасного протокола HTTPS -* Настройки запуска приложения -* Перезагрузка приложения -* Запуск нескольких экземпляров приложения -* Управление памятью -* Использование перечисленных функций перед запуском приложения +* Безопасность — HTTPS +* Запуск при старте +* Перезапуски +* Репликацию (количество запущенных процессов) +* Память +* Предварительные шаги перед запуском -## Управление памятью +## Память { #memory } -При **запуске одного процесса на контейнер** вы получаете относительно понятный, стабильный и ограниченный объём памяти, потребляемый одним контейнером. +Если вы запускаете **один процесс на контейнер**, у каждого контейнера будет более-менее чётко определённый, стабильный и ограниченный объём потребляемой памяти (контейнеров может быть несколько при репликации). -Вы можете установить аналогичные ограничения по памяти при конфигурировании своей системы управления контейнерами (например, **Kubernetes**). Таким образом система сможет **изменять количество контейнеров** на **доступных ей машинах** приводя в соответствие количество памяти нужной контейнерам с количеством памяти доступной в кластере (наборе доступных машин). +Затем вы можете задать такие же лимиты и требования по памяти в конфигурации вашей системы управления контейнерами (например, в **Kubernetes**). Так система сможет **реплицировать контейнеры** на **доступных машинах**, учитывая объём необходимой памяти и доступной памяти в машинах кластера. -Если у вас **простенькое** приложение, вероятно у вас не будет **необходимости** устанавливать жёсткие ограничения на выделяемую ему память. Но если приложение **использует много памяти** (например, оно использует модели **машинного обучения**), вам следует проверить, как много памяти ему требуется и отрегулировать **количество контейнеров** запущенных на **каждой машине** (может быть даже добавить машин в кластер). +Если приложение **простое**, это, вероятно, **не будет проблемой**, и жёсткие лимиты памяти можно не указывать. Но если вы **используете много памяти** (например, с моделями **Машинного обучения**), проверьте, сколько памяти потребляется, и отрегулируйте **число контейнеров** на **каждой машине** (и, возможно, добавьте машины в кластер). -Если вы запускаете **несколько процессов в контейнере**, то должны быть уверены, что эти процессы не **займут памяти больше**, чем доступно для контейнера. +Если вы запускаете **несколько процессов в контейнере**, нужно убедиться, что их суммарное потребление **не превысит доступную память**. -## Подготовительные шаги при запуске контейнеров +## Предварительные шаги перед запуском и контейнеры { #previous-steps-before-starting-and-containers } -Есть два основных подхода, которые вы можете использовать при запуске контейнеров (Docker, Kubernetes и т.п.). +Если вы используете контейнеры (например, Docker, Kubernetes), есть два основных подхода. -### Множество контейнеров +### Несколько контейнеров { #multiple-containers } -Когда вы запускаете **множество контейнеров**, в каждом из которых работает **только один процесс** (например, в кластере **Kubernetes**), может возникнуть необходимость иметь **отдельный контейнер**, который осуществит **предварительные шаги перед запуском** остальных контейнеров (например, применяет миграции к базе данных). +Если у вас **несколько контейнеров**, и, вероятно, каждый запускает **один процесс** (например, в кластере **Kubernetes**), то вы, скорее всего, захотите иметь **отдельный контейнер**, выполняющий **предварительные шаги** в одном контейнере и одном процессе **до** запуска реплицированных контейнеров-воркеров. -/// info | "Информация" +/// info | Информация -При использовании Kubernetes, это может быть Инициализирующий контейнер. +Если вы используете Kubernetes, это, вероятно, будет Init Container. /// -При отсутствии такой необходимости (допустим, не нужно применять миграции к базе данных, а только проверить, что она готова принимать соединения), вы можете проводить такую проверку в каждом контейнере перед запуском его основного процесса и запускать все контейнеры **одновременно**. +Если в вашем случае нет проблемы с тем, чтобы выполнять эти предварительные шаги **многократно и параллельно** (например, вы не запускаете миграции БД, а только проверяете готовность БД), вы можете просто выполнить их в каждом контейнере прямо перед стартом основного процесса. -### Только один контейнер +### Один контейнер { #single-container } -Если у вас несложное приложение для работы которого достаточно **одного контейнера**, но в котором работает **несколько процессов** (или один процесс), то прохождение предварительных шагов можно осуществить в этом же контейнере до запуска основного процесса. Официальный Docker-образ поддерживает такие действия. +Если у вас простая схема с **одним контейнером**, который затем запускает несколько **воркеров** (или один процесс), можно выполнить подготовительные шаги в этом же контейнере непосредственно перед запуском процесса с приложением. -## Официальный Docker-образ с Gunicorn и Uvicorn +### Базовый Docker-образ { #base-docker-image } -Я подготовил для вас Docker-образ, в который включён Gunicorn управляющий процессами (воркерами) Uvicorn, в соответствии с концепциями рассмотренными в предыдущей главе: [Рабочие процессы сервера (воркеры) - Gunicorn совместно с Uvicorn](server-workers.md){.internal-link target=_blank}. +Ранее существовал официальный Docker-образ FastAPI: tiangolo/uvicorn-gunicorn-fastapi. Сейчас он помечен как устаревший. ⛔️ -Этот образ может быть полезен для ситуаций описанных тут: [Множество процессов внутри контейнера для особых случаев](#_11). +Скорее всего, вам **не стоит** использовать этот базовый образ (или какой-либо аналогичный). -* tiangolo/uvicorn-gunicorn-fastapi. +Если вы используете **Kubernetes** (или другое) и уже настраиваете **репликацию** на уровне кластера через несколько **контейнеров**, в этих случаях лучше **собрать образ с нуля**, как описано выше: [Создать Docker-образ для FastAPI](#build-a-docker-image-for-fastapi). -/// warning | "Предупреждение" +А если вам нужны несколько воркеров, просто используйте опцию командной строки `--workers`. -Скорее всего у вас **нет необходимости** в использовании этого образа или подобного ему и лучше создать свой образ с нуля как описано тут: [Создать Docker-образ для FastAPI](#docker-fastapi). +/// note | Технические подробности + +Этот Docker-образ был создан в то время, когда Uvicorn не умел управлять и перезапускать «упавших» воркеров, и приходилось использовать Gunicorn вместе с Uvicorn, что добавляло заметную сложность, лишь бы Gunicorn управлял и перезапускал воркеров Uvicorn. + +Но теперь, когда Uvicorn (и команда `fastapi`) поддерживают `--workers`, нет причин использовать базовый Docker-образ вместо сборки своего (кода получается примерно столько же 😅). /// -В этом образе есть **автоматический** механизм подстройки для запуска **необходимого количества процессов** в соответствии с доступным количеством ядер процессора. +## Развёртывание образа контейнера { #deploy-the-container-image } -В нём установлены **разумные значения по умолчанию**, но можно изменять и обновлять конфигурацию с помощью **переменных окружения** или конфигурационных файлов. - -Он также поддерживает прохождение **Подготовительных шагов при запуске контейнеров** при помощи скрипта. - -/// tip | "Подсказка" - -Для просмотра всех возможных настроек перейдите на страницу этого Docker-образа: tiangolo/uvicorn-gunicorn-fastapi. - -/// - -### Количество процессов в официальном Docker-образе - -**Количество процессов** в этом образе **вычисляется автоматически** и зависит от доступного количества **ядер** центрального процессора. - -Это означает, что он будет пытаться **выжать** из процессора как можно больше **производительности**. - -Но вы можете изменять и обновлять конфигурацию с помощью **переменных окружения** и т.п. - -Поскольку количество процессов зависит от процессора, на котором работает контейнер, **объём потребляемой памяти** также будет зависеть от этого. - -А значит, если вашему приложению требуется много оперативной памяти (например, оно использует модели машинного обучения) и Ваш сервер имеет центральный процессор с большим количеством ядер, но **не слишком большим объёмом оперативной памяти**, то может дойти до того, что контейнер попытается занять памяти больше, чем доступно, из-за чего будет падение производительности (или сервер вовсе упадёт). 🚨 - - -### Написание `Dockerfile` - -Итак, теперь мы можем написать `Dockerfile` основанный на этом официальном Docker-образе: - -```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 - -COPY ./requirements.txt /app/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt - -COPY ./app /app -``` - -### Большие приложения - -Если вы успели ознакомиться с разделом [Приложения содержащие много файлов](../tutorial/bigger-applications.md){.internal-link target=_blank}, состоящие из множества файлов, Ваш Dockerfile может выглядеть так: - -```Dockerfile hl_lines="7" -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 - -COPY ./requirements.txt /app/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt - -COPY ./app /app/app -``` - -### Как им пользоваться - -Если вы используете **Kubernetes** (или что-то вроде того), скорее всего вам **не нужно** использовать официальный Docker-образ (или другой похожий) в качестве основы, так как управление **количеством запущенных контейнеров** должно быть настроено на уровне кластера. В таком случае лучше **создать образ с нуля**, как описано в разделе Создать [Docker-образ для FastAPI](#docker-fastapi). - -Официальный образ может быть полезен в отдельных случаях, описанных выше в разделе [Множество процессов внутри контейнера для особых случаев](#_11). Например, если ваше приложение **достаточно простое**, не требует запуска в кластере и способно уместиться в один контейнер, то его настройки по умолчанию будут работать довольно хорошо. Или же вы развертываете его с помощью **Docker Compose**, работаете на одном сервере и т. д - -## Развёртывание образа контейнера - -После создания образа контейнера существует несколько способов его развёртывания. +После того как у вас есть образ контейнера (Docker), его можно развёртывать несколькими способами. Например: -* С использованием **Docker Compose** при развёртывании на одном сервере -* С использованием **Kubernetes** в кластере -* С использованием режима Docker Swarm в кластере -* С использованием других инструментов, таких как Nomad -* С использованием облачного сервиса, который будет управлять разворачиванием вашего контейнера +* С **Docker Compose** на одном сервере +* В кластере **Kubernetes** +* В кластере Docker Swarm Mode +* С другим инструментом, например Nomad +* С облачным сервисом, который принимает ваш образ контейнера и разворачивает его -## Docker-образ и Poetry +## Docker-образ с `uv` { #docker-image-with-uv } -Если вы пользуетесь Poetry для управления зависимостями вашего проекта, то можете использовать многоэтапную сборку образа: +Если вы используете uv для установки и управления проектом, следуйте их руководству по Docker для uv. -```{ .dockerfile .annotate } -# (1) -FROM python:3.9 as requirements-stage +## Резюме { #recap } -# (2) -WORKDIR /tmp +Используя системы контейнеризации (например, **Docker** и **Kubernetes**), довольно просто закрыть все **концепции развертывания**: -# (3) -RUN pip install poetry +* HTTPS +* Запуск при старте +* Перезапуски +* Репликация (количество запущенных процессов) +* Память +* Предварительные шаги перед запуском -# (4) -COPY ./pyproject.toml ./poetry.lock* /tmp/ +В большинстве случаев вы, вероятно, не захотите использовать какой-либо базовый образ, а вместо этого **соберёте образ контейнера с нуля** на основе официального Docker-образа Python. -# (5) -RUN poetry export -f requirements.txt --output requirements.txt --without-hashes - -# (6) -FROM python:3.9 - -# (7) -WORKDIR /code - -# (8) -COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt - -# (9) -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -# (10) -COPY ./app /code/app - -# (11) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] -``` - -1. Это первый этап, которому мы дадим имя `requirements-stage`. - -2. Установите директорию `/tmp` в качестве рабочей директории. - - В ней будет создан файл `requirements.txt` - -3. На этом шаге установите Poetry. - -4. Скопируйте файлы `pyproject.toml` и `poetry.lock` в директорию `/tmp`. - - Поскольку название файла написано как `./poetry.lock*` (с `*` в конце), то ничего не сломается, если такой файл не будет найден. - -5. Создайте файл `requirements.txt`. - -6. Это второй (и последний) этап сборки, который и создаст окончательный образ контейнера. - -7. Установите директорию `/code` в качестве рабочей. - -8. Скопируйте файл `requirements.txt` в директорию `/code`. - - Этот файл находится в образе, созданном на предыдущем этапе, которому мы дали имя requirements-stage, потому при копировании нужно написать `--from-requirements-stage`. - -9. Установите зависимости, указанные в файле `requirements.txt`. - -10. Скопируйте папку `app` в папку `/code`. - -11. Запустите `uvicorn`, указав ему использовать объект `app`, расположенный в `app.main`. - -/// tip | "Подсказка" - -Если ткнёте на кружок с плюсом, то увидите объяснения, что происходит в этой строке. - -/// - -**Этапы сборки Docker-образа** являются частью `Dockerfile` и работают как **временные образы контейнеров**. Они нужны только для создания файлов, используемых в дальнейших этапах. - -Первый этап был нужен только для **установки Poetry** и **создания файла `requirements.txt`**, в которым прописаны зависимости вашего проекта, взятые из файла `pyproject.toml`. - -На **следующем этапе** `pip` будет использовать файл `requirements.txt`. - -В итоговом образе будет содержаться **только последний этап сборки**, предыдущие этапы будут отброшены. - -При использовании Poetry, имеет смысл использовать **многоэтапную сборку Docker-образа**, потому что на самом деле вам не нужен Poetry и его зависимости в окончательном образе контейнера, вам **нужен только** сгенерированный файл `requirements.txt` для установки зависимостей вашего проекта. - -А на последнем этапе, придерживаясь описанных ранее правил, создаётся итоговый образ - -### Использование прокси-сервера завершения TLS и Poetry - -И снова повторюсь, если используете прокси-сервер (балансировщик нагрузки), такой как Nginx или Traefik, добавьте в команду запуска опцию `--proxy-headers`: - -```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] -``` - -## Резюме - -При помощи систем контейнеризации (таких, как **Docker** и **Kubernetes**), становится довольно просто обрабатывать все **концепции развертывания**: - -* Использование более безопасного протокола HTTPS -* Настройки запуска приложения -* Перезагрузка приложения -* Запуск нескольких экземпляров приложения -* Управление памятью -* Использование перечисленных функций перед запуском приложения - -В большинстве случаев Вам, вероятно, не нужно использовать какой-либо базовый образ, **лучше создать образ контейнера с нуля** на основе официального Docker-образа Python. - -Позаботившись о **порядке написания** инструкций в `Dockerfile`, вы сможете использовать **кэш Docker'а**, **минимизировав время сборки**, максимально повысив свою производительность (и не заскучать). 😎 - -В некоторых особых случаях вы можете использовать официальный образ Docker для FastAPI. 🤓 +Заботясь о **порядке** инструкций в `Dockerfile`и используя **кэш Docker**, вы можете **минимизировать время сборки**, чтобы повысить продуктивность (и не скучать). 😎 diff --git a/docs/ru/docs/deployment/https.md b/docs/ru/docs/deployment/https.md index 3d487c465f..05a03255e8 100644 --- a/docs/ru/docs/deployment/https.md +++ b/docs/ru/docs/deployment/https.md @@ -1,207 +1,231 @@ -# Об HTTPS +# Об HTTPS { #about-https } -Обычно представляется, что HTTPS это некая опция, которая либо "включена", либо нет. +Легко предположить, что HTTPS — это что-то, что просто «включено» или нет. -Но всё несколько сложнее. +Но на самом деле всё гораздо сложнее. -/// tip | "Заметка" +/// tip | Совет -Если вы торопитесь или вам не интересно, можете перейти на следующую страницу этого пошагового руководства по размещению приложений на серверах с использованием различных технологий. +Если вы торопитесь или вам это не важно, переходите к следующим разделам с пошаговыми инструкциями по настройке всего разными способами. /// -Чтобы **изучить основы HTTPS** для клиента, перейдите по ссылке https://howhttps.works/. +Чтобы **изучить основы HTTPS** с точки зрения пользователя, загляните на https://howhttps.works/. -Здесь же представлены некоторые концепции, которые **разработчик** должен иметь в виду при размышлениях об HTTPS: +Теперь, со стороны **разработчика**, вот несколько вещей, которые стоит держать в голове, размышляя об 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**. +* Для HTTPS **серверу** нужно **иметь «сертификаты»**, сгенерированные **третьей стороной**. + * Эти сертификаты на самом деле **приобретаются** у третьей стороны, а не «генерируются». +* У сертификатов есть **срок действия**. + * Они **истекают**. + * После этого их нужно **обновлять**, то есть **получать заново** у третьей стороны. +* Шифрование соединения происходит на **уровне TCP**. + * Это на один уровень **ниже HTTP**. + * Поэтому **сертификаты и шифрование** обрабатываются **до HTTP**. +* **TCP не знает о «доменах»**. Только об IP-адресах. + * Информация о **конкретном домене** передаётся в **данных HTTP**. +* **HTTPS-сертификаты** «сертифицируют» **определённый домен**, но протокол и шифрование происходят на уровне TCP, **до того как** становится известен домен, с которым идёт работа. +* **По умолчанию** это означает, что вы можете иметь **лишь один HTTPS-сертификат на один IP-адрес**. + * Неважно, насколько мощный у вас сервер или насколько маленькие приложения на нём работают. + * Однако у этого есть **решение**. +* Есть **расширение** протокола **TLS** (того самого, что занимается шифрованием на уровне TCP, до HTTP) под названием **SNI**. + * Это расширение SNI позволяет одному серверу (с **одним IP-адресом**) иметь **несколько HTTPS-сертификатов** и обслуживать **несколько HTTPS-доменов/приложений**. + * Чтобы это работало, **один** компонент (программа), запущенный на сервере и слушающий **публичный IP-адрес**, должен иметь **все HTTPS-сертификаты** на этом сервере. +* **После** установления защищённого соединения, протокол обмена данными — **всё ещё HTTP**. + * Содержимое **зашифровано**, несмотря на то, что оно отправляется по **протоколу HTTP**. -Обычной практикой является иметь **одну программу/HTTP-сервер** запущенную на сервере (машине, хосте и т.д.) и **ответственную за всю работу с HTTPS**: +Обычно на сервере (машине, хосте и т.п.) запускают **одну программу/HTTP‑сервер**, которая **управляет всей частью, связанной с HTTPS**: принимает **зашифрованные HTTPS-запросы**, отправляет **расшифрованные HTTP-запросы** в само HTTP‑приложение, работающее на том же сервере (в нашем случае это приложение **FastAPI**), получает **HTTP-ответ** от приложения, **шифрует его** с использованием подходящего **HTTPS‑сертификата** и отправляет клиенту по **HTTPS**. Такой сервер часто называют **прокси‑сервером TLS-терминации**. -* получение **зашифрованных HTTPS-запросов** -* отправка **расшифрованных HTTP запросов** в соответствующее HTTP-приложение, работающее на том же сервере (в нашем случае, это приложение **FastAPI**) -* получение **HTTP-ответа** от приложения -* **шифрование ответа** используя подходящий **сертификат HTTPS** -* отправка зашифрованного **HTTPS-ответа клиенту**. -Такой сервер часто называют **Прокси-сервер завершения работы TLS** или просто "прокси-сервер". +Некоторые варианты, которые вы можете использовать как прокси‑сервер TLS-терминации: -Вот некоторые варианты, которые вы можете использовать в качестве такого прокси-сервера: - -* Traefik (может обновлять сертификаты) -* Caddy (может обновлять сертификаты) +* Traefik (умеет обновлять сертификаты) +* Caddy (умеет обновлять сертификаты) * Nginx * HAProxy -## Let's Encrypt (центр сертификации) +## Let's Encrypt { #lets-encrypt } -До появления Let's Encrypt **сертификаты HTTPS** приходилось покупать у третьих сторон. +До появления Let's Encrypt эти **HTTPS-сертификаты** продавались доверенными третьими сторонами. -Процесс получения такого сертификата был трудоёмким, требовал предоставления подтверждающих документов и сертификаты стоили дорого. +Процесс получения таких сертификатов был неудобным, требовал бумажной волокиты, а сами сертификаты были довольно дорогими. -Но затем консорциумом Linux Foundation был создан проект **Let's Encrypt**. +Затем появился **Let's Encrypt**. -Он автоматически предоставляет **бесплатные сертификаты HTTPS**. Эти сертификаты используют все стандартные криптографические способы шифрования. Они имеют небольшой срок годности (около 3 месяцев), благодаря чему они даже **более безопасны**. +Это проект Linux Foundation. Он предоставляет **HTTPS‑сертификаты бесплатно**, в автоматическом режиме. Эти сертификаты используют стандартные криптографические механизмы и имеют короткий срок действия (около 3 месяцев), поэтому **безопасность фактически выше** благодаря уменьшенному сроку жизни. -При запросе на получение сертификата, он автоматически генерируется и домен проверяется на безопасность. Это позволяет обновлять сертификаты автоматически. +Домены безопасно проверяются, а сертификаты выдаются автоматически. Это также позволяет автоматизировать процесс их продления. -Суть идеи в автоматическом получении и обновлении этих сертификатов, чтобы все могли пользоваться **безопасным HTTPS. Бесплатно. В любое время.** +Идея — автоматизировать получение и продление сертификатов, чтобы у вас был **безопасный HTTPS, бесплатно и навсегда**. -## HTTPS для разработчиков +## HTTPS для разработчиков { #https-for-developers } -Ниже, шаг за шагом, с заострением внимания на идеях, важных для разработчика, описано, как может выглядеть HTTPS API. +Ниже приведён пример того, как может выглядеть HTTPS‑API, шаг за шагом, с акцентом на идеях, важных для разработчиков. -### Имя домена +### Имя домена { #domain-name } -Чаще всего, всё начинается с **приобретения имени домена**. Затем нужно настроить DNS-сервер (вероятно у того же провайдера, который выдал вам домен). +Чаще всего всё начинается с **приобретения** **имени домена**. Затем вы настраиваете его на DNS‑сервере (возможно, у того же облачного провайдера). -Далее, возможно, вы получаете "облачный" сервер (виртуальную машину) или что-то типа этого, у которого есть постоянный **публичный IP-адрес**. +Скорее всего, вы получите облачный сервер (виртуальную машину) или что-то подобное, и у него будет постоянный **публичный IP-адрес**. -На DNS-сервере (серверах) вам следует настроить соответствующую ресурсную запись ("`запись A`"), указав, что **Ваш домен** связан с публичным **IP-адресом вашего сервера**. +На DNS‑сервере(ах) вы настроите запись («`A record`» - запись типа A), указывающую, что **ваш домен** должен указывать на публичный **IP‑адрес вашего сервера**. -Обычно эту запись достаточно указать один раз, при первоначальной настройке всего сервера. +Обычно это делается один раз — при первоначальной настройке всего. -/// tip | "Заметка" +/// tip | Совет -Уровни протоколов, работающих с именами доменов, намного ниже HTTPS, но об этом следует упомянуть здесь, так как всё зависит от доменов и IP-адресов. +Часть про доменное имя относится к этапам задолго до HTTPS, но так как всё зависит от домена и IP‑адреса, здесь стоит это упомянуть. /// -### DNS +### DNS { #dns } -Теперь давайте сфокусируемся на работе с HTTPS. +Теперь сфокусируемся на собственно частях, связанных с HTTPS. -Всё начинается с того, что браузер спрашивает у **DNS-серверов**, какой **IP-адрес связан с доменом**, для примера возьмём домен `someapp.example.com`. +Сначала браузер спросит у **DNS‑серверов**, какой **IP соответствует домену**, в нашем примере `someapp.example.com`. -DNS-сервера присылают браузеру определённый **IP-адрес**, тот самый публичный IP-адрес вашего сервера, который вы указали в ресурсной "записи А" при настройке. +DNS‑серверы ответят браузеру, какой **конкретный IP‑адрес** использовать. Это будет публичный IP‑адрес вашего сервера, который вы указали в настройках DNS. - + -### Рукопожатие TLS +### Начало TLS-рукопожатия { #tls-handshake-start } -В дальнейшем браузер будет взаимодействовать с этим IP-адресом через **port 443** (общепринятый номер порта для HTTPS). +Далее браузер будет общаться с этим IP‑адресом на **порту 443** (порт HTTPS). -Первым шагом будет установление соединения между клиентом (браузером) и сервером и выбор криптографического ключа (для шифрования). +Первая часть взаимодействия — установить соединение между клиентом и сервером и договориться о криптографических ключах и т.п. - + -Эта часть клиент-серверного взаимодействия устанавливает TLS-соединение и называется **TLS-рукопожатием**. +Это взаимодействие клиента и сервера для установления TLS‑соединения называется **TLS‑рукопожатием**. -### TLS с расширением SNI +### TLS с расширением SNI { #tls-with-sni-extension } -На сервере **только один процесс** может прослушивать определённый **порт** определённого **IP-адреса**. На сервере могут быть и другие процессы, слушающие другие порты этого же IP-адреса, но никакой процесс не может быть привязан к уже занятой комбинации IP-адрес:порт. Эта комбинация называется "сокет". +На сервере **только один процесс** может слушать конкретный **порт** на конкретном **IP‑адресе**. Могут быть другие процессы, слушающие другие порты на том же IP‑адресе, но не более одного процесса на каждую комбинацию IP‑адреса и порта. -По умолчанию TLS (HTTPS) использует порт `443`. Потому этот же порт будем использовать и мы. +По умолчанию TLS (HTTPS) использует порт `443`. Значит, он нам и нужен. -И раз уж только один процесс может слушать этот порт, то это будет процесс **прокси-сервера завершения работы TLS**. +Так как только один процесс может слушать этот порт, делать это будет **прокси‑сервер TLS-терминации**. -Прокси-сервер завершения работы TLS будет иметь доступ к одному или нескольким **TLS-сертификатам** (сертификаты HTTPS). +У прокси‑сервера TLS-терминации будет доступ к одному или нескольким **TLS‑сертификатам** (HTTPS‑сертификатам). -Используя **расширение SNI** упомянутое выше, прокси-сервер из имеющихся сертификатов TLS (HTTPS) выберет тот, который соответствует имени домена, указанному в запросе от клиента. +Используя **расширение SNI**, упомянутое выше, прокси‑сервер TLS-терминации определит, какой из доступных TLS (HTTPS)‑сертификатов нужно использовать для этого соединения, выбрав тот, который соответствует домену, ожидаемому клиентом. -То есть будет выбран сертификат для домена `someapp.example.com`. +В нашем случае это будет сертификат для `someapp.example.com`. - + -Клиент уже **доверяет** тому, кто выдал этот TLS-сертификат (в нашем случае - Let's Encrypt, но мы ещё обсудим это), потому может **проверить**, действителен ли полученный от сервера сертификат. +Клиент уже **доверяет** организации, выдавшей этот TLS‑сертификат (в нашем случае — Let's Encrypt, но об этом позже), поэтому может **проверить**, что сертификат действителен. -Затем, используя этот сертификат, клиент и прокси-сервер **выбирают способ шифрования** данных для устанавливаемого **TCP-соединения**. На этом операция **TLS-рукопожатия** завершена. +Затем, используя сертификат, клиент и прокси‑сервер TLS-терминации **договариваются о способе шифрования** остальной **TCP‑коммуникации**. На этом **TLS‑рукопожатие** завершено. -В дальнейшем клиент и сервер будут взаимодействовать по **зашифрованному TCP-соединению**, как предлагается в протоколе TLS. И на основе этого TCP-соедениния будет создано **HTTP-соединение**. +После этого у клиента и сервера есть **зашифрованное TCP‑соединение** — это и предоставляет TLS. И они могут использовать это соединение, чтобы начать собственно **HTTP‑обмен**. -Таким образом, **HTTPS** это тот же **HTTP**, но внутри **безопасного TLS-соединения** вместо чистого (незашифрованного) TCP-соединения. +Собственно, **HTTPS** — это обычный **HTTP** внутри **защищённого TLS‑соединения**, вместо чистого (незашифрованного) TCP‑соединения. -/// tip | "Заметка" +/// tip | Совет -Обратите внимание, что шифрование происходит на **уровне TCP**, а не на более высоком уровне HTTP. +Обратите внимание, что шифрование обмена происходит на **уровне TCP**, а не на уровне HTTP. /// -### HTTPS-запрос +### HTTPS‑запрос { #https-request } -Теперь, когда между клиентом и сервером (в нашем случае, браузером и прокси-сервером) создано **зашифрованное TCP-соединение**, они могут начать **обмен данными по протоколу HTTP**. +Теперь, когда у клиента и сервера (конкретно у браузера и прокси‑сервера TLS-терминации) есть **зашифрованное TCP‑соединение**, они могут начать **HTTP‑обмен**. -Так клиент отправляет **HTTPS-запрос**. То есть обычный HTTP-запрос, но через зашифрованное TLS-содинение. +Клиент отправляет **HTTPS‑запрос**. Это обычный HTTP‑запрос через зашифрованное TLS‑соединение. - + -### Расшифровка запроса +### Расшифровка запроса { #decrypt-the-request } -Прокси-сервер, используя согласованный с клиентом ключ, расшифрует полученный **зашифрованный запрос** и передаст **обычный (незашифрованный) HTTP-запрос** процессу, запускающему приложение (например, процессу Uvicorn запускающему приложение FastAPI). +Прокси‑сервер TLS-терминации использует согласованное шифрование, чтобы **расшифровать запрос**, и передаёт **обычный (расшифрованный) HTTP‑запрос** процессу, запускающему приложение (например, процессу с Uvicorn, который запускает приложение FastAPI). - + -### HTTP-ответ +### HTTP‑ответ { #http-response } -Приложение обработает запрос и вернёт **обычный (незашифрованный) HTTP-ответ** прокси-серверу. +Приложение обработает запрос и отправит **обычный (незашифрованный) HTTP‑ответ** прокси‑серверу TLS-терминации. - + -### HTTPS-ответ +### HTTPS‑ответ { #https-response } -Пркоси-сервер **зашифрует ответ** используя ранее согласованный с клиентом способ шифрования (которые содержатся в сертификате для домена `someapp.example.com`) и отправит его браузеру. +Затем прокси‑сервер TLS-терминации **зашифрует ответ** с использованием ранее согласованного способа шифрования (который начали использовать для сертификата для `someapp.example.com`) и отправит его обратно в браузер. -Наконец, браузер проверит ответ, в том числе, что тот зашифрован с нужным ключом, **расшифрует его** и обработает. +Далее браузер проверит, что ответ корректен и зашифрован правильным криптографическим ключом и т.п., затем **расшифрует ответ** и обработает его. - + -Клиент (браузер) знает, что ответ пришёл от правильного сервера, так как использует методы шифрования, согласованные ими раннее через **HTTPS-сертификат**. +Клиент (браузер) узнает, что ответ пришёл от правильного сервера, потому что используется способ шифрования, о котором они договорились ранее с помощью **HTTPS‑сертификата**. -### Множество приложений +### Несколько приложений { #multiple-applications } -На одном и том же сервере (или серверах) можно разместить **множество приложений**, например, другие программы с API или базы данных. +На одном и том же сервере (или серверах) могут работать **несколько приложений**, например другие программы с API или база данных. -Напомню, что только один процесс (например, прокси-сервер) может прослушивать определённый порт определённого IP-адреса. -Но другие процессы и приложения тоже могут работать на этом же сервере (серверах), если они не пытаются использовать уже занятую **комбинацию IP-адреса и порта** (сокет). +Только один процесс может обрабатывать конкретную комбинацию IP и порта (в нашем примере — прокси‑сервер TLS-терминации), но остальные приложения/процессы тоже могут работать на сервере(ах), пока они не пытаются использовать ту же **комбинацию публичного IP и порта**. - + -Таким образом, сервер завершения TLS может обрабатывать HTTPS-запросы и использовать сертификаты для **множества доменов** или приложений и передавать запросы правильным адресатам (другим приложениям). +Таким образом, прокси‑сервер TLS-терминации может обрабатывать HTTPS и сертификаты для **нескольких доменов** (для нескольких приложений), а затем передавать запросы нужному приложению в каждом случае. -### Обновление сертификата +### Продление сертификата { #certificate-renewal } -В недалёком будущем любой сертификат станет **просроченным** (примерно через три месяца после получения). +Со временем каждый сертификат **истечёт** (примерно через 3 месяца после получения). -Когда это произойдёт, можно запустить другую программу, которая подключится к Let's Encrypt и обновит сертификат(ы). Существуют прокси-серверы, которые могут сделать это действие самостоятельно. +Затем будет другая программа (иногда это отдельная программа, иногда — тот же прокси‑сервер TLS-терминации), которая свяжется с Let's Encrypt и продлит сертификат(ы). - + -**TLS-сертификаты** не привязаны к IP-адресу, но **связаны с именем домена**. +**TLS‑сертификаты** **связаны с именем домена**, а не с IP‑адресом. -Так что при обновлении сертификатов программа должна **подтвердить** центру сертификации (Let's Encrypt), что обновление запросил **"владелец", который контролирует этот домен**. +Поэтому, чтобы продлить сертификаты, программа продления должна **доказать** удостоверяющему центру (Let's Encrypt), что она действительно **«владеет» и контролирует этот домен**. -Есть несколько путей осуществления этого. Самые популярные из них: +Для этого, учитывая разные потребности приложений, есть несколько способов. Популярные из них: -* **Изменение записей DNS**. - * Для такого варианта Ваша программа обновления должна уметь работать с API DNS-провайдера, обслуживающего Ваши DNS-записи. Не у всех провайдеров есть такой API, так что этот способ не всегда применим. -* **Запуск в качестве программы-сервера** (как минимум, на время обновления сертификатов) на публичном IP-адресе домена. - * Как уже не раз упоминалось, только один процесс может прослушивать определённый порт определённого IP-адреса. - * Это одна из причин использования прокси-сервера ещё и в качестве программы обновления сертификатов. - * В случае, если обновлением сертификатов занимается другая программа, вам понадобится остановить прокси-сервер, запустить программу обновления сертификатов на сокете, предназначенном для прокси-сервера, настроить прокси-сервер на работу с новыми сертификатами и перезапустить его. Эта схема далека от идеальной, так как Ваши приложения будут недоступны на время отключения прокси-сервера. +* **Изменить некоторые DNS‑записи**. + * Для этого программа продления должна поддерживать API DNS‑провайдера, поэтому, в зависимости от используемого провайдера DNS, этот вариант может быть доступен или нет. +* **Запуститься как сервер** (как минимум на время получения сертификатов) на публичном IP‑адресе, связанном с доменом. + * Как сказано выше, только один процесс может слушать конкретный IP и порт. + * Это одна из причин, почему очень удобно, когда тот же прокси‑сервер TLS-терминации также занимается процессом продления сертификатов. + * В противном случае вам, возможно, придётся временно остановить прокси‑сервер TLS-терминации, запустить программу продления для получения сертификатов, затем настроить их в прокси‑сервере TLS-терминации и перезапустить его. Это не идеально, так как ваше приложение(я) будут недоступны, пока прокси‑сервер TLS-терминации остановлен. -Весь этот процесс обновления, одновременный с обслуживанием запросов, является одной из основных причин, по которой желательно иметь **отдельную систему для работы с HTTPS** в виде прокси-сервера завершения TLS, а не просто использовать сертификаты TLS непосредственно с сервером приложений (например, Uvicorn). +Весь этот процесс продления, совмещённый с обслуживанием приложения, — одна из главных причин иметь **отдельную систему для работы с HTTPS** в виде прокси‑сервера TLS-терминации, вместо использования TLS‑сертификатов напрямую в сервере приложения (например, Uvicorn). -## Резюме +## Пересылаемые HTTP-заголовки прокси { #proxy-forwarded-headers } -Наличие **HTTPS** очень важно и довольно **критично** в большинстве случаев. Однако, Вам, как разработчику, не нужно тратить много сил на это, достаточно **понимать эти концепции** и принципы их работы. +Когда вы используете прокси для обработки HTTPS, ваш **сервер приложения** (например, Uvicorn через FastAPI CLI) ничего не знает о процессе HTTPS, он общается обычным HTTP с **прокси‑сервером TLS-терминации**. -Но узнав базовые основы **HTTPS** вы можете легко совмещать разные инструменты, которые помогут вам в дальнейшей разработке. +Обычно этот **прокси** на лету добавляет некоторые HTTP‑заголовки перед тем, как переслать запрос на **сервер приложения**, чтобы тот знал, что запрос был **проксирован**. -В следующих главах я покажу вам несколько примеров, как настраивать **HTTPS** для приложений **FastAPI**. 🔒 +/// note | Технические детали + +Заголовки прокси: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +Тем не менее, так как **сервер приложения** не знает, что он находится за доверенным **прокси**, по умолчанию он не будет доверять этим заголовкам. + +Но вы можете настроить **сервер приложения**, чтобы он доверял *пересылаемым* заголовкам, отправленным **прокси**. Если вы используете FastAPI CLI, вы можете использовать *опцию CLI* `--forwarded-allow-ips`, чтобы указать, с каких IP‑адресов следует доверять этим *пересылаемым* заголовкам. + +Например, если **сервер приложения** получает запросы только от доверенного **прокси**, вы можете установить `--forwarded-allow-ips="*"`, чтобы доверять всем входящим IP, так как он всё равно будет получать запросы только с IP‑адреса, используемого **прокси**. + +Таким образом, приложение сможет знать свой публичный URL, использует ли оно HTTPS, какой домен и т.п. + +Это будет полезно, например, для корректной обработки редиректов. + +/// tip | Совет + +Подробнее об этом вы можете узнать в документации: [За прокси — Включить пересылаемые заголовки прокси](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} + +/// + +## Резюме { #recap } + +Наличие **HTTPS** очень важно и во многих случаях довольно **критично**. Большая часть усилий, которые вам, как разработчику, нужно приложить вокруг HTTPS, — это просто **понимание этих концепций** и того, как они работают. + +Зная базовую информацию о **HTTPS для разработчиков**, вы сможете легко комбинировать и настраивать разные инструменты, чтобы управлять всем этим простым способом. + +В некоторых из следующих глав я покажу вам несколько конкретных примеров настройки **HTTPS** для приложений **FastAPI**. 🔒 diff --git a/docs/ru/docs/deployment/index.md b/docs/ru/docs/deployment/index.md index e88ddc3e2c..c85fa0d529 100644 --- a/docs/ru/docs/deployment/index.md +++ b/docs/ru/docs/deployment/index.md @@ -1,16 +1,16 @@ -# Развёртывание +# Развёртывание { #deployment } Развернуть приложение **FastAPI** довольно просто. -## Да что такое это ваше - "развёртывание"?! +## Что означает развёртывание { #what-does-deployment-mean } Термин **развёртывание** (приложения) означает выполнение необходимых шагов, чтобы сделать приложение **доступным для пользователей**. -Обычно **веб-приложения** размещают на удалённом компьютере с серверной программой, которая обеспечивает хорошую производительность, стабильность и т. д., Чтобы ваши пользователи могли эффективно, беспрерывно и беспроблемно обращаться к приложению. +Для **веб-API** это обычно означает размещение его на **удалённой машине** с **серверной программой**, обеспечивающей хорошую производительность, стабильность и т.д., чтобы ваши **пользователи** могли **получать доступ** к приложению эффективно и без перебоев или проблем. -Это отличается от **разработки**, когда вы постоянно меняете код, делаете в нём намеренные ошибки и исправляете их, останавливаете и перезапускаете сервер разработки и т. д. +Это отличается от этапов **разработки**, когда вы постоянно меняете код, ломаете его и исправляете, останавливаете и перезапускаете сервер разработки и т.д. -## Стратегии развёртывания +## Стратегии развёртывания { #deployment-strategies } В зависимости от вашего конкретного случая, есть несколько способов сделать это. diff --git a/docs/ru/docs/deployment/manually.md b/docs/ru/docs/deployment/manually.md index 78363cef88..93287372a2 100644 --- a/docs/ru/docs/deployment/manually.md +++ b/docs/ru/docs/deployment/manually.md @@ -1,33 +1,82 @@ -# Запуск сервера вручную - Uvicorn +# Запуск сервера вручную { #run-a-server-manually } -Для запуска приложения **FastAPI** на удалённой серверной машине вам необходим программный сервер, поддерживающий протокол ASGI, такой как **Uvicorn**. +## Используйте команду `fastapi run` { #use-the-fastapi-run-command } -Существует три наиболее распространённые альтернативы: +Коротко: используйте `fastapi run`, чтобы запустить ваше приложение FastAPI: -* Uvicorn: высокопроизводительный ASGI сервер. -* Hypercorn: ASGI сервер, помимо прочего поддерживающий HTTP/2 и Trio. -* Daphne: ASGI сервер, созданный для Django Channels. +
-## Сервер как машина и сервер как программа +```console +$ fastapi run main.py -В этих терминах есть некоторые различия и вам следует запомнить их. 💡 + FastAPI Starting production server 🚀 -Слово "**сервер**" чаще всего используется в двух контекстах: + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp -- удалённый или расположенный в "облаке" компьютер (физическая или виртуальная машина). -- программа, запущенная на таком компьютере (например, Uvicorn). + module 🐍 main.py -Просто запомните, если вам встретился термин "сервер", то обычно он подразумевает что-то из этих двух смыслов. + code Importing the FastAPI app object from the module with + the following code: -Когда имеют в виду именно удалённый компьютер, часто говорят просто **сервер**, но ещё его называют **машина**, **ВМ** (виртуальная машина), **нода**. Все эти термины обозначают одно и то же - удалённый компьютер, обычно под управлением Linux, на котором вы запускаете программы. + from main import app -## Установка программного сервера + app Using import string: main:app -Вы можете установить сервер, совместимый с протоколом ASGI, так: + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs -//// tab | Uvicorn + Logs: -* Uvicorn, очень быстрый ASGI сервер, основанный на библиотеках uvloop и httptools. + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +В большинстве случаев этого достаточно. 😎 + +Этой командой, например, можно запускать приложение **FastAPI** в контейнере, на сервере и т.д. + +## ASGI‑серверы { #asgi-servers } + +Давайте немного углубимся в детали. + +FastAPI использует стандарт для построения Python‑веб‑фреймворков и серверов под названием ASGI. FastAPI — ASGI-веб‑фреймворк. + +Главное, что вам нужно, чтобы запустить приложение **FastAPI** (или любое другое ASGI‑приложение) на удалённой серверной машине, — это программа ASGI‑сервера, такая как **Uvicorn**; именно он используется по умолчанию в команде `fastapi`. + +Есть несколько альтернатив, например: + +* Uvicorn: высокопроизводительный ASGI‑сервер. +* Hypercorn: ASGI‑сервер, среди прочего совместимый с HTTP/2 и Trio. +* Daphne: ASGI‑сервер, созданный для Django Channels. +* Granian: HTTP‑сервер на Rust для Python‑приложений. +* NGINX Unit: NGINX Unit — лёгкая и многофункциональная среда выполнения веб‑приложений. + +## Сервер как машина и сервер как программа { #server-machine-and-server-program } + +Есть небольшой нюанс в терминологии, о котором стоит помнить. 💡 + +Слово «сервер» обычно используют и для обозначения удалённого/облачного компьютера (физической или виртуальной машины), и для программы, работающей на этой машине (например, Uvicorn). + +Имейте в виду, что слово «сервер» в целом может означать любое из этих двух. + +Когда речь идёт об удалённой машине, её зачастую называют **сервер**, а также **машина**, **VM** (виртуальная машина), **нода**. Всё это — варианты названия удалённой машины, обычно под управлением Linux, на которой вы запускаете программы. + +## Установка серверной программы { #install-the-server-program } + +При установке FastAPI он поставляется с продакшн‑сервером Uvicorn, и вы можете запустить его командой `fastapi run`. + +Но вы также можете установить ASGI‑сервер вручную. + +Создайте [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и затем установите серверное приложение. + +Например, чтобы установить Uvicorn:
@@ -39,39 +88,21 @@ $ pip install "uvicorn[standard]"
-/// tip | "Подсказка" +Аналогично устанавливаются и другие ASGI‑серверы. -С опцией `standard`, Uvicorn будет устанавливаться и использоваться с некоторыми дополнительными рекомендованными зависимостями. +/// tip | Совет -В них входит `uvloop`, высокопроизводительная замена `asyncio`, которая значительно ускоряет работу асинхронных программ. +С добавлением `standard` Uvicorn установит и будет использовать ряд рекомендованных дополнительных зависимостей. + +В их числе `uvloop` — высокопроизводительная замена `asyncio`, дающая серьёзный прирост производительности при параллельной работе. + +Если вы устанавливаете FastAPI, например так: `pip install "fastapi[standard]"`, вы уже получаете и `uvicorn[standard]`. /// -//// +## Запуск серверной программы { #run-the-server-program } -//// tab | Hypercorn - -* Hypercorn, ASGI сервер, поддерживающий протокол HTTP/2. - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...или какой-либо другой ASGI сервер. - -//// - -## Запуск серверной программы - -Затем запустите ваше приложение так же, как было указано в руководстве ранее, но без опции `--reload`: - -//// tab | Uvicorn +Если вы установили ASGI‑сервер вручную, обычно нужно передать строку импорта в специальном формате, чтобы он смог импортировать ваше приложение FastAPI:
@@ -83,81 +114,44 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
-//// +/// note | Примечание -//// tab | Hypercorn +Команда `uvicorn main:app` означает: -
+* `main`: файл `main.py` (Python‑«модуль»). +* `app`: объект, созданный в `main.py` строкой `app = FastAPI()`. -```console -$ hypercorn main:app --bind 0.0.0.0:80 +Эквивалентно: -Running on 0.0.0.0:8080 over http (CTRL + C to quit) +```Python +from main import app ``` -
- -//// - -/// warning | "Предупреждение" - -Не забудьте удалить опцию `--reload`, если ранее пользовались ею. - -Включение опции `--reload` требует дополнительных ресурсов, влияет на стабильность работы приложения и может повлечь прочие неприятности. - -Она сильно помогает во время **разработки**, но **не следует** использовать её при **реальной работе** приложения. - /// -## Hypercorn с Trio +У каждого альтернативного ASGI‑сервера будет похожая команда; подробнее см. в их документации. -Starlette и **FastAPI** основаны на AnyIO, которая делает их совместимыми как с asyncio - стандартной библиотекой Python, так и с Trio. +/// warning | Предупреждение +Uvicorn и другие серверы поддерживают опцию `--reload`, полезную в период разработки. -Тем не менее Uvicorn совместим только с asyncio и обычно используется совместно с `uvloop`, высокопроизводительной заменой `asyncio`. +Опция `--reload` потребляет значительно больше ресурсов, менее стабильна и т.п. -Но если вы хотите использовать **Trio** напрямую, то можете воспользоваться **Hypercorn**, так как они совместимы. ✨ +Она сильно помогает во время **разработки**, но в **продакшн** её использовать **не следует**. -### Установка Hypercorn с Trio +/// -Для начала, вам нужно установить Hypercorn с поддержкой Trio: +## Концепции развёртывания { #deployment-concepts } -
+В этих примерах серверная программа (например, Uvicorn) запускает **один процесс**, слушающий все IP‑адреса (`0.0.0.0`) на заранее заданном порту (например, `80`). -```console -$ pip install "hypercorn[trio]" ----> 100% -``` +Это базовая идея. Но, вероятно, вам понадобится позаботиться и о некоторых дополнительных вещах, например: -
+* Безопасность — HTTPS +* Запуск при старте системы +* Перезапуски +* Репликация (количество запущенных процессов) +* Память +* Предварительные шаги перед запуском -### Запуск с 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/server-workers.md b/docs/ru/docs/deployment/server-workers.md new file mode 100644 index 0000000000..e756ab3774 --- /dev/null +++ b/docs/ru/docs/deployment/server-workers.md @@ -0,0 +1,139 @@ +# Серверные воркеры — Uvicorn с воркерами { #server-workers-uvicorn-with-workers } + +Давайте снова вспомним те концепции деплоя, о которых говорили ранее: + +* Безопасность — HTTPS +* Запуск при старте +* Перезапуски +* **Репликация (количество запущенных процессов)** +* Память +* Предварительные шаги перед запуском + +До этого момента, следуя руководствам в документации, вы, вероятно, запускали **серверную программу**, например с помощью команды `fastapi`, которая запускает Uvicorn в **одном процессе**. + +При деплое приложения вам, скорее всего, захочется использовать **репликацию процессов**, чтобы задействовать **несколько ядер** и иметь возможность обрабатывать больше запросов. + +Как вы видели в предыдущей главе о [Концепциях деплоя](concepts.md){.internal-link target=_blank}, существует несколько стратегий. + +Здесь я покажу, как использовать **Uvicorn** с **воркер-процессами** через команду `fastapi` или напрямую через команду `uvicorn`. + +/// info | Информация + +Если вы используете контейнеры, например Docker или Kubernetes, я расскажу об этом подробнее в следующей главе: [FastAPI в контейнерах — Docker](docker.md){.internal-link target=_blank}. + +В частности, при запуске в **Kubernetes** вам, скорее всего, **не** понадобится использовать воркеры — вместо этого запускайте **один процесс Uvicorn на контейнер**, но об этом подробнее далее в той главе. + +/// + +## Несколько воркеров { #multiple-workers } + +Можно запустить несколько воркеров с помощью опции командной строки `--workers`: + +//// tab | `fastapi` + +Если вы используете команду `fastapi`: + +
+ +```console +$ fastapi run --workers 4 main.py + + FastAPI Starting production server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. +``` + +
+ +//// + +//// tab | `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 воркер-процесса. + +Также видно, что выводится **PID** каждого процесса: `27365` — для родительского процесса (это **менеджер процессов**) и по одному для каждого воркер-процесса: `27368`, `27369`, `27370` и `27367`. + +## Концепции деплоя { #deployment-concepts } + +Здесь вы увидели, как использовать несколько **воркеров**, чтобы **распараллелить** выполнение приложения, задействовать **несколько ядер** CPU и обслуживать **больше запросов**. + +Из списка концепций деплоя выше использование воркеров в основном помогает с **репликацией**, и немного — с **перезапусками**, но об остальных по-прежнему нужно позаботиться: + +* **Безопасность — HTTPS** +* **Запуск при старте** +* ***Перезапуски*** +* Репликация (количество запущенных процессов) +* **Память** +* **Предварительные шаги перед запуском** + +## Контейнеры и Docker { #containers-and-docker } + +В следующей главе о [FastAPI в контейнерах — Docker](docker.md){.internal-link target=_blank} я объясню стратегии, которые можно использовать для решения остальных **концепций деплоя**. + +Я покажу, как **собрать свой образ с нуля**, чтобы запускать один процесс Uvicorn. Это простой подход и, вероятно, именно то, что вам нужно при использовании распределённой системы управления контейнерами, такой как **Kubernetes**. + +## Резюме { #recap } + +Вы можете использовать несколько воркер-процессов с опцией командной строки `--workers` в командах `fastapi` или `uvicorn`, чтобы задействовать **многоядерные CPU**, запуская **несколько процессов параллельно**. + +Вы можете использовать эти инструменты и идеи, если настраиваете **собственную систему деплоя** и самостоятельно закрываете остальные концепции деплоя. + +Перейдите к следующей главе, чтобы узнать о **FastAPI** в контейнерах (например, Docker и Kubernetes). Вы увидите, что эти инструменты тоже предлагают простые способы решить другие **концепции деплоя**. ✨ diff --git a/docs/ru/docs/deployment/versions.md b/docs/ru/docs/deployment/versions.md index 17b6446d94..58d5aa110d 100644 --- a/docs/ru/docs/deployment/versions.md +++ b/docs/ru/docs/deployment/versions.md @@ -1,50 +1,50 @@ -# О версиях FastAPI +# О версиях FastAPI { #about-fastapi-versions } -**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Однако его разработка все еще продолжается. +**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Но его разработка всё ещё движется быстрыми темпами. Часто добавляются новые функции, регулярно исправляются баги, код продолжает постоянно совершенствоваться. -По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя соглашению о Семантическом Версионировании. +По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя Семантическому версионированию. -Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом. +Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом. -## Закрепите вашу версию `fastapi` +## Закрепите вашу версию `fastapi` { #pin-your-fastapi-version } Первым делом вам следует "закрепить" конкретную последнюю используемую версию **FastAPI**, которая корректно работает с вашим приложением. -Например, в своём приложении вы используете версию `0.45.0`. +Например, в своём приложении вы используете версию `0.112.0`. Если вы используете файл `requirements.txt`, вы можете указать версию следующим способом: ```txt -fastapi==0.45.0 +fastapi[standard]==0.112.0 ``` -это означает, что вы будете использовать именно версию `0.45.0`. +это означает, что вы будете использовать именно версию `0.112.0`. Или вы можете закрепить версию следующим способом: ```txt -fastapi>=0.45.0,<0.46.0 +fastapi[standard]>=0.112.0,<0.113.0 ``` -это значит, что вы используете версии `0.45.0` или выше, но меньше чем `0.46.0`. Например, версия `0.45.2` все еще будет подходить. +это значит, что вы используете версии `0.112.0` или выше, но меньше чем `0.113.0`. Например, версия `0.112.2` всё ещё будет подходить. -Если вы используете любой другой инструмент для управления зависимостями, например Poetry, Pipenv или др., у них у всех имеется способ определения специфической версии для ваших пакетов. +Если вы используете любой другой инструмент для управления установками/зависимостями, например `uv`, Poetry, Pipenv или др., у них у всех имеется способ определения специфической версии для ваших пакетов. -## Доступные версии +## Доступные версии { #available-versions } -Вы можете посмотреть доступные версии (например, проверить последнюю на данный момент) в [примечаниях к выпуску](../release-notes.md){.internal-link target=_blank}. +Вы можете посмотреть доступные версии (например, проверить последнюю на данный момент) в [Примечаниях к выпуску](../release-notes.md){.internal-link target=_blank}. -## О версиях +## О версиях { #about-versions } Следуя соглашению о Семантическом Версионировании, любые версии ниже `1.0.0` потенциально могут добавить обратно несовместимые изменения. FastAPI следует соглашению в том, что любые изменения "ПАТЧ"-версии предназначены для исправления багов и внесения обратно совместимых изменений. -/// tip | "Подсказка" +/// tip | Подсказка -"ПАТЧ" - это последнее число. Например, в `0.2.3`, ПАТЧ-версия - это `3`. +"ПАТЧ" — это последнее число. Например, в `0.2.3`, ПАТЧ-версия — это `3`. /// @@ -56,13 +56,13 @@ fastapi>=0.45.0,<0.46.0 Обратно несовместимые изменения и новые функции добавляются в "МИНОРНЫЕ" версии. -/// tip | "Подсказка" +/// tip | Подсказка -"МИНОРНАЯ" версия - это число в середине. Например, в `0.2.3` МИНОРНАЯ версия - это `2`. +"МИНОРНАЯ" версия — это число в середине. Например, в `0.2.3` МИНОРНАЯ версия — это `2`. /// -## Обновление версий FastAPI +## Обновление версий FastAPI { #upgrading-the-fastapi-versions } Вам следует добавить тесты для вашего приложения. @@ -70,9 +70,9 @@ fastapi>=0.45.0,<0.46.0 После создания тестов вы можете обновить свою версию **FastAPI** до более новой. После этого следует убедиться, что ваш код работает корректно, запустив тесты. -Если все работает корректно, или после внесения необходимых изменений все ваши тесты проходят, только тогда вы можете закрепить вашу новую версию `fastapi`. +Если всё работает корректно, или после внесения необходимых изменений все ваши тесты проходят, только тогда вы можете закрепить вашу новую версию `fastapi`. -## О Starlette +## О Starlette { #about-starlette } Не следует закреплять версию `starlette`. @@ -80,14 +80,14 @@ fastapi>=0.45.0,<0.46.0 Так что решение об используемой версии Starlette, вы можете оставить **FastAPI**. -## О Pydantic +## О Pydantic { #about-pydantic } Pydantic включает свои собственные тесты для **FastAPI**, так что новые версии Pydantic (выше `1.0.0`) всегда совместимы с FastAPI. -Вы можете закрепить любую версию Pydantic, которая вам подходит, выше `1.0.0` и ниже `2.0.0`. +Вы можете закрепить любую версию Pydantic, которая вам подходит, выше `1.0.0`. Например: ```txt -pydantic>=1.2.0,<2.0.0 +pydantic>=2.7.0,<3.0.0 ``` diff --git a/docs/ru/docs/environment-variables.md b/docs/ru/docs/environment-variables.md new file mode 100644 index 0000000000..6291b79d26 --- /dev/null +++ b/docs/ru/docs/environment-variables.md @@ -0,0 +1,298 @@ +# Переменные окружения { #environment-variables } + +/// tip | Совет + +Если вы уже знаете, что такое «переменные окружения» и как их использовать, можете пропустить это. + +/// + +Переменная окружения (также известная как «**env var**») - это переменная, которая живет **вне** кода Python, в **операционной системе**, и может быть прочитана вашим кодом Python (или другими программами). + +Переменные окружения могут быть полезны для работы с **настройками** приложений, как часть **установки** Python и т.д. + +## Создание и использование переменных окружения { #create-and-use-env-vars } + +Можно **создавать** и использовать переменные окружения в **оболочке (терминале)**, не прибегая к помощи Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Вы можете создать переменную окружения MY_NAME с помощью +$ export MY_NAME="Wade Wilson" + +// Затем её можно использовать в других программах, например +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Создайте переменную окружения MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Используйте её с другими программами, например +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Чтение переменных окружения в python { #read-env-vars-in-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: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Здесь мы еще не устанавливаем переменную окружения +$ python main.py + +// Поскольку мы не задали переменную окружения, мы получим значение по умолчанию + +Hello World from Python + +// Но если мы сначала создадим переменную окружения +$ export MY_NAME="Wade Wilson" + +// А затем снова запустим программу +$ python main.py + +// Теперь она прочитает переменную окружения + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Здесь мы еще не устанавливаем переменную окружения +$ python main.py + +// Поскольку мы не задали переменную окружения, мы получим значение по умолчанию + +Hello World from Python + +// Но если мы сначала создадим переменную окружения +$ $Env: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 | Совет + +Подробнее об этом можно прочитать на сайте The Twelve-Factor App: Config. + +/// + +## Типизация и Валидация { #types-and-validation } + +Эти переменные окружения могут работать только с **текстовыми строками**, поскольку они являются внешними по отношению к Python и должны быть совместимы с другими программами и остальной системой (и даже с различными операционными системами, такими как Linux, Windows, macOS). + +Это означает, что **любое значение**, считанное в Python из переменной окружения, **будет `str`**, и любое преобразование к другому типу или любая проверка должны быть выполнены в коде. + +Подробнее об использовании переменных окружения для работы с **настройками приложения** вы узнаете в [Расширенное руководство пользователя - Настройки и переменные среды](./advanced/settings.md){.internal-link target=_blank}. + +## Переменная окружения `PATH` { #path-environment-variable } + +Существует **специальная** переменная окружения **`PATH`**, которая используется операционными системами (Linux, macOS, Windows) для поиска программ для запуска. + +Значение переменной `PATH` - это длинная строка, состоящая из каталогов, разделенных двоеточием `:` в Linux и macOS, и точкой с запятой `;` в Windows. + +Например, переменная окружения `PATH` может выглядеть следующим образом: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Это означает, что система должна искать программы в каталогах: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +Это означает, что система должна искать программы в каталогах: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +Когда вы вводите **команду** в терминале, операционная система **ищет** программу в **каждой из тех директорий**, которые перечислены в переменной окружения `PATH`. + +Например, когда вы вводите `python` в терминале, операционная система ищет программу под названием `python` в **первой директории** в этом списке. + +Если она ее находит, то **использует ее**. В противном случае она продолжает искать в **других каталогах**. + +### Установка Python и обновление `PATH` { #installing-python-and-updating-the-path } + +При установке Python вас могут спросить, нужно ли обновить переменную окружения `PATH`. + +//// tab | Linux, macOS + +Допустим, вы устанавливаете Python, и он оказывается в каталоге `/opt/custompython/bin`. + +Если вы скажете «да», чтобы обновить переменную окружения `PATH`, то программа установки добавит `/opt/custompython/bin` в переменную окружения `PATH`. + +Это может выглядеть следующим образом: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +Таким образом, когда вы набираете `python` в терминале, система найдет программу Python в `/opt/custompython/bin` (последний каталог) и использует ее. + +//// + +//// tab | Windows + +Допустим, вы устанавливаете Python, и он оказывается в каталоге `C:\opt\custompython\bin`. + +Если вы согласитесь обновить переменную окружения `PATH`, то программа установки добавит `C:\opt\custompython\bin` в переменную окружения `PATH`. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +Таким образом, когда вы набираете `python` в терминале, система найдет программу Python в `C:\opt\custompython\bin` (последний каталог) и использует ее. + +//// + +Итак, если вы напечатаете: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +Система **найдет** программу `python` в `/opt/custompython/bin` и запустит ее. + +Это примерно эквивалентно набору текста: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +Система **найдет** программу `python` в каталоге `C:\opt\custompython\bin\python` и запустит ее. + +Это примерно эквивалентно набору текста: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +Эта информация будет полезна при изучении [Виртуальных окружений](virtual-environments.md){.internal-link target=_blank}. + +## Вывод { #conclusion } + +Благодаря этому вы должны иметь базовое представление о том, что такое **переменные окружения** и как использовать их в Python. + +Подробнее о них вы также можете прочитать в статье о переменных окружения на википедии. + +Во многих случаях не всегда очевидно, как переменные окружения могут быть полезны и применимы. Но они постоянно появляются в различных сценариях разработки, поэтому знать о них полезно. + +Например, эта информация понадобится вам в следующем разделе, посвященном [Виртуальным окружениям](virtual-environments.md). diff --git a/docs/ru/docs/fastapi-cli.md b/docs/ru/docs/fastapi-cli.md new file mode 100644 index 0000000000..72cf55e7b0 --- /dev/null +++ b/docs/ru/docs/fastapi-cli.md @@ -0,0 +1,75 @@ +# FastAPI CLI { #fastapi-cli } + +**FastAPI CLI** это программа командной строки, которую вы можете использовать для запуска вашего FastAPI приложения, для управления FastAPI-проектом, а также для многих других вещей. + +`fastapi-cli` устанавливается вместе со стандартным пакетом FastAPI (при запуске команды `pip install "fastapi[standard]"`). Данный пакет предоставляет доступ к программе `fastapi` через терминал. + +Чтобы запустить приложение FastAPI в режиме разработки, вы можете использовать команду `fastapi dev`: + +
+ +```console +$ fastapi dev main.py + + FastAPI Starting development server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to + quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. +``` + +
+ +Приложение командной строки `fastapi` это и есть **FastAPI CLI**. + +FastAPI CLI берет путь к вашей Python-программе (напр. `main.py`) и автоматически находит объект `FastAPI` (обычно это `app`), затем определяет правильный процесс импорта и запускает сервер приложения. + +Для работы в режиме продакшн вместо `fastapi dev` нужно использовать `fastapi run`. 🚀 + +Внутри **FastAPI CLI** используется Uvicorn, высокопроизводительный, готовый к работе в продакшне ASGI-сервер. 😎 + +## `fastapi dev` { #fastapi-dev } + +Вызов `fastapi dev` запускает режим разработки. + +По умолчанию включена авто-перезагрузка (**auto-reload**), благодаря этому при изменении кода происходит перезагрузка сервера приложения. Эта установка требует значительных ресурсов и делает систему менее стабильной. Используйте её только при разработке. Приложение слушает входящие подключения на IP `127.0.0.1`. Это IP адрес вашей машины, предназначенный для внутренних коммуникаций (`localhost`). + +## `fastapi run` { #fastapi-run } + +Вызов `fastapi run` по умолчанию запускает FastAPI в режиме продакшн. + +По умолчанию авто-перезагрузка (**auto-reload**) отключена. Приложение слушает входящие подключения на IP `0.0.0.0`, т.е. на всех доступных адресах компьютера. Таким образом, приложение будет находиться в публичном доступе для любого, кто может подсоединиться к вашей машине. Продуктовые приложения запускаются именно так, например, с помощью контейнеров. + +В большинстве случаев вы будете (и должны) использовать прокси-сервер ("termination proxy"), который будет поддерживать HTTPS поверх вашего приложения. Всё будет зависеть от того, как вы развертываете приложение: за вас это либо сделает ваш провайдер, либо вам придется сделать настройки самостоятельно. + +/// tip | Подсказка + +Вы можете больше узнать об этом в [документации по развертыванию](deployment/index.md){.internal-link target=_blank}. + +/// diff --git a/docs/ru/docs/features.md b/docs/ru/docs/features.md index 31f245e7a3..703ff951ef 100644 --- a/docs/ru/docs/features.md +++ b/docs/ru/docs/features.md @@ -1,23 +1,21 @@ -# Основные свойства +# Возможности { #features } -## Основные свойства FastAPI +## Возможности FastAPI { #fastapi-features } **FastAPI** предлагает вам следующее: -### Использование открытых стандартов +### Основано на открытых стандартах { #based-on-open-standards } -* OpenAPI для создания API, включая объявления операций пути, параметров, тела запроса, безопасности и т.д. - - -* Автоматическое документирование моделей данных в соответствии с JSON Schema (так как спецификация OpenAPI сама основана на JSON Schema). -* Разработан, придерживаясь этих стандартов, после тщательного их изучения. Эти стандарты изначально включены во фреймфорк, а не являются дополнительной надстройкой. +* OpenAPI для создания API, включая объявления операций пути, параметров, тел запросов, безопасности и т. д. +* Автоматическая документация моделей данных с помощью JSON Schema (так как сама спецификация OpenAPI основана на JSON Schema). +* Разработан вокруг этих стандартов, после тщательного их изучения. Это не дополнительная надстройка поверх. * Это также позволяет использовать автоматическую **генерацию клиентского кода** на многих языках. -### Автоматически генерируемая документация +### Автоматическая документация { #automatic-docs } -Интерактивная документация для API и исследования пользовательских веб-интерфейсов. Поскольку этот фреймворк основан на OpenAPI, существует несколько вариантов документирования, 2 из которых включены по умолчанию. +Интерактивная документация для API и исследовательские веб-интерфейсы. Поскольку фреймворк основан на OpenAPI, существует несколько вариантов документирования, 2 из них включены по умолчанию. -* Swagger UI, с интерактивным взаимодействием, вызывает и тестирует ваш API прямо из браузера. +* Swagger UI, с интерактивным исследованием, вызовом и тестированием вашего API прямо из браузера. ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) @@ -25,22 +23,21 @@ ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Только современный Python +### Только современный Python { #just-modern-python } -Все эти возможности основаны на стандартных **аннотациях типов Python 3.8** (благодаря Pydantic). Не нужно изучать новый синтаксис. Только лишь стандартный современный Python. +Все основано на стандартных **аннотациях типов Python** (благодаря Pydantic). Не нужно изучать новый синтаксис. Только стандартный современный Python. -Если вам нужно освежить знания, как использовать аннотации типов в Python (даже если вы не используете FastAPI), выделите 2 минуты и просмотрите краткое руководство: [Введение в аннотации типов Python¶ -](python-types.md){.internal-link target=_blank}. +Если вам нужно освежить знания о типах в Python (даже если вы не используете FastAPI), выделите 2 минуты и просмотрите краткое руководство: [Типы Python](python-types.md){.internal-link target=_blank}. -Вы пишете на стандартном Python с аннотациями типов: +Вы пишете стандартный Python с типами: ```Python from datetime import date from pydantic import BaseModel -# Объявляем параметр user_id с типом `str` -# и получаем поддержку редактора внутри функции +# Объявляем параметр как `str` +# и получаем поддержку редактора кода внутри функции def main(user_id: str): return user_id @@ -66,21 +63,21 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` -/// info | "Информация" +/// info | Информация `**second_user_data` означает: -Передать ключи и значения словаря `second_user_data`, в качестве аргументов типа "ключ-значение", это эквивалентно: `User(id=4, name="Mary", joined="2018-11-30")` . +Передать ключи и значения словаря `second_user_data` в качестве аргументов "ключ-значение", эквивалентно: `User(id=4, name="Mary", joined="2018-11-30")` /// -### Поддержка редакторов (IDE) +### Поддержка редакторов (IDE) { #editor-support } Весь фреймворк был продуман так, чтобы быть простым и интуитивно понятным в использовании, все решения были проверены на множестве редакторов еще до начала разработки, чтобы обеспечить наилучшие условия при написании кода. -В опросе Python-разработчиков было выяснено, что наиболее часто используемой функцией редакторов, является "автодополнение". +В опросах Python‑разработчиков видно, что одной из самых часто используемых функций является «автозавершение». -Вся структура **FastAPI** основана на удовлетворении этой возможности. Автодополнение работает везде. +Вся структура **FastAPI** основана на удовлетворении этой возможности. Автозавершение работает везде. Вам редко нужно будет возвращаться к документации. @@ -94,23 +91,23 @@ my_second_user: User = User(**second_user_data) ![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) -Вы будете получать автодополнение кода даже там, где вы считали это невозможным раньше. -Как пример, ключ `price` внутри тела JSON (который может быть вложенным), приходящего в запросе. +Вы будете получать автозавершение кода даже там, где вы считали это невозможным раньше. Как пример, ключ `price` внутри тела JSON (который может быть вложенным), приходящего в запросе. -Больше никаких неправильных имён ключей, метания по документации или прокручивания кода вверх и вниз, в попытках узнать - использовали вы ранее `username` или `user_name`. +Больше никаких неправильных имён ключей, метания по документации или прокручивания кода вверх и вниз в попытках узнать — использовали вы ранее `username` или `user_name`. -### Краткость -FastAPI имеет продуманные значения **по умолчанию** для всего, с произвольными настройками везде. Все параметры могут быть тонко подстроены так, чтобы делать то, что вам нужно и определять необходимый вам API. +### Краткость { #short } -Но, по умолчанию, всё это **"и так работает"**. +FastAPI имеет продуманные значения **по умолчанию** для всего, с опциональными настройками везде. Все параметры могут быть тонко подстроены так, чтобы делать то, что вам нужно, и определять необходимый вам API. -### Проверка значений +Но по умолчанию всё **«просто работает»**. -* Проверка значений для большинства (или всех?) **типов данных** Python, включая: +### Проверка значений { #validation } + +* Проверка значений для большинства (или всех?) **типов данных** Python, включая: * Объекты JSON (`dict`). - * Массивы JSON (`list`) с установленными типами элементов. + * Массив JSON (`list`) с определёнными типами элементов. * Строковые (`str`) поля с ограничением минимальной и максимальной длины. - * Числа (`int`, `float`) с минимальными и максимальными значениями и т.п. + * Числа (`int`, `float`) с минимальными и максимальными значениями и т. п. * Проверка для более экзотических типов, таких как: * URL. @@ -118,11 +115,11 @@ FastAPI имеет продуманные значения **по умолчан * UUID. * ...и другие. -Все проверки обрабатываются хорошо зарекомендовавшим себя и надежным **Pydantic**. +Все проверки обрабатываются хорошо зарекомендовавшим себя и надёжным **Pydantic**. -### Безопасность и аутентификация +### Безопасность и аутентификация { #security-and-authentication } -Встроеные функции безопасности и аутентификации. Без каких-либо компромиссов с базами данных или моделями данных. +Встроенные функции безопасности и аутентификации. Без каких‑либо компромиссов с базами данных или моделями данных. Все схемы безопасности, определённые в OpenAPI, включая: @@ -137,68 +134,68 @@ FastAPI имеет продуманные значения **по умолчан Все инструменты и компоненты спроектированы для многократного использования и легко интегрируются с вашими системами, хранилищами данных, реляционными и NoSQL базами данных и т. д. -### Внедрение зависимостей +### Внедрение зависимостей { #dependency-injection } FastAPI включает в себя чрезвычайно простую в использовании, но чрезвычайно мощную систему Внедрения зависимостей. -* Даже зависимости могут иметь зависимости, создавая иерархию или **"графы" зависимостей**. +* Даже зависимости могут иметь зависимости, создавая иерархию или **«граф» зависимостей**. * Всё **автоматически обрабатывается** фреймворком. * Все зависимости могут запрашивать данные из запросов и **дополнять операции пути** ограничениями и автоматической документацией. -* **Автоматическая проверка** даже для параметров *операций пути*, определенных в зависимостях. -* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т.д. -* **Никаких компромиссов** с базами данных, интерфейсами и т.д. Но легкая интеграция со всеми ними. +* **Автоматическая проверка** даже для параметров *операций пути*, определённых в зависимостях. +* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т. д. +* **Никаких компромиссов** с базами данных, интерфейсами и т. д. Но при этом — лёгкая интеграция со всеми ними. -### Нет ограничений на "Плагины" +### Нет ограничений на "Плагины" { #unlimited-plug-ins } -Или, другими словами, нет сложностей с ними, импортируйте и используйте нужный вам код. +Или, другими словами, нет необходимости в них — просто импортируйте и используйте нужный вам код. -Любая интеграция разработана настолько простой в использовании (с зависимостями), что вы можете создать "плагин" для своего приложения в пару строк кода, используя ту же структуру и синтаксис, что и для ваших *операций пути*. +Любая интеграция разработана настолько простой в использовании (с зависимостями), что вы можете создать «плагин» для своего приложения в пару строк кода, используя ту же структуру и синтаксис, что и для ваших *операций пути*. -### Проверен +### Проверен { #tested } -* 100% покрытие тестами. -* 100% аннотирование типов в кодовой базе. -* Используется в реально работающих приложениях. +* 100% покрытие тестами. +* 100% аннотирование типов в кодовой базе. +* Используется в продакшн‑приложениях. -## Основные свойства Starlette +## Возможности Starlette { #starlette-features } -**FastAPI** основан на Starlette и полностью совместим с ним. Так что, любой дополнительный код Starlette, который у вас есть, будет также работать. +**FastAPI** основан на Starlette и полностью совместим с ним. Так что любой дополнительный код Starlette, который у вас есть, также будет работать. -На самом деле, `FastAPI` - это класс, унаследованный от `Starlette`. Таким образом, если вы уже знаете или используете Starlette, большая часть функционала будет работать так же. +На самом деле, `FastAPI` — это подкласс `Starlette`. Таким образом, если вы уже знаете или используете Starlette, большая часть функционала будет работать так же. -С **FastAPI** вы получаете все возможности **Starlette** (так как FastAPI это всего лишь Starlette на стероидах): +С **FastAPI** вы получаете все возможности **Starlette** (так как FastAPI — это всего лишь Starlette на стероидах): -* Серьёзно впечатляющая производительность. Это один из самых быстрых фреймворков на Python, наравне с приложениями использующими **NodeJS** или **Go**. +* Серьёзно впечатляющая производительность. Это один из самых быстрых фреймворков на Python, наравне с **NodeJS** и **Go**. * Поддержка **WebSocket**. -* Фоновые задачи для процессов. +* Фоновые задачи в том же процессе. * События запуска и выключения. -* Тестовый клиент построен на библиотеке HTTPX. +* Тестовый клиент построен на HTTPX. * **CORS**, GZip, статические файлы, потоковые ответы. * Поддержка **сессий и cookie**. * 100% покрытие тестами. * 100% аннотирование типов в кодовой базе. -## Особенности и возможности Pydantic +## Возможности Pydantic { #pydantic-features } -**FastAPI** основан на Pydantic и полностью совместим с ним. Так что, любой дополнительный код Pydantic, который у вас есть, будет также работать. +**FastAPI** полностью совместим с (и основан на) Pydantic. Поэтому любой дополнительный код Pydantic, который у вас есть, также будет работать. -Включая внешние библиотеки, также основанные на Pydantic, такие как: ORM'ы, ODM'ы для баз данных. +Включая внешние библиотеки, также основанные на Pydantic, такие как ORM’ы, ODM’ы для баз данных. Это также означает, что во многих случаях вы можете передавать тот же объект, который получили из запроса, **непосредственно в базу данных**, так как всё проверяется автоматически. И наоборот, во многих случаях вы можете просто передать объект, полученный из базы данных, **непосредственно клиенту**. -С **FastAPI** вы получаете все возможности **Pydantic** (так как, FastAPI основан на Pydantic, для обработки данных): +С **FastAPI** вы получаете все возможности **Pydantic** (так как FastAPI основан на Pydantic для обработки данных): -* **Никакой нервотрёпки** : - * Не нужно изучать новых схем в микроязыках. - * Если вы знаете аннотации типов в Python, вы знаете, как использовать Pydantic. -* Прекрасно сочетается с вашими **IDE/linter/мозгом**: - * Потому что структуры данных pydantic - это всего лишь экземпляры классов, определённых вами. Автодополнение, проверка кода, mypy и ваша интуиция - всё будет работать с вашими проверенными данными. -* Проверка **сложных структур**: - * Использование иерархических моделей Pydantic; `List`, `Dict` и т.п. из модуля `typing` (входит в стандартную библиотеку Python). - * Валидаторы позволяют четко и легко определять, проверять и документировать сложные схемы данных в виде JSON Schema. - * У вас могут быть глубоко **вложенные объекты JSON** и все они будут проверены и аннотированы. +* **Никакой нервотрёпки**: + * Не нужно изучать новые схемы в микроязыках. + * Если вы знаете типы в Python, вы знаете, как использовать Pydantic. +* Прекрасно сочетается с вашим **IDE/linter/мозгом**: + * Потому что структуры данных pydantic — это всего лишь экземпляры классов, определённых вами; автозавершение, проверка кода, mypy и ваша интуиция — всё будет работать с вашими валидированными данными. +* Валидация **сложных структур**: + * Использование иерархических моделей Pydantic; `List`, `Dict` и т. п. из модуля `typing` (входит в стандартную библиотеку Python). + * Валидаторы позволяют чётко и легко определять, проверять и документировать сложные схемы данных в виде JSON Schema. + * У вас могут быть глубоко **вложенные объекты JSON**, и все они будут проверены и аннотированы. * **Расширяемость**: - * Pydantic позволяет определять пользовательские типы данных или расширять проверку методами модели, с помощью проверочных декораторов. + * Pydantic позволяет определять пользовательские типы данных или расширять проверку методами модели с помощью декораторов валидаторов. * 100% покрытие тестами. diff --git a/docs/ru/docs/help-fastapi.md b/docs/ru/docs/help-fastapi.md index fa82008171..6bfabb96cb 100644 --- a/docs/ru/docs/help-fastapi.md +++ b/docs/ru/docs/help-fastapi.md @@ -1,261 +1,255 @@ -# Помочь FastAPI - Получить помощь +# Помочь FastAPI - Получить помощь { #help-fastapi-get-help } Нравится ли Вам **FastAPI**? -Хотели бы Вы помочь FastAPI, его пользователям и автору? +Хотели бы Вы помочь FastAPI, другим пользователям и автору? -Может быть у Вас возникли трудности с **FastAPI** и Вам нужна помощь? +Или Вы хотите получить помощь по **FastAPI**? -Есть несколько очень простых способов оказания помощи (иногда достаточно всего лишь одного или двух кликов). +Есть несколько очень простых способов помочь (иногда достаточно всего лишь одного-двух кликов). И также есть несколько способов получить помощь. -## Подписаться на новостную рассылку +## Подписаться на новостную рассылку { #subscribe-to-the-newsletter } Вы можете подписаться на редкую [новостную рассылку **FastAPI и его друзья**](newsletter.md){.internal-link target=_blank} и быть в курсе о: * Новостях о FastAPI и его друзьях 🚀 * Руководствах 📝 * Возможностях ✨ -* Исправлениях 🚨 +* Ломающих изменениях 🚨 * Подсказках и хитростях ✅ -## Подписаться на FastAPI в Twitter +## Подписаться на FastAPI в X (Twitter) { #follow-fastapi-on-x-twitter } -Подписаться на @fastapi в **Twitter** для получения наисвежайших новостей о **FastAPI**. 🐦 +Подписаться на @fastapi в **X (Twitter)** для получения наисвежайших новостей о **FastAPI**. 🐦 -## Добавить **FastAPI** звезду на GitHub +## Добавить **FastAPI** звезду на GitHub { #star-fastapi-in-github } -Вы можете добавить FastAPI "звезду" на GitHub (кликнуть на кнопку звезды в верхнем правом углу экрана): https://github.com/fastapi/fastapi. ⭐️ +Вы можете добавить FastAPI "звезду" на GitHub (кликнув на кнопку звезды в правом верхнем углу): https://github.com/fastapi/fastapi. ⭐️ -Чем больше звёзд, тем легче другим пользователям найти нас и увидеть, что проект уже стал полезным для многих. +Чем больше звёзд, тем легче другим пользователям найти проект и увидеть, что он уже оказался полезным для многих. -## Отслеживать свежие выпуски в репозитории на GitHub +## Отслеживать свежие выпуски в репозитории на GitHub { #watch-the-github-repository-for-releases } -Вы можете "отслеживать" FastAPI на GitHub (кликните по кнопке "watch" наверху справа): https://github.com/fastapi/fastapi. 👀 +Вы можете "отслеживать" FastAPI на GitHub (кликнув по кнопке "watch" наверху справа): https://github.com/fastapi/fastapi. 👀 -Там же Вы можете указать в настройках - "Releases only". +Там же Вы можете выбрать "Releases only". С такой настройкой Вы будете получать уведомления на вашу электронную почту каждый раз, когда появится новый релиз (новая версия) **FastAPI** с исправлениями ошибок и новыми возможностями. -## Связаться с автором +## Связаться с автором { #connect-with-the-author } -Можно связаться со мной (Себястьян Рамирез / `tiangolo`), автором FastAPI. +Можно связаться со мной (Sebastián Ramírez / `tiangolo`), автором. Вы можете: * Подписаться на меня на **GitHub**. * Посмотреть другие мои проекты с открытым кодом, которые могут быть полезны Вам. - * Подписавшись на меня Вы сможете получать уведомления, что я создал новый проект с открытым кодом,. -* Подписаться на меня в **Twitter** или в Mastodon. - * Поделиться со мной, как Вы используете FastAPI (я обожаю читать про это). - * Получать уведомления, когда я делаю объявления и представляю новые инструменты. - * Вы также можете подписаться на @fastapi в Twitter (это отдельный аккаунт). -* Подписаться на меня в **Linkedin**. - * Получать уведомления, когда я делаю объявления и представляю новые инструменты (правда чаще всего я использую Twitter 🤷‍♂). -* Читать, что я пишу (или подписаться на меня) в **Dev.to** или в **Medium**. - * Читать другие идеи, статьи и читать об инструментах созданных мной. - * Подпишитесь на меня, чтобы прочитать, когда я опубликую что-нибудь новое. + * Подписаться, чтобы видеть, когда я создаю новый проект с открытым кодом. +* Подписаться на меня в **X (Twitter)** или в Mastodon. + * Поделиться со мной, как Вы используете FastAPI (я обожаю это читать). + * Узнавать, когда я делаю объявления или выпускаю новые инструменты. + * Вы также можете подписаться на @fastapi в X (Twitter) (это отдельный аккаунт). +* Подписаться на меня в **LinkedIn**. + * Узнавать, когда я делаю объявления или выпускаю новые инструменты (хотя чаще я использую X (Twitter) 🤷‍♂). +* Читать, что я пишу (или подписаться на меня) на **Dev.to** или **Medium**. + * Читать другие идеи, статьи и о созданных мной инструментах. + * Подписаться, чтобы читать, когда я публикую что-то новое. -## Оставить сообщение в Twitter о **FastAPI** +## Оставить сообщение в X (Twitter) о **FastAPI** { #tweet-about-fastapi } -Оставьте сообщение в Twitter о **FastAPI** и позвольте мне и другим узнать - почему он Вам нравится. 🎉 +Оставьте сообщение в X (Twitter) о **FastAPI** и позвольте мне и другим узнать, почему он Вам нравится. 🎉 -Я люблю узнавать о том, как **FastAPI** используется, что Вам понравилось в нём, в каких проектах/компаниях Вы используете его и т.п. +Я люблю узнавать о том, как **FastAPI** используется, что Вам понравилось в нём, в каких проектах/компаниях Вы его используете и т.д. -## Оставить голос за FastAPI +## Оставить голос за FastAPI { #vote-for-fastapi } * Голосуйте за **FastAPI** в Slant. -* Голосуйте за **FastAPI** в AlternativeTo. -* Расскажите, как Вы используете **FastAPI** на StackShare. +* Голосуйте за **FastAPI** в AlternativeTo. +* Расскажите, что Вы используете **FastAPI** на StackShare. -## Помочь другим с их проблемами на GitHub +## Помочь другим с вопросами на GitHub { #help-others-with-questions-in-github } -Вы можете посмотреть, какие проблемы испытывают другие люди и попытаться помочь им. Чаще всего это вопросы, на которые, весьма вероятно, Вы уже знаете ответ. 🤓 +Вы можете попробовать помочь другим с их вопросами в: -Если Вы будете много помогать людям с решением их проблем, Вы можете стать официальным [Экспертом FastAPI](fastapi-people.md#_3){.internal-link target=_blank}. 🎉 +* GitHub Discussions +* GitHub Issues -Только помните, самое важное при этом - доброта. Столкнувшись с проблемой, люди расстраиваются и часто задают вопросы не лучшим образом, но постарайтесь быть максимально доброжелательным. 🤗 +Во многих случаях Вы уже можете знать ответы на эти вопросы. 🤓 -Идея сообщества **FastAPI** в том, чтобы быть добродушным и гостеприимными. Не допускайте издевательств или неуважительного поведения по отношению к другим. Мы должны заботиться друг о друге. +Если Вы много помогаете людям с их вопросами, Вы станете официальным [Экспертом FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉 + +Только помните, самое важное — постарайтесь быть добрыми. Люди приходят со своими разочарованиями и часто задают вопросы не лучшим образом, но постарайтесь, насколько можете, быть доброжелательными. 🤗 + +Идея сообщества **FastAPI** — быть доброжелательным и гостеприимным. В то же время не допускайте травлю или неуважительное поведение по отношению к другим. Мы должны заботиться друг о друге. --- -Как помочь другим с их проблемами: +Как помочь другим с вопросами (в обсуждениях или Issues): -### Понять вопрос +### Понять вопрос { #understand-the-question } -* Удостоверьтесь, что поняли **цель** и обстоятельства случая вопрошающего. +* Убедитесь, что поняли **цель** и кейс использования задающего вопрос. -* Затем проверьте, что вопрос (в подавляющем большинстве - это вопросы) Вам **ясен**. +* Затем проверьте, что вопрос (в подавляющем большинстве это вопросы) сформулирован **ясно**. -* Во многих случаях вопрос касается решения, которое пользователь придумал сам, но может быть и решение **получше**. Если Вы поймёте проблему и обстоятельства случая, то сможете предложить **альтернативное решение**. +* Во многих случаях спрашивают о воображаемом решении пользователя, но может быть решение **получше**. Если Вы лучше поймёте проблему и кейс, сможете предложить **альтернативное решение**. -* Ежели вопрос Вам непонятен, запросите больше **деталей**. +* Если вопрос непонятен, запросите больше **деталей**. -### Воспроизвести проблему +### Воспроизвести проблему { #reproduce-the-problem } -В большинстве случаев есть что-то связанное с **исходным кодом** вопрошающего. +В большинстве случаев и вопросов есть что-то связанное с **исходным кодом** автора. -И во многих случаях будет предоставлен только фрагмент этого кода, которого недостаточно для **воспроизведения проблемы**. +Во многих случаях предоставляют только фрагмент кода, но этого недостаточно, чтобы **воспроизвести проблему**. -* Попросите предоставить минимальный воспроизводимый пример, который можно **скопировать** и запустить локально дабы увидеть такую же ошибку, или поведение, или лучше понять обстоятельства случая. +* Попросите предоставить минимальный воспроизводимый пример, который Вы сможете **скопировать-вставить** и запустить локально, чтобы увидеть ту же ошибку или поведение, или лучше понять их кейс. -* Если на Вас нахлынуло великодушие, то можете попытаться **создать похожий пример** самостоятельно, основываясь только на описании проблемы. Но имейте в виду, что это может занять много времени и, возможно, стоит сначала позадавать вопросы для прояснения проблемы. +* Если чувствуете себя особенно великодушными, можете попытаться **создать такой пример** сами, основываясь только на описании проблемы. Просто помните, что это может занять много времени, и, возможно, сначала лучше попросить уточнить проблему. -### Предложить решение +### Предложить решение { #suggest-solutions } -* После того как Вы поняли вопрос, Вы можете дать **ответ**. +* После того как Вы поняли вопрос, Вы можете дать возможный **ответ**. -* Следует понять **основную проблему и обстоятельства случая**, потому что может быть решение лучше, чем то, которое пытались реализовать. +* Во многих случаях лучше понять **исходную проблему или кейс**, потому что может существовать способ решить её лучше, чем то, что пытаются сделать. -### Попросить закрыть проблему +### Попросить закрыть { #ask-to-close } -Если Вам ответили, высоки шансы, что Вам удалось решить проблему, поздравляю, **Вы - герой**! 🦸 +Если Вам ответили, велика вероятность, что Вы решили их проблему, поздравляю, **Вы — герой**! 🦸 -* В таком случае, если вопрос решён, попросите **закрыть проблему**. +* Теперь, если проблема решена, можно попросить их: + * В GitHub Discussions: пометить комментарий как **answer** (ответ). + * В GitHub Issues: **закрыть** Issue. -## Отслеживать репозиторий на GitHub +## Отслеживать репозиторий на GitHub { #watch-the-github-repository } -Вы можете "отслеживать" FastAPI на GitHub (кликните по кнопке "watch" наверху справа): https://github.com/fastapi/fastapi. 👀 +Вы можете "отслеживать" FastAPI на GitHub (кликнув по кнопке "watch" наверху справа): https://github.com/fastapi/fastapi. 👀 -Если Вы выберете "Watching" вместо "Releases only", то будете получать уведомления когда кто-либо попросит о помощи с решением его проблемы. +Если Вы выберете "Watching" вместо "Releases only", то будете получать уведомления, когда кто-либо создаёт новый вопрос или Issue. Вы также можете указать, что хотите получать уведомления только о новых Issues, или обсуждениях, или пулл-реквестах и т.д. -Тогда Вы можете попробовать решить эту проблему. +Тогда Вы можете попробовать помочь им с решением этих вопросов. -## Запросить помощь с решением проблемы +## Задать вопросы { #ask-questions } -Вы можете создать новый запрос с просьбой о помощи в репозитории на GitHub, например: +Вы можете создать новый вопрос в репозитории GitHub, например: -* Задать **вопрос** или попросить помощи в решении **проблемы**. -* Предложить новое **улучшение**. +* Задать **вопрос** или спросить о **проблеме**. +* Предложить новую **возможность**. -**Заметка**: Если Вы создаёте подобные запросы, то я попрошу Вас также оказывать аналогичную помощь другим. 😉 +**Заметка**: если Вы это сделаете, то я попрошу Вас также помогать другим. 😉 -## Проверять пул-реквесты +## Проверять пулл-реквесты { #review-pull-requests } -Вы можете помочь мне проверять пул-реквесты других участников. +Вы можете помочь мне проверять пулл-реквесты других участников. -И повторюсь, постарайтесь быть доброжелательным. 🤗 +И, снова, постарайтесь быть доброжелательными. 🤗 --- -О том, что нужно иметь в виду при проверке пул-реквестов: +О том, что нужно иметь в виду и как проверять пулл-реквест: -### Понять проблему +### Понять проблему { #understand-the-problem } -* Во-первых, убедитесь, что **поняли проблему**, которую пул-реквест пытается решить. Для этого может потребоваться продолжительное обсуждение. +* Во-первых, убедитесь, что **поняли проблему**, которую пулл-реквест пытается решить. Возможно, это обсуждалось более подробно в GitHub Discussion или Issue. -* Также есть вероятность, что пул-реквест не актуален, так как проблему можно решить **другим путём**. В таком случае Вы можете указать на этот факт. +* Также есть вероятность, что пулл-реквест не нужен, так как проблему можно решить **другим путём**. Тогда Вы можете предложить или спросить об этом. -### Не переживайте о стиле +### Не переживайте о стиле { #dont-worry-about-style } -* Не стоит слишком беспокоиться о таких вещах, как стиль сообщений в коммитах или количество коммитов. При слиянии пул-реквеста с основной веткой, я буду сжимать и настраивать всё вручную. +* Не стоит слишком беспокоиться о таких вещах, как стиль сообщений в коммитах — при слиянии я выполню squash и настрою коммит вручную. -* Также не беспокойтесь о правилах стиля, для проверки сего есть автоматизированные инструменты. +* Также не беспокойтесь о правилах стиля, это уже проверяют автоматизированные инструменты. -И если всё же потребуется какой-то другой стиль, я попрошу Вас об этом напрямую или добавлю сам коммиты с необходимыми изменениями. +Если будет нужна какая-то другая стилистика или единообразие, я попрошу об этом напрямую или добавлю поверх свои коммиты с нужными изменениями. -### Проверить код +### Проверить код { #check-the-code } -* Проверьте и прочитайте код, посмотрите, какой он имеет смысл, **запустите его локально** и посмотрите, действительно ли он решает поставленную задачу. +* Проверьте и прочитайте код, посмотрите, логичен ли он, **запустите его локально** и проверьте, действительно ли он решает проблему. -* Затем, используя **комментарий**, сообщите, что Вы сделали проверку, тогда я буду знать, что Вы действительно проверили код. +* Затем оставьте **комментарий**, что Вы это сделали, так я пойму, что Вы действительно проверили код. -/// info | "Информация" +/// info | Информация -К сожалению, я не могу так просто доверять пул-реквестам, у которых уже есть несколько одобрений. +К сожалению, я не могу просто доверять PR-ам только потому, что у них есть несколько одобрений. -Бывали случаи, что пул-реквесты имели 3, 5 или больше одобрений, вероятно из-за привлекательного описания, но когда я проверял эти пул-реквесты, они оказывались сломаны, содержали ошибки или вовсе не решали проблему, которую, как они утверждали, должны были решить. 😅 +Несколько раз было так, что у PR-ов было 3, 5 или больше одобрений, вероятно из-за привлекательного описания, но когда я их проверял, они оказывались сломанными, содержали баги или вовсе не решали заявленную проблему. 😅 -Потому это действительно важно - проверять и запускать код, и комментарием уведомлять меня, что Вы проделали эти действия. 🤓 +Поэтому очень важно действительно прочитать и запустить код и сообщить мне об этом в комментарии. 🤓 /// -* Если Вы считаете, что пул-реквест можно упростить, то можете попросить об этом, но не нужно быть слишком придирчивым, может быть много субъективных точек зрения (и у меня тоже будет своя 🙈), поэтому будет лучше, если Вы сосредоточитесь на фундаментальных вещах. +* Если PR можно упростить, Вы можете попросить об этом, но не нужно быть слишком придирчивым — может быть много субъективных мнений (и у меня тоже 🙈), поэтому лучше сосредоточиться на фундаментальных вещах. -### Тестировать +### Тестировать { #tests } -* Помогите мне проверить, что у пул-реквеста есть **тесты**. +* Помогите мне проверить, что у PR есть **тесты**. -* Проверьте, что тесты **падали** до пул-реквеста. 🚨 +* Проверьте, что тесты **падают** до PR. 🚨 -* Затем проверьте, что тесты **не валятся** после пул-реквеста. ✅ +* Затем проверьте, что тесты **проходят** после PR. ✅ -* Многие пул-реквесты не имеют тестов, Вы можете **напомнить** о необходимости добавления тестов или даже **предложить** какие-либо свои тесты. Это одна из тех вещей, которые отнимают много времени и Вы можете помочь с этим. +* Многие PR не имеют тестов — Вы можете **напомнить** добавить тесты или даже **предложить** некоторые тесты сами. Это одна из самых трудозатратных частей, и здесь Вы можете очень помочь. -* Затем добавьте комментарий, что Вы испробовали в ходе проверки. Таким образом я буду знать, как Вы произвели проверку. 🤓 +* Затем добавьте комментарий, что Вы попробовали, чтобы я знал, что Вы это проверили. 🤓 -## Создать пул-реквест +## Создать пулл-реквест { #create-a-pull-request } -Вы можете [сделать вклад](contributing.md){.internal-link target=_blank} в код фреймворка используя пул-реквесты, например: +Вы можете [сделать вклад](contributing.md){.internal-link target=_blank} в исходный код пулл-реквестами, например: -* Исправить опечатку, которую Вы нашли в документации. -* Поделиться статьёй, видео или подкастом о FastAPI, которые Вы создали или нашли изменив этот файл. - * Убедитесь, что Вы добавили свою ссылку в начало соответствующего раздела. -* Помочь с [переводом документации](contributing.md#_8){.internal-link target=_blank} на Ваш язык. - * Вы также можете проверять переводы сделанные другими. +* Исправить опечатку, найденную в документации. +* Поделиться статьёй, видео или подкастом о FastAPI, которые Вы создали или нашли, изменив этот файл. + * Убедитесь, что добавили свою ссылку в начало соответствующего раздела. +* Помочь с [переводом документации](contributing.md#translations){.internal-link target=_blank} на Ваш язык. + * Вы также можете проверять переводы, сделанные другими. * Предложить новые разделы документации. -* Исправить существующуе проблемы/баги. +* Исправить существующую проблему/баг. * Убедитесь, что добавили тесты. * Добавить новую возможность. * Убедитесь, что добавили тесты. - * Убедитесь, что добавили документацию, если она необходима. + * Убедитесь, что добавили документацию, если это уместно. -## Помочь поддерживать FastAPI +## Помочь поддерживать FastAPI { #help-maintain-fastapi } Помогите мне поддерживать **FastAPI**! 🤓 -Предстоит ещё много работы и, по большей части, **ВЫ** можете её сделать. +Предстоит ещё много работы, и, по большей части, **ВЫ** можете её сделать. Основные задачи, которые Вы можете выполнить прямо сейчас: -* [Помочь другим с их проблемами на GitHub](#github_1){.internal-link target=_blank} (смотрите вышестоящую секцию). -* [Проверить пул-реквесты](#-){.internal-link target=_blank} (смотрите вышестоящую секцию). +* [Помочь другим с вопросами на GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (смотрите секцию выше). +* [Проверять пулл-реквесты](#review-pull-requests){.internal-link target=_blank} (смотрите секцию выше). -Эти две задачи **отнимают больше всего времени**. Это основная работа по поддержке FastAPI. +Именно эти две задачи **забирают больше всего времени**. Это основная работа по поддержке FastAPI. -Если Вы можете помочь мне с этим, **Вы помогаете поддерживать FastAPI** и следить за тем, чтобы он продолжал **развиваться быстрее и лучше**. 🚀 +Если Вы можете помочь мне с этим, **Вы помогаете поддерживать FastAPI** и делаете так, чтобы он продолжал **развиваться быстрее и лучше**. 🚀 -## Подключиться к чату +## Подключиться к чату { #join-the-chat } -Подключайтесь к 👥 чату в Discord 👥 и общайтесь с другими участниками сообщества FastAPI. +Подключайтесь к 👥 серверу чата в Discord 👥 и общайтесь с другими участниками сообщества FastAPI. -/// tip | "Подсказка" +/// tip | Подсказка -Вопросы по проблемам с фреймворком лучше задавать в GitHub issues, так больше шансов, что Вы получите помощь от [Экспертов FastAPI](fastapi-people.md#_3){.internal-link target=_blank}. +По вопросам — задавайте их в GitHub Discussions, так гораздо выше шанс, что Вы получите помощь от [Экспертов FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. -Используйте этот чат только для бесед на отвлечённые темы. +Используйте чат только для прочих общих бесед. /// -### Не использовать чаты для вопросов +### Не используйте чат для вопросов { #dont-use-the-chat-for-questions } -Имейте в виду, что чаты позволяют больше "свободного общения", потому там легко задавать вопросы, которые слишком общие и на которые труднее ответить, так что Вы можете не получить нужные Вам ответы. +Имейте в виду, что в чатах, благодаря "свободному общению", легко задать вопросы, которые слишком общие и на которые сложнее ответить, поэтому Вы можете не получить ответы. -В разделе "проблемы" на GitHub, есть шаблон, который поможет Вам написать вопрос правильно, чтобы Вам было легче получить хороший ответ или даже решить проблему самостоятельно, прежде чем Вы зададите вопрос. В GitHub я могу быть уверен, что всегда отвечаю на всё, даже если это займет какое-то время. И я не могу сделать то же самое в чатах. 😅 +На GitHub шаблон поможет Вам правильно сформулировать вопрос, чтобы Вам было легче получить хороший ответ или даже решить проблему самостоятельно ещё до того, как спросите. И на GitHub я могу следить за тем, чтобы всегда отвечать на всё, даже если это занимает время. А с чатами я не могу сделать этого лично. 😅 -Кроме того, общение в чатах не так легкодоступно для поиска, как в GitHub, потому вопросы и ответы могут потеряться среди другого общения. И только проблемы решаемые на GitHub учитываются в получении лычки [Эксперт FastAPI](fastapi-people.md#_3){.internal-link target=_blank}, так что весьма вероятно, что Вы получите больше внимания на GitHub. +Кроме того, переписка в чатах хуже ищется, чем на GitHub, поэтому вопросы и ответы могут теряться среди остальных сообщений. И только те, что на GitHub, учитываются для получения лычки [Эксперт FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, так что вероятнее всего Вы получите больше внимания именно на GitHub. -С другой стороны, в чатах тысячи пользователей, а значит есть большие шансы в любое время найти там кого-то, с кем можно поговорить. 😄 +С другой стороны, в чатах тысячи пользователей, так что почти всегда есть шанс найти там кого-то для разговора. 😄 -## Спонсировать автора +## Спонсировать автора { #sponsor-the-author } -Вы также можете оказать мне финансовую поддержку посредством спонсорства через GitHub. - -Там можно просто купить мне кофе ☕️ в знак благодарности. 😄 - -А ещё Вы можете стать Серебряным или Золотым спонсором для FastAPI. 🏅🎉 - -## Спонсировать инструменты, на которых зиждется мощь FastAPI - -Как Вы могли заметить в документации, FastAPI опирается на плечи титанов: Starlette и Pydantic. - -Им тоже можно оказать спонсорскую поддержку: - -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) +Если Ваш **продукт/компания** зависят от **FastAPI** или связаны с ним и Вы хотите донести до пользователей информацию о себе, Вы можете спонсировать автора (меня) через GitHub Sponsors. В зависимости от уровня поддержки Вы можете получить дополнительные бонусы, например, бейдж в документации. 🎁 --- -Благодарствую! 🚀 +Спасибо! 🚀 diff --git a/docs/ru/docs/history-design-future.md b/docs/ru/docs/history-design-future.md index 96604e3a4e..9cdd53376d 100644 --- a/docs/ru/docs/history-design-future.md +++ b/docs/ru/docs/history-design-future.md @@ -1,4 +1,4 @@ -# История создания и дальнейшее развитие +# История, проектирование и будущее { #history-design-and-future } Однажды, один из пользователей **FastAPI** задал вопрос: @@ -6,7 +6,7 @@ Что ж, вот небольшая часть истории проекта. -## Альтернативы +## Альтернативы { #alternatives } В течение нескольких лет я, возглавляя различные команды разработчиков, создавал довольно сложные API для машинного обучения, распределённых систем, асинхронных задач, баз данных NoSQL и т.д. @@ -24,45 +24,47 @@ Я всячески избегал создания нового фреймворка в течение нескольких лет. Сначала я пытался собрать все нужные возможности, которые ныне есть в **FastAPI**, используя множество различных фреймворков, плагинов и инструментов. -Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти возможности сразу. Взять самые лучшие идеи из предыдущих инструментов и, используя введённые в Python подсказки типов (которых не было до версии 3.6), объединить их. +Но в какой-то момент не осталось другого выбора, кроме как создать что-то, что предоставляло бы все эти возможности сразу. Взять самые лучшие идеи из предыдущих инструментов и, используя введённые в Python аннотации типов (которых не было до версии 3.6), объединить их. -## Исследования +## Исследования { #investigation } Благодаря опыту использования существующих альтернатив, мы с коллегами изучили их основные идеи и скомбинировали собранные знания наилучшим образом. -Например, стало ясно, что необходимо брать за основу стандартные подсказки типов Python, а самым лучшим подходом является использование уже существующих стандартов. +Например, стало ясно, что необходимо брать за основу стандартные аннотации типов Python. + +Также наилучшим подходом является использование уже существующих стандартов. Итак, прежде чем приступить к написанию **FastAPI**, я потратил несколько месяцев на изучение OpenAPI, JSON Schema, OAuth2, и т.п. для понимания их взаимосвязей, совпадений и различий. -## Дизайн +## Проектирование { #design } Затем я потратил некоторое время на придумывание "API" разработчика, который я хотел иметь как пользователь (как разработчик, использующий FastAPI). -Я проверил несколько идей на самых популярных редакторах кода среди Python-разработчиков: PyCharm, VS Code, Jedi. +Я проверил несколько идей на самых популярных редакторах кода: PyCharm, VS Code, редакторы на базе Jedi. -Данные по редакторам я взял из опроса Python-разработчиков, который охватываает около 80% пользователей. +Данные по редакторам я взял из опроса Python-разработчиков, который охватывает около 80% пользователей. Это означает, что **FastAPI** был специально проверен на редакторах, используемых 80% Python-разработчиками. И поскольку большинство других редакторов, как правило, работают аналогичным образом, все его преимущества должны работать практически для всех редакторов. -Таким образом, я смог найти наилучшие способы сократить дублирование кода, обеспечить повсеместное автодополнение, проверку типов и ошибок и т.д. +Таким образом, я смог найти наилучшие способы сократить дублирование кода, обеспечить повсеместное автозавершение, проверку типов и ошибок и т.д. И все это, чтобы все пользователи могли получать наилучший опыт разработки. -## Зависимости +## Зависимости { #requirements } Протестировав несколько вариантов, я решил, что в качестве основы буду использовать **Pydantic** и его преимущества. -По моим предложениям был изменён код этого фреймворка, чтобы сделать его полностью совместимым с JSON Schema, поддержать различные способы определения ограничений и улучшить помощь редакторов (проверки типов, автозаполнение). +По моим предложениям был изменён код этого фреймворка, чтобы сделать его полностью совместимым с JSON Schema, поддержать различные способы определения ограничений и улучшить поддержку в редакторах кода (проверки типов, автозавершение) на основе тестов в нескольких редакторах. -В то же время, я принимал участие в разработке **Starlette**, ещё один из основных компонентов FastAPI. +В то же время, я принимал участие в разработке **Starlette**, ещё один из основных компонентов FastAPI. -## Разработка +## Разработка { #development } К тому времени, когда я начал создавать **FastAPI**, большинство необходимых деталей уже существовало, дизайн был определён, зависимости и прочие инструменты были готовы, а знания о стандартах и спецификациях были четкими и свежими. -## Будущее +## Будущее { #future } Сейчас уже ясно, что **FastAPI** со своими идеями стал полезен многим людям. diff --git a/docs/ru/docs/how-to/conditional-openapi.md b/docs/ru/docs/how-to/conditional-openapi.md new file mode 100644 index 0000000000..dc987ae26b --- /dev/null +++ b/docs/ru/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# Условный OpenAPI { #conditional-openapi } + +При необходимости вы можете использовать настройки и переменные окружения, чтобы условно настраивать OpenAPI в зависимости от окружения и даже полностью его отключать. + +## О безопасности, API и документации { #about-security-apis-and-docs } + +Скрытие пользовательских интерфейсов документации в продакшн *не должно* быть способом защиты вашего API. + +Это не добавляет дополнительной безопасности вашему API, *операции пути* (обработчики пути) всё равно будут доступны по своим путям. + +Если в вашем коде есть уязвимость, она всё равно останется. + +Сокрытие документации лишь усложняет понимание того, как взаимодействовать с вашим API, и может усложнить его отладку в продакшн. Это можно считать просто разновидностью безопасности через сокрытие. + +Если вы хотите обезопасить свой API, есть несколько более эффективных вещей, которые можно сделать, например: + +* Убедитесь, что у вас чётко определены Pydantic-модели для тел запросов и ответов. +* Настройте необходимые разрешения и роли с помощью зависимостей. +* Никогда не храните пароли в открытом виде, только хэши паролей. +* Реализуйте и используйте известные криптографические инструменты, например pwdlib и JWT-токены, и т.д. +* Добавьте более тонкое управление доступом с помощью OAuth2 scopes (областей) там, где это необходимо. +* ...и т.п. + +Тем не менее, у вас может быть очень специфичный случай использования, когда действительно нужно отключить документацию API для некоторых окружений (например, в продакшн) или в зависимости от настроек из переменных окружения. + +## Условный OpenAPI из настроек и переменных окружения { #conditional-openapi-from-settings-and-env-vars } + +Вы можете легко использовать те же настройки Pydantic, чтобы настроить сгенерированный OpenAPI и интерфейсы документации. + +Например: + +{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} + +Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию — `"/openapi.json"`. + +Затем используем её при создании приложения FastAPI. + +Далее вы можете отключить OpenAPI (включая интерфейсы документации), установив переменную окружения `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/ru/docs/how-to/configure-swagger-ui.md b/docs/ru/docs/how-to/configure-swagger-ui.md new file mode 100644 index 0000000000..4793cc9db3 --- /dev/null +++ b/docs/ru/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Настройка Swagger UI { #configure-swagger-ui } + +Вы можете настроить дополнительные параметры Swagger UI. + +Чтобы настроить их, передайте аргумент `swagger_ui_parameters` при создании объекта приложения `FastAPI()` или в функцию `get_swagger_ui_html()`. + +`swagger_ui_parameters` принимает словарь с настройками, которые передаются в Swagger UI напрямую. + +FastAPI преобразует эти настройки в **JSON**, чтобы они были совместимы с JavaScript, поскольку именно это требуется Swagger UI. + +## Отключить подсветку синтаксиса { #disable-syntax-highlighting } + +Например, вы можете отключить подсветку синтаксиса в Swagger UI. + +Без изменения настроек подсветка синтаксиса включена по умолчанию: + + + +Но вы можете отключить её, установив `syntaxHighlight` в `False`: + +{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} + +…и после этого Swagger UI больше не будет показывать подсветку синтаксиса: + + + +## Изменить тему { #change-the-theme } + +Аналогично вы можете задать тему подсветки синтаксиса с ключом "syntaxHighlight.theme" (обратите внимание, что посередине стоит точка): + +{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} + +Эта настройка изменит цветовую тему подсветки синтаксиса: + + + +## Изменить параметры Swagger UI по умолчанию { #change-default-swagger-ui-parameters } + +FastAPI включает некоторые параметры конфигурации по умолчанию, подходящие для большинства случаев. + +Это включает следующие настройки по умолчанию: + +{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} + +Вы можете переопределить любую из них, указав другое значение в аргументе `swagger_ui_parameters`. + +Например, чтобы отключить `deepLinking`, можно передать такие настройки в `swagger_ui_parameters`: + +{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} + +## Другие параметры Swagger UI { #other-swagger-ui-parameters } + +Чтобы увидеть все остальные возможные настройки, прочитайте официальную документацию по параметрам Swagger UI. + +## Настройки только для JavaScript { #javascript-only-settings } + +Swagger UI также допускает другие настройки, которые являются **чисто JavaScript-объектами** (например, JavaScript-функциями). + +FastAPI также включает следующие настройки `presets` (только для JavaScript): + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +Это объекты **JavaScript**, а не строки, поэтому напрямую передать их из Python-кода нельзя. + +Если вам нужны такие настройки только для JavaScript, используйте один из методов выше. Переопределите *операцию пути* Swagger UI и вручную напишите любой необходимый JavaScript. diff --git a/docs/ru/docs/how-to/custom-docs-ui-assets.md b/docs/ru/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 0000000000..c07a9695b9 --- /dev/null +++ b/docs/ru/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# Свои статические ресурсы UI документации (самостоятельный хостинг) { #custom-docs-ui-static-assets-self-hosting } + +Документация API использует **Swagger UI** и **ReDoc**, и для каждого из них нужны некоторые файлы JavaScript и CSS. + +По умолчанию эти файлы отдаются с CDN. + +Но это можно настроить: вы можете указать конкретный CDN или отдавать файлы самостоятельно. + +## Пользовательский CDN для JavaScript и CSS { #custom-cdn-for-javascript-and-css } + +Допустим, вы хотите использовать другой CDN, например `https://unpkg.com/`. + +Это может быть полезно, если, например, вы живёте в стране, где некоторые URL ограничены. + +### Отключить автоматическую документацию { #disable-the-automatic-docs } + +Первый шаг — отключить автоматическую документацию, так как по умолчанию она использует стандартный CDN. + +Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} + +### Подключить пользовательскую документацию { #include-the-custom-docs } + +Теперь вы можете создать *операции пути* для пользовательской документации. + +Вы можете переиспользовать внутренние функции FastAPI для создания HTML-страниц документации и передать им необходимые аргументы: + +* `openapi_url`: URL, по которому HTML-страница документации сможет получить схему OpenAPI для вашего API. Здесь можно использовать атрибут `app.openapi_url`. +* `title`: заголовок вашего API. +* `oauth2_redirect_url`: здесь можно использовать `app.swagger_ui_oauth2_redirect_url`, чтобы оставить значение по умолчанию. +* `swagger_js_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **JavaScript**. Это URL вашего пользовательского CDN. +* `swagger_css_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **CSS**. Это URL вашего пользовательского CDN. + +Аналогично и для ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | Совет + +*Операция пути* для `swagger_ui_redirect` — это вспомогательный эндпоинт на случай, когда вы используете OAuth2. + +Если вы интегрируете свой API с провайдером OAuth2, вы сможете аутентифицироваться и вернуться к документации API с полученными учётными данными, а затем взаимодействовать с ним, используя реальную аутентификацию OAuth2. + +Swagger UI сделает это за вас «за кулисами», но для этого ему нужен этот вспомогательный «redirect» эндпоинт. + +/// + +### Создайте *операцию пути*, чтобы проверить { #create-a-path-operation-to-test-it } + +Чтобы убедиться, что всё работает, создайте *операцию пути*: + +{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} + +### Тестирование { #test-it } + +Теперь вы должны иметь возможность открыть свою документацию по адресу http://127.0.0.1:8000/docs и перезагрузить страницу — «ассеты» (статические файлы) будут загружаться с нового CDN. + +## Самостоятельный хостинг JavaScript и CSS для документации { #self-hosting-javascript-and-css-for-docs } + +Самостоятельный хостинг JavaScript и CSS может быть полезен, если, например, вам нужно, чтобы приложение продолжало работать в офлайне, без доступа к открытому Интернету, или в локальной сети. + +Здесь вы увидите, как отдавать эти файлы самостоятельно, в том же приложении FastAPI, и настроить документацию на их использование. + +### Структура файлов проекта { #project-file-structure } + +Допустим, структура файлов вашего проекта выглядит так: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Теперь создайте директорию для хранения этих статических файлов. + +Новая структура файлов может выглядеть так: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Скачайте файлы { #download-the-files } + +Скачайте статические файлы, необходимые для документации, и поместите их в директорию `static/`. + +Скорее всего, вы можете кликнуть правой кнопкой на каждой ссылке и выбрать что-то вроде «Сохранить ссылку как...». + +**Swagger UI** использует файлы: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +А **ReDoc** использует файл: + +* `redoc.standalone.js` + +После этого структура файлов может выглядеть так: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Предоставьте доступ к статическим файлам { #serve-the-static-files } + +* Импортируйте `StaticFiles`. +* Смонтируйте экземпляр `StaticFiles()` в определённый путь. + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} + +### Протестируйте статические файлы { #test-the-static-files } + +Запустите своё приложение и откройте http://127.0.0.1:8000/static/redoc.standalone.js. + +Вы должны увидеть очень длинный JavaScript-файл для **ReDoc**. + +Он может начинаться примерно так: + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +Это подтверждает, что ваше приложение умеет отдавать статические файлы и что вы поместили файлы документации в нужное место. + +Теперь можно настроить приложение так, чтобы документация использовала эти статические файлы. + +### Отключить автоматическую документацию для статических файлов { #disable-the-automatic-docs-for-static-files } + +Так же, как и при использовании пользовательского CDN, первым шагом будет отключение автоматической документации, так как по умолчанию она использует CDN. + +Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} + +### Подключить пользовательскую документацию со статическими файлами { #include-the-custom-docs-for-static-files } + +Аналогично пользовательскому CDN, теперь вы можете создать *операции пути* для собственной документации. + +Снова можно переиспользовать внутренние функции FastAPI для создания HTML-страниц документации и передать им необходимые аргументы: + +* `openapi_url`: URL, по которому HTML-страница документации сможет получить схему OpenAPI для вашего API. Здесь можно использовать атрибут `app.openapi_url`. +* `title`: заголовок вашего API. +* `oauth2_redirect_url`: здесь можно использовать `app.swagger_ui_oauth2_redirect_url`, чтобы оставить значение по умолчанию. +* `swagger_js_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **JavaScript**. **Это тот файл, который теперь отдаёт ваше собственное приложение**. +* `swagger_css_url`: URL, по которому HTML для документации Swagger UI сможет получить файл **CSS**. **Это тот файл, который теперь отдаёт ваше собственное приложение**. + +Аналогично и для ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | Совет + +*Операция пути* для `swagger_ui_redirect` — это вспомогательный эндпоинт на случай, когда вы используете OAuth2. + +Если вы интегрируете свой API с провайдером OAuth2, вы сможете аутентифицироваться и вернуться к документации API с полученными учётными данными, а затем взаимодействовать с ним, используя реальную аутентификацию OAuth2. + +Swagger UI сделает это за вас «за кулисами», но для этого ему нужен этот вспомогательный «redirect» эндпоинт. + +/// + +### Создайте *операцию пути* для теста статических файлов { #create-a-path-operation-to-test-static-files } + +Чтобы убедиться, что всё работает, создайте *операцию пути*: + +{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} + +### Тестирование UI со статическими файлами { #test-static-files-ui } + +Теперь вы можете отключить Wi‑Fi, открыть свою документацию по адресу http://127.0.0.1:8000/docs и перезагрузить страницу. + +Даже без Интернета вы сможете видеть документацию к своему API и взаимодействовать с ним. diff --git a/docs/ru/docs/how-to/custom-request-and-route.md b/docs/ru/docs/how-to/custom-request-and-route.md new file mode 100644 index 0000000000..1b8d7f7ed3 --- /dev/null +++ b/docs/ru/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# Пользовательские классы Request и APIRoute { #custom-request-and-apiroute-class } + +В некоторых случаях может понадобиться переопределить логику, используемую классами `Request` и `APIRoute`. + +В частности, это может быть хорошей альтернативой логике в middleware. + +Например, если вы хотите прочитать или изменить тело запроса до того, как оно будет обработано вашим приложением. + +/// danger | Опасность + +Это «продвинутая» возможность. + +Если вы только начинаете работать с **FastAPI**, возможно, стоит пропустить этот раздел. + +/// + +## Сценарии использования { #use-cases } + +Некоторые сценарии: + +* Преобразование тел запросов, не в формате JSON, в JSON (например, `msgpack`). +* Распаковка тел запросов, сжатых с помощью gzip. +* Автоматическое логирование всех тел запросов. + +## Обработка пользовательского кодирования тела запроса { #handling-custom-request-body-encodings } + +Посмотрим как использовать пользовательский подкласс `Request` для распаковки gzip-запросов. + +И подкласс `APIRoute`, чтобы использовать этот пользовательский класс запроса. + +### Создать пользовательский класс `GzipRequest` { #create-a-custom-gziprequest-class } + +/// tip | Совет + +Это учебный пример, демонстрирующий принцип работы. Если вам нужна поддержка Gzip, вы можете использовать готовый [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}. + +/// + +Сначала создадим класс `GzipRequest`, который переопределит метод `Request.body()` и распакует тело запроса при наличии соответствующего HTTP-заголовка. + +Если в заголовке нет `gzip`, он не будет пытаться распаковывать тело. + +Таким образом, один и тот же класс маршрута сможет обрабатывать как gzip-сжатые, так и несжатые запросы. + +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} + +### Создать пользовательский класс `GzipRoute` { #create-a-custom-gziproute-class } + +Далее создадим пользовательский подкласс `fastapi.routing.APIRoute`, который будет использовать `GzipRequest`. + +На этот раз он переопределит метод `APIRoute.get_route_handler()`. + +Этот метод возвращает функцию. Именно эта функция получает HTTP-запрос и возвращает HTTP-ответ. + +Здесь мы используем её, чтобы создать `GzipRequest` из исходного HTTP-запроса. + +{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} + +/// note | Технические детали + +У `Request` есть атрибут `request.scope` — это просто Python-`dict`, содержащий метаданные, связанные с HTTP-запросом. + +У `Request` также есть `request.receive` — функция для «получения» тела запроса. + +И `dict` `scope`, и функция `receive` являются частью спецификации ASGI. + +Именно этих двух компонентов — `scope` и `receive` — достаточно, чтобы создать новый экземпляр `Request`. + +Чтобы узнать больше о `Request`, см. документацию Starlette о запросах. + +/// + +Единственное, что делает по-другому функция, возвращённая `GzipRequest.get_route_handler`, — преобразует `Request` в `GzipRequest`. + +Благодаря этому наш `GzipRequest` позаботится о распаковке данных (при необходимости) до передачи их в наши *операции пути*. + +Дальше вся логика обработки остаётся прежней. + +Но благодаря изменениям в `GzipRequest.body` тело запроса будет автоматически распаковано при необходимости, когда оно будет загружено **FastAPI**. + +## Доступ к телу запроса в обработчике исключений { #accessing-the-request-body-in-an-exception-handler } + +/// tip | Совет + +Для решения этой задачи, вероятно, намного проще использовать `body` в пользовательском обработчике `RequestValidationError` ([Обработка ошибок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + +Но этот пример всё равно актуален и показывает, как взаимодействовать с внутренними компонентами. + +/// + +Тем же подходом можно воспользоваться, чтобы получить доступ к телу запроса в обработчике исключений. + +Нужно лишь обработать запрос внутри блока `try`/`except`: + +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} + +Если произойдёт исключение, экземпляр `Request` всё ещё будет в области видимости, поэтому мы сможем прочитать тело запроса и использовать его при обработке ошибки: + +{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} + +## Пользовательский класс `APIRoute` в роутере { #custom-apiroute-class-in-a-router } + +Вы также можете задать параметр `route_class` у `APIRouter`: + +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} + +В этом примере *операции пути*, объявленные в `router`, будут использовать пользовательский класс `TimedRoute` и получат дополнительный HTTP-заголовок `X-Response-Time` в ответе с временем, затраченным на формирование ответа: + +{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} diff --git a/docs/ru/docs/how-to/extending-openapi.md b/docs/ru/docs/how-to/extending-openapi.md new file mode 100644 index 0000000000..2897fb89ba --- /dev/null +++ b/docs/ru/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# Расширение OpenAPI { #extending-openapi } + +Иногда может понадобиться изменить сгенерированную схему OpenAPI. + +В этом разделе показано, как это сделать. + +## Обычный процесс { #the-normal-process } + +Обычный (по умолчанию) процесс выглядит так. + +Приложение `FastAPI` (экземпляр) имеет метод `.openapi()`, который должен возвращать схему OpenAPI. + +В процессе создания объекта приложения регистрируется *операция пути* (обработчик пути) для `/openapi.json` (или для того, что указано в вашем `openapi_url`). + +Она просто возвращает JSON-ответ с результатом вызова метода приложения `.openapi()`. + +По умолчанию метод `.openapi()` проверяет свойство `.openapi_schema`: если в нём уже есть данные, возвращает их. + +Если нет — генерирует схему с помощью вспомогательной функции `fastapi.openapi.utils.get_openapi`. + +Функция `get_openapi()` принимает параметры: + +* `title`: Заголовок OpenAPI, отображается в документации. +* `version`: Версия вашего API, например `2.5.0`. +* `openapi_version`: Версия используемой спецификации OpenAPI. По умолчанию — последняя: `3.1.0`. +* `summary`: Краткое описание API. +* `description`: Описание вашего API; может включать Markdown и будет отображается в документации. +* `routes`: Список маршрутов — это каждая зарегистрированная *операция пути*. Берутся из `app.routes`. + +/// info | Информация + +Параметр `summary` доступен в OpenAPI 3.1.0 и выше, поддерживается FastAPI версии 0.99.0 и выше. + +/// + +## Переопределение значений по умолчанию { #overriding-the-defaults } + +Используя информацию выше, вы можете той же вспомогательной функцией сгенерировать схему OpenAPI и переопределить любые нужные части. + +Например, добавим расширение OpenAPI ReDoc для включения собственного логотипа. + +### Обычный **FastAPI** { #normal-fastapi } + +Сначала напишите приложение **FastAPI** как обычно: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} + +### Сгенерируйте схему OpenAPI { #generate-the-openapi-schema } + +Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} + +### Измените схему OpenAPI { #modify-the-openapi-schema } + +Теперь можно добавить расширение ReDoc, добавив кастомный `x-logo` в «объект» `info` в схеме OpenAPI: + +{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} + +### Кэшируйте схему OpenAPI { #cache-the-openapi-schema } + +Вы можете использовать свойство `.openapi_schema` как «кэш» для хранения сгенерированной схемы. + +Так приложению не придётся генерировать схему каждый раз, когда пользователь открывает документацию API. + +Она будет создана один раз, а затем тот же кэшированный вариант будет использоваться для последующих запросов. + +{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} + +### Переопределите метод { #override-the-method } + +Теперь вы можете заменить метод `.openapi()` на вашу новую функцию. + +{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} + +### Проверьте { #check-it } + +Перейдите на http://127.0.0.1:8000/redoc — вы увидите, что используется ваш кастомный логотип (в этом примере — логотип **FastAPI**): + + diff --git a/docs/ru/docs/how-to/general.md b/docs/ru/docs/how-to/general.md new file mode 100644 index 0000000000..029ea1d274 --- /dev/null +++ b/docs/ru/docs/how-to/general.md @@ -0,0 +1,39 @@ +# Общее — Как сделать — Рецепты { #general-how-to-recipes } + +Здесь несколько указателей на другие места в документации для общих или частых вопросов. + +## Фильтрация данных — Безопасность { #filter-data-security } + +Чтобы убедиться, что вы не возвращаете больше данных, чем следует, прочитайте документацию: [Руководство — Модель ответа — Возвращаемый тип](../tutorial/response-model.md){.internal-link target=_blank}. + +## Теги в документации — OpenAPI { #documentation-tags-openapi } + +Чтобы добавить теги к вашим *операциям пути* и группировать их в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Теги](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. + +## Краткое описание и описание в документации — OpenAPI { #documentation-summary-and-description-openapi } + +Чтобы добавить краткое описание и описание к вашим *операциям пути* и отобразить их в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Краткое описание и описание](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. + +## Описание ответа в документации — OpenAPI { #documentation-response-description-openapi } + +Чтобы задать описание ответа, отображаемое в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Описание ответа](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. + +## Документация — пометить операцию пути устаревшей — OpenAPI { #documentation-deprecate-a-path-operation-openapi } + +Чтобы пометить *операцию пути* как устаревшую и показать это в интерфейсе документации, прочитайте документацию: [Руководство — Конфигурации операций пути — Пометить операцию пути устаревшей](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. + +## Преобразование любых данных к формату, совместимому с JSON { #convert-any-data-to-json-compatible } + +Чтобы преобразовать любые данные к формату, совместимому с JSON, прочитайте документацию: [Руководство — JSON-совместимый кодировщик](../tutorial/encoder.md){.internal-link target=_blank}. + +## Метаданные OpenAPI — Документация { #openapi-metadata-docs } + +Чтобы добавить метаданные в вашу схему OpenAPI, включая лицензию, версию, контакты и т.д., прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md){.internal-link target=_blank}. + +## Пользовательский URL OpenAPI { #openapi-custom-url } + +Чтобы настроить URL OpenAPI (или удалить его), прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. + +## URL документации OpenAPI { #openapi-docs-urls } + +Чтобы изменить URL, используемые для автоматически сгенерированных пользовательских интерфейсов документации, прочитайте документацию: [Руководство — Метаданные и URL документации](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/ru/docs/how-to/graphql.md b/docs/ru/docs/how-to/graphql.md new file mode 100644 index 0000000000..9ed6d95ca1 --- /dev/null +++ b/docs/ru/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL { #graphql } + +Так как **FastAPI** основан на стандарте **ASGI**, очень легко интегрировать любую библиотеку **GraphQL**, также совместимую с ASGI. + +Вы можете комбинировать обычные *операции пути* FastAPI с GraphQL в одном приложении. + +/// tip | Совет + +**GraphQL** решает некоторые очень специфические задачи. + +У него есть как **преимущества**, так и **недостатки** по сравнению с обычными **веб-API**. + +Убедитесь, что **выгоды** для вашего случая использования перевешивают **недостатки**. 🤓 + +/// + +## Библиотеки GraphQL { #graphql-libraries } + +Ниже приведены некоторые библиотеки **GraphQL** с поддержкой **ASGI**. Их можно использовать с **FastAPI**: + +* Strawberry 🍓 + * С документацией для FastAPI +* Ariadne + * С документацией для FastAPI +* Tartiflette + * С Tartiflette ASGI для интеграции с ASGI +* Graphene + * С starlette-graphene3 + +## GraphQL со Strawberry { #graphql-with-strawberry } + +Если вам нужно или хочется работать с **GraphQL**, **Strawberry** — **рекомендуемая** библиотека, так как её дизайн ближе всего к дизайну **FastAPI**, всё основано на **аннотациях типов**. + +В зависимости от вашего сценария использования вы можете предпочесть другую библиотеку, но если бы вы спросили меня, я, скорее всего, предложил бы попробовать **Strawberry**. + +Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI: + +{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} + +Подробнее о Strawberry можно узнать в документации Strawberry. + +А также в документации по интеграции Strawberry с FastAPI. + +## Устаревший `GraphQLApp` из Starlette { #older-graphqlapp-from-starlette } + +В предыдущих версиях Starlette был класс `GraphQLApp` для интеграции с Graphene. + +Он был объявлен устаревшим в Starlette, но если у вас есть код, который его использовал, вы можете легко **мигрировать** на starlette-graphene3, который решает ту же задачу и имеет **почти идентичный интерфейс**. + +/// tip | Совет + +Если вам нужен GraphQL, я всё же рекомендую посмотреть Strawberry, так как он основан на аннотациях типов, а не на пользовательских классах и типах. + +/// + +## Подробнее { #learn-more } + +Подробнее о **GraphQL** вы можете узнать в официальной документации GraphQL. + +Также можно почитать больше о каждой из указанных выше библиотек по приведённым ссылкам. diff --git a/docs/ru/docs/how-to/index.md b/docs/ru/docs/how-to/index.md new file mode 100644 index 0000000000..228c125dde --- /dev/null +++ b/docs/ru/docs/how-to/index.md @@ -0,0 +1,13 @@ +# Как сделать — Рецепты { #how-to-recipes } + +Здесь вы найдете разные рецепты и руководства «как сделать» по различным темам. + +Большинство из этих идей более-менее независимы, и в большинстве случаев вам стоит изучать их только если они напрямую относятся к вашему проекту. + +Если что-то кажется интересным и полезным для вашего проекта, смело изучайте; в противном случае, вероятно, можно просто пропустить. + +/// tip | Совет + +Если вы хотите изучить FastAPI структурированно (рекомендуется), вместо этого читайте [Учебник — Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} по главам. + +/// diff --git a/docs/ru/docs/how-to/separate-openapi-schemas.md b/docs/ru/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 0000000000..5b12140167 --- /dev/null +++ b/docs/ru/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,104 @@ +# Разделять схемы OpenAPI для входа и выхода или нет { #separate-openapi-schemas-for-input-and-output-or-not } + +При использовании **Pydantic v2** сгенерированный OpenAPI становится чуть более точным и **корректным**, чем раньше. 😎 + +На самом деле, в некоторых случаях в OpenAPI будет даже **две JSON схемы** для одной и той же Pydantic‑модели: для входа и для выхода — в зависимости от наличия **значений по умолчанию**. + +Посмотрим, как это работает, и как это изменить при необходимости. + +## Pydantic‑модели для входа и выхода { #pydantic-models-for-input-and-output } + +Предположим, у вас есть Pydantic‑модель со значениями по умолчанию, как здесь: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### Модель для входа { #model-for-input } + +Если использовать эту модель как входную, как здесь: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +…то поле `description` **не будет обязательным**, потому что у него значение по умолчанию `None`. + +### Входная модель в документации { #input-model-in-docs } + +В документации это видно: у поля `description` нет **красной звёздочки** — оно не отмечено как обязательное: + +
+ +
+ +### Модель для выхода { #model-for-output } + +Но если использовать ту же модель как выходную, как здесь: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +…то, поскольку у `description` есть значение по умолчанию, даже если вы **ничего не вернёте** для этого поля, оно всё равно будет иметь это **значение по умолчанию**. + +### Модель для данных ответа { #model-for-output-response-data } + +Если поработать с интерактивной документацией и посмотреть ответ, то, хотя код ничего не добавил в одно из полей `description`, JSON‑ответ содержит значение по умолчанию (`null`): + +
+ +
+ +Это означает, что у него **всегда будет какое‑то значение**, просто иногда это значение может быть `None` (или `null` в JSON). + +Следовательно, клиентам, использующим ваш API, не нужно проверять наличие этого значения: они могут **исходить из того, что поле всегда присутствует**, а в некоторых случаях имеет значение по умолчанию `None`. + +В OpenAPI это описывается тем, что поле помечается как **обязательное**, поскольку оно всегда присутствует. + +Из‑за этого JSON Schema для модели может отличаться в зависимости от использования для **входа** или **выхода**: + +* для **входа** `description` не будет обязательным +* для **выхода** оно будет **обязательным** (и при этом может быть `None`, или, в терминах JSON, `null`) + +### Выходная модель в документации { #model-for-output-in-docs } + +В документации это тоже видно, что **оба**: `name` и `description`, помечены **красной звёздочкой** как **обязательные**: + +
+ +
+ +### Модели для входа и выхода в документации { #model-for-input-and-output-in-docs } + +Если посмотреть все доступные схемы (JSON Schema) в OpenAPI, вы увидите две: `Item-Input` и `Item-Output`. + +Для `Item-Input` поле `description` **не является обязательным** — красной звёздочки нет. + +А для `Item-Output` `description` **обязательно** — красная звёздочка есть. + +
+ +
+ +Благодаря этой возможности **Pydantic v2** документация вашего API становится более **точной**; если у вас есть сгенерированные клиенты и SDK, они тоже будут точнее, с лучшим **удобством для разработчиков** и большей консистентностью. 🎉 + +## Не разделять схемы { #do-not-separate-schemas } + +Однако бывают случаи, когда вы хотите иметь **одну и ту же схему для входа и выхода**. + +Главный сценарий — когда у вас уже есть сгенерированный клиентский код/SDK, и вы пока не хотите обновлять весь этот автогенерируемый код/SDK (рано или поздно вы это сделаете, но не сейчас). + +В таком случае вы можете отключить эту функциональность в FastAPI с помощью параметра `separate_input_output_schemas=False`. + +/// info | Информация + +Поддержка `separate_input_output_schemas` появилась в FastAPI `0.102.0`. 🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### Одна и та же схема для входной и выходной моделей в документации { #same-schema-for-input-and-output-models-in-docs } + +Теперь для этой модели будет одна общая схема и для входа, и для выхода — только `Item`, и в ней `description` будет **не обязательным**: + +
+ +
+ +Это то же поведение, что и в Pydantic v1. 🤓 diff --git a/docs/ru/docs/how-to/testing-database.md b/docs/ru/docs/how-to/testing-database.md new file mode 100644 index 0000000000..18f4deeca5 --- /dev/null +++ b/docs/ru/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Тестирование базы данных { #testing-a-database } + +Вы можете изучить базы данных, SQL и SQLModel в документации SQLModel. 🤓 + +Есть мини-руководство по использованию SQLModel с FastAPI. ✨ + +В этом руководстве есть раздел о тестировании SQL-баз данных. 😎 diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md index 3aa4d82d03..75cd63223d 100644 --- a/docs/ru/docs/index.md +++ b/docs/ru/docs/index.md @@ -1,4 +1,4 @@ -# FastAPI +# FastAPI { #fastapi } - -

- FastAPI -

-

- Ìlànà wẹ́ẹ́bù FastAPI, iṣẹ́ gíga, ó rọrùn láti kọ̀, o yára láti kóòdù, ó sì ṣetán fún iṣelọpọ ní lílo -

-

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

- ---- - -**Àkọsílẹ̀**: https://fastapi.tiangolo.com - -**Orisun Kóòdù**: https://github.com/fastapi/fastapi - ---- - -FastAPI jẹ́ ìgbàlódé, tí ó yára (iṣẹ-giga), ìlànà wẹ́ẹ́bù fún kikọ àwọn API pẹ̀lú Python èyí tí ó da lori àwọn ìtọ́kasí àmì irúfẹ́ Python. - -Àwọn ẹya pàtàkì ni: - -* **Ó yára**: Iṣẹ tí ó ga púpọ̀, tí ó wa ni ibamu pẹ̀lú **NodeJS** àti **Go** (ọpẹ si Starlette àti Pydantic). [Ọkan nínú àwọn ìlànà Python ti o yára jùlọ ti o wa](#isesi). -* **Ó yára láti kóòdù**: O mu iyara pọ si láti kọ àwọn ẹya tuntun kóòdù nipasẹ "Igba ìdá ọgọ́rùn-ún" (i.e. 200%) si "ọ̀ọ́dúrún ìdá ọgọ́rùn-ún" (i.e. 300%). -* **Àìtọ́ kékeré**: O n din aṣiṣe ku bi ọgbon ìdá ọgọ́rùn-ún (i.e. 40%) ti eda eniyan (oṣiṣẹ kóòdù) fa. * -* **Ọgbọ́n àti ìmọ̀**: Atilẹyin olootu nla. Ìparí nibi gbogbo. Àkókò díẹ̀ nipa wíwá ibi tí ìṣòro kóòdù wà. -* **Irọrun**: A kọ kí ó le rọrun láti lo àti láti kọ ẹkọ nínú rè. Ó máa fún ọ ní àkókò díẹ̀ látı ka àkọsílẹ. -* **Ó kúkurú ní kikọ**: Ó dín àtúnkọ àti àtúntò kóòdù kù. Ìkéde àṣàyàn kọ̀ọ̀kan nínú rẹ̀ ní ọ̀pọ̀lọpọ̀ àwọn ìlò. O ṣe iranlọwọ láti má ṣe ní ọ̀pọ̀lọpọ̀ àṣìṣe. -* **Ó lágbára**: Ó ń ṣe àgbéjáde kóòdù tí ó ṣetán fún ìṣelọ́pọ̀. Pẹ̀lú àkọsílẹ̀ tí ó máa ṣàlàyé ara rẹ̀ fún ẹ ní ìbáṣepọ̀ aládàáṣiṣẹ́ pẹ̀lú rè. -* **Ajohunše/Ìtọ́kasí**: Ó da lori (àti ibamu ni kikun pẹ̀lú) àwọn ìmọ ajohunše/ìtọ́kasí fún àwọn API: OpenAPI (èyí tí a mọ tẹlẹ si Swagger) àti JSON Schema. - -* iṣiro yi da lori àwọn idanwo tí ẹgbẹ ìdàgbàsókè FastAPI ṣe, nígbàtí wọn kọ àwọn ohun elo iṣelọpọ kóòdù pẹ̀lú rẹ. - -## Àwọn onígbọ̀wọ́ - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Àwọn onígbọ̀wọ́ míràn - -## Àwọn ero àti èsì - -"_[...] Mò ń lo **FastAPI** púpọ̀ ní lẹ́nu àìpẹ́ yìí. [...] Mo n gbero láti lo o pẹ̀lú àwọn ẹgbẹ mi fún gbogbo iṣẹ **ML wa ni Microsoft**. Diẹ nínú wọn ni afikun ti ifilelẹ àwọn ẹya ara ti ọja **Windows** wa pẹ̀lú àwọn ti **Office**._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_A gba àwọn ohun èlò ìwé afọwọkọ **FastAPI** tí kò yí padà láti ṣẹ̀dá olùpín **REST** tí a lè béèrè lọ́wọ́ rẹ̀ láti gba **àsọtẹ́lẹ̀**. [fún Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** ni inudidun láti kede itusilẹ orisun kóòdù ti ìlànà iṣọkan **iṣakoso Ìṣòro** wa: **Ìfiránṣẹ́**! [a kọ pẹ̀lú **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_Inú mi dùn púpọ̀ nípa **FastAPI**. Ó mú inú ẹnì dùn púpọ̀!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Ní tòótọ́, ohun tí o kọ dára ó sì tún dán. Ní ọ̀pọ̀lọpọ̀ ọ̀nà, ohun tí mo fẹ́ kí **Hug** jẹ́ nìyẹn - ó wúni lórí gan-an láti rí ẹnìkan tí ó kọ́ nǹkan bí èyí._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_Ti o ba n wa láti kọ ọkan **ìlànà igbalode** fún kikọ àwọn REST API, ṣayẹwo **FastAPI** [...] Ó yára, ó rọrùn láti lò, ó sì rọrùn láti kọ́[...]_" - -"_A ti yipada si **FastAPI** fún **APIs** wa [...] Mo lérò pé wà á fẹ́ràn rẹ̀ [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -"_Ti ẹnikẹni ba n wa láti kọ iṣelọpọ API pẹ̀lú Python, èmi yóò ṣe'dúró fún **FastAPI**. Ó jẹ́ ohun tí **àgbékalẹ̀ rẹ̀ lẹ́wà**, **ó rọrùn láti lò** àti wipe ó ni **ìwọ̀n gíga**, o tí dí **bọtini paati** nínú alakọkọ API ìdàgbàsókè kikọ fún wa, àti pe o ni ipa lori adaṣiṣẹ àti àwọn iṣẹ gẹ́gẹ́ bíi Onímọ̀-ẹ̀rọ TAC tí órí Íńtánẹ́ẹ̀tì_" - -
Deon Pillsbury - Cisco (ref)
- ---- - -## **Typer**, FastAPI ti CLIs - - - -Ti o ba n kọ ohun èlò CLI láti ṣeé lọ nínú ohun èlò lori ebute kọmputa dipo API, ṣayẹwo **Typer**. - -**Typer** jẹ́ àbúrò ìyá FastAPI kékeré. Àti pé wọ́n kọ́ láti jẹ́ **FastAPI ti CLIs**. ⌨️ 🚀 - -## Èròjà - -FastAPI dúró lórí àwọn èjìká tí àwọn òmíràn: - -* Starlette fún àwọn ẹ̀yà ayélujára. -* Pydantic fún àwọn ẹ̀yà àkójọf'áyẹ̀wò. - -## Fifi sórí ẹrọ - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
-Iwọ yóò tún nílò olupin ASGI, fún iṣelọpọ bii Uvicorn tabi Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Àpẹẹrẹ - -### Ṣẹ̀dá rẹ̀ - -* Ṣẹ̀dá fáìlì `main.py (èyí tíí ṣe, akọkọ.py)` pẹ̀lú: - -```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} -``` - -
-Tàbí lò async def... - -Tí kóòdù rẹ̀ bá ń lò `async` / `await`, lò `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} -``` - -**Akiyesi**: - -Tí o kò bá mọ̀, ṣàyẹ̀wò ibi tí a ti ní _"In a hurry?"_ (i.e. _"Ní kíákíá?"_) nípa `async` and `await` nínú àkọsílẹ̀. - -
- -### Mu ṣiṣẹ - -Mú olupin ṣiṣẹ pẹ̀lú: - -
- -```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. -``` - -
- -
-Nipa aṣẹ kóòdù náà uvicorn main:app --reload... - -Àṣẹ `uvicorn main:app` ń tọ́ka sí: - -* `main`: fáìlì náà 'main.py' (Python "module"). -* `app` jẹ object( i.e. nǹkan) tí a ṣẹ̀dá nínú `main.py` pẹ̀lú ilà `app = FastAPI()`. -* `--reload`: èyí yóò jẹ́ ki olupin tún bẹ̀rẹ̀ lẹ́hìn àwọn àyípadà kóòdù. Jọ̀wọ́, ṣe èyí fún ìdàgbàsókè kóòdù nìkan, má ṣe é ṣe lori àgbéjáde kóòdù tabi fún iṣelọpọ kóòdù. - - -
- -### Ṣayẹwo rẹ - -Ṣii aṣàwákiri kọ̀ǹpútà rẹ ni http://127.0.0.1:8000/items/5?q=somequery. - -Ìwọ yóò sì rí ìdáhùn JSON bíi: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -O tí ṣẹ̀dá API èyí tí yóò: - -* Gbà àwọn ìbéèrè HTTP ni àwọn _ipa ọ̀nà_ `/` àti `/items/{item_id}`. -* Èyí tí àwọn _ipa ọ̀nà_ (i.e. _paths_) méjèèjì gbà àwọn iṣẹ `GET` (a tun mọ si _àwọn ọna_ HTTP). -* Èyí tí _ipa ọ̀nà_ (i.e. _paths_) `/items/{item_id}` ní _àwọn ohun-ini ipa ọ̀nà_ tí ó yẹ kí ó jẹ́ `int` i.e. `ÒǸKÀ`. -* Èyí tí _ipa ọ̀nà_ (i.e. _paths_) `/items/{item_id}` ní àṣàyàn `str` _àwọn ohun-ini_ (i.e. _query parameter_) `q`. - -### Ìbáṣepọ̀ àkọsílẹ̀ API - -Ní báyìí, lọ sí http://127.0.0.1:8000/docs. - -Lẹ́yìn náà, iwọ yóò rí ìdáhùn àkọsílẹ̀ API tí ó jẹ́ ìbáṣepọ̀ alaifọwọyi/aládàáṣiṣẹ́ (tí a pèṣè nípaṣẹ̀ Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Ìdàkejì àkọsílẹ̀ API - -Ní báyìí, lọ sí http://127.0.0.1:8000/redoc. - -Wà á rí àwọn àkọsílẹ̀ aládàáṣiṣẹ́ mìíràn (tí a pese nipasẹ ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Àpẹẹrẹ ìgbésókè mìíràn - -Ní báyìí ṣe àtúnṣe fáìlì `main.py` láti gba kókó èsì láti inú ìbéèrè `PUT`. - -Ní báyìí, ṣe ìkéde kókó èsì API nínú kóòdù rẹ nipa lílo àwọn ìtọ́kasí àmì irúfẹ́ Python, ọpẹ́ pàtàkìsi sí 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} -``` - -Olupin yóò tún ṣe àtúnṣe laifọwọyi/aládàáṣiṣẹ́ (nítorí wípé ó se àfikún `-reload` si àṣẹ kóòdù `uvicorn` lókè). - -### Ìbáṣepọ̀ ìgbésókè àkọsílẹ̀ API - -Ní báyìí, lọ sí http://127.0.0.1:8000/docs. - -* Ìbáṣepọ̀ àkọsílẹ̀ API yóò ṣe imudojuiwọn àkọsílẹ̀ API laifọwọyi, pẹ̀lú kókó èsì ìdáhùn API tuntun: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Tẹ bọtini "Gbiyanju rẹ" i.e. "Try it out", yóò gbà ọ́ láàyè láti jẹ́ kí ó tẹ́ àlàyé tí ó nílò kí ó le sọ̀rọ̀ tààrà pẹ̀lú API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Lẹhinna tẹ bọtini "Ṣiṣe" i.e. "Execute", olùmúlò (i.e. user interface) yóò sọrọ pẹ̀lú API rẹ, yóò ṣe afiranṣẹ àwọn èròjà, pàápàá jùlọ yóò gba àwọn àbájáde yóò si ṣafihan wọn loju ìbòjú: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Ìdàkejì ìgbésókè àkọsílẹ̀ API - -Ní báyìí, lọ sí http://127.0.0.1:8000/redoc. - -* Ìdàkejì àkọsílẹ̀ API yóò ṣ'afihan ìbéèrè èròjà/pàrámítà tuntun àti kókó èsì ti API: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Àtúnyẹ̀wò - -Ni akopọ, ìwọ yóò kéde ni **kete** àwọn iru èròjà/pàrámítà, kókó èsì API, abbl (i.e. àti bẹbẹ lọ), bi àwọn èròjà iṣẹ. - -O ṣe ìyẹn pẹ̀lú irúfẹ́ àmì ìtọ́kasí ìgbàlódé Python. - -O ò nílò láti kọ́ síńtáàsì tuntun, ìlànà tàbí ọ̀wọ́ kíláàsì kan pàtó, abbl (i.e. àti bẹbẹ lọ). - -Ìtọ́kasí **Python** - -Fún àpẹẹrẹ, fún `int`: - -```Python -item_id: int -``` - -tàbí fún àwòṣe `Item` tí ó nira díẹ̀ síi: - -```Python -item: Item -``` - -... àti pẹ̀lú ìkéde kan ṣoṣo yẹn ìwọ yóò gbà: - -* Atilẹyin olootu, pẹ̀lú: - * Pipari. - * Àyẹ̀wò irúfẹ́ àmì ìtọ́kasí. -* Ìfọwọ́sí àkójọf'áyẹ̀wò (i.e. data): - * Aṣiṣe alaifọwọyi/aládàáṣiṣẹ́ àti aṣiṣe ti ó hàn kedere nígbàtí àwọn àkójọf'áyẹ̀wò (i.e. data) kò wulo tabi tí kò fẹsẹ̀ múlẹ̀. - * Ìfọwọ́sí fún ohun elo JSON tí ó jìn gan-an. -* Ìyípadà tí input àkójọf'áyẹ̀wò: tí ó wà láti nẹtiwọọki si àkójọf'áyẹ̀wò àti irúfẹ́ àmì ìtọ́kasí Python. Ó ń ka láti: - * JSON. - * èròjà ọ̀nà tí ò gbé gbà. - * èròjà ìbéèrè. - * Àwọn Kúkì - * Àwọn Àkọlé - * Àwọn Fọọmu - * Àwọn Fáìlì -* Ìyípadà èsì àkójọf'áyẹ̀wò: yíyípadà láti àkójọf'áyẹ̀wò àti irúfẹ́ àmì ìtọ́kasí Python si nẹtiwọọki (gẹ́gẹ́ bí JSON): - * Yí irúfẹ́ àmì ìtọ́kasí padà (`str`, `int`, `float`, `bool`, `list`, abbl i.e. àti bèbè ló). - * Àwọn ohun èlò `datetime`. - * Àwọn ohun èlò `UUID`. - * Àwọn awoṣẹ́ ibi ìpamọ́ àkójọf'áyẹ̀wò. - * ...àti ọ̀pọ̀lọpọ̀ díẹ̀ síi. -* Ìbáṣepọ̀ àkọsílẹ̀ API aládàáṣiṣẹ́, pẹ̀lú ìdàkejì àgbékalẹ̀-àwọn-olùmúlò (i.e user interfaces) méjì: - * Àgbékalẹ̀-olùmúlò Swagger. - * ReDoc. - ---- - -Nisinsin yi, tí ó padà sí àpẹẹrẹ ti tẹ́lẹ̀, **FastAPI** yóò: - -* Fọwọ́ sí i pé `item_id` wà nínú ọ̀nà ìbéèrè HTTP fún `GET` àti `PUT`. -* Fọwọ́ sí i pé `item_id` jẹ́ irúfẹ́ àmì ìtọ́kasí `int` fún ìbéèrè HTTP `GET` àti `PUT`. - * Tí kìí bá ṣe bẹ, oníbàárà yóò ríi àṣìṣe tí ó wúlò, kedere. -* Ṣàyẹ̀wò bóyá ìbéèrè àṣàyàn pàrámítà kan wà tí orúkọ rẹ̀ ń jẹ́ `q` (gẹ́gẹ́ bíi `http://127.0.0.1:8000/items/foo?q=somequery`) fún ìbéèrè HTTP `GET`. - * Bí wọ́n ṣe kéde pàrámítà `q` pẹ̀lú `= None`, ó jẹ́ àṣàyàn (i.e optional). - * Láìsí `None` yóò nílò (gẹ́gẹ́ bí kókó èsì ìbéèrè HTTP ṣe wà pẹ̀lú `PUT`). -* Fún àwọn ìbéèrè HTTP `PUT` sí `/items/{item_id}`, kà kókó èsì ìbéèrè HTTP gẹ́gẹ́ bí JSON: - * Ṣàyẹ̀wò pé ó ní àbùdá tí ó nílò èyí tíí ṣe `name` i.e. `orúkọ` tí ó yẹ kí ó jẹ́ `str`. - * Ṣàyẹ̀wò pé ó ní àbùdá tí ó nílò èyí tíí ṣe `price` i.e. `iye` tí ó gbọ́dọ̀ jẹ́ `float`. - * Ṣàyẹ̀wò pé ó ní àbùdá àṣàyàn `is_offer`, tí ó yẹ kí ó jẹ́ `bool`, tí ó bá wà níbẹ̀. - * Gbogbo èyí yóò tún ṣiṣẹ́ fún àwọn ohun èlò JSON tí ó jìn gidi gan-an. -* Yìí padà láti àti sí JSON lai fi ọwọ́ yi. -* Ṣe àkọsílẹ̀ ohun gbogbo pẹ̀lú OpenAPI, èyí tí yóò wà ní lílo nípaṣẹ̀: - * Àwọn ètò àkọsílẹ̀ ìbáṣepọ̀. - * Aládàáṣiṣẹ́ oníbárà èlètò tíí ṣẹ̀dá kóòdù, fún ọ̀pọ̀lọpọ̀ àwọn èdè. -* Pese àkọsílẹ̀ òní ìbáṣepọ̀ ti àwọn àgbékalẹ̀ ayélujára méjì tààrà. - ---- - -A ń ṣẹ̀ṣẹ̀ ń mú ẹyẹ bọ́ làpò ní, ṣùgbọ́n ó ti ni òye bí gbogbo rẹ̀ ṣe ń ṣiṣẹ́. - -Gbiyanju láti yí ìlà padà pẹ̀lú: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...láti: - -```Python - ... "item_name": item.name ... -``` - -...ṣí: - -```Python - ... "item_price": item.price ... -``` - -.. kí o sì wo bí olóòtú rẹ yóò ṣe parí àwọn àbùdá náà fúnra rẹ̀, yóò sì mọ irúfẹ́ wọn: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -Fún àpẹẹrẹ pípé síi pẹ̀lú àwọn àbùdá mìíràn, wo Ìdánilẹ́kọ̀ọ́ - Ìtọ́sọ́nà Olùmúlò. - -**Itaniji gẹ́gẹ́ bí isọ'ye**: ìdánilẹ́kọ̀ọ́ - itọsọna olùmúlò pẹ̀lú: - -* Ìkéde àṣàyàn **pàrámítà** láti àwọn oriṣiriṣi ibòmíràn gẹ́gẹ́ bíi: àwọn **àkọlé èsì API**, **kúkì**, **ààyè fọọmu**, àti **fáìlì**. -* Bíi ó ṣe lé ṣètò **àwọn ìdíwọ́ ìfọwọ́sí** bí `maximum_length` tàbí `regex`. -* Ó lágbára púpọ̀ ó sì rọrùn láti lo ètò **Àfikún Ìgbẹ́kẹ̀lé Kóòdù**. -* Ààbò àti ìfọwọ́sowọ́pọ̀, pẹ̀lú àtìlẹ́yìn fún **OAuth2** pẹ̀lú **àmì JWT** àti **HTTP Ipilẹ ìfọwọ́sowọ́pọ̀**. -* Àwọn ìlànà ìlọsíwájú (ṣùgbọ́n tí ó rọrùn bákan náà) fún ìkéde **àwọn àwòṣe JSON tó jinlẹ̀** (ọpẹ́ pàtàkìsi sí Pydantic). -* Iṣọpọ **GraphQL** pẹ̀lú Strawberry àti àwọn ohun èlò ìwé kóòdù afọwọkọ mìíràn tí kò yí padà. -* Ọpọlọpọ àwọn àfikún àwọn ẹ̀yà (ọpẹ́ pàtàkìsi sí Starlette) bí: - * **WebSockets** - * àwọn ìdánwò tí ó rọrùn púpọ̀ lórí HTTPX àti `pytest` - * **CORS** - * **Cookie Sessions** - * ...àti síwájú síi. - -## Ìṣesí - -Àwọn àlá TechEmpower fi hàn pé **FastAPI** ń ṣiṣẹ́ lábẹ́ Uvicorn gẹ́gẹ́ bí ọ̀kan lára àwọn ìlànà Python tí ó yára jùlọ tí ó wà, ní ìsàlẹ̀ Starlette àti Uvicorn fúnra wọn (tí FastAPI ń lò fúnra rẹ̀). (*) - -Láti ní òye síi nípa rẹ̀, wo abala àwọn Àlá. - -## Àṣàyàn Àwọn Àfikún Ìgbẹ́kẹ̀lé Kóòdù - -Èyí tí Pydantic ń lò: - -* email-validator - fún ifọwọsi ímeèlì. -* pydantic-settings - fún ètò ìsàkóso. -* pydantic-extra-types - fún àfikún oríṣi láti lọ pẹ̀lú Pydantic. - -Èyí tí Starlette ń lò: - -* httpx - Nílò tí ó bá fẹ́ láti lọ `TestClient`. -* jinja2 - Nílò tí ó bá fẹ́ láti lọ iṣeto awoṣe aiyipada. -* python-multipart - Nílò tí ó bá fẹ́ láti ṣe àtìlẹ́yìn fún "àyẹ̀wò" fọọmu, pẹ̀lú `request.form()`. -* itsdangerous - Nílò fún àtìlẹ́yìn `SessionMiddleware`. -* pyyaml - Nílò fún àtìlẹ́yìn Starlette's `SchemaGenerator` (ó ṣe ṣe kí ó má nílò rẹ̀ fún FastAPI). - -Èyí tí FastAPI / Starlette ń lò: - -* uvicorn - Fún olupin tí yóò sẹ́ àmúyẹ àti tí yóò ṣe ìpèsè fún iṣẹ́ rẹ tàbí ohun èlò rẹ. -* orjson - Nílò tí ó bá fẹ́ láti lọ `ORJSONResponse`. -* ujson - Nílò tí ó bá fẹ́ láti lọ `UJSONResponse`. - -Ó lè fi gbogbo àwọn wọ̀nyí sórí ẹrọ pẹ̀lú `pip install "fastapi[all]"`. - -## Iwe-aṣẹ - -Iṣẹ́ yìí ni iwe-aṣẹ lábẹ́ àwọn òfin tí iwe-aṣẹ MIT. diff --git a/docs/yo/mkdocs.yml b/docs/yo/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/yo/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/docs/zh-hant/docs/about/index.md b/docs/zh-hant/docs/about/index.md new file mode 100644 index 0000000000..5dcee68f2d --- /dev/null +++ b/docs/zh-hant/docs/about/index.md @@ -0,0 +1,3 @@ +# 關於 FastAPI + +關於 FastAPI、其設計、靈感來源等更多資訊。 🤓 diff --git a/docs/zh-hant/docs/async.md b/docs/zh-hant/docs/async.md new file mode 100644 index 0000000000..09e2bf9949 --- /dev/null +++ b/docs/zh-hant/docs/async.md @@ -0,0 +1,442 @@ +# 並行與 async / await + +有關*路徑操作函式*的 `async def` 語法的細節與非同步 (asynchronous) 程式碼、並行 (concurrency) 與平行 (parallelism) 的一些背景知識。 + +## 趕時間嗎? + +TL;DR: + +如果你正在使用要求你以 `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 | 注意 + +你只能在 `async def` 建立的函式內使用 `await`。 + +/// + +--- + +如果你使用的是第三方函式庫並且它需要與某些外部資源(例如資料庫、API、檔案系統等)進行通訊,但不支援 `await`(目前大多數資料庫函式庫都是這樣),在這種情況下,你可以像平常一樣使用 `def` 宣告*路徑操作函式*,如下所示: + +```Python hl_lines="2" +@app.get('/') +def results(): + results = some_library() + return results +``` + +--- + +如果你的應用程式不需要與外部資源進行任何通訊並等待其回應,請使用 `async def`。 + +--- + +如果你不確定該用哪個,直接用 `def` 就好。 + +--- + +**注意**:你可以在*路徑操作函式*中混合使用 `def` 和 `async def` ,並使用最適合你需求的方式來定義每個函式。FastAPI 會幫你做正確的處理。 + +無論如何,在上述哪種情況下,FastAPI 仍將以非同步方式運行,並且速度非常快。 + +但透過遵循上述步驟,它將能進行一些效能最佳化。 + +## 技術細節 + +現代版本的 Python 支援使用 **「協程」** 的 **`async` 和 `await`** 語法來寫 **「非同步程式碼」**。 + +接下來我們逐一介紹: + +* **非同步程式碼** +* **`async` 和 `await`** +* **協程** + +## 非同步程式碼 + +非同步程式碼僅意味著程式語言 💬 有辦法告訴電腦/程式 🤖 在程式碼中的某個點,它 🤖 需要等待某些事情完成。讓我們假設這些事情被稱為「慢速檔案」📝。 + +因此,在等待「慢速檔案」📝 完成的這段時間,電腦可以去處理一些其他工作。 + +接著程式 🤖 會在有空檔時回來查看是否有等待的工作已經完成,並執行必要的後續操作。 + +接下來,它 🤖 完成第一個工作(例如我們的「慢速檔案」📝)並繼續執行相關的所有操作。 +這個「等待其他事情」通常指的是一些相對較慢的(與處理器和 RAM 記憶體的速度相比)的 I/O 操作,比如說: + +* 透過網路傳送來自用戶端的資料 +* 從網路接收來自用戶端的資料 +* 從磁碟讀取檔案內容 +* 將內容寫入磁碟 +* 遠端 API 操作 +* 資料庫操作 +* 資料庫查詢 +* 等等 + +由於大部分的執行時間都消耗在等待 I/O 操作上,因此這些操作被稱為 "I/O 密集型" 操作。 + +之所以稱為「非同步」,是因為電腦/程式不需要與那些耗時的任務「同步」,等待任務完成的精確時間,然後才能取得結果並繼續工作。 + +相反地,非同步系統在任務完成後,可以讓任務稍微等一下(幾微秒),等待電腦/程式完成手頭上的其他工作,然後再回來取得結果繼續進行。 + +相對於「非同步」(asynchronous),「同步」(synchronous)也常被稱作「順序性」(sequential),因為電腦/程式會依序執行所有步驟,即便這些步驟涉及等待,才會切換到其他任務。 + +### 並行與漢堡 + +上述非同步程式碼的概念有時也被稱為「並行」,它不同於「平行」。 + +並行和平行都與 "不同的事情或多或少同時發生" 有關。 + +但並行和平行之間的細節是完全不同的。 + +為了理解差異,請想像以下有關漢堡的故事: + +### 並行漢堡 + +你和你的戀人去速食店,排隊等候時,收銀員正在幫排在你前面的人點餐。😍 + + + +輪到你了,你給你與你的戀人點了兩個豪華漢堡。🍔🍔 + + + +收銀員通知廚房準備你的漢堡(儘管他們還在為前面其他顧客準備食物)。 + + + +之後你完成付款。💸 + +收銀員給你一個號碼牌。 + + + +在等待漢堡的同時,你可以與戀人選一張桌子,然後坐下來聊很長一段時間(因為漢堡十分豪華,準備特別費工。) + +這段時間,你還能欣賞你的戀人有多麼的可愛、聰明與迷人。✨😍✨ + + + +當你和戀人邊聊天邊等待時,你會不時地查看櫃檯上的顯示的號碼,確認是否已經輪到你了。 + +然後在某個時刻,終於輪到你了。你走到櫃檯,拿了漢堡,然後回到桌子上。 + + + +你和戀人享用這頓大餐,整個過程十分開心✨ + + + +/// info + +漂亮的插畫來自 Ketrina Thompson. 🎨 + +/// + +--- + +想像你是故事中的電腦或程式 🤖。 + +當你排隊時,你在放空😴,等待輪到你,沒有做任何「生產性」的事情。但這沒關係,因為收銀員只是接單(而不是準備食物),所以排隊速度很快。 + +然後,當輪到你時,你開始做真正「有生產力」的工作,處理菜單,決定你想要什麼,替戀人選擇餐點,付款,確認你給了正確的帳單或信用卡,檢查你是否被正確收費,確認訂單中的項目是否正確等等。 + +但是,即使你還沒有拿到漢堡,你與收銀員的工作已經「暫停」了 ⏸,因為你必須等待 🕙 漢堡準備好。 + +但當你離開櫃檯,坐到桌子旁,拿著屬於你的號碼等待時,你可以把注意力 🔀 轉移到戀人身上,並開始「工作」⏯ 🤓——也就是和戀人調情 😍。這時你又開始做一些非常「有生產力」的事情。 + +接著,收銀員 💁 將你的號碼顯示在櫃檯螢幕上,並告訴你「漢堡已經做好了」。但你不會瘋狂地立刻跳起來,因為顯示的號碼變成了你的。你知道沒有人會搶走你的漢堡,因為你有自己的號碼,他們也有他們的號碼。 + +所以你會等戀人講完故事(完成當前的工作 ⏯/正在進行的任務 🤓),然後微笑著溫柔地說你要去拿漢堡了 ⏸。 + +然後你走向櫃檯 🔀,回到已經完成的最初任務 ⏯,拿起漢堡,說聲謝謝,並帶回桌上。這就結束了與櫃檯的互動步驟/任務 ⏹,接下來會產生一個新的任務,「吃漢堡」 🔀 ⏯,而先前的「拿漢堡」任務已經完成了 ⏹。 + +### 平行漢堡 + +現在,讓我們來想像這裡不是「並行漢堡」,而是「平行漢堡」。 + +你和戀人一起去吃平行的速食餐。 + +你們站在隊伍中,前面有幾位(假設有 8 位)既是收銀員又是廚師的員工,他們同時接單並準備餐點。 + +所有排在你前面的人都在等著他們的漢堡準備好後才會離開櫃檯,因為每位收銀員在接完單後,馬上會去準備漢堡,然後才回來處理下一個訂單。 + + + +終於輪到你了,你為你和你的戀人點了兩個非常豪華的漢堡。 + +你付款了 💸。 + + + +收銀員走進廚房準備食物。 + +你站在櫃檯前等待 🕙,以免其他人先拿走你的漢堡,因為這裡沒有號碼牌系統。 + + + +由於你和戀人都忙著不讓別人搶走你的漢堡,等漢堡準備好時,你根本無法專心和戀人互動。😞 + +這是「同步」(synchronous)工作,你和收銀員/廚師 👨‍🍳 是「同步化」的。你必須等到 🕙 收銀員/廚師 👨‍🍳 完成漢堡並交給你的那一刻,否則別人可能會拿走你的餐點。 + + + +最終,經過長時間的等待 🕙,收銀員/廚師 👨‍🍳 拿著漢堡回來了。 + + + +你拿著漢堡,和你的戀人回到餐桌。 + +你們僅僅是吃完漢堡,然後就結束了。⏹ + + + +整個過程中沒有太多的談情說愛,因為大部分時間 🕙 都花在櫃檯前等待。😞 + +/// info + +漂亮的插畫來自 Ketrina Thompson. 🎨 + +/// + +--- + +在這個平行漢堡的情境下,你是一個程式 🤖 且有兩個處理器(你和戀人),兩者都在等待 🕙 並專注於等待櫃檯上的餐點 🕙,等待的時間非常長。 + +這家速食店有 8 個處理器(收銀員/廚師)。而並行漢堡店可能只有 2 個處理器(一位收銀員和一位廚師)。 + +儘管如此,最終的體驗並不是最理想的。😞 + +--- + +這是與漢堡類似的故事。🍔 + +一個更「現實」的例子,想像一間銀行。 + +直到最近,大多數銀行都有多位出納員 👨‍💼👨‍💼👨‍💼👨‍💼,以及一條長長的隊伍 🕙🕙🕙🕙🕙🕙🕙🕙。 + +所有的出納員都在一個接一個地滿足每位客戶的所有需求 👨‍💼⏯。 + +你必須長時間排隊 🕙,不然就會失去機會。 + +所以,你不會想帶你的戀人 😍 一起去銀行辦事 🏦。 + +### 漢堡結論 + +在「和戀人一起吃速食漢堡」的這個場景中,由於有大量的等待 🕙,使用並行系統 ⏸🔀⏯ 更有意義。 + +這也是大多數 Web 應用的情況。 + +許多用戶正在使用你的應用程式,而你的伺服器則在等待 🕙 這些用戶不那麼穩定的網路來傳送請求。 + +接著,再次等待 🕙 回應。 + +這種「等待」 🕙 通常以微秒來衡量,但累加起來,最終還是花費了很多等待時間。 + +這就是為什麼對於 Web API 來說,使用非同步程式碼 ⏸🔀⏯ 是非常有意義的。 + +這種類型的非同步性正是 NodeJS 成功的原因(儘管 NodeJS 不是平行的),這也是 Go 語言作為程式語言的一個強大優勢。 + +這與 **FastAPI** 所能提供的性能水平相同。 + +你可以同時利用並行性和平行性,進一步提升效能,這比大多數已測試的 NodeJS 框架都更快,並且與 Go 語言相當,而 Go 是一種更接近 C 的編譯語言(感謝 Starlette)。 + +### 並行比平行更好嗎? + +不是的!這不是故事的本意。 + +並行與平行不同。並行在某些 **特定** 的需要大量等待的情境下表現更好。正因如此,並行在 Web 應用程式開發中通常比平行更有優勢。但並不是所有情境都如此。 + +因此,為了平衡報導,想像下面這個短故事 + +> 你需要打掃一間又大又髒的房子。 + +*是的,這就是全部的故事。* + +--- + +這裡沒有任何需要等待 🕙 的地方,只需要在房子的多個地方進行大量的工作。 + +你可以像漢堡的例子那樣輪流進行,先打掃客廳,再打掃廚房,但由於你不需要等待 🕙 任何事情,只需要持續地打掃,輪流並不會影響任何結果。 + +無論輪流執行與否(並行),你都需要相同的工時完成任務,同時需要執行相同工作量。 + +但是,在這種情境下,如果你可以邀請8位前收銀員/廚師(現在是清潔工)來幫忙,每個人(加上你)負責房子的某個區域,這樣你就可以 **平行** 地更快完成工作。 + +在這個場景中,每個清潔工(包括你)都是一個處理器,完成工作的一部分。 + +由於大多數的執行時間都花在實際的工作上(而不是等待),而電腦中的工作由 CPU 完成,因此這些問題被稱為「CPU 密集型」。 + +--- + +常見的 CPU 密集型操作範例包括那些需要進行複雜數學計算的任務。 + +例如: + +* **音訊**或**圖像處理**; +* **電腦視覺**:一張圖片由數百萬個像素組成,每個像素有 3 個值/顏色,處理這些像素通常需要同時進行大量計算; +* **機器學習**: 通常需要大量的「矩陣」和「向量」運算。想像一個包含數字的巨大電子表格,並所有的數字同時相乘; +* **深度學習**: 這是機器學習的子領域,同樣適用。只不過這不僅僅是一張數字表格,而是大量的數據集合,並且在很多情況下,你會使用特殊的處理器來構建或使用這些模型。 + +### 並行 + 平行: Web + 機器學習 + +使用 **FastAPI**,你可以利用並行的優勢,這在 Web 開發中非常常見(這也是 NodeJS 的最大吸引力)。 + +但你也可以利用平行與多行程 (multiprocessing)(讓多個行程同時運行) 的優勢來處理機器學習系統中的 **CPU 密集型**工作。 + +這一點,再加上 Python 是 **資料科學**、機器學習,尤其是深度學習的主要語言,讓 **FastAPI** 成為資料科學/機器學習 Web API 和應用程式(以及許多其他應用程式)的絕佳選擇。 + +想了解如何在生產環境中實現這種平行性,請參見 [部屬](deployment/index.md){.internal-link target=_blank}。 + +## `async` 和 `await` + +現代 Python 版本提供一種非常直觀的方式定義非同步程式碼。這使得它看起來就像正常的「順序」程式碼,並在適當的時機「等待」。 + +當某個操作需要等待才能回傳結果,並且支援這些新的 Python 特性時,你可以像這樣編寫程式碼: + +```Python +burgers = await get_burgers(2) +``` + +這裡的關鍵是 `await`。它告訴 Python 必須等待 ⏸ `get_burgers(2)` 完成它的工作 🕙, 然後將結果儲存在 `burgers` 中。如此,Python 就可以在此期間去處理其他事情 🔀 ⏯ (例如接收另一個請求)。 + +要讓 `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`,Python Python 知道在該函式內需要注意 `await`,並且它可以「暫停」 ⏸ 執行該函式,然後執行其他任務 🔀 後回來。 + +當你想要呼叫 `async def` 函式時,必須使用「await」。因此,這樣寫將無法運行: + +```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 會知道如何正確處理這個問題。 + +但如果你想在沒有 FastAPI 的情況下使用 `async` / `await`,你也可以這樣做。 + +### 編寫自己的非同步程式碼 + +Starlette (和 **FastAPI**) 是基於 AnyIO 實作的,這使得它們與 Python 標準函式庫相容 asyncioTrio。 + +特別是,你可以直接使用 AnyIO 來處理更複雜的並行使用案例,這些案例需要你在自己的程式碼中使用更高階的模式。 + +即使你不使用 **FastAPI**,你也可以使用 AnyIO 來撰寫自己的非同步應用程式,並獲得高相容性及一些好處(例如結構化並行)。 + +### 其他形式的非同步程式碼 + +使用 `async` 和 `await` 的風格在語言中相對較新。 + +但它使處理異步程式碼變得更加容易。 + +相同的語法(或幾乎相同的語法)最近也被包含在現代 JavaScript(無論是瀏覽器還是 NodeJS)中。 + +但在此之前,處理異步程式碼要更加複雜和困難。 + +在較舊的 Python 版本中,你可能會使用多執行緒或 Gevent。但這些程式碼要更難以理解、調試和思考。 + +在較舊的 NodeJS / 瀏覽器 JavaScript 中,你會使用「回呼」,這可能會導致“回呼地獄”。 + +## 協程 + +**協程** 只是 `async def` 函式所回傳的非常特殊的事物名稱。Python 知道它是一個類似函式的東西,可以啟動它,並且在某個時刻它會結束,但它也可能在內部暫停 ⏸,只要遇到 `await`。 + +這種使用 `async` 和 `await` 的非同步程式碼功能通常被概括為「協程」。這與 Go 語言的主要特性「Goroutines」相似。 + +## 結論 + +讓我們再次回顧之前的句子: + +> 現代版本的 Python 支持使用 **"協程"** 的 **`async` 和 `await`** 語法來寫 **"非同步程式碼"**。 + +現在應該能明白其含意了。✨ + +這些就是驅動 FastAPI(通過 Starlette)運作的原理,也讓它擁有如此驚人的效能。 + +## 非常技術性的細節 + +/// warning + +你大概可以跳過這段。 + +這裡是有關 FastAPI 內部技術細節。 + +如果你有相當多的技術背景(例如協程、執行緒、阻塞等),並且對 FastAPI 如何處理 `async def` 與常規 `def` 感到好奇,請繼續閱讀。 + +/// + +### 路徑操作函数 + +當你使用 `def` 而不是 `async def` 宣告*路徑操作函式*時,該函式會在外部的執行緒池(threadpool)中執行,然後等待結果,而不是直接呼叫(因為這樣會阻塞伺服器)。 + +如果你來自於其他不以這種方式運作的非同步框架,而且你習慣於使用普通的 `def` 定義僅進行簡單計算的*路徑操作函式*,目的是獲得微小的性能增益(大約 100 奈秒),請注意,在 FastAPI 中,效果會完全相反。在這些情況下,最好使用 `async def`除非你的*路徑操作函式*執行阻塞的 I/O 的程式碼。 + +不過,在這兩種情況下,**FastAPI** [仍然很快](index.md#_11){.internal-link target=_blank}至少與你之前的框架相當(或者更快)。 + +### 依賴項(Dependencies) + +同樣適用於[依賴項](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/zh-hant/docs/deployment/cloud.md b/docs/zh-hant/docs/deployment/cloud.md new file mode 100644 index 0000000000..426937d3e4 --- /dev/null +++ b/docs/zh-hant/docs/deployment/cloud.md @@ -0,0 +1,13 @@ +# 在雲端部署 FastAPI + +你幾乎可以使用**任何雲端供應商**來部署你的 FastAPI 應用程式。 + +在大多數情況下,主要的雲端供應商都有部署 FastAPI 的指南。 + +## 雲端供應商 - 贊助商 + +一些雲端供應商 ✨ [**贊助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,這確保了 FastAPI 及其**生態系統**持續健康地**發展**。 + +這也展現了他們對 FastAPI 和其**社群**(包括你)的真正承諾,他們不僅希望為你提供**優質的服務**,還希望確保你擁有一個**良好且健康的框架**:FastAPI。🙇 + +你可能會想嘗試他們的服務,以下有他們的指南. diff --git a/docs/zh-hant/docs/deployment/index.md b/docs/zh-hant/docs/deployment/index.md new file mode 100644 index 0000000000..1726562b40 --- /dev/null +++ b/docs/zh-hant/docs/deployment/index.md @@ -0,0 +1,21 @@ +# 部署 + +部署 **FastAPI** 應用程式相對容易。 + +## 部署是什麼意思 + +**部署**應用程式指的是執行一系列必要的步驟,使其能夠**讓使用者存取和使用**。 + +對於一個 **Web API**,部署通常涉及將其放置在**遠端伺服器**上,並使用性能優良且穩定的**伺服器程式**,確保使用者能夠高效、無中斷地存取應用程式,且不會遇到問題。 + +這與**開發**階段形成鮮明對比,在**開發**階段,你會不斷更改程式碼、破壞程式碼、修復程式碼,然後停止和重新啟動伺服器等。 + +## 部署策略 + +根據你的使用場景和使用工具,有多種方法可以實現此目的。 + +你可以使用一些工具自行**部署伺服器**,你也可以使用能為你完成部分工作的**雲端服務**,或其他可能的選項。 + +我將向你展示在部署 **FastAPI** 應用程式時你可能應該記住的一些主要概念(儘管其中大部分適用於任何其他類型的 Web 應用程式)。 + +在接下來的部分中,你將看到更多需要記住的細節以及一些技巧。 ✨ diff --git a/docs/zh-hant/docs/environment-variables.md b/docs/zh-hant/docs/environment-variables.md new file mode 100644 index 0000000000..a1598fc018 --- /dev/null +++ b/docs/zh-hant/docs/environment-variables.md @@ -0,0 +1,298 @@ +# 環境變數 + +/// tip + +如果你已經知道什麼是「環境變數」並且知道如何使用它們,你可以放心跳過這一部分。 + +/// + +環境變數(也稱為「**env var**」)是一個獨立於 Python 程式碼**之外**的變數,它存在於**作業系統**中,可以被你的 Python 程式碼(或其他程式)讀取。 + +環境變數對於處理應用程式**設定**(作為 Python **安裝**的一部分等方面)非常有用。 + +## 建立和使用環境變數 + +你在 **shell(終端機)**中就可以**建立**和使用環境變數,並不需要用到 Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// 你可以使用以下指令建立一個名為 MY_NAME 的環境變數 +$ export MY_NAME="Wade Wilson" + +// 然後,你可以在其他程式中使用它,例如 +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | 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 程式: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// 這裡我們還沒有設定環境變數 +$ python main.py + +// 因為我們沒有設定環境變數,所以我們得到的是預設值 + +Hello World from Python + +// 但是如果我們事先建立過一個環境變數 +$ export MY_NAME="Wade Wilson" + +// 然後再次呼叫程式 +$ python main.py + +// 現在就可以讀取到環境變數了 + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// 這裡我們還沒有設定環境變數 +$ python main.py + +// 因為我們沒有設定環境變數,所以我們得到的是預設值 + +Hello World from Python + +// 但是如果我們事先建立過一個環境變數 +$ $Env: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 + +你可以在 The Twelve-Factor App: 配置中了解更多資訊。 + +/// + +## 型別和驗證 + +這些環境變數只能處理**文字字串**,因為它們是位於 Python 範疇之外的,必須與其他程式和作業系統的其餘部分相容(甚至與不同的作業系統相容,如 Linux、Windows、macOS)。 + +這意味著從環境變數中讀取的**任何值**在 Python 中都將是一個 `str`,任何型別轉換或驗證都必須在程式碼中完成。 + +你將在[進階使用者指南 - 設定和環境變數](./advanced/settings.md)中了解更多關於使用環境變數處理**應用程式設定**的資訊。 + +## `PATH` 環境變數 + +有一個**特殊的**環境變數稱為 **`PATH`**,作業系統(Linux、macOS、Windows)用它來查找要執行的程式。 + +`PATH` 變數的值是一個長字串,由 Linux 和 macOS 上的冒號 `:` 分隔的目錄組成,而在 Windows 上則是由分號 `;` 分隔的。 + +例如,`PATH` 環境變數可能如下所示: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +這意味著系統應該在以下目錄中查找程式: + +- `/usr/local/bin` +- `/usr/bin` +- `/bin` +- `/usr/sbin` +- `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +這意味著系統應該在以下目錄中查找程式: + +- `C:\Program Files\Python312\Scripts` +- `C:\Program Files\Python312` +- `C:\Windows\System32` + +//// + +當你在終端機中輸入一個**指令**時,作業系統會在 `PATH` 環境變數中列出的**每個目錄**中**查找**程式。 + +例如,當你在終端機中輸入 `python` 時,作業系統會在該列表中的**第一個目錄**中查找名為 `python` 的程式。 + +如果找到了,那麼作業系統將**使用它**;否則,作業系統會繼續在**其他目錄**中查找。 + +### 安裝 Python 並更新 `PATH` + +安裝 Python 時,可能會詢問你是否要更新 `PATH` 環境變數。 + +//// tab | Linux, macOS + +假設你安裝了 Python,並將其安裝在目錄 `/opt/custompython/bin` 中。 + +如果你選擇更新 `PATH` 環境變數,那麼安裝程式會將 `/opt/custompython/bin` 加入到 `PATH` 環境變數中。 + +它看起來大致會是這樣: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +如此一來,當你在終端機輸入 `python` 時,系統會在 `/opt/custompython/bin` 中找到 Python 程式(最後一個目錄)並使用它。 + +//// + +//// tab | Windows + +假設你安裝了 Python,並將其安裝在目錄 `C:\opt\custompython\bin` 中。 + +如果你選擇更新 `PATH` 環境變數(在 Python 安裝程式中,這個選項是名為 `Add Python x.xx to PATH` 的勾選框——譯者註),那麼安裝程式會將 `C:\opt\custompython\bin` 加入到 `PATH` 環境變數中。 + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +如此一來,當你在終端機輸入 `python` 時,系統會在 `C:\opt\custompython\bin` 中找到 Python 程式(最後一個目錄)並使用它。 + +//// + +因此,如果你輸入: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +系統會在 `/opt/custompython/bin` 中**找到** `python` 程式並執行它。 + +這大致等同於輸入以下指令: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +系統會在 `C:\opt\custompython\bin\python` 中**找到** `python` 程式並執行它。 + +這大致等同於輸入以下指令: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +當學習[虛擬環境](virtual-environments.md)時,這些資訊將會很有用。 + +## 結論 + +透過這個教學,你應該對**環境變數**是什麼以及如何在 Python 中使用它們有了基本的了解。 + +你也可以在環境變數 - 維基百科 (Wikipedia for Environment Variable) 中了解更多關於它們的資訊。 + +在許多情況下,環境變數的用途和適用性可能不會立刻顯現。但是在開發過程中,它們會在許多不同的場景中出現,因此瞭解它們是非常必要的。 + +例如,你在接下來的[虛擬環境](virtual-environments.md)章節中將需要這些資訊。 diff --git a/docs/zh-hant/docs/fastapi-cli.md b/docs/zh-hant/docs/fastapi-cli.md new file mode 100644 index 0000000000..b107e7e73f --- /dev/null +++ b/docs/zh-hant/docs/fastapi-cli.md @@ -0,0 +1,83 @@ +# FastAPI CLI + +**FastAPI CLI** 是一個命令列程式,能用來運行你的 FastAPI 應用程式、管理你的 FastAPI 專案等。 + +當你安裝 FastAPI(例如使用 `pip install "fastapi[standard]"`),它會包含一個叫做 `fastapi-cli` 的套件,這個套件提供了 `fastapi` 命令。 + +要運行你的 FastAPI 應用程式來進行開發,你可以使用 `fastapi dev` 命令: + +
+ +```console +$ fastapi dev main.py +INFO Using path main.py +INFO Resolved absolute path /home/user/code/awesomeapp/main.py +INFO Searching for package file structure from directories with __init__.py files +INFO Importing from /home/user/code/awesomeapp + + ╭─ Python module file ─╮ + │ │ + │ 🐍 main.py │ + │ │ + ╰──────────────────────╯ + +INFO Importing module main +INFO Found importable FastAPI app + + ╭─ Importable FastAPI app ─╮ + │ │ + │ from main import app │ + │ │ + ╰──────────────────────────╯ + +INFO Using import string main:app + + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + fastapi run + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [2265862] using WatchFiles +INFO: Started server process [2265873] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +`fastapi` 命令列程式就是 **FastAPI CLI**。 + +FastAPI CLI 接收你的 Python 程式路徑(例如 `main.py`),並自動檢測 FastAPI 實例(通常命名為 `app`),確定正確的引入模組流程,然後運行該應用程式。 + +在生產環境,你應該使用 `fastapi run` 命令。 🚀 + +**FastAPI CLI** 內部使用了 Uvicorn,這是一個高效能、適合生產環境的 ASGI 伺服器。 😎 + +## `fastapi dev` + +執行 `fastapi dev` 會啟動開發模式。 + +預設情況下,**auto-reload** 功能是啟用的,當你對程式碼進行修改時,伺服器會自動重新載入。這會消耗較多資源,並且可能比禁用時更不穩定。因此,你應該只在開發環境中使用此功能。它也會在 IP 位址 `127.0.0.1` 上監聽,這是用於你的機器與自身通訊的 IP 位址(`localhost`)。 + +## `fastapi run` + +執行 `fastapi run` 會以生產模式啟動 FastAPI。 + +預設情況下,**auto-reload** 功能是禁用的。它也會在 IP 位址 `0.0.0.0` 上監聽,表示會監聽所有可用的 IP 地址,這樣任何能與該機器通訊的人都可以公開存取它。這通常是你在生產環境中運行應用程式的方式,例如在容器中運行時。 + +在大多數情況下,你會(也應該)有一個「終止代理」來處理 HTTPS,這取決於你如何部署你的應用程式,你的服務供應商可能會為你做這件事,或者你需要自己設置它。 + +/// tip + +你可以在[部署文件](deployment/index.md){.internal-link target=_blank}中了解更多相關資訊。 + +/// diff --git a/docs/zh-hant/docs/features.md b/docs/zh-hant/docs/features.md new file mode 100644 index 0000000000..f44d28a7f8 --- /dev/null +++ b/docs/zh-hant/docs/features.md @@ -0,0 +1,207 @@ +# 特性 + +## FastAPI 特性 + +**FastAPI** 提供了以下内容: + +### 建立在開放標準的基礎上 + +* 使用 OpenAPI 來建立 API,包含路徑操作、參數、請求內文、安全性等聲明。 +* 使用 JSON Schema(因為 OpenAPI 本身就是基於 JSON Schema)自動生成資料模型文件。 +* 經過縝密的研究後圍繞這些標準進行設計,而不是事後在已有系統上附加的一層功能。 +* 這也讓我們在多種語言中可以使用自動**用戶端程式碼生成**。 + +### 能夠自動生成文件 + +FastAPI 能生成互動式 API 文件和探索性的 Web 使用者介面。由於該框架基於 OpenAPI,因此有多種選擇,預設提供了兩種。 + +* Swagger UI 提供互動式探索,讓你可以直接從瀏覽器呼叫並測試你的 API 。 + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* ReDoc 提供結構性的文件,讓你可以在瀏覽器中查看。 + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + + +### 現代 Python + +這一切都基於標準的 **Python 型別**宣告(感謝 Pydantic)。無需學習新的語法,只需使用標準的現代 Python。 + +如果你需要 2 分鐘來學習如何使用 Python 型別(即使你不使用 FastAPI),可以看看這個簡短的教學:[Python 型別](python-types.md){.internal-link target=_blank}。 + +如果你寫帶有 Python 型別的程式碼: + +```python +from datetime import date + +from pydantic import BaseModel + +# 宣告一個變數為 string +# 並在函式中獲得 editor support +def main(user_id: str): + return user_id + + +# 宣告一個 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` 字典直接作為 key-value 引數傳遞,等同於:`User(id=4, name="Mary", joined="2018-11-30")` + +/// + +### 多種編輯器支援 + +整個框架的設計是為了讓使用變得簡單且直觀,在開始開發之前,所有決策都在多個編輯器上進行了測試,以確保提供最佳的開發體驗。 + +在最近的 Python 開發者調查中,我們能看到 被使用最多的功能是 autocompletion,此功能可以預測將要輸入文字,並自動補齊。 + +整個 **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) + +你將能進行程式碼補齊,這是在之前你可能曾認為不可能的事。例如,請求 JSON body(可能是巢狀的)中的鍵 `price`。 + +這樣比較不會輸錯鍵名,不用來回翻看文件,也不用來回滾動尋找你最後使用的 `username` 或者 `user_name`。 + + + +### 簡潔 + +FastAPI 為你提供了**預設值**,讓你不必在初期進行繁瑣的配置,一切都可以自動運作。如果你有更具體的需求,則可以進行調整和自定義, + +但在大多數情況下,你只需要直接使用預設值,就能順利完成 API 開發。 + +### 驗證 + +所有的驗證都由完善且強大的 **Pydantic** 處理。 + +* 驗證大部分(甚至所有?)的 Python **資料型別**,包括: + * JSON 物件 (`dict`)。 + * JSON 陣列 (`list`) 定義項目型別。 + * 字串 (`str`) 欄位,定義最小或最大長度。 + * 數字 (`int`, `float`) 與其最大值和最小值等。 + +* 驗證外來的型別,比如: + * URL + * Email + * UUID + + +### 安全性及身份驗證 + +FastAPI 已經整合了安全性和身份驗證的功能,但不會強制與特定的資料庫或資料模型進行綁定。 + +OpenAPI 中定義的安全模式,包括: + +* HTTP 基本認證。 +* **OAuth2**(也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 查看教學。 +* API 密鑰,在: + * 標頭(Header) + * 查詢參數 + * Cookies,等等。 + +加上来自 Starlette(包括 **session cookie**)的所有安全特性。 + +所有的這些都是可重複使用的工具和套件,可以輕鬆與你的系統、資料儲存(Data Stores)、關聯式資料庫(RDBMS)以及非關聯式資料庫(NoSQL)等等整合。 + + +### 依賴注入(Dependency Injection) + +FastAPI 有一個使用簡單,但是非常強大的依賴注入系統。 + +* 依賴項甚至可以有自己的依賴,從而形成一個層級或**依賴圖**的結構。 +* 所有**自動化處理**都由框架完成。 +* 依賴項不僅能從請求中提取資料,還能**對 API 的路徑操作進行強化**,並自動生成文檔。 +* 即使是依賴項中定義的*路徑操作參數*,也會**自動進行驗證**。 +* 支持複雜的用戶身份驗證系統、**資料庫連接**等。 +* 不與資料庫、前端等進行強制綁定,但能輕鬆整合它們。 + + +### 無限制「擴充功能」 + +或者說,無需其他額外配置,直接導入並使用你所需要的程式碼。 + +任何整合都被設計得非常簡單易用(通過依賴注入),你只需用與*路徑操作*相同的結構和語法,用兩行程式碼就能為你的應用程式建立一個「擴充功能」。 + + +### 測試 + +* 100% 的測試覆蓋率。 +* 100% 的程式碼有型別註釋。 +* 已能夠在生產環境應用程式中使用。 + +## Starlette 特性 + +**FastAPI** 完全相容且基於 Starlette。所以,你有其他的 Starlette 程式碼也能正常運作。FastAPI 繼承了 Starlette 的所有功能,如果你已經知道或者使用過 Starlette,大部分的功能會以相同的方式運作。 + +通過 **FastAPI** 你可以獲得所有 **Starlette** 的特性(FastAPI 就像加強版的 Starlette): + +* 性能極其出色。它是 Python 可用的最快框架之一,和 **NodeJS** 及 **Go** 相當。 +* **支援 WebSocket**。 +* 能在行程內處理背景任務。 +* 支援啟動和關閉事件。 +* 有基於 HTTPX 的測試用戶端。 +* 支援 **CORS**、GZip、靜態檔案、串流回應。 +* 支援 **Session 和 Cookie** 。 +* 100% 測試覆蓋率。 +* 100% 型別註釋的程式碼庫。 + +## Pydantic 特性 + +**FastAPI** 完全相容且基於 Pydantic。所以,你有其他 Pydantic 程式碼也能正常工作。 + +相容包括基於 Pydantic 的外部函式庫, 例如用於資料庫的 ORMs, ODMs。 + +這也意味著在很多情況下,你可以把從請求中獲得的物件**直接傳到資料庫**,因為所有資料都會自動進行驗證。 + +反之亦然,在很多情況下,你也可以把從資料庫中獲取的物件**直接傳給客戶端**。 + +通過 **FastAPI** 你可以獲得所有 **Pydantic** 的特性(FastAPI 基於 Pydantic 做了所有的資料處理): + +* **更簡單**: + * 不需要學習新的 micro-language 來定義結構。 + * 如果你知道 Python 型別,你就知道如何使用 Pydantic。 +* 和你的 **IDE/linter/brain** 都能好好配合: + * 因為 Pydantic 的資料結構其實就是你自己定義的類別實例,所以自動補齊、linting、mypy 以及你的直覺都能很好地在經過驗證的資料上發揮作用。 +* 驗證**複雜結構**: + * 使用 Pydantic 模型時,你可以把資料結構分層設計,並且用 Python 的 `List` 和 `Dict` 等型別來定義。 + * 驗證器讓我們可以輕鬆地定義和檢查複雜的資料結構,並把它們轉換成 JSON Schema 進行記錄。 + * 你可以擁有深層**巢狀的 JSON** 物件,並對它們進行驗證和註釋。 +* **可擴展**: + * Pydantic 讓我們可以定義客製化的資料型別,或者你可以使用帶有 validator 裝飾器的方法來擴展模型中的驗證功能。 +* 100% 測試覆蓋率。 diff --git a/docs/zh-hant/docs/how-to/index.md b/docs/zh-hant/docs/how-to/index.md new file mode 100644 index 0000000000..db740140d0 --- /dev/null +++ b/docs/zh-hant/docs/how-to/index.md @@ -0,0 +1,13 @@ +# 使用指南 - 範例集 + +在這裡,你將會看到**不同主題**的範例或「如何使用」的指南。 + +大多數這些想法都是**獨立**的,在大多數情況下,你只需要研究那些直接適用於**你的專案**的東西。 + +如果有些東西看起來很有趣且對你的專案很有用的話再去讀它,否則你可能可以跳過它們。 + +/// tip + +如果你想要以結構化的方式**學習 FastAPI**(推薦),請前往[教學 - 使用者指南](../tutorial/index.md){.internal-link target=_blank}逐章閱讀。 + +/// diff --git a/docs/zh-hant/docs/index.md b/docs/zh-hant/docs/index.md index d260b5b796..4390d96096 100644 --- a/docs/zh-hant/docs/index.md +++ b/docs/zh-hant/docs/index.md @@ -6,7 +6,7 @@

- Test + Test Coverage @@ -81,13 +81,13 @@ FastAPI 是一個現代、快速(高效能)的 web 框架,用於 Python "_我對 **FastAPI** 興奮得不得了。它太有趣了!_" -

+
Brian Okken - Python Bytes podcast host (ref)
--- "_老實說,你建造的東西看起來非常堅固和精緻。在很多方面,這就是我想要的,看到有人建造它真的很鼓舞人心。_" -
Timothy Crosley - Hug creator (ref)
+
Timothy Crosley - Hug creator (ref)
--- @@ -95,7 +95,7 @@ FastAPI 是一個現代、快速(高效能)的 web 框架,用於 Python "_我們的 **APIs** 已經改用 **FastAPI** [...] 我想你會喜歡它 [...]_" -
Ines Montani - Matthew Honnibal - Explosion AI 創辦人 - spaCy creators (ref) - (ref)
+
Ines Montani - Matthew Honnibal - Explosion AI 創辦人 - spaCy creators (ref) - (ref)
--- @@ -117,7 +117,7 @@ FastAPI 是一個現代、快速(高效能)的 web 框架,用於 Python FastAPI 是站在以下巨人的肩膀上: -- Starlette 負責網頁的部分 +- Starlette 負責網頁的部分 - Pydantic 負責資料的部分 ## 安裝 @@ -132,7 +132,7 @@ $ pip install fastapi -你同時也會需要 ASGI 伺服器用於生產環境,像是 UvicornHypercorn。 +你同時也會需要 ASGI 伺服器用於生產環境,像是 UvicornHypercorn
@@ -457,7 +457,7 @@ item: Item 用於 FastAPI / Starlette: -- uvicorn - 用於加載和運行應用程式的服務器。 +- uvicorn - 用於加載和運行應用程式的服務器。 - orjson - 使用 `ORJSONResponse`時必須安裝。 - ujson - 使用 `UJSONResponse` 時必須安裝。 diff --git a/docs/zh-hant/docs/resources/index.md b/docs/zh-hant/docs/resources/index.md new file mode 100644 index 0000000000..f4c70a3a04 --- /dev/null +++ b/docs/zh-hant/docs/resources/index.md @@ -0,0 +1,3 @@ +# 資源 + +額外的資源、外部連結、文章等。 ✈️ diff --git a/docs/zh-hant/docs/tutorial/first-steps.md b/docs/zh-hant/docs/tutorial/first-steps.md new file mode 100644 index 0000000000..d6684bc513 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/first-steps.md @@ -0,0 +1,331 @@ +# 第一步 + +最簡單的 FastAPI 檔案可能看起來像這樣: + +{* ../../docs_src/first_steps/tutorial001.py *} + +將其複製到一個名為 `main.py` 的文件中。 + +執行即時重新載入伺服器(live server): + +
+ +```console +$ fastapi dev main.py +INFO Using path main.py +INFO Resolved absolute path /home/user/code/awesomeapp/main.py +INFO Searching for package file structure from directories with __init__.py files +INFO Importing from /home/user/code/awesomeapp + + ╭─ Python module file ─╮ + │ │ + │ 🐍 main.py │ + │ │ + ╰──────────────────────╯ + +INFO Importing module main +INFO Found importable FastAPI app + + ╭─ Importable FastAPI app ─╮ + │ │ + │ from main import app │ + │ │ + ╰──────────────────────────╯ + +INFO Using import string main:app + + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + fastapi run + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [2265862] using WatchFiles +INFO: Started server process [2265873] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +在輸出中,有一列類似於: + +```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** 標準來生成一個 「schema」 與你的所有 API。 + +#### 「Schema」 + +「schema」是對某個事物的定義或描述。它並不是實作它的程式碼,而僅僅是一個抽象的描述。 + +#### API 「schema」 + +在這種情況下,OpenAPI 是一個規範,它規定了如何定義 API 的 schema。 + +這個 schema 定義包含了你的 API 路徑、可能接收的參數等內容。 + +#### 資料 「schema」 + +「schema」這個術語也可能指某些資料的結構,比如 JSON 內容的結構。 + +在這種情況下,它指的是 JSON 的屬性、資料型別等。 + +#### OpenAPI 和 JSON Schema + +OpenAPI 定義了 API 的 schema。這個 schema 包含了使用 **JSON Schema** 定義的資料,這是 JSON 資料 schema 的標準。 + +#### 檢查 `openapi.json` + +如果你好奇原始的 OpenAPI schema 長什麼樣子,FastAPI 會自動生成一個包含所有 API 描述的 JSON (schema)。 + +你可以直接在 http://127.0.0.1:8000/openapi.json 查看它。 + +它會顯示一個 JSON,類似於: + +```JSON +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +#### OpenAPI 的用途 + +OpenAPI schema 驅動了兩個互動式文件系統。 + +而且有許多替代方案,所有這些都是基於 OpenAPI。你可以輕鬆地將任何這些替代方案添加到使用 **FastAPI** 建置的應用程式中。 + +你也可以用它自動生成程式碼,讓前端、手機應用程式或物聯網設備等與你的 API 進行通訊。 + +## 逐步回顧 + +### 第一步:引入 `FastAPI` + +{* ../../docs_src/first_steps/tutorial001.py h1[1] *} + +`FastAPI` 是一個 Python 類別,提供所有 API 的全部功能。 + +/// note | Technical Details + +`FastAPI` 是一個直接繼承自 `Starlette` 的類別。 + +你同樣可以透過 `FastAPI` 來使用 Starlette 所有的功能。 + +/// + +### 第二步:建立一個 `FastAPI` 「實例」 + +{* ../../docs_src/first_steps/tutorial001.py h1[3] *} + +這裡的 `app` 變數將會是 `FastAPI` 類別的「實例」。 + +這將是你建立所有 API 的主要互動點。 + +### 第三步:建立一個 *路徑操作* + +#### 路徑 + +這裡的「路徑」指的是 URL 中自第一個 `/` 以後的部分。 + +例如,在 URL 中: + +``` +https://example.com/items/foo +``` + +……的路徑將會是: + +``` +/items/foo +``` + +/// info + +「路徑」也常被稱為「端點 endpoint」或「路由 route」。 + +/// + +在建置 API 時,「路徑」是分離「關注點」和「資源」的主要方式。 + +#### 操作 + +這裡的「操作」指的是 HTTP 的「方法」之一。 + +其中包括: + +* `POST` +* `GET` +* `PUT` +* `DELETE` + +……以及更少見的: + +* `OPTIONS` +* `HEAD` +* `PATCH` +* `TRACE` + +在 HTTP 協定中,你可以使用這些「方法」之一(或更多)與每個路徑進行通信。 + +--- + +在建置 API 時,你通常使用這些特定的 HTTP 方法來執行特定的動作。 + +通常你使用: + +* `POST`:用來建立資料。 +* `GET`:用來讀取資料。 +* `PUT`:用來更新資料。 +* `DELETE`:用來刪除資料。 + +所以,在 OpenAPI 中,每個 HTTP 方法都被稱為「操作」。 + +我們將會稱它們為「**操作**」。 + +#### 定義一個 *路徑操作裝飾器* + +{* ../../docs_src/first_steps/tutorial001.py h1[6] *} + +`@app.get("/")` 告訴 **FastAPI** 那個函式負責處理請求: + +* 路徑 `/` +* 使用 get操作 + +/// info | `@decorator` Info + +Python 中的 `@something` 語法被稱為「裝飾器」。 + +你把它放在一個函式上面。像一個漂亮的裝飾帽子(我猜這是術語的來源)。 + +一個「裝飾器」會對下面的函式做一些事情。 + +在這種情況下,這個裝飾器告訴 **FastAPI** 那個函式對應於 **路徑** `/` 和 **操作** `get`. + +這就是「**路徑操作裝飾器**」。 + +/// + +你也可以使用其他的操作: + +* `@app.post()` +* `@app.put()` +* `@app.delete()` + +以及更少見的: + +* `@app.options()` +* `@app.head()` +* `@app.patch()` +* `@app.trace()` + +/// tip + +你可以自由地使用每個操作(HTTP 方法)。 + +**FastAPI** 不強制任何特定的意義。 + +這裡的資訊作為一個指南,而不是要求。 + +例如,當使用 GraphQL 時,你通常只使用 `POST` 操作。 + +/// + +### 第四步:定義 **路徑操作函式** + +這是我們的「**路徑操作函式**」: + +* **path**: 是 `/`. +* **operation**: 是 `get`. +* **function**: 是裝飾器下面的函式(在 `@app.get("/")` 下面)。 + +{* ../../docs_src/first_steps/tutorial001.py h1[7] *} + +這就是一個 Python 函式。 + +它將會在 **FastAPI** 收到一個請求時被呼叫,使用 `GET` 操作。 + +在這種情況下,它是一個 `async` 函式。 + +--- + +你可以將它定義為一個正常的函式,而不是 `async def`: + +{* ../../docs_src/first_steps/tutorial003.py h1[7] *} + +/// note + +如果你不知道差別,請查看 [Async: *"In a hurry?"*](../async.md#in-a-hurry){.internal-link target=_blank}. + +/// + +### 第五步:回傳內容 + +{* ../../docs_src/first_steps/tutorial001.py h1[8] *} + +你可以返回一個 `dict`、`list`、單個值作為 `str`、`int` 等。 + +你也可以返回 Pydantic 模型(稍後你會看到更多關於這方面的內容)。 + +有很多其他物件和模型會自動轉換為 JSON(包括 ORMs,等等)。試用你最喜歡的,很有可能它們已經有支援。 + +## 回顧 + +* 引入 `FastAPI`. +* 建立一個 `app` 實例。 +* 寫一個 **路徑操作裝飾器** 使用裝飾器像 `@app.get("/")`。 +* 定義一個 **路徑操作函式**;例如,`def root(): ...`。 +* 使用命令 `fastapi dev` 執行開發伺服器。 diff --git a/docs/zh-hant/docs/tutorial/index.md b/docs/zh-hant/docs/tutorial/index.md new file mode 100644 index 0000000000..ae0056f528 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/index.md @@ -0,0 +1,102 @@ +# 教學 - 使用者指南 + +本教學將一步一步展示如何使用 **FastAPI** 及其大多數功能。 + +每個部分都是在前一部分的基礎上逐步建置的,但內容結構是按主題分開的,因此你可以直接跳到任何特定的部分,解決你具體的 API 需求。 + +它也被設計成可作為未來的參考,讓你隨時回來查看所需的內容。 + +## 運行程式碼 + +所有程式碼區塊都可以直接複製和使用(它們實際上是經過測試的 Python 檔案)。 + +要運行任何範例,請將程式碼複製到 `main.py` 檔案,並使用以下命令啟動 `fastapi dev`: + +
+ +```console +$ fastapi dev main.py +INFO Using path main.py +INFO Resolved absolute path /home/user/code/awesomeapp/main.py +INFO Searching for package file structure from directories with __init__.py files +INFO Importing from /home/user/code/awesomeapp + + ╭─ Python module file ─╮ + │ │ + │ 🐍 main.py │ + │ │ + ╰──────────────────────╯ + +INFO Importing module main +INFO Found importable FastAPI app + + ╭─ Importable FastAPI app ─╮ + │ │ + │ from main import app │ + │ │ + ╰──────────────────────────╯ + +INFO Using import string main:app + + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + fastapi run + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [2265862] using WatchFiles +INFO: Started server process [2265873] +INFO: Waiting for application startup. +INFO: Application startup complete. + +``` + +
+ +**強烈建議**你編寫或複製程式碼、進行修改並在本地端運行。 + +在編輯器中使用它,才能真正體會到 FastAPI 的好處,可以看到你只需編寫少量程式碼,以及所有的型別檢查、自動補齊等功能。 + +--- + +## 安裝 FastAPI + +第一步是安裝 FastAPI。 + +確保你建立一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank},啟用它,然後**安裝 FastAPI**: + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +/// note + +當你使用 `pip install "fastapi[standard]"` 安裝時,會包含一些預設的可選標準依賴項。 + +如果你不想包含那些可選的依賴項,你可以使用 `pip install fastapi` 來安裝。 + +/// + +## 進階使用者指南 + +還有一個**進階使用者指南**你可以稍後閱讀。 + +**進階使用者指南**建立在這個教學之上,使用相同的概念,並教你一些額外的功能。 + +但首先你應該閱讀**教學 - 使用者指南**(你正在閱讀的內容)。 + +它被設計成你可以使用**教學 - 使用者指南**來建立一個完整的應用程式,然後根據你的需求,使用一些額外的想法來擴展它。 diff --git a/docs/zh-hant/docs/tutorial/query-param-models.md b/docs/zh-hant/docs/tutorial/query-param-models.md new file mode 100644 index 0000000000..76ee740169 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/query-param-models.md @@ -0,0 +1,68 @@ +# 查詢參數模型 + +如果你有一組具有相關性的**查詢參數**,你可以建立一個 **Pydantic 模型**來聲明它們。 + +這將允許你在**多個地方**去**重複使用模型**,並且一次性為所有參數聲明驗證和元資料 (metadata)。😎 + +/// note + +FastAPI 從 `0.115.0` 版本開始支援這個特性。🤓 + +/// + +## 使用 Pydantic 模型的查詢參數 + +在一個 **Pydantic 模型**中聲明你需要的**查詢參數**,然後將參數聲明為 `Query`: + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +**FastAPI** 將會從請求的**查詢參數**中**提取**出**每個欄位**的資料,並將其提供給你定義的 Pydantic 模型。 + +## 查看文件 + +你可以在 `/docs` 頁面的 UI 中查看查詢參數: + +
+ +
+ +## 禁止額外的查詢參數 + +在一些特殊的使用場景中(可能不是很常見),你可能希望**限制**你要收到的查詢參數。 + +你可以使用 Pydantic 的模型設定來 `forbid`(禁止)任何 `extra`(額外)欄位: + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +如果客戶端嘗試在**查詢參數**中發送一些**額外的**資料,他們將會收到一個**錯誤**回應。 + +例如,如果客戶端嘗試發送一個值為 `plumbus` 的 `tool` 查詢參數,如: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +他們將收到一個**錯誤**回應,告訴他們查詢參數 `tool` 是不允許的: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## 總結 + +你可以使用 **Pydantic 模型**在 **FastAPI** 中聲明**查詢參數**。😎 + +/// tip + +劇透警告:你也可以使用 Pydantic 模型來聲明 cookie 和 headers,但你將在本教學的後面部分閱讀到這部分內容。🤫 + +/// diff --git a/docs/zh-hant/docs/virtual-environments.md b/docs/zh-hant/docs/virtual-environments.md new file mode 100644 index 0000000000..d8e31e08e0 --- /dev/null +++ b/docs/zh-hant/docs/virtual-environments.md @@ -0,0 +1,844 @@ +# 虛擬環境 + +當你在 Python 專案中工作時,你可能會需要使用一個**虛擬環境**(或類似的機制)來隔離你為每個專案安裝的套件。 + +/// info + +如果你已經了解虛擬環境,知道如何建立和使用它們,你可以考慮跳過這一部分。🤓 + +/// + +/// tip + +**虛擬環境**和**環境變數**是不同的。 + +**環境變數**是系統中的一個變數,可以被程式使用。 + +**虛擬環境**是一個包含一些檔案的目錄。 + +/// + +/// info + +這個頁面將教你如何使用**虛擬環境**以及了解它們的工作原理。 + +如果你計畫使用一個**可以為你管理一切的工具**(包括安裝 Python),試試 uv。 + +/// + +## 建立一個專案 + +首先,為你的專案建立一個目錄。 + +我(指原作者 —— 譯者注)通常會在我的主目錄下建立一個名為 `code` 的目錄。 + +在這個目錄下,我再為每個專案建立一個目錄。 + +
+ +```console +// 進入主目錄 +$ cd +// 建立一個用於存放所有程式碼專案的目錄 +$ mkdir code +// 進入 code 目錄 +$ cd code +// 建立一個用於存放這個專案的目錄 +$ mkdir awesome-project +// 進入這個專案的目錄 +$ cd awesome-project +``` + +
+ +## 建立一個虛擬環境 + +在開始一個 Python 專案的**第一時間**,**在你的專案內部**建立一個虛擬環境。 + +/// tip + +你只需要**在每個專案中操作一次**,而不是每次工作時都操作。 + +/// + +//// tab | `venv` + +你可以使用 Python 自帶的 `venv` 模組來建立一個虛擬環境。 + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | 上述命令的含義 + +* `python`: 使用名為 `python` 的程式 +* `-m`: 以腳本的方式呼叫一個模組,我們將告訴它接下來使用哪個模組 +* `venv`: 使用名為 `venv` 的模組,這個模組通常隨 Python 一起安裝 +* `.venv`: 在新目錄 `.venv` 中建立虛擬環境 + +/// + +//// + +//// tab | `uv` + +如果你安裝了 `uv`,你也可以使用它來建立一個虛擬環境。 + +
+ +```console +$ uv venv +``` + +
+ +/// tip + +預設情況下,`uv` 會在一個名為 `.venv` 的目錄中建立一個虛擬環境。 + +但你可以透過傳遞一個額外的引數來自訂它,指定目錄的名稱。 + +/// + +//// + +這個命令會在一個名為 `.venv` 的目錄中建立一個新的虛擬環境。 + +/// details | `.venv`,或是其他名稱 + +你可以在不同的目錄下建立虛擬環境,但通常我們會把它命名為 `.venv`。 + +/// + +## 啟動虛擬環境 + +啟動新的虛擬環境來確保你運行的任何 Python 指令或安裝的套件都能使用到它。 + +/// tip + +**每次**開始一個**新的終端會話**來在這個專案工作時,你都需要執行這個操作。 + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +或者,如果你在 Windows 上使用 Bash(例如 Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip + +每次你在這個環境中安裝一個**新的套件**時,都需要**重新啟動**這個環境。 + +這麼做確保了當你使用一個由這個套件安裝的**終端(CLI)程式**時,你使用的是你的虛擬環境中的程式,而不是全域安裝、可能版本不同的程式。 + +/// + +## 檢查虛擬環境是否啟動 + +檢查虛擬環境是否啟動(前面的指令是否生效)。 + +/// tip + +這是**非必需的**,但這是一個很好的方法,可以**檢查**一切是否按預期工作,以及你是否使用了你打算使用的虛擬環境。 + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +如果它顯示了在你專案(在這個例子中是 `awesome-project`)的 `.venv/bin/python` 中的 `python` 二進位檔案,那麼它就生效了。🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +如果它顯示了在你專案(在這個例子中是 `awesome-project`)的 `.venv\Scripts\python` 中的 `python` 二進位檔案,那麼它就生效了。🎉 + +//// + +## 升級 `pip` + +/// tip + +如果你使用 `uv` 來安裝內容,而不是 `pip`,那麼你就不需要升級 `pip`。😎 + +/// + +如果你使用 `pip` 來安裝套件(它是 Python 的預設元件),你應該將它**升級**到最新版本。 + +在安裝套件時出現的許多奇怪的錯誤都可以透過先升級 `pip` 來解決。 + +/// tip + +通常你只需要在建立虛擬環境後**執行一次**這個操作。 + +/// + +確保虛擬環境是啟動的(使用上面的指令),然後運行: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## 加入 `.gitignore` + +如果你使用 **Git**(這是你應該使用的),加入一個 `.gitignore` 檔案來排除你的 `.venv` 中的所有內容。 + +/// tip + +如果你使用 `uv` 來建立虛擬環境,它會自動為你完成這個操作,你可以跳過這一步。😎 + +/// + +/// tip + +通常你只需要在建立虛擬環境後**執行一次**這個操作。 + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | 上述指令的含義 + +- `echo "*"`: 將在終端中「顯示」文本 `*`(接下來的部分會對這個操作進行一些修改) +- `>`: 使左邊的指令顯示到終端的任何內容實際上都不會被顯示,而是會被寫入到右邊的檔案中 +- `.gitignore`: 被寫入文本的檔案的名稱 + +而 `*` 對於 Git 來說意味著「所有內容」。所以,它會忽略 `.venv` 目錄中的所有內容。 + +該指令會建立一個名為 .gitignore 的檔案,內容如下: + +```gitignore +* +``` + +/// + +## 安裝套件 + +在啟用虛擬環境後,你可以在其中安裝套件。 + +/// tip + +當你需要安裝或升級套件時,執行本操作**一次**; + +如果你需要再升級版本或新增套件,你可以**再次執行此操作**。 + +/// + +### 直接安裝套件 + +如果你急於安裝,不想使用檔案來聲明專案的套件依賴,你可以直接安裝它們。 + +/// tip + +將程式所需的套件及其版本放在檔案中(例如 `requirements.txt` 或 `pyproject.toml`)是個好(而且非常好)的主意。 + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +如果你有 `uv`: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### 從 `requirements.txt` 安裝 + +如果你有一個 `requirements.txt` 檔案,你可以使用它來安裝其中的套件。 + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +如果你有 `uv`: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | 關於 `requirements.txt` + +一個包含一些套件的 `requirements.txt` 檔案看起來應該是這樣的: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## 執行程式 + +在啟用虛擬環境後,你可以執行你的程式,它將使用虛擬環境中的 Python 和你在其中安裝的套件。 + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## 設定編輯器 + +你可能會用到編輯器,請確保設定它使用你建立的相同虛擬環境(它可能會自動偵測到),以便你可以獲得自動完成和内嵌錯誤提示。 + +例如: + +* VS Code +* PyCharm + +/// tip + +通常你只需要在建立虛擬環境時執行此操作**一次**。 + +/// + +## 退出虛擬環境 + +當你完成工作後,你可以**退出**虛擬環境。 + +
+ +```console +$ deactivate +``` + +
+ +這樣,當你執行 `python` 時它不會嘗試從已安裝套件的虛擬環境中執行。 + +## 開始工作 + +現在你已經準備好開始你的工作了。 + + + +/// tip + +你想要理解上面的所有內容嗎? + +繼續閱讀。👇🤓 + +/// + +## 為什麼要使用虛擬環境 + +你需要安裝 Python 才能使用 FastAPI。 + +接下來,你需要**安裝** FastAPI 以及你想使用的其他**套件**。 + +要安裝套件,你通常會使用隨 Python 一起提供的 `pip` 指令(或類似的替代工具)。 + +然而,如果你直接使用 `pip`,套件將會安裝在你的**全域 Python 環境**中(即 Python 的全域安裝)。 + +### 存在的問題 + +那麼,在全域 Python 環境中安裝套件有什麼問題呢? + +有時候,你可能會開發許多不同的程式,而這些程式各自依賴於**不同的套件**;有些專案甚至需要依賴於**相同套件的不同版本**。😱 + +例如,你可能會建立一個名為 `philosophers-stone` 的專案,這個程式依賴於另一個名為 **`harry` 的套件,並使用版本 `1`**。因此,你需要安裝 `harry`。 + +```mermaid +flowchart LR + stone(philosophers-stone) -->|需要| harry-1[harry v1] +``` + +然而,在此之後,你又建立了另一個名為 `prisoner-of-azkaban` 的專案,而這個專案也依賴於 `harry`,但需要的是 **`harry` 版本 `3`**。 + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |需要| harry-3[harry v3] +``` + +現在的問題是,如果你在全域環境中安裝套件而不是在本地**虛擬環境**中,你將面臨選擇安裝哪個版本的 `harry` 的困境。 + +如果你想運行 `philosophers-stone`,你需要先安裝 `harry` 版本 `1`,例如: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +然後你會在全域 Python 環境中安裝 `harry` 版本 `1`。 + +```mermaid +flowchart LR + subgraph global[全域環境] + harry-1[harry v1] + end + subgraph stone-project[專案 philosophers-stone] + stone(philosophers-stone) -->|需要| harry-1 + end +``` + +但如果你想運行 `prisoner-of-azkaban`,你需要解除安裝 `harry` 版本 `1` 並安裝 `harry` 版本 `3`(或者只要你安裝版本 `3`,版本 `1` 就會自動移除)。 + +
+ +```console +$ pip install "harry==3" +``` + +
+ +於是,你在全域 Python 環境中安裝了 `harry` 版本 `3`。 + +如果你再次嘗試運行 `philosophers-stone`,很可能會**無法正常運作**,因為它需要的是 `harry` 版本 `1`。 + +```mermaid +flowchart LR + subgraph global[全域環境] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[專案 philosophers-stone] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[專案 prisoner-of-azkaban] + azkaban(prisoner-of-azkaban) --> |需要| harry-3 + end +``` + +/// tip + +Python 套件在推出**新版本**時通常會儘量**避免破壞性更改**,但最好還是要謹慎,在安裝新版本前進行測試,以確保一切能正常運行。 + +/// + +現在,想像一下如果有**許多**其他**套件**,它們都是你的**專案所依賴的**。這樣是非常難以管理的。你可能會發現有些專案使用了一些**不相容的套件版本**,而無法得知為什麼某些程式無法正常運作。 + +此外,取決於你的操作系統(例如 Linux、Windows、macOS),它可能已經預先安裝了 Python。在這種情況下,它可能已經有一些系統所需的套件和特定版本。如果你在全域 Python 環境中安裝套件,可能會**破壞**某些隨作業系統一起安裝的程式。 + +## 套件安裝在哪裡 + +當你安裝 Python 時,它會在你的電腦中建立一些目錄並放置一些檔案。 + +其中一些目錄專門用來存放你所安裝的所有套件。 + +當你運行: + +
+ +```console +// 先別去運行這個指令,這只是個示例 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +這會從 PyPI 下載一個壓縮檔案,其中包含 FastAPI 的程式碼。 + +它還會**下載** FastAPI 所依賴的其他套件的檔案。 + +接著,它會**解壓**所有這些檔案,並將它們放在你的電腦中的某個目錄中。 + +預設情況下,這些下載和解壓的檔案會放置於隨 Python 安裝的目錄中,即**全域環境**。 + +## 什麼是虛擬環境 + +解決套件都安裝在全域環境中的問題方法是為你所做的每個專案使用一個**虛擬環境**。 + +虛擬環境是一個**目錄**,與全域環境非常相似,你可以在其中針對某個專案安裝套件。 + +這樣,每個專案都會有自己的虛擬環境(`.venv` 目錄),其中包含自己的套件。 + +```mermaid +flowchart TB + subgraph stone-project[專案 philosophers-stone] + stone(philosophers-stone) --->|需要| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[專案 prisoner-of-azkaban] + azkaban(prisoner-of-azkaban) --->|需要| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## 啟用虛擬環境意味著什麼 + +當你啟用了虛擬環境,例如: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +或者如果你在 Windows 上使用 Bash(例如 Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +這個命令會建立或修改一些[環境變數](environment-variables.md){.internal-link target=_blank},這些環境變數將在接下來的指令中可用。 + +其中之一是 `PATH` 變數。 + +/// tip + +你可以在 [環境變數](environment-variables.md#path-environment-variable){.internal-link target=_blank} 部分了解更多關於 `PATH` 環境變數的內容。 + +/// + +啟用虛擬環境會將其路徑 `.venv/bin`(在 Linux 和 macOS 上)或 `.venv\Scripts`(在 Windows 上)加入到 `PATH` 環境變數中。 + +假設在啟用環境之前,`PATH` 變數看起來像這樣: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +這意味著系統會在以下目錄中查找程式: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +這意味著系統會在以下目錄中查找程式: + +* `C:\Windows\System32` + +//// + +啟用虛擬環境後,`PATH` 變數會變成這樣: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +這意味著系統現在會首先在以下目錄中查找程式: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +然後再在其他目錄中查找。 + +因此,當你在終端機中輸入 `python` 時,系統會在以下目錄中找到 Python 程式: + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +並使用這個。 + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +這意味著系統現在會首先在以下目錄中查找程式: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +然後再在其他目錄中查找。 + +因此,當你在終端機中輸入 `python` 時,系統會在以下目錄中找到 Python 程式: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +並使用這個。 + +//// + +一個重要的細節是,虛擬環境路徑會被放在 `PATH` 變數的**開頭**。系統會在找到任何其他可用的 Python **之前**找到它。這樣,當你運行 `python` 時,它會使用**虛擬環境中的** Python,而不是任何其他 `python`(例如,全域環境中的 `python`)。 + +啟用虛擬環境還會改變其他一些內容,但這是它所做的最重要的事情之一。 + +## 檢查虛擬環境 + +當你檢查虛擬環境是否啟動時,例如: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +這表示將使用的 `python` 程式是**在虛擬環境中**的那一個。 + +在 Linux 和 macOS 中使用 `which`,在 Windows PowerShell 中使用 `Get-Command`。 + +這個指令的運作方式是,它會在 `PATH` 環境變數中搜尋,依序**逐個路徑**查找名為 `python` 的程式。一旦找到,它會**顯示該程式的路徑**。 + +最重要的是,當你呼叫 `python` 時,將執行的就是這個確切的 "`python`"。 + +因此,你可以確認是否在正確的虛擬環境中。 + +/// tip + +啟動一個虛擬環境,取得一個 Python,然後**切換到另一個專案**是件很容易的事; + +但如果第二個專案**無法正常運作**,那可能是因為你使用了來自其他專案的虛擬環境的、**不正確的 Python**。 + +因此,檢查正在使用的 `python` 是非常實用的。🤓 + +/// + +## 為什麼要停用虛擬環境 + +例如,你可能正在一個專案 `philosophers-stone` 上工作,**啟動了該虛擬環境**,安裝了套件並使用了該環境, + +然後你想要在**另一個專案** `prisoner-of-azkaban` 上工作, + +你進入那個專案: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +如果你不去停用 `philosophers-stone` 的虛擬環境,當你在終端中執行 `python` 時,它會嘗試使用 `philosophers-stone` 中的 Python。 + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// 匯入 sirius 錯誤,未安裝 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +但如果你停用虛擬環境並啟用 `prisoner-of-askaban` 的新虛擬環境,那麼當你執行 `python` 時,它會使用 `prisoner-of-askaban` 中虛擬環境的 Python。 + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// 你不需要在舊目錄中操作停用,你可以在任何地方操作停用,甚至在切換到另一個專案之後 😎 +$ deactivate + +// 啟用 prisoner-of-azkaban/.venv 中的虛擬環境 🚀 +$ source .venv/bin/activate + +// 現在當你執行 python 時,它會在這個虛擬環境中找到已安裝的 sirius 套件 ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## 替代方案 + +這是一個簡單的指南,幫助你入門並教會你如何理解一切**底層**的原理。 + +有許多**替代方案**來管理虛擬環境、套件依賴(requirements)、專案。 + +當你準備好並想要使用一個工具來**管理整個專案**、套件依賴、虛擬環境等,建議你嘗試 uv。 + +`uv` 可以執行許多操作,它可以: + +* 為你**安裝 Python**,包括不同的版本 +* 為你的專案管理**虛擬環境** +* 安裝**套件** +* 為你的專案管理套件的**依賴和版本** +* 確保你有一個**精確**的套件和版本集合來安裝,包括它們的依賴項,這樣你可以確保專案在生產環境中運行的狀態與開發時在你的電腦上運行的狀態完全相同,這被稱為**鎖定** +* 還有很多其他功能 + +## 結論 + +如果你讀過並理解了所有這些,現在**你對虛擬環境的了解已超過許多開發者**。🤓 + +未來當你為看起來複雜的問題除錯時,了解這些細節很可能會有所幫助,你會知道**它是如何在底層運作的**。😎 diff --git a/docs/zh/docs/advanced/additional-responses.md b/docs/zh/docs/advanced/additional-responses.md index 1fc6349337..362ef9460a 100644 --- a/docs/zh/docs/advanced/additional-responses.md +++ b/docs/zh/docs/advanced/additional-responses.md @@ -16,9 +16,7 @@ **FastAPI**将采用该模型,生成其`JSON Schema`并将其包含在`OpenAPI`中的正确位置。 例如,要声明另一个具有状态码 `404` 和`Pydantic`模型 `Message` 的响应,可以写: -```Python hl_lines="18 22" -{!../../../docs_src/additional_responses/tutorial001.py!} -``` +{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} /// note @@ -163,9 +161,7 @@ 例如,您可以添加一个额外的媒体类型` image/png` ,声明您的路径操作可以返回JSON对象(媒体类型 `application/json` )或PNG图像: -```Python hl_lines="19-24 28" -{!../../../docs_src/additional_responses/tutorial002.py!} -``` +{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} /// note @@ -191,9 +187,7 @@ 以及一个状态码为 `200` 的响应,它使用您的 `response_model` ,但包含自定义的 `example` : -```Python hl_lines="20-31" -{!../../../docs_src/additional_responses/tutorial003.py!} -``` +{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} 所有这些都将被合并并包含在您的OpenAPI中,并在API文档中显示: @@ -219,9 +213,7 @@ new_dict = {**old_dict, "new key": "new value"} ``` 您可以使用该技术在路径操作中重用一些预定义的响应,并将它们与其他自定义响应相结合。 **例如:** -```Python hl_lines="13-17 26" -{!../../../docs_src/additional_responses/tutorial004.py!} -``` +{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} ## 有关OpenAPI响应的更多信息 要了解您可以在响应中包含哪些内容,您可以查看OpenAPI规范中的以下部分: diff --git a/docs/zh/docs/advanced/additional-status-codes.md b/docs/zh/docs/advanced/additional-status-codes.md index 06320faad1..b048a2a170 100644 --- a/docs/zh/docs/advanced/additional-status-codes.md +++ b/docs/zh/docs/advanced/additional-status-codes.md @@ -14,11 +14,9 @@ 要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为为你要的值。 -```Python hl_lines="4 25" -{!../../../docs_src/additional_status_codes/tutorial001.py!} -``` +{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *} -/// warning | "警告" +/// warning | 警告 当你直接返回一个像上面例子中的 `Response` 对象时,它会直接返回。 @@ -28,7 +26,7 @@ FastAPI 不会用模型等对该响应进行序列化。 /// -/// note | "技术细节" +/// note | 技术细节 你也可以使用 `from starlette.responses import JSONResponse`。  diff --git a/docs/zh/docs/advanced/advanced-dependencies.md b/docs/zh/docs/advanced/advanced-dependencies.md index 3d8afbb62c..8375bd48ea 100644 --- a/docs/zh/docs/advanced/advanced-dependencies.md +++ b/docs/zh/docs/advanced/advanced-dependencies.md @@ -18,9 +18,7 @@ Python 可以把类实例变为**可调用项**。 为此,需要声明 `__call__` 方法: -```Python hl_lines="10" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[10] *} 本例中,**FastAPI** 使用 `__call__` 检查附加参数及子依赖项,稍后,还要调用它向*路径操作函数*传递值。 @@ -28,9 +26,7 @@ Python 可以把类实例变为**可调用项**。 接下来,使用 `__init__` 声明用于**参数化**依赖项的实例参数: -```Python hl_lines="7" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[7] *} 本例中,**FastAPI** 不使用 `__init__`,我们要直接在代码中使用。 @@ -38,9 +34,7 @@ Python 可以把类实例变为**可调用项**。 使用以下代码创建类实例: -```Python hl_lines="16" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[16] *} 这样就可以**参数化**依赖项,它包含 `checker.fixed_content` 的属性 - `"bar"`。 @@ -56,11 +50,9 @@ checker(q="somequery") ……并用*路径操作函数*的参数 `fixed_content_included` 返回依赖项的值: -```Python hl_lines="20" -{!../../../docs_src/dependencies/tutorial011.py!} -``` +{* ../../docs_src/dependencies/tutorial011.py hl[20] *} -/// tip | "提示" +/// tip | 提示 本章示例有些刻意,也看不出有什么用处。 diff --git a/docs/zh/docs/advanced/async-tests.md b/docs/zh/docs/advanced/async-tests.md new file mode 100644 index 0000000000..b5ac15b5b6 --- /dev/null +++ b/docs/zh/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# 异步测试 + +您已经了解了如何使用 `TestClient` 测试 **FastAPI** 应用程序。但是到目前为止,您只了解了如何编写同步测试,而没有使用 `async` 异步函数。 + +在测试中能够使用异步函数可能会很有用,比如当您需要异步查询数据库的时候。想象一下,您想要测试向 FastAPI 应用程序发送请求,然后验证您的后端是否成功在数据库中写入了正确的数据,与此同时您使用了异步的数据库的库。 + +让我们看看如何才能实现这一点。 + +## pytest.mark.anyio + +如果我们想在测试中调用异步函数,那么我们的测试函数必须是异步的。 AnyIO 为此提供了一个简洁的插件,它允许我们指定一些测试函数要异步调用。 + +## HTTPX + +即使您的 **FastAPI** 应用程序使用普通的 `def` 函数而不是 `async def` ,它本质上仍是一个 `async` 异步应用程序。 + +`TestClient` 在内部通过一些“魔法”操作,使得您可以在普通的 `def` 测试函数中调用异步的 FastAPI 应用程序,并使用标准的 pytest。但当我们在异步函数中使用它时,这种“魔法”就不再生效了。由于测试以异步方式运行,我们无法在测试函数中继续使用 `TestClient`。 + +`TestClient` 是基于 HTTPX 的。幸运的是,我们可以直接使用它来测试API。 + +## 示例 + +举个简单的例子,让我们来看一个[更大的应用](../tutorial/bigger-applications.md){.internal-link target=_blank}和[测试](../tutorial/testing.md){.internal-link target=_blank}中描述的类似文件结构: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +文件 `main.py` 将包含: + +{* ../../docs_src/async_tests/main.py *} + +文件 `test_main.py` 将包含针对 `main.py` 的测试,现在它可能看起来如下: + +{* ../../docs_src/async_tests/test_main.py *} + +## 运行测试 + +您可以通过以下方式照常运行测试: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## 详细说明 + +这个标记 `@pytest.mark.anyio` 会告诉 pytest 该测试函数应该被异步调用: + +{* ../../docs_src/async_tests/test_main.py hl[7] *} + +/// tip + +请注意,测试函数现在用的是 `async def`,而不是像以前使用 `TestClient` 时那样只是 `def` 。 + +/// + +我们现在可以使用应用程序创建一个 `AsyncClient` ,并使用 `await` 向其发送异步请求。 + +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} + +这相当于: + +```Python +response = client.get('/') +``` + +我们曾经通过它向 `TestClient` 发出请求。 + +/// tip + +请注意,我们正在将 async/await 与新的 `AsyncClient` 一起使用——请求是异步的。 + +/// + +/// warning + +如果您的应用程序依赖于生命周期事件, `AsyncClient` 将不会触发这些事件。为了确保它们被触发,请使用 florimondmanca/asgi-lifespan 中的 `LifespanManager` 。 + +/// + +## 其他异步函数调用 + +由于测试函数现在是异步的,因此除了在测试中向 FastAPI 应用程序发送请求之外,您现在还可以调用(和使用 `await` 等待)其他 `async` 异步函数,就和您在代码中的其他任何地方调用它们的方法一样。 + +/// tip + +如果您在测试程序中集成异步函数调用的时候遇到一个 `RuntimeError: Task attached to a different loop` 的报错(例如,使用 MongoDB 的 MotorClient 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如通过 `'@app.on_event("startup")` 回调函数进行初始化。 + +/// diff --git a/docs/zh/docs/advanced/behind-a-proxy.md b/docs/zh/docs/advanced/behind-a-proxy.md index 52f6acda1a..f8f61c8a36 100644 --- a/docs/zh/docs/advanced/behind-a-proxy.md +++ b/docs/zh/docs/advanced/behind-a-proxy.md @@ -37,7 +37,7 @@ browser --> proxy proxy --> server ``` -/// tip | "提示" +/// tip | 提示 IP `0.0.0.0` 常用于指程序监听本机或服务器上的所有有效 IP。 @@ -78,7 +78,7 @@ $ uvicorn main:app --root-path /api/v1 Hypercorn 也支持 `--root-path `选项。 -/// note | "技术细节" +/// note | 技术细节 ASGI 规范定义的 `root_path` 就是为了这种用例。 @@ -92,9 +92,7 @@ ASGI 规范定义的 `root_path` 就是为了这种用例。 我们在这里的信息里包含 `roo_path` 只是为了演示。 -```Python hl_lines="8" -{!../../../docs_src/behind_a_proxy/tutorial001.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} 然后,用以下命令启动 Uvicorn: @@ -121,9 +119,7 @@ $ uvicorn main:app --root-path /api/v1 还有一种方案,如果不能提供 `--root-path` 或等效的命令行选项,则在创建 FastAPI 应用时要设置 `root_path` 参数。 -```Python hl_lines="3" -{!../../../docs_src/behind_a_proxy/tutorial002.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} 传递 `root_path` 给 `FastAPI` 与传递 `--root-path` 命令行选项给 Uvicorn 或 Hypercorn 一样。 @@ -172,7 +168,7 @@ Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶 这个文件把 Traefik 监听端口设置为 `9999`,并设置要使用另一个文件 `routes.toml`。 -/// tip | "提示" +/// tip | 提示 使用端口 9999 代替标准的 HTTP 端口 80,这样就不必使用管理员权限运行(`sudo`)。 @@ -242,7 +238,7 @@ $ uvicorn main:app --root-path /api/v1 } ``` -/// tip | "提示" +/// tip | 提示 注意,就算访问 `http://127.0.0.1:8000/app`,也显示从选项 `--root-path` 中提取的 `/api/v1`,这是 `root_path` 的值。 @@ -289,7 +285,7 @@ $ uvicorn main:app --root-path /api/v1 ## 附加的服务器 -/// warning | "警告" +/// warning | 警告 此用例较难,可以跳过。 @@ -303,9 +299,7 @@ $ uvicorn main:app --root-path /api/v1 例如: -```Python hl_lines="4-7" -{!../../../docs_src/behind_a_proxy/tutorial003.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} 这段代码生产如下 OpenAPI 概图: @@ -332,7 +326,7 @@ $ uvicorn main:app --root-path /api/v1 } ``` -/// tip | "提示" +/// tip | 提示 注意,自动生成服务器时,`url` 的值 `/api/v1` 提取自 `roog_path`。 @@ -342,7 +336,7 @@ $ uvicorn main:app --root-path /api/v1 -/// tip | "提示" +/// tip | 提示 API 文档与所选的服务器进行交互。 @@ -352,9 +346,7 @@ API 文档与所选的服务器进行交互。 如果不想让 **FastAPI** 包含使用 `root_path` 的自动服务器,则要使用参数 `root_path_in_servers=False`: -```Python hl_lines="9" -{!../../../docs_src/behind_a_proxy/tutorial004.py!} -``` +{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} 这样,就不会在 OpenAPI 概图中包含服务器了。 diff --git a/docs/zh/docs/advanced/custom-response.md b/docs/zh/docs/advanced/custom-response.md index 9594c72ac4..22a9b4b510 100644 --- a/docs/zh/docs/advanced/custom-response.md +++ b/docs/zh/docs/advanced/custom-response.md @@ -12,7 +12,7 @@ 并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或者 `UJSONResponse` 的时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 `response_model` 自动转换(和过滤)。 -/// note | "说明" +/// note | 说明 如果你使用不带有任何媒体类型的响应类,FastAPI 认为你的响应没有任何内容,所以不会在生成的OpenAPI文档中记录响应格式。 @@ -24,11 +24,9 @@ 导入你想要使用的 `Response` 类(子类)然后在 *路径操作装饰器* 中声明它。 -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001b.py!} -``` +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} -/// info | "提示" +/// info | 提示 参数 `response_class` 也会用来定义响应的「媒体类型」。 @@ -38,7 +36,7 @@ /// -/// tip | "小贴士" +/// tip | 小贴士 `ORJSONResponse` 目前只在 FastAPI 中可用,而在 Starlette 中不可用。 @@ -51,11 +49,9 @@ * 导入 `HTMLResponse`。 * 将 `HTMLResponse` 作为你的 *路径操作* 的 `response_class` 参数传入。 -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial002.py!} -``` +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} -/// info | "提示" +/// info | 提示 参数 `response_class` 也会用来定义响应的「媒体类型」。 @@ -71,17 +67,15 @@ 和上面一样的例子,返回一个 `HTMLResponse` 看起来可能是这样: -```Python hl_lines="2 7 19" -{!../../../docs_src/custom_response/tutorial003.py!} -``` +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} -/// warning | "警告" +/// warning | 警告 *路径操作函数* 直接返回的 `Response` 不会被 OpenAPI 的文档记录(比如,`Content-Type` 不会被文档记录),并且在自动化交互文档中也是不可见的。 /// -/// info | "提示" +/// info | 提示 当然,实际的 `Content-Type` 头,状态码等等,将来自于你返回的 `Response` 对象。 @@ -97,9 +91,7 @@ 比如像这样: -```Python hl_lines="7 23 21" -{!../../../docs_src/custom_response/tutorial004.py!} -``` +{* ../../docs_src/custom_response/tutorial004.py hl[7,23,21] *} 在这个例子中,函数 `generate_html_response()` 已经生成并返回 `Response` 对象而不是在 `str` 中返回 HTML。 @@ -115,7 +107,7 @@ 要记得你可以使用 `Response` 来返回任何其他东西,甚至创建一个自定义的子类。 -/// note | "技术细节" +/// note | 技术细节 你也可以使用 `from starlette.responses import HTMLResponse`。 @@ -139,9 +131,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 media_type 的 Content-Type 头,并为文本类型附加一个字符集。 -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} ### `HTMLResponse` @@ -151,9 +141,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 接受文本或字节并返回纯文本响应。 -```Python hl_lines="2 7 9" -{!../../../docs_src/custom_response/tutorial005.py!} -``` +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} ### `JSONResponse` @@ -170,17 +158,15 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 `UJSONResponse` 是一个使用 `ujson` 的可选 JSON 响应。 -/// warning | "警告" +/// warning | 警告 在处理某些边缘情况时,`ujson` 不如 Python 的内置实现那么谨慎。 /// -```Python hl_lines="2 7" -{!../../../docs_src/custom_response/tutorial001.py!} -``` +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} -/// tip | "小贴士" +/// tip | 小贴士 `ORJSONResponse` 可能是一个更快的选择。 @@ -190,17 +176,13 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 返回 HTTP 重定向。默认情况下使用 307 状态代码(临时重定向)。 -```Python hl_lines="2 9" -{!../../../docs_src/custom_response/tutorial006.py!} -``` +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} ### `StreamingResponse` 采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。 -```Python hl_lines="2 14" -{!../../../docs_src/custom_response/tutorial007.py!} -``` +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} #### 对类似文件的对象使用 `StreamingResponse` @@ -208,11 +190,9 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 包括许多与云存储,视频处理等交互的库。 -```Python hl_lines="2 10-12 14" -{!../../../docs_src/custom_response/tutorial008.py!} -``` +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} -/// tip | "小贴士" +/// tip | 小贴士 注意在这里,因为我们使用的是不支持 `async` 和 `await` 的标准 `open()`,我们使用普通的 `def` 声明了路径操作。 @@ -231,9 +211,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 文件响应将包含适当的 `Content-Length`,`Last-Modified` 和 `ETag` 的响应头。 -```Python hl_lines="2 10" -{!../../../docs_src/custom_response/tutorial009.py!} -``` +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} ## 额外文档 diff --git a/docs/zh/docs/advanced/dataclasses.md b/docs/zh/docs/advanced/dataclasses.md index 72567e2458..c74ce65c3e 100644 --- a/docs/zh/docs/advanced/dataclasses.md +++ b/docs/zh/docs/advanced/dataclasses.md @@ -4,9 +4,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic 但 FastAPI 还可以使用数据类(`dataclasses`): -```Python hl_lines="1 7-12 19-20" -{!../../../docs_src/dataclasses/tutorial001.py!} -``` +{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} 这还是借助于 **Pydantic** 及其内置的 `dataclasses`。 @@ -20,7 +18,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic 数据类的和运作方式与 Pydantic 模型相同。实际上,它的底层使用的也是 Pydantic。 -/// info | "说明" +/// info | 说明 注意,数据类不支持 Pydantic 模型的所有功能。 @@ -34,9 +32,7 @@ FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic 在 `response_model` 参数中使用 `dataclasses`: -```Python hl_lines="1 7-13 19" -{!../../../docs_src/dataclasses/tutorial002.py!} -``` +{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} 本例把数据类自动转换为 Pydantic 数据类。 @@ -53,7 +49,7 @@ API 文档中也会显示相关概图: 本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`: ```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } -{!../../../docs_src/dataclasses/tutorial003.py!} +{!../../docs_src/dataclasses/tutorial003.py!} ``` 1. 本例依然要从标准的 `dataclasses` 中导入 `field`; diff --git a/docs/zh/docs/advanced/events.md b/docs/zh/docs/advanced/events.md index c9389f533c..1ef6cdd3ce 100644 --- a/docs/zh/docs/advanced/events.md +++ b/docs/zh/docs/advanced/events.md @@ -1,22 +1,118 @@ -# 事件:启动 - 关闭 +# 生命周期事件 -**FastAPI** 支持定义在应用启动前,或应用关闭后执行的事件处理器(函数)。 +你可以定义在应用**启动**前执行的逻辑(代码)。这意味着在应用**开始接收请求**之前,这些代码只会被执行**一次**。 -事件函数既可以声明为异步函数(`async def`),也可以声明为普通函数(`def`)。 +同样地,你可以定义在应用**关闭**时应执行的逻辑。在这种情况下,这段代码将在**处理可能的多次请求后**执行**一次**。 -/// warning | "警告" +因为这段代码在应用开始接收请求**之前**执行,也会在处理可能的若干请求**之后**执行,它覆盖了整个应用程序的**生命周期**("生命周期"这个词很重要😉)。 -**FastAPI** 只执行主应用中的事件处理器,不执行[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}中的事件处理器。 +这对于设置你需要在整个应用中使用的**资源**非常有用,这些资源在请求之间**共享**,你可能需要在之后进行**释放**。例如,数据库连接池,或加载一个共享的机器学习模型。 + +## 用例 + +让我们从一个示例用例开始,看看如何解决它。 + +假设你有几个**机器学习的模型**,你想要用它们来处理请求。 + +相同的模型在请求之间是共享的,因此并非每个请求或每个用户各自拥有一个模型。 + +假设加载模型可能**需要相当长的时间**,因为它必须从**磁盘**读取大量数据。因此你不希望每个请求都加载它。 + +你可以在模块/文件的顶部加载它,但这也意味着即使你只是在运行一个简单的自动化测试,它也会**加载模型**,这样测试将**变慢**,因为它必须在能够独立运行代码的其他部分之前等待模型加载完成。 + +这就是我们要解决的问题——在处理请求前加载模型,但只是在应用开始接收请求前,而不是代码执行时。 + +## 生命周期 lifespan + +你可以使用`FastAPI()`应用的`lifespan`参数和一个上下文管理器(稍后我将为你展示)来定义**启动**和**关闭**的逻辑。 + +让我们从一个例子开始,然后详细介绍。 + +我们使用`yield`创建了一个异步函数`lifespan()`像这样: + +```Python hl_lines="16 19" +{!../../docs_src/events/tutorial003.py!} +``` + +在这里,我们在 `yield` 之前将(虚拟的)模型函数放入机器学习模型的字典中,以此模拟加载模型的耗时**启动**操作。这段代码将在应用程序**开始处理请求之前**执行,即**启动**期间。 + +然后,在 `yield` 之后,我们卸载模型。这段代码将会在应用程序**完成处理请求后**执行,即在**关闭**之前。这可以释放诸如内存或 GPU 之类的资源。 + +/// tip | 提示 + +**关闭**事件只会在你停止应用时触发。 + +可能你需要启动一个新版本,或者你只是你厌倦了运行它。 🤷 /// -## `startup` 事件 +## 生命周期函数 + +首先要注意的是,我们定义了一个带有 `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!} +``` + +在 Python 中, **上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用。 + +```Python +with open("file.txt") as file: + file.read() +``` + +Python 的最近几个版本也有了一个**异步上下文管理器**,你可以通过 `async with` 来使用: + +```Python +async with lifespan(app): + await do_stuff() +``` + +你可以像上面一样创建了一个上下文管理器或者异步上下文管理器,它的作用是在进入 `with` 块时,执行 `yield` 之前的代码,并且在离开 `with` 块时,执行 `yield` 后面的代码。 + +但在我们上面的例子里,我们并不是直接使用,而是传递给 FastAPI 来供其使用。 + +`FastAPI()` 的 `lifespan` 参数接受一个**异步上下文管理器**,所以我们可以把我们新定义的上下文管理器 `lifespan` 传给它。 + +```Python hl_lines="22" +{!../../docs_src/events/tutorial003.py!} +``` + +## 替代事件(弃用) + +/// warning | 警告 + +配置**启动**和**关闭**事件的推荐方法是使用 `FastAPI()` 应用的 `lifespan` 参数,如前所示。如果你提供了一个 `lifespan` 参数,启动(`startup`)和关闭(`shutdown`)事件处理器将不再生效。要么使用 `lifespan`,要么配置所有事件,两者不能共用。 + +你可以跳过这一部分。 + +/// + +有一种替代方法可以定义在**启动**和**关闭**期间执行的逻辑。 + +**FastAPI** 支持定义在应用启动前,或应用关闭时执行的事件处理器(函数)。 + +事件函数既可以声明为异步函数(`async def`),也可以声明为普通函数(`def`)。 + +### `startup` 事件 使用 `startup` 事件声明 `app` 启动前运行的函数: -```Python hl_lines="8" -{!../../../docs_src/events/tutorial001.py!} -``` +{* ../../docs_src/events/tutorial001.py hl[8] *} 本例中,`startup` 事件处理器函数为项目数据库(只是**字典**)提供了一些初始值。 @@ -24,23 +120,21 @@ 只有所有 `startup` 事件处理器运行完毕,**FastAPI** 应用才开始接收请求。 -## `shutdown` 事件 +### `shutdown` 事件 使用 `shutdown` 事件声明 `app` 关闭时运行的函数: -```Python hl_lines="6" -{!../../../docs_src/events/tutorial002.py!} -``` +{* ../../docs_src/events/tutorial002.py hl[6] *} 此处,`shutdown` 事件处理器函数在 `log.txt` 中写入一行文本 `Application shutdown`。 -/// info | "说明" +/// info | 说明 `open()` 函数中,`mode="a"` 指的是**追加**。因此这行文本会添加在文件已有内容之后,不会覆盖之前的内容。 /// -/// tip | "提示" +/// tip | 提示 注意,本例使用 Python `open()` 标准函数与文件交互。 @@ -52,8 +146,28 @@ /// -/// info | "说明" +### `startup` 和 `shutdown` 一起使用 -有关事件处理器的详情,请参阅 Starlette 官档 - 事件。 +启动和关闭的逻辑很可能是连接在一起的,你可能希望启动某个东西然后结束它,获取一个资源然后释放它等等。 + +在不共享逻辑或变量的不同函数中处理这些逻辑比较困难,因为你需要在全局变量中存储值或使用类似的方式。 + +因此,推荐使用 `lifespan` 。 + +## 技术细节 + +只是为好奇者提供的技术细节。🤓 + +在底层,这部分是生命周期协议的一部分,参见 ASGI 技术规范,定义了称为启动(`startup`)和关闭(`shutdown`)的事件。 + +/// info | 说明 + +有关事件处理器的详情,请参阅 Starlette 官档 - 事件。 + +包括如何处理生命周期状态,这可以用于程序的其他部分。 /// + +## 子应用 + +🚨 **FastAPI** 只会触发主应用中的生命周期事件,不包括[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}中的。 diff --git a/docs/zh/docs/advanced/generate-clients.md b/docs/zh/docs/advanced/generate-clients.md index 56aad3bd26..bcb9ba2bf7 100644 --- a/docs/zh/docs/advanced/generate-clients.md +++ b/docs/zh/docs/advanced/generate-clients.md @@ -16,21 +16,7 @@ 让我们从一个简单的 FastAPI 应用开始: -//// tab | Python 3.9+ - -```Python hl_lines="7-9 12-13 16-17 21" -{!> ../../../docs_src/generate_clients/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-11 14-15 18 19 23" -{!> ../../../docs_src/generate_clients/tutorial001.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} 请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item` 和 `ResponseMessage`。 @@ -135,21 +121,7 @@ frontend-app@1.0.0 generate-client /home/user/code/frontend-app 例如,您可以有一个用 `items` 的部分和另一个用于 `users` 的部分,它们可以用标签来分隔: -//// tab | Python 3.9+ - -```Python hl_lines="21 26 34" -{!> ../../../docs_src/generate_clients/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23 28 36" -{!> ../../../docs_src/generate_clients/tutorial002.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} ### 生成带有标签的 TypeScript 客户端 @@ -196,21 +168,7 @@ FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID** 然后,你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**: -//// tab | Python 3.9+ - -```Python hl_lines="6-7 10" -{!> ../../../docs_src/generate_clients/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8-9 12" -{!> ../../../docs_src/generate_clients/tutorial003.py!} -``` - -//// +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} ### 使用自定义操作ID生成TypeScript客户端 @@ -232,9 +190,7 @@ FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID** 我们可以将 OpenAPI JSON 下载到一个名为`openapi.json`的文件中,然后使用以下脚本**删除此前缀的标签**: -```Python -{!../../../docs_src/generate_clients/tutorial004.py!} -``` +{* ../../docs_src/generate_clients/tutorial004.py *} 通过这样做,操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。 diff --git a/docs/zh/docs/advanced/middleware.md b/docs/zh/docs/advanced/middleware.md index 926082b94f..65e8c183f2 100644 --- a/docs/zh/docs/advanced/middleware.md +++ b/docs/zh/docs/advanced/middleware.md @@ -43,7 +43,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") **FastAPI** 为常见用例提供了一些中间件,下面介绍怎么使用这些中间件。 -/// note | "技术细节" +/// note | 技术细节 以下几个示例中也可以使用 `from starlette.middleware.something import SomethingMiddleware`。 @@ -57,17 +57,13 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") 任何传向 `http` 或 `ws` 的请求都会被重定向至安全方案。 -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial001.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} ## `TrustedHostMiddleware` 强制所有传入请求都必须正确设置 `Host` 请求头,以防 HTTP 主机头攻击。 -```Python hl_lines="2 6-8" -{!../../../docs_src/advanced_middleware/tutorial002.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} 支持以下参数: @@ -81,9 +77,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") 中间件会处理标准响应与流响应。 -```Python hl_lines="2 6" -{!../../../docs_src/advanced_middleware/tutorial003.py!} -``` +{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} 支持以下参数: @@ -98,4 +92,4 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") * Uvicorn 的 `ProxyHeadersMiddleware` * MessagePack -其它可用中间件详见 Starlette 官档 -  中间件ASGI Awesome 列表。 +其它可用中间件详见 Starlette 官档 -  中间件ASGI Awesome 列表。 diff --git a/docs/zh/docs/advanced/openapi-callbacks.md b/docs/zh/docs/advanced/openapi-callbacks.md index 7c7323cb5e..f021eb10ae 100644 --- a/docs/zh/docs/advanced/openapi-callbacks.md +++ b/docs/zh/docs/advanced/openapi-callbacks.md @@ -31,11 +31,9 @@ API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建 这部分代码很常规,您对绝大多数代码应该都比较熟悉了: -```Python hl_lines="10-14 37-54" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[10:14,37:54] *} -/// tip | "提示" +/// tip | 提示 `callback_url` 查询参数使用 Pydantic 的 URL 类型。 @@ -64,7 +62,7 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) 本例没有实现回调本身(只是一行代码),只有文档部分。 -/// tip | "提示" +/// tip | 提示 实际的回调只是 HTTP 请求。 @@ -80,7 +78,7 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) 我们要使用与存档*外部 API* 相同的知识……通过创建外部 API 要实现的*路径操作*(您的 API 要调用的)。 -/// tip | "提示" +/// tip | 提示 编写存档回调的代码时,假设您是*外部开发者*可能会用的上。并且您当前正在实现的是*外部 API*,不是*您自己的 API*。 @@ -92,9 +90,7 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) 首先,新建包含一些用于回调的 `APIRouter`。 -```Python hl_lines="5 26" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[5,26] *} ### 创建回调*路径操作* @@ -105,9 +101,7 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) * 声明要接收的请求体,例如,`body: InvoiceEvent` * 还要声明要返回的响应,例如,`response_model=InvoiceEventReceived` -```Python hl_lines="17-19 22-23 29-33" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[17:19,22:23,29:33] *} 回调*路径操作*与常规*路径操作*有两点主要区别: @@ -163,7 +157,7 @@ JSON 请求体包含如下内容: } ``` -/// tip | "提示" +/// tip | 提示 注意,回调 URL包含 `callback_url` (`https://www.external.org/events`)中的查询参数,还有 JSON 请求体内部的发票 ID(`2expen51ve`)。 @@ -175,11 +169,9 @@ JSON 请求体包含如下内容: 现在使用 API *路径操作装饰器*的参数 `callbacks`,从回调路由传递属性 `.routes`(实际上只是路由/路径操作的**列表**): -```Python hl_lines="36" -{!../../../docs_src/openapi_callbacks/tutorial001.py!} -``` +{* ../../docs_src/openapi_callbacks/tutorial001.py hl[36] *} -/// tip | "提示" +/// tip | 提示 注意,不能把路由本身(`invoices_callback_router`)传递给 `callback=`,要传递 `invoices_callback_router.routes` 中的 `.routes` 属性。 diff --git a/docs/zh/docs/advanced/openapi-webhooks.md b/docs/zh/docs/advanced/openapi-webhooks.md new file mode 100644 index 0000000000..92ae8db15f --- /dev/null +++ b/docs/zh/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# OpenAPI 网络钩子 + +有些情况下,您可能想告诉您的 API **用户**,您的应用程序可以携带一些数据调用*他们的*应用程序(给它们发送请求),通常是为了**通知**某种**事件**。 + +这意味着,除了您的用户向您的 API 发送请求的一般情况,**您的 API**(或您的应用)也可以向**他们的系统**(他们的 API、他们的应用)**发送请求**。 + +这通常被称为**网络钩子**(Webhook)。 + +## 使用网络钩子的步骤 + +通常的过程是**您**在代码中**定义**要发送的消息,即**请求的主体**。 + +您还需要以某种方式定义您的应用程序将在**何时**发送这些请求或事件。 + +**用户**会以某种方式(例如在某个网页仪表板上)定义您的应用程序发送这些请求应该使用的 **URL**。 + +所有关于注册网络钩子的 URL 的**逻辑**以及发送这些请求的实际代码都由您决定。您可以在**自己的代码**中以任何想要的方式来编写它。 + +## 使用 `FastAPI` 和 OpenAPI 文档化网络钩子 + +使用 **FastAPI**,您可以利用 OpenAPI 来自定义这些网络钩子的名称、您的应用可以发送的 HTTP 操作类型(例如 `POST`、`PUT` 等)以及您的应用将发送的**请求体**。 + +这能让您的用户更轻松地**实现他们的 API** 来接收您的**网络钩子**请求,他们甚至可能能够自动生成一些自己的 API 代码。 + +/// info + +网络钩子在 OpenAPI 3.1.0 及以上版本中可用,FastAPI `0.99.0` 及以上版本支持。 + +/// + +## 带有网络钩子的应用程序 + +当您创建一个 **FastAPI** 应用程序时,有一个 `webhooks` 属性可以用来定义网络钩子,方式与您定义*路径操作*的时候相同,例如使用 `@app.webhooks.post()` 。 + +{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} + +您定义的网络钩子将被包含在 `OpenAPI` 的架构中,并出现在自动生成的**文档 UI** 中。 + +/// info + +`app.webhooks` 对象实际上只是一个 `APIRouter` ,与您在使用多个文件来构建应用程序时所使用的类型相同。 + +/// + +请注意,使用网络钩子时,您实际上并没有声明一个*路径*(比如 `/items/` ),您传递的文本只是这个网络钩子的**标识符**(事件的名称)。例如在 `@app.webhooks.post("new-subscription")` 中,网络钩子的名称是 `new-subscription` 。 + +这是因为我们预计**您的用户**会以其他方式(例如通过网页仪表板)来定义他们希望接收网络钩子的请求的实际 **URL 路径**。 + +### 查看文档 + +现在您可以启动您的应用程序并访问 http://127.0.0.1:8000/docs. + +您会看到您的文档不仅有正常的*路径操作*显示,现在还多了一些**网络钩子**: + + diff --git a/docs/zh/docs/advanced/path-operation-advanced-configuration.md b/docs/zh/docs/advanced/path-operation-advanced-configuration.md index c378469162..12600eddb1 100644 --- a/docs/zh/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/zh/docs/advanced/path-operation-advanced-configuration.md @@ -12,9 +12,7 @@ 务必确保每个操作路径的 `operation_id` 都是唯一的。 -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} ### 使用 *路径操作函数* 的函数名作为 operationId @@ -22,9 +20,7 @@ 你应该在添加了所有 *路径操作* 之后执行此操作。 -```Python hl_lines="2 12 13 14 15 16 17 18 19 20 21 24" -{!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12,13,14,15,16,17,18,19,20,21,24] *} /// tip @@ -44,9 +40,7 @@ 使用参数 `include_in_schema` 并将其设置为 `False` ,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了)。 -```Python hl_lines="6" -{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} ## docstring 的高级描述 @@ -57,6 +51,4 @@ 剩余部分不会出现在文档中,但是其他工具(比如 Sphinx)可以使用剩余部分。 -```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29" -{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19,20,21,22,23,24,25,26,27,28,29] *} diff --git a/docs/zh/docs/advanced/response-change-status-code.md b/docs/zh/docs/advanced/response-change-status-code.md index a289cf2017..cc1f2a73eb 100644 --- a/docs/zh/docs/advanced/response-change-status-code.md +++ b/docs/zh/docs/advanced/response-change-status-code.md @@ -20,9 +20,7 @@ 然后你可以在这个*临时*响应对象中设置`status_code`。 -```Python hl_lines="1 9 12" -{!../../../docs_src/response_change_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} 然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 diff --git a/docs/zh/docs/advanced/response-cookies.md b/docs/zh/docs/advanced/response-cookies.md index dd942a981b..d5f2fe6fc5 100644 --- a/docs/zh/docs/advanced/response-cookies.md +++ b/docs/zh/docs/advanced/response-cookies.md @@ -4,9 +4,7 @@ 你可以在 *路径函数* 中定义一个类型为 `Response`的参数,这样你就可以在这个临时响应对象中设置cookie了。 -```Python hl_lines="1 8-9" -{!../../../docs_src/response_cookies/tutorial002.py!} -``` +{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *} 而且你还可以根据你的需要响应不同的对象,比如常用的 `dict`,数据库model等。 @@ -24,9 +22,7 @@ 然后设置Cookies,并返回: -```Python hl_lines="10-12" -{!../../../docs_src/response_cookies/tutorial001.py!} -``` +{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} /// tip @@ -40,7 +36,7 @@ ### 更多信息 -/// note | "技术细节" +/// note | 技术细节 你也可以使用`from starlette.responses import Response` 或者 `from starlette.responses import JSONResponse`。 @@ -50,4 +46,4 @@ /// -如果你想查看所有可用的参数和选项,可以参考 Starlette帮助文档 +如果你想查看所有可用的参数和选项,可以参考 Starlette帮助文档 diff --git a/docs/zh/docs/advanced/response-directly.md b/docs/zh/docs/advanced/response-directly.md index b2c7de8fd3..4d9cd53f2f 100644 --- a/docs/zh/docs/advanced/response-directly.md +++ b/docs/zh/docs/advanced/response-directly.md @@ -14,7 +14,7 @@ 事实上,你可以返回任意 `Response` 或者任意 `Response` 的子类。 -/// tip | "小贴士" +/// tip | 小贴士 `JSONResponse` 本身是一个 `Response` 的子类。 @@ -35,11 +35,9 @@ 对于这些情况,在将数据传递给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据。 -```Python hl_lines="4 6 20 21" -{!../../../docs_src/response_directly/tutorial001.py!} -``` +{* ../../docs_src/response_directly/tutorial001.py hl[4,6,20,21] *} -/// note | "技术细节" +/// note | 技术细节 你也可以使用 `from starlette.responses import JSONResponse`。 @@ -57,9 +55,7 @@ 你可以把你的 XML 内容放到一个字符串中,放到一个 `Response` 中,然后返回。 -```Python hl_lines="1 18" -{!../../../docs_src/response_directly/tutorial002.py!} -``` +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} ## 说明 diff --git a/docs/zh/docs/advanced/response-headers.md b/docs/zh/docs/advanced/response-headers.md index e18d1620da..5c6a62e93c 100644 --- a/docs/zh/docs/advanced/response-headers.md +++ b/docs/zh/docs/advanced/response-headers.md @@ -5,9 +5,7 @@ 你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。 然后你可以在这个*临时*响应对象中设置头部。 -```Python hl_lines="1 7-8" -{!../../../docs_src/response_headers/tutorial002.py!} -``` +{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *} 然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 @@ -20,12 +18,11 @@ 你也可以在直接返回`Response`时添加头部。 按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: -```Python hl_lines="10-12" -{!../../../docs_src/response_headers/tutorial001.py!} -``` + +{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} -/// note | "技术细节" +/// note | 技术细节 你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。 @@ -39,4 +36,4 @@ 请注意,可以使用'X-'前缀添加自定义专有头部。 -但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在Starlette的CORS文档中记录的`expose_headers`参数。 +但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在Starlette的CORS文档中记录的`expose_headers`参数。 diff --git a/docs/zh/docs/advanced/security/http-basic-auth.md b/docs/zh/docs/advanced/security/http-basic-auth.md index a76353186d..599429f9d2 100644 --- a/docs/zh/docs/advanced/security/http-basic-auth.md +++ b/docs/zh/docs/advanced/security/http-basic-auth.md @@ -20,35 +20,7 @@ HTTP 基础授权让浏览器显示内置的用户名与密码提示。 * 返回类型为 `HTTPBasicCredentials` 的对象: * 包含发送的 `username` 与 `password` -//// tab | Python 3.9+ - -```Python hl_lines="4 8 12" -{!> ../../../docs_src/security/tutorial006_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="2 7 11" -{!> ../../../docs_src/security/tutorial006_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="2 6 10" -{!> ../../../docs_src/security/tutorial006.py!} -``` - -//// +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} 第一次打开 URL(或在 API 文档中点击 **Execute** 按钮)时,浏览器要求输入用户名与密码: @@ -68,35 +40,7 @@ HTTP 基础授权让浏览器显示内置的用户名与密码提示。 然后我们可以使用 `secrets.compare_digest()` 来确保 `credentials.username` 是 `"stanleyjobson"`,且 `credentials.password` 是`"swordfish"`。 -//// tab | Python 3.9+ - -```Python hl_lines="1 12-24" -{!> ../../../docs_src/security/tutorial007_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 12-24" -{!> ../../../docs_src/security/tutorial007_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="1 11-21" -{!> ../../../docs_src/security/tutorial007.py!} -``` - -//// +{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} 这类似于: @@ -160,32 +104,4 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": 检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加请求头 `WWW-Authenticate`,让浏览器再次显示登录提示: -//// tab | Python 3.9+ - -```Python hl_lines="26-30" -{!> ../../../docs_src/security/tutorial007_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="26-30" -{!> ../../../docs_src/security/tutorial007_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="23-27" -{!> ../../../docs_src/security/tutorial007.py!} -``` - -//// +{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/zh/docs/advanced/security/index.md b/docs/zh/docs/advanced/security/index.md index 836086ae27..267e7ced70 100644 --- a/docs/zh/docs/advanced/security/index.md +++ b/docs/zh/docs/advanced/security/index.md @@ -4,7 +4,7 @@ 除 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性. -/// tip | "小贴士" +/// tip | 小贴士 接下来的章节 **并不一定是 "高级的"**. diff --git a/docs/zh/docs/advanced/security/oauth2-scopes.md b/docs/zh/docs/advanced/security/oauth2-scopes.md index b75ae11a4b..784c384901 100644 --- a/docs/zh/docs/advanced/security/oauth2-scopes.md +++ b/docs/zh/docs/advanced/security/oauth2-scopes.md @@ -10,7 +10,7 @@ OAuth2 也是脸书、谷歌、GitHub、微软、推特等第三方身份验证 本章介绍如何在 **FastAPI** 应用中使用 OAuth2 作用域管理验证与授权。 -/// warning | "警告" +/// warning | 警告 本章内容较难,刚接触 FastAPI 的新手可以跳过。 @@ -46,7 +46,7 @@ OpenAPI 中(例如 API 文档)可以定义**安全方案**。 * 脸书和 Instagram 使用 `instagram_basic` * 谷歌使用 `https://www.googleapis.com/auth/drive` -/// info | "说明" +/// info | 说明 OAuth2 中,**作用域**只是声明特定权限的字符串。 @@ -62,9 +62,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 首先,快速浏览一下以下代码与**用户指南**中 [OAuth2 实现密码哈希与 Bearer JWT 令牌验证](../../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!} -``` +{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:124,128:134,139,153] *} 下面,我们逐步说明修改的代码内容。 @@ -74,9 +72,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 `scopes` 参数接收**字典**,键是作用域、值是作用域的描述: -```Python hl_lines="62-65" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[62:65] *} 因为声明了作用域,所以登录或授权时会在 API 文档中显示。 @@ -94,7 +90,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 这样,返回的 JWT 令牌中就包含了作用域。 -/// danger | "危险" +/// danger | 危险 为了简明起见,本例把接收的作用域直接添加到了令牌里。 @@ -102,9 +98,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 /// -```Python hl_lines="153" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[153] *} ## 在*路径操作*与依赖项中声明作用域 @@ -122,7 +116,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 本例要求使用作用域 `me`(还可以使用更多作用域)。 -/// note | "笔记" +/// note | 笔记 不必在不同位置添加不同的作用域。 @@ -130,11 +124,9 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 /// -```Python hl_lines="4 139 166" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[4,139,166] *} -/// info | "技术细节" +/// info | 技术细节 `Security` 实际上是 `Depends` 的子类,而且只比 `Depends` 多一个参数。 @@ -154,13 +146,11 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 该依赖项函数本身不需要作用域,因此,可以使用 `Depends` 和 `oauth2_scheme`。不需要指定安全作用域时,不必使用 `Security`。 -此处还声明了从 `fastapin.security` 导入的 `SecurityScopes` 类型的特殊参数。 +此处还声明了从 `fastapi.security` 导入的 `SecurityScopes` 类型的特殊参数。 `SecuriScopes` 类与 `Request` 类似(`Request` 用于直接提取请求对象)。 -```Python hl_lines="8 105" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[8,105] *} ## 使用 `scopes` @@ -174,9 +164,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 该异常包含了作用域所需的(如有),以空格分割的字符串(使用 `scope_str`)。该字符串要放到包含作用域的 `WWW-Authenticate` 请求头中(这也是规范的要求)。 -```Python hl_lines="105 107-115" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[105,107:115] *} ## 校验 `username` 与数据形状 @@ -192,9 +180,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 还可以使用用户名验证用户,如果没有用户,也会触发之前创建的异常。 -```Python hl_lines="46 116-127" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[46,116:127] *} ## 校验 `scopes` @@ -202,9 +188,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 为此,要使用包含所有作用域**字符串列表**的 `security_scopes.scopes`, 。 -```Python hl_lines="128-134" -{!../../../docs_src/security/tutorial005.py!} -``` +{* ../../docs_src/security/tutorial005.py hl[128:134] *} ## 依赖项树与作用域 @@ -231,7 +215,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 * `security_scopes.scopes` 包含*路径操作* `read_users_me` 的 `["me"]`,因为它在依赖项里被声明 * `security_scopes.scopes` 包含用于*路径操作* `read_system_status` 的 `[]`(空列表),并且它的依赖项 `get_current_user` 也没有声明任何 `scope` -/// tip | "提示" +/// tip | 提示 此处重要且**神奇**的事情是,`get_current_user` 检查每个*路径操作*时可以使用不同的 `scopes` 列表。 @@ -275,7 +259,7 @@ OAuth2 中,**作用域**只是声明特定权限的字符串。 最安全的是代码流,但实现起来更复杂,而且需要更多步骤。因为它更复杂,很多第三方身份验证应用最终建议使用隐式流。 -/// note | "笔记" +/// note | 笔记 每个身份验证应用都会采用不同方式会命名流,以便融合入自己的品牌。 diff --git a/docs/zh/docs/advanced/settings.md b/docs/zh/docs/advanced/settings.md index 37a2d98d31..e33da136fa 100644 --- a/docs/zh/docs/advanced/settings.md +++ b/docs/zh/docs/advanced/settings.md @@ -150,9 +150,7 @@ Hello World from Python 您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。 -```Python hl_lines="2 5-8 11" -{!../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} /// tip @@ -168,9 +166,7 @@ Hello World from Python 然后,您可以在应用程序中使用新的 `settings` 对象: -```Python hl_lines="18-20" -{!../../../docs_src/settings/tutorial001.py!} -``` +{* ../../docs_src/settings/tutorial001.py hl[18:20] *} ### 运行服务器 @@ -204,15 +200,11 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app 例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容: -```Python -{!../../../docs_src/settings/app01/config.py!} -``` +{* ../../docs_src/settings/app01/config.py *} 然后在一个名为 `main.py` 的文件中使用它: -```Python hl_lines="3 11-13" -{!../../../docs_src/settings/app01/main.py!} -``` +{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} /// tip @@ -230,9 +222,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app 根据前面的示例,您的 `config.py` 文件可能如下所示: -```Python hl_lines="10" -{!../../../docs_src/settings/app02/config.py!} -``` +{* ../../docs_src/settings/app02/config.py hl[10] *} 请注意,现在我们不创建默认实例 `settings = Settings()`。 @@ -240,35 +230,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app 现在我们创建一个依赖项,返回一个新的 `config.Settings()`。 -//// tab | Python 3.9+ - -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="6 12-13" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ 非注解版本 - -/// tip - -如果可能,请尽量使用 `Annotated` 版本。 - -/// - -```Python hl_lines="5 11-12" -{!> ../../../docs_src/settings/app02/main.py!} -``` - -//// +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} /// tip @@ -280,43 +242,13 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app 然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。 -//// tab | Python 3.9+ - -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17 19-21" -{!> ../../../docs_src/settings/app02_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ 非注解版本 - -/// tip - -如果可能,请尽量使用 `Annotated` 版本。 - -/// - -```Python hl_lines="16 18-20" -{!> ../../../docs_src/settings/app02/main.py!} -``` - -//// +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} ### 设置和测试 然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象: -```Python hl_lines="9-10 13 21" -{!../../../docs_src/settings/app02/test_main.py!} -``` +{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} 在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。 @@ -357,9 +289,7 @@ APP_NAME="ChimichangApp" 然后,您可以使用以下方式更新您的 `config.py`: -```Python hl_lines="9-10" -{!../../../docs_src/settings/app03/config.py!} -``` +{* ../../docs_src/settings/app03/config.py hl[9:10] *} 在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。 @@ -392,35 +322,7 @@ def get_settings(): 但是,由于我们在顶部使用了 `@lru_cache` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️ -//// tab | Python 3.9+ - -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 11" -{!> ../../../docs_src/settings/app03_an/main.py!} -``` - -//// - -//// tab | Python 3.8+ 非注解版本 - -/// tip - -如果可能,请尽量使用 `Annotated` 版本。 - -/// - -```Python hl_lines="1 10" -{!> ../../../docs_src/settings/app03/main.py!} -``` - -//// +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} 然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。 diff --git a/docs/zh/docs/advanced/sub-applications.md b/docs/zh/docs/advanced/sub-applications.md index a26301b50a..c42be2849a 100644 --- a/docs/zh/docs/advanced/sub-applications.md +++ b/docs/zh/docs/advanced/sub-applications.md @@ -10,9 +10,7 @@ 首先,创建主(顶层)**FastAPI** 应用及其*路径操作*: -```Python hl_lines="3 6-8" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *} ### 子应用 @@ -20,9 +18,7 @@ 子应用只是另一个标准 FastAPI 应用,但这个应用是被**挂载**的应用: -```Python hl_lines="11 14-16" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *} ### 挂载子应用 @@ -30,9 +26,7 @@ 本例的子应用挂载在 `/subapi` 路径下: -```Python hl_lines="11 19" -{!../../../docs_src/sub_applications/tutorial001.py!} -``` +{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *} ### 查看文档 diff --git a/docs/zh/docs/advanced/templates.md b/docs/zh/docs/advanced/templates.md index b09644e393..e627eed980 100644 --- a/docs/zh/docs/advanced/templates.md +++ b/docs/zh/docs/advanced/templates.md @@ -27,24 +27,22 @@ $ pip install jinja2 * 在返回模板的*路径操作*中声明 `Request` 参数 * 使用 `templates` 渲染并返回 `TemplateResponse`, 传递模板的名称、request对象以及一个包含多个键值对(用于Jinja2模板)的"context"字典, -```Python hl_lines="4 11 15-16" -{!../../../docs_src/templates/tutorial001.py!} -``` +{* ../../docs_src/templates/tutorial001.py hl[4,11,15:16] *} -/// note | "笔记" +/// note | 笔记 在FastAPI 0.108.0,Starlette 0.29.0之前,`name`是第一个参数。 并且,在此之前,`request`对象是作为context的一部分以键值对的形式传递的。 /// -/// tip | "提示" +/// tip | 提示 通过声明 `response_class=HTMLResponse`,API 文档就能识别响应的对象是 HTML。 /// -/// note | "技术细节" +/// note | 技术细节 您还可以使用 `from starlette.templating import Jinja2Templates`。 @@ -57,7 +55,7 @@ $ pip install jinja2 编写模板 `templates/item.html`,代码如下: ```jinja hl_lines="7" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` ### 模板上下文 @@ -111,17 +109,17 @@ Item ID: 42 你还可以在模板内部将 `url_for()`用于静态文件,例如你挂载的 `name="static"`的 `StaticFiles`。 ```jinja hl_lines="4" -{!../../../docs_src/templates/templates/item.html!} +{!../../docs_src/templates/templates/item.html!} ``` 本例中,它将链接到 `static/styles.css`中的CSS文件: ```CSS hl_lines="4" -{!../../../docs_src/templates/static/styles.css!} +{!../../docs_src/templates/static/styles.css!} ``` 因为使用了 `StaticFiles`, **FastAPI** 应用会自动提供位于 URL `/static/styles.css`的 CSS 文件。 ## 更多说明 -包括测试模板等更多详情,请参阅 Starlette 官方文档 - 模板。 +包括测试模板等更多详情,请参阅 Starlette 官方文档 - 模板。 diff --git a/docs/zh/docs/advanced/testing-database.md b/docs/zh/docs/advanced/testing-database.md deleted file mode 100644 index 58bf9af8c5..0000000000 --- a/docs/zh/docs/advanced/testing-database.md +++ /dev/null @@ -1,101 +0,0 @@ -# 测试数据库 - -您还可以使用[测试依赖项](testing-dependencies.md){.internal-link target=_blank}中的覆盖依赖项方法变更测试的数据库。 - -实现设置其它测试数据库、在测试后回滚数据、或预填测试数据等操作。 - -本章的主要思路与上一章完全相同。 - -## 为 SQL 应用添加测试 - -为了使用测试数据库,我们要升级 [SQL 关系型数据库](../tutorial/sql-databases.md){.internal-link target=_blank} 一章中的示例。 - -应用的所有代码都一样,直接查看那一章的示例代码即可。 - -本章只是新添加了测试文件。 - -正常的依赖项 `get_db()` 返回数据库会话。 - -测试时使用覆盖依赖项返回自定义数据库会话代替正常的依赖项。 - -本例中,要创建仅用于测试的临时数据库。 - -## 文件架构 - -创建新文件 `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 | "提示" - -`overrider_get_db()` 与 `get_db` 的代码几乎完全一样,只是 `overrider_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/zh/docs/advanced/testing-dependencies.md b/docs/zh/docs/advanced/testing-dependencies.md index cc9a38200e..8d53a6d496 100644 --- a/docs/zh/docs/advanced/testing-dependencies.md +++ b/docs/zh/docs/advanced/testing-dependencies.md @@ -28,11 +28,9 @@ 这样一来,**FastAPI** 就会调用覆盖依赖项,不再调用原依赖项。 -```Python hl_lines="26-27 30" -{!../../../docs_src/dependency_testing/tutorial001.py!} -``` +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} -/// tip | "提示" +/// tip | 提示 **FastAPI** 应用中的任何位置都可以实现覆盖依赖项。 @@ -48,7 +46,7 @@ FastAPI 可以覆盖这些位置的依赖项。 app.dependency_overrides = {} ``` -/// tip | "提示" +/// tip | 提示 如果只在某些测试时覆盖依赖项,您可以在测试开始时(在测试函数内)设置覆盖依赖项,并在结束时(在测试函数结尾)重置覆盖依赖项。 diff --git a/docs/zh/docs/advanced/testing-events.md b/docs/zh/docs/advanced/testing-events.md index 222a67c8cf..71b3739c3d 100644 --- a/docs/zh/docs/advanced/testing-events.md +++ b/docs/zh/docs/advanced/testing-events.md @@ -2,6 +2,4 @@ 使用 `TestClient` 和 `with` 语句,在测试中运行事件处理器(`startup` 与 `shutdown`)。 -```Python hl_lines="9-12 20-24" -{!../../../docs_src/app_testing/tutorial003.py!} -``` +{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/zh/docs/advanced/testing-websockets.md b/docs/zh/docs/advanced/testing-websockets.md index 795b739458..b84647a3ed 100644 --- a/docs/zh/docs/advanced/testing-websockets.md +++ b/docs/zh/docs/advanced/testing-websockets.md @@ -4,12 +4,10 @@ 为此,要在 `with` 语句中使用 `TestClient` 连接 WebSocket。 -```Python hl_lines="27-31" -{!../../../docs_src/app_testing/tutorial002.py!} -``` +{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} -/// note | "笔记" +/// note | 笔记 -更多细节详见 Starlette 官档 - 测试 WebSockets。 +更多细节详见 Starlette 官档 - 测试 WebSockets。 /// diff --git a/docs/zh/docs/advanced/using-request-directly.md b/docs/zh/docs/advanced/using-request-directly.md index bc9e898d97..a9658c0348 100644 --- a/docs/zh/docs/advanced/using-request-directly.md +++ b/docs/zh/docs/advanced/using-request-directly.md @@ -15,7 +15,7 @@ ## `Request` 对象的细节 -实际上,**FastAPI** 的底层是 **Starlette**,**FastAPI** 只不过是在 **Starlette** 顶层提供了一些工具,所以能直接使用 Starlette 的 `Request` 对象。 +实际上,**FastAPI** 的底层是 **Starlette**,**FastAPI** 只不过是在 **Starlette** 顶层提供了一些工具,所以能直接使用 Starlette 的 `Request` 对象。 但直接从 `Request` 对象提取数据时(例如,读取请求体),**FastAPI** 不会验证、转换和存档数据(为 API 文档使用 OpenAPI)。 @@ -29,13 +29,11 @@ 此时,需要直接访问请求。 -```Python hl_lines="1 7-8" -{!../../../docs_src/using_request_directly/tutorial001.py!} -``` +{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} 把*路径操作函数*的参数类型声明为 `Request`,**FastAPI** 就能把 `Request` 传递到参数里。 -/// tip | "提示" +/// tip | 提示 注意,本例除了声明请求参数之外,还声明了路径参数。 @@ -47,9 +45,9 @@ ## `Request` 文档 -更多细节详见 Starlette 官档 - `Request` 对象。 +更多细节详见 Starlette 官档 - `Request` 对象。 -/// note | "技术细节" +/// note | 技术细节 您也可以使用 `from starlette.requests import Request`。 diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md index 3fcc36dfee..005ed9242c 100644 --- a/docs/zh/docs/advanced/websockets.md +++ b/docs/zh/docs/advanced/websockets.md @@ -34,19 +34,15 @@ $ pip install websockets 但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: -```Python hl_lines="2 6-38 41-43" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} ## 创建 `websocket` 在您的 **FastAPI** 应用程序中,创建一个 `websocket`: -```Python hl_lines="1 46-47" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} -/// note | "技术细节" +/// note | 技术细节 您也可以使用 `from starlette.websockets import WebSocket`。 @@ -58,9 +54,7 @@ $ pip install websockets 在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 -```Python hl_lines="48-52" -{!../../../docs_src/websockets/tutorial001.py!} -``` +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} 您可以接收和发送二进制、文本和 JSON 数据。 @@ -109,57 +103,7 @@ $ uvicorn main:app --reload 它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同: -//// tab | Python 3.10+ - -```Python hl_lines="68-69 82" -{!> ../../../docs_src/websockets/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="68-69 82" -{!> ../../../docs_src/websockets/tutorial002_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="69-70 83" -{!> ../../../docs_src/websockets/tutorial002_an.py!} -``` - -//// - -//// tab | Python 3.10+ 非带注解版本 - -/// tip - -如果可能,请尽量使用 `Annotated` 版本。 - -/// - -```Python hl_lines="66-67 79" -{!> ../../../docs_src/websockets/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ 非带注解版本 - -/// tip - -如果可能,请尽量使用 `Annotated` 版本。 - -/// - -```Python hl_lines="68-69 81" -{!> ../../../docs_src/websockets/tutorial002.py!} -``` - -//// +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -200,21 +144,7 @@ $ uvicorn main:app --reload 当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 -//// tab | Python 3.9+ - -```Python hl_lines="79-81" -{!> ../../../docs_src/websockets/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="81-83" -{!> ../../../docs_src/websockets/tutorial003.py!} -``` - -//// +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} 尝试以下操作: @@ -242,5 +172,5 @@ Client #1596980209979 left the chat 要了解更多选项,请查看 Starlette 的文档: -* [WebSocket 类](https://www.starlette.io/websockets/) -* [基于类的 WebSocket 处理](https://www.starlette.io/endpoints/#websocketendpoint)。 +* [WebSocket 类](https://www.starlette.dev/websockets/) +* [基于类的 WebSocket 处理](https://www.starlette.dev/endpoints/#websocketendpoint)。 diff --git a/docs/zh/docs/advanced/wsgi.md b/docs/zh/docs/advanced/wsgi.md index 179ec88aab..363025a343 100644 --- a/docs/zh/docs/advanced/wsgi.md +++ b/docs/zh/docs/advanced/wsgi.md @@ -12,9 +12,7 @@ 之后将其挂载到某一个路径下。 -```Python hl_lines="2-3 22" -{!../../../docs_src/wsgi/tutorial001.py!} -``` +{* ../../docs_src/wsgi/tutorial001.py hl[2:3,22] *} ## 检查 diff --git a/docs/zh/docs/async.md b/docs/zh/docs/async.md index 822ceeee49..4028ed51aa 100644 --- a/docs/zh/docs/async.md +++ b/docs/zh/docs/async.md @@ -251,7 +251,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 这与 **FastAPI** 的性能水平相同。 -您可以同时拥有并行性和异步性,您可以获得比大多数经过测试的 NodeJS 框架更高的性能,并且与 Go 不相上下, Go 是一种更接近于 C 的编译语言(全部归功于 Starlette)。 +你可以同时拥有并行性和异步性,你可以获得比大多数经过测试的 NodeJS 框架更高的性能,并且与 Go 不相上下, Go 是一种更接近于 C 的编译语言(全部归功于 Starlette)。 ### 并发比并行好吗? @@ -275,7 +275,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 但在这种情况下,如果你能带上 8 名前收银员/厨师,现在是清洁工一起清扫,他们中的每一个人(加上你)都能占据房子的一个区域来清扫,你就可以在额外的帮助下并行的更快地完成所有工作。 -在这个场景中,每个清洁工(包括您)都将是一个处理器,完成这个工作的一部分。 +在这个场景中,每个清洁工(包括你)都将是一个处理器,完成这个工作的一部分。 由于大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由 CPU 完成的,所以他们称这些问题为"CPU 密集型"。 @@ -292,9 +292,9 @@ CPU 密集型操作的常见示例是需要复杂的数学处理。 ### 并发 + 并行: Web + 机器学习 -使用 **FastAPI**,您可以利用 Web 开发中常见的并发机制的优势(NodeJS 的主要吸引力)。 +使用 **FastAPI**,你可以利用 Web 开发中常见的并发机制的优势(NodeJS 的主要吸引力)。 -并且,您也可以利用并行和多进程(让多个进程并行运行)的优点来处理与机器学习系统中类似的 **CPU 密集型** 工作。 +并且,你也可以利用并行和多进程(让多个进程并行运行)的优点来处理与机器学习系统中类似的 **CPU 密集型** 工作。 这一点,再加上 Python 是**数据科学**、机器学习(尤其是深度学习)的主要语言这一简单事实,使得 **FastAPI** 与数据科学/机器学习 Web API 和应用程序(以及其他许多应用程序)非常匹配。 @@ -304,7 +304,7 @@ CPU 密集型操作的常见示例是需要复杂的数学处理。 现代版本的 Python 有一种非常直观的方式来定义异步代码。这使它看起来就像正常的"顺序"代码,并在适当的时候"等待"。 -当有一个操作需要等待才能给出结果,且支持这个新的 Python 特性时,您可以编写如下代码: +当有一个操作需要等待才能给出结果,且支持这个新的 Python 特性时,你可以编写如下代码: ```Python burgers = await get_burgers(2) @@ -340,7 +340,7 @@ burgers = get_burgers(2) --- -因此,如果您使用的库告诉您可以使用 `await` 调用它,则需要使用 `async def` 创建路径操作函数 ,如: +因此,如果你使用的库告诉你可以使用 `await` 调用它,则需要使用 `async def` 创建路径操作函数 ,如: ```Python hl_lines="2-3" @app.get('/burgers') @@ -351,15 +351,15 @@ async def read_burgers(): ### 更多技术细节 -您可能已经注意到,`await` 只能在 `async def` 定义的函数内部使用。 +你可能已经注意到,`await` 只能在 `async def` 定义的函数内部使用。 但与此同时,必须"等待"通过 `async def` 定义的函数。因此,带 `async def` 的函数也只能在 `async def` 定义的函数内部调用。 那么,这关于先有鸡还是先有蛋的问题,如何调用第一个 `async` 函数? -如果您使用 **FastAPI**,你不必担心这一点,因为"第一个"函数将是你的路径操作函数,FastAPI 将知道如何做正确的事情。 +如果你使用 **FastAPI**,你不必担心这一点,因为"第一个"函数将是你的路径操作函数,FastAPI 将知道如何做正确的事情。 -但如果您想在没有 FastAPI 的情况下使用 `async` / `await`,则可以这样做。 +但如果你想在没有 FastAPI 的情况下使用 `async` / `await`,则可以这样做。 ### 编写自己的异步代码 @@ -367,7 +367,9 @@ Starlette (和 **FastAPI**) 是基于 AnyIO 来处理高级的并发用例,这些用例需要在自己的代码中使用更高级的模式。 -即使您没有使用 **FastAPI**,您也可以使用 AnyIO 编写自己的异步程序,使其拥有较高的兼容性并获得一些好处(例如, 结构化并发)。 +即使你没有使用 **FastAPI**,你也可以使用 AnyIO 编写自己的异步程序,使其拥有较高的兼容性并获得一些好处(例如, 结构化并发)。 + +我(指原作者 —— 译者注)基于 AnyIO 新建了一个库,作为一个轻量级的封装层,用来优化类型注解,同时提供了更好的**自动补全**、**内联错误提示**等功能。这个库还附带了一个友好的入门指南和教程,能帮助你**理解**并编写**自己的异步代码**:Asyncer。如果你有**结合使用异步代码和常规**(阻塞/同步)代码的需求,这个库会特别有用。 ### 其他形式的异步代码 @@ -381,7 +383,7 @@ Starlette (和 **FastAPI**) 是基于 Gevent。但代码的理解、调试和思考都要复杂许多。 -在以前版本的 NodeJS / 浏览器 JavaScript 中,你会使用"回调",因此也可能导致回调地狱。 +在以前版本的 NodeJS / 浏览器 JavaScript 中,你会使用"回调",因此也可能导致“回调地狱”。 ## 协程 @@ -407,7 +409,7 @@ Starlette (和 **FastAPI**) 是基于 I/O 的代码。 +如果你使用过另一个不以上述方式工作的异步框架,并且你习惯于用普通的 `def` 定义普通的仅计算路径操作函数,以获得微小的性能增益(大约100纳秒),请注意,在 FastAPI 中,效果将完全相反。在这些情况下,最好使用 `async def`,除非路径操作函数内使用执行阻塞 I/O 的代码。 -在这两种情况下,与您之前的框架相比,**FastAPI** 可能[仍然很快](index.md#_11){.internal-link target=_blank}。 +在这两种情况下,与你之前的框架相比,**FastAPI** 可能[仍然很快](index.md#_11){.internal-link target=_blank}。 ### 依赖 @@ -429,9 +431,9 @@ Starlette (和 **FastAPI**) 是基于 赶时间吗?. +否则,你最好应该遵守的指导原则赶时间吗?. diff --git a/docs/zh/docs/contributing.md b/docs/zh/docs/contributing.md deleted file mode 100644 index 85b341a8d6..0000000000 --- a/docs/zh/docs/contributing.md +++ /dev/null @@ -1,472 +0,0 @@ -# 开发 - 贡献 - -首先,你可能想了解 [帮助 FastAPI 及获取帮助](help-fastapi.md){.internal-link target=_blank}的基本方式。 - -## 开发 - -如果你已经克隆了源码仓库,并且需要深入研究代码,下面是设置开发环境的指南。 - -### 通过 `venv` 管理虚拟环境 - -你可以使用 Python 的 `venv` 模块在一个目录中创建虚拟环境: - -
- -```console -$ python -m venv env -``` - -
- -这将使用 Python 程序创建一个 `./env/` 目录,然后你将能够为这个隔离的环境安装软件包。 - -### 激活虚拟环境 - -使用以下方法激活新环境: - -//// tab | Linux, macOS - -
- -```console -$ source ./env/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .\env\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Or if you use Bash for Windows (e.g. Git Bash): - -
- -```console -$ source ./env/Scripts/activate -``` - -
- -//// - -要检查操作是否成功,运行: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which pip - -some/directory/fastapi/env/bin/pip -``` - -
- -//// - -//// tab | 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 - -如果你创建一个导入并使用 FastAPI 的 Python 文件,然后使用虚拟环境中的 Python 运行它,它将使用你本地的 FastAPI 源码。 - -并且如果你更改该本地 FastAPI 的源码,由于它是通过 `-e` 安装的,当你再次运行那个 Python 文件,它将使用你刚刚编辑过的最新版本的 FastAPI。 - -这样,你不必再去重新"安装"你的本地版本即可测试所有更改。 - -/// note | "技术细节" - -仅当你使用此项目中的 `requirements.txt` 安装而不是直接使用 `pip install fastapi` 安装时,才会发生这种情况。 - -这是因为在 `requirements.txt` 中,本地的 FastAPI 是使用“可编辑” (`-e`)选项安装的 - -/// - -### 格式化 - -你可以运行下面的脚本来格式化和清理所有代码: - -
- -```console -$ bash scripts/format.sh -``` - -
- -它还会自动对所有导入代码进行排序整理。 - -为了使整理正确进行,你需要在当前环境中安装本地的 FastAPI,即在运行上述段落中的命令时添加 `-e`。 - -## 文档 - -首先,请确保按上述步骤设置好环境,这将安装所有需要的依赖。 - -### 实时文档 - -在本地开发时,可以使用该脚本构建站点并检查所做的任何更改,并实时重载: - -
- -```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`。 - -这样,你可以编辑文档 / 源文件并实时查看更改。 - -/// tip - -或者你也可以手动执行和该脚本一样的操作 - -进入语言目录,如果是英文文档,目录则是 `docs/en/`: - -```console -$ cd docs/en/ -``` - -在该目录执行 `mkdocs` 命令 - -```console -$ mkdocs serve --dev-addr 8008 -``` - -/// - -#### Typer CLI (可选) - -本指引向你展示了如何直接用 `python` 运行 `./scripts/docs.py` 中的脚本。 - -但你也可以使用 Typer CLI,而且在安装了补全功能后,你将可以在终端中对命令进行自动补全。 - -如果你已经安装 Typer CLI ,则可以使用以下命令安装自动补全功能: - -
- -```console -$ typer --install-completion - -zsh completion installed in /home/user/.bashrc. -Completion will take effect once you restart the terminal. -``` - -
- -### 文档架构 - -文档使用 MkDocs 生成。 - -在 `./scripts/docs.py` 中还有额外工具 / 脚本来处理翻译。 - -/// tip - -你不需要去了解 `./scripts/docs.py` 中的代码,只需在命令行中使用它即可。 - -/// - -所有文档均在 `./docs/en/` 目录中以 Markdown 文件格式保存。 - -许多的教程中都有一些代码块,大多数情况下,这些代码是可以直接运行的,因为这些代码不是写在 Markdown 文件里的,而是写在 `./docs_src/` 目录中的 Python 文件里。 - -在生成站点的时候,这些 Python 文件会被打包进文档中。 - -### 测试文档 - -大多数的测试实际上都是针对文档中的示例源文件运行的。 - -这有助于确保: - -* 文档始终是最新的。 -* 文档示例可以直接运行。 -* 绝大多数特性既在文档中得以阐述,又通过测试覆盖进行保障。 - - -### 应用和文档同时运行 - -如果你使用以下方式运行示例程序: - -
- -```console -$ uvicorn tutorial001:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -由于 Uvicorn 默认使用 `8000` 端口 ,因此运行在 `8008` 端口上的文档不会与之冲突。 - -### 翻译 - -**非常感谢**你能够参与文档的翻译!这项工作需要社区的帮助才能完成。 🌎 🚀 - -以下是参与帮助翻译的步骤。 - -#### 建议和指南 - -* 在当前 已有的 pull requests 中查找你使用的语言,添加要求修改或同意合并的评审意见。 - -/// tip - -你可以为已有的 pull requests 添加包含修改建议的评论。 - -详情可查看关于 添加 pull request 评审意见 以同意合并或要求修改的文档。 - -/// - -* 检查在 GitHub Discussion 是否有关于你所用语言的协作翻译。 如果有,你可以订阅它,当有一条新的 PR 请求需要评审时,系统会自动将其添加到讨论中,你也会收到对应的推送。 - -* 每翻译一个页面新增一个 pull request。这将使其他人更容易对其进行评审。 - -对于我(译注:作者使用西班牙语和英语)不懂的语言,我将在等待其他人评审翻译之后将其合并。 - -* 你还可以查看是否有你所用语言的翻译,并对其进行评审,这将帮助我了解翻译是否正确以及能否将其合并。 - * 可以在 GitHub Discussions 中查看。 - * 也可以在现有 PR 中通过你使用的语言标签来筛选对应的 PR,举个例子,对于西班牙语,标签是 `lang-es`。 - -* 请使用相同的 Python 示例,且只需翻译文档中的文本,不用修改其它东西。 - -* 请使用相同的图片、文件名以及链接地址,不用修改其它东西。 - -* 你可以从 ISO 639-1 代码列表 表中查找你想要翻译语言的两位字母代码。 - -#### 已有的语言 - -假设你想将某个页面翻译成已经翻译了一些页面的语言,例如西班牙语。 - -对于西班牙语来说,它的两位字母代码是 `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 -``` - -
- -/// tip - -或者你也可以手动执行和该脚本一样的操作 - -进入语言目录,对于西班牙语的翻译,目录是 `docs/es/`: - -```console -$ cd docs/es/ -``` - -在该目录执行 `mkdocs` 命令 - -```console -$ mkdocs serve --dev-addr 8008 -``` - -/// - -现在你可以访问 http://127.0.0.1:8008 实时查看你所做的更改。 - -如果你查看 FastAPI 的线上文档网站,会看到每种语言都有所有的文档页面,但是某些页面并未被翻译并且会有一处关于缺少翻译的提示。(但是当你像上面这样在本地运行文档时,你只会看到已经翻译的页面。) - -现在假设你要为 [Features](features.md){.internal-link target=_blank} 章节添加翻译。 - -* 复制下面的文件: - -``` -docs/en/docs/features.md -``` - -* 粘贴到你想要翻译语言目录的相同位置,比如: - -``` -docs/es/docs/features.md -``` - -/// tip - -注意路径和文件名的唯一变化是语言代码,从 `en` 更改为 `es`。 - -/// - -回到浏览器你就可以看到刚刚更新的章节了。🎉 - -现在,你可以翻译这些内容并在保存文件后进行预览。 - -#### 新语言 - -假设你想要为尚未有任何页面被翻译的语言添加翻译。 - -假设你想要添加克里奥尔语翻译,而且文档中还没有该语言的翻译。 - -点击上面提到的“ISO 639-1 代码列表”链接,可以查到“克里奥尔语”的代码为 `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 -``` - -
- -现在,你可以在编辑器中查看新创建的目录 `docs/ht/`。 - -这条命令会生成一个从 `en` 版本继承了所有属性的配置文件 `docs/ht/mkdocs.yml`: - -```yaml -INHERIT: ../en/mkdocs.yml -``` - -/// tip - -你也可以自己手动创建包含这些内容的文件。 - -/// - -这条命令还会生成一个文档主页 `docs/ht/index.md`,你可以从这个文件开始翻译。 - -然后,你可以根据上面的"已有语言"的指引继续进行翻译。 - -翻译完成后,你就可以用 `docs/ht/mkdocs.yml` 和 `docs/ht/index.md` 发起 PR 了。🎉 - -#### 预览结果 - -你可以执行 `./scripts/docs.py live` 命令来预览结果(或者 `mkdocs serve`)。 - -但是当你完成翻译后,你可以像在线上展示一样测试所有内容,包括所有其他语言。 - -为此,首先构建所有文档: - -
- -```console -// Use the command "build-all", this will take a bit -$ python ./scripts/docs.py build-all - -Building docs for: en -Building docs for: es -Successfully built docs for: es -``` - -
- -这样会对每一种语言构建一个独立的文档站点,并最终把这些站点全部打包输出到 `./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 -``` - -
- -## 测试 - -你可以在本地运行下面的脚本来测试所有代码并生成 HTML 格式的覆盖率报告: - -
- -```console -$ bash scripts/test-cov-html.sh -``` - -
- -该命令生成了一个 `./htmlcov/` 目录,如果你在浏览器中打开 `./htmlcov/index.html` 文件,你可以交互式地浏览被测试所覆盖的代码区块,并注意是否缺少了任何区块。 diff --git a/docs/zh/docs/deployment/cloud.md b/docs/zh/docs/deployment/cloud.md index b086b7b6b8..8a892a560b 100644 --- a/docs/zh/docs/deployment/cloud.md +++ b/docs/zh/docs/deployment/cloud.md @@ -10,7 +10,4 @@ 这表明了他们对 FastAPI 及其**社区**(您)的真正承诺,因为他们不仅想为您提供**良好的服务**,而且还想确保您拥有一个**良好且健康的框架**:FastAPI。 🙇 -您可能想尝试他们的服务并阅读他们的指南: - -* Platform.sh -* Porter +您可能想尝试他们的服务并阅读他们的指南. diff --git a/docs/zh/docs/deployment/concepts.md b/docs/zh/docs/deployment/concepts.md index 7a0b6c3d25..f7208da7c5 100644 --- a/docs/zh/docs/deployment/concepts.md +++ b/docs/zh/docs/deployment/concepts.md @@ -220,7 +220,7 @@ 这些工作进程将是运行您的应用程序的进程,它们将执行主要计算以接收 **请求** 并返回 **响应**,并且它们将加载您放入 RAM 中的变量中的任何内容。 - + 当然,除了您的应用程序之外,同一台机器可能还运行**其他进程**。 diff --git a/docs/zh/docs/deployment/https.md b/docs/zh/docs/deployment/https.md index 9c963d587f..d994c4add8 100644 --- a/docs/zh/docs/deployment/https.md +++ b/docs/zh/docs/deployment/https.md @@ -86,7 +86,7 @@ DNS 服务器会告诉浏览器使用某个特定的 **IP 地址**。 这将是你在 DNS 服务器中为你的服务器配置的公共 IP 地址。 - + ### TLS 握手开始 @@ -94,7 +94,7 @@ DNS 服务器会告诉浏览器使用某个特定的 **IP 地址**。 这将是 通信的第一部分只是建立客户端和服务器之间的连接并决定它们将使用的加密密钥等。 - + 客户端和服务器之间建立 TLS 连接的过程称为 **TLS 握手**。 @@ -112,7 +112,7 @@ TLS 终止代理可以访问一个或多个 **TLS 证书**(HTTPS 证书)。 在这种情况下,它将使用`someapp.example.com`的证书。 - + 客户端已经**信任**生成该 TLS 证书的实体(在本例中为 Let's Encrypt,但我们稍后会看到),因此它可以**验证**该证书是否有效。 @@ -134,19 +134,19 @@ TLS 终止代理可以访问一个或多个 **TLS 证书**(HTTPS 证书)。 接下来,客户端发送一个 **HTTPS 请求**。 这其实只是一个通过 TLS 加密连接的 HTTP 请求。 - + ### 解密请求 TLS 终止代理将使用协商好的加密算法**解密请求**,并将**(解密的)HTTP 请求**传输到运行应用程序的进程(例如运行 FastAPI 应用的 Uvicorn 进程)。 - + ### HTTP 响应 应用程序将处理请求并向 TLS 终止代理发送**(未加密)HTTP 响应**。 - + ### HTTPS 响应 @@ -154,7 +154,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**( 接下来,浏览器将验证响应是否有效和是否使用了正确的加密密钥等。然后它会**解密响应**并处理它。 - + 客户端(浏览器)将知道响应来自正确的服务器,因为它使用了他们之前使用 **HTTPS 证书** 协商出的加密算法。 @@ -164,7 +164,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**( 只有一个进程可以处理特定的 IP 和端口(在我们的示例中为 TLS 终止代理),但其他应用程序/进程也可以在服务器上运行,只要它们不尝试使用相同的 **公共 IP 和端口的组合**。 - + 这样,TLS 终止代理就可以为多个应用程序处理**多个域名**的 HTTPS 和证书,然后在每种情况下将请求传输到正确的应用程序。 @@ -174,7 +174,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**( 然后,会有另一个程序(在某些情况下是另一个程序,在某些情况下可能是同一个 TLS 终止代理)与 Let's Encrypt 通信并更新证书。 - + **TLS 证书** **与域名相关联**,而不是与 IP 地址相关联。 diff --git a/docs/zh/docs/deployment/manually.md b/docs/zh/docs/deployment/manually.md index 30ee7a1e95..2c2784a640 100644 --- a/docs/zh/docs/deployment/manually.md +++ b/docs/zh/docs/deployment/manually.md @@ -1,12 +1,62 @@ -# 手动运行服务器 - Uvicorn +# 手动运行服务器 -在远程服务器计算机上运行 **FastAPI** 应用程序所需的主要东西是 ASGI 服务器程序,例如 **Uvicorn**。 +## 使用 `fastapi run` 命令 -有 3 个主要可选方案: +简而言之,使用 `fastapi run` 来运行您的 FastAPI 应用程序: -* Uvicorn:高性能 ASGI 服务器。 +
+ +```console +$ fastapi run main.py + + FastAPI Starting production server 🚀 + + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with + the following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +这在大多数情况下都能正常运行。😎 + +例如,您可以使用该命令在容器、服务器等环境中启动您的 **FastAPI** 应用。 + +## ASGI 服务器 + +让我们深入了解一些细节。 + +FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称为 ASGI。FastAPI 本质上是一个 ASGI Web 框架。 + +要在远程服务器上运行 **FastAPI** 应用(或任何其他 ASGI 应用),您需要一个 ASGI 服务器程序,例如 **Uvicorn**。它是 `fastapi` 命令默认使用的 ASGI 服务器。 + +除此之外,还有其他一些可选的 ASGI 服务器,例如: + +* Uvicorn:高性能 ASGI 服务器。 * Hypercorn:与 HTTP/2 和 Trio 等兼容的 ASGI 服务器。 * Daphne:为 Django Channels 构建的 ASGI 服务器。 +* Granian:基于 Rust 的 HTTP 服务器,专为 Python 应用设计。 +* NGINX Unit:NGINX Unit 是一个轻量级且灵活的 Web 应用运行时环境。 ## 服务器主机和服务器程序 @@ -21,11 +71,13 @@ ## 安装服务器程序 -您可以使用以下命令安装 ASGI 兼容服务器: +当您安装 FastAPI 时,它自带一个生产环境服务器——Uvicorn,并且您可以使用 `fastapi run` 命令来启动它。 -//// tab | Uvicorn +不过,您也可以手动安装 ASGI 服务器。 -* Uvicorn,一个快如闪电 ASGI 服务器,基于 uvloop 和 httptools 构建。 +请确保您创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装服务器应用程序。 + +例如,要安装 Uvicorn,可以运行以下命令:
@@ -37,39 +89,21 @@ $ pip install "uvicorn[standard]"
+类似的流程也适用于任何其他 ASGI 服务器程序。 + /// tip -通过添加`standard`,Uvicorn 将安装并使用一些推荐的额外依赖项。 +通过添加 `standard` 选项,Uvicorn 将安装并使用一些推荐的额外依赖项。 -其中包括`uvloop`,它是`asyncio`的高性能替代品,它提供了巨大的并发性能提升。 +其中包括 `uvloop`,这是 `asyncio` 的高性能替代方案,能够显著提升并发性能。 + +当您使用 `pip install "fastapi[standard]"` 安装 FastAPI 时,实际上也会安装 `uvicorn[standard]`。 /// -//// - -//// tab | Hypercorn - -* Hypercorn,一个也与 HTTP/2 兼容的 ASGI 服务器。 - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...或任何其他 ASGI 服务器。 - -//// - ## 运行服务器程序 -您可以按照之前教程中的相同方式运行应用程序,但不使用`--reload`选项,例如: - -//// tab | Uvicorn +如果您手动安装了 ASGI 服务器,通常需要以特定格式传递一个导入字符串,以便服务器能够正确导入您的 FastAPI 应用:
@@ -81,79 +115,43 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
-//// +/// note -//// tab | Hypercorn +命令 `uvicorn main:app` 的含义如下: -
+* `main`:指的是 `main.py` 文件(即 Python “模块”)。 +* `app`:指的是 `main.py` 文件中通过 `app = FastAPI()` 创建的对象。 -```console -$ hypercorn main:app --bind 0.0.0.0:80 +它等价于以下导入语句: -Running on 0.0.0.0:8080 over http (CTRL + C to quit) +```Python +from main import app ``` -
- -//// - -/// warning - -如果您正在使用`--reload`选项,请记住删除它。 - - `--reload` 选项消耗更多资源,并且更不稳定。 - - 它在**开发**期间有很大帮助,但您**不应该**在**生产环境**中使用它。 - /// -## Hypercorn with Trio +每种 ASGI 服务器程序通常都会有类似的命令,您可以在它们的官方文档中找到更多信息。 -Starlette 和 **FastAPI** 基于 AnyIO, 所以它们才能同时与 Python 的标准库 asyncioTrio 兼容。 +/// warning -尽管如此,Uvicorn 目前仅与 asyncio 兼容,并且通常使用 `uvloop`, 它是`asyncio`的高性能替代品。 +Uvicorn 和其他服务器支持 `--reload` 选项,该选项在开发过程中非常有用。 -但如果你想直接使用**Trio**,那么你可以使用**Hypercorn**,因为它支持它。 ✨ +但 `--reload` 选项会消耗更多资源,且相对不稳定。 -### 安装具有 Trio 的 Hypercorn +它对于**开发阶段**非常有帮助,但在**生产环境**中**不应该**使用。 -首先,您需要安装具有 Trio 支持的 Hypercorn: - -
- -```console -$ pip install "hypercorn[trio]" ----> 100% -``` - -
- -### Run with Trio - -然后你可以传递值`trio`给命令行选项`--worker-class`: - -
- -```console -$ hypercorn main:app --worker-class trio -``` - -
- -这将通过您的应用程序启动 Hypercorn,并使用 Trio 作为后端。 - -现在您可以在应用程序内部使用 Trio。 或者更好的是,您可以使用 AnyIO,使您的代码与 Trio 和 asyncio 兼容。 🎉 +/// ## 部署概念 -这些示例运行服务器程序(例如 Uvicorn),启动**单个进程**,在所有 IP(`0.0.0.0`)上监听预定义端口(例如`80`)。 +这些示例运行服务器程序(例如 Uvicorn),启动**单个进程**,在所有 IP(`0.0.0.0`)上监听预定义端口(例如`80`)。 这是基本思路。 但您可能需要处理一些其他事情,例如: * 安全性 - HTTPS * 启动时运行 * 重新启动 -* Replication(运行的进程数) +* 复制(运行的进程数) * 内存 * 开始前的步骤 diff --git a/docs/zh/docs/deployment/server-workers.md b/docs/zh/docs/deployment/server-workers.md index eb0252a5c9..e46ba7a09d 100644 --- a/docs/zh/docs/deployment/server-workers.md +++ b/docs/zh/docs/deployment/server-workers.md @@ -1,4 +1,4 @@ -# Server Workers - Gunicorn with Uvicorn +# 服务器工作进程(Workers) - 使用 Uvicorn 的多工作进程模式 让我们回顾一下之前的部署概念: @@ -9,125 +9,79 @@ * 内存 * 启动前的先前步骤 -到目前为止,通过文档中的所有教程,您可能已经在**单个进程**上运行了像 Uvicorn 这样的**服务器程序**。 +到目前为止,在文档中的所有教程中,您可能一直是在运行一个**服务器程序**,例如使用 `fastapi` 命令来启动 Uvicorn,而它默认运行的是**单进程模式**。 -部署应用程序时,您可能希望进行一些**进程复制**,以利用**多核**并能够处理更多请求。 +部署应用程序时,您可能希望进行一些**进程复制**,以利用**多核** CPU 并能够处理更多请求。 正如您在上一章有关[部署概念](concepts.md){.internal-link target=_blank}中看到的,您可以使用多种策略。 -在这里我将向您展示如何将 **Gunicorn** 与 **Uvicorn worker 进程** 一起使用。 +在本章节中,我将向您展示如何使用 `fastapi` 命令或直接使用 `uvicorn` 命令以**多工作进程模式**运行 **Uvicorn**。 /// info 如果您正在使用容器,例如 Docker 或 Kubernetes,我将在下一章中告诉您更多相关信息:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 -特别是,当在 **Kubernetes** 上运行时,您可能**不想**使用 Gunicorn,而是运行 **每个容器一个 Uvicorn 进程**,但我将在本章后面告诉您这一点。 +比较特别的是,在 **Kubernetes** 环境中运行时,您通常**不需要**使用多个工作进程,而是**每个容器运行一个 Uvicorn 进程**。不过,我会在本章节的后续部分详细介绍这一点。 /// -## Gunicorn with Uvicorn Workers +## 多个工作进程 -**Gunicorn**主要是一个使用**WSGI标准**的应用服务器。 这意味着 Gunicorn 可以为 Flask 和 Django 等应用程序提供服务。 Gunicorn 本身与 **FastAPI** 不兼容,因为 FastAPI 使用最新的 **ASGI 标准**。 +您可以使用 `--workers` 命令行选项来启动多个工作进程: -但 Gunicorn 支持充当 **进程管理器** 并允许用户告诉它要使用哪个特定的 **worker类**。 然后 Gunicorn 将使用该类启动一个或多个 **worker进程**。 +//// tab | `fastapi` -**Uvicorn** 有一个 Gunicorn 兼容的worker类。 - -使用这种组合,Gunicorn 将充当 **进程管理器**,监听 **端口** 和 **IP**。 它会将通信**传输**到运行**Uvicorn类**的worker进程。 - -然后与Gunicorn兼容的**Uvicorn worker**类将负责将Gunicorn发送的数据转换为ASGI标准以供FastAPI使用。 - -## 安装 Gunicorn 和 Uvicorn +如果您使用 `fastapi` 命令:
```console -$ pip install "uvicorn[standard]" gunicorn +$ fastapi run --workers 4 main.py ----> 100% + FastAPI Starting production server 🚀 + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. ```
-这将安装带有`standard`扩展包(以获得高性能)的 Uvicorn 和 Gunicorn。 +//// -## Run Gunicorn with Uvicorn Workers +//// tab | `uvicorn` -接下来你可以通过以下命令运行Gunicorn: - -
- -```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`"的 Python 模块,因此是文件 `main.py`。 `app` 是 **FastAPI** 应用程序的变量名称。 - * 你可以想象 `main:app` 相当于一个 Python `import` 语句,例如: - - ```Python - from main import app - ``` - - * 因此,`main:app` 中的冒号相当于 `from main import app` 中的 Python `import` 部分。 - -* `--workers`:要使用的worker进程数量,每个进程将运行一个 Uvicorn worker进程,在本例中为 4 个worker进程。 - -* `--worker-class`:在worker进程中使用的与 Gunicorn 兼容的工作类。 - * 这里我们传递了 Gunicorn 可以导入和使用的类: - - ```Python - import uvicorn.workers.UvicornWorker - ``` - -* `--bind`:这告诉 Gunicorn 要监听的 IP 和端口,使用冒号 (`:`) 分隔 IP 和端口。 - * 如果您直接运行 Uvicorn,则可以使用`--host 0.0.0.0`和`--port 80`,而不是`--bind 0.0.0.0:80`(Gunicorn 选项)。 - - -在输出中,您可以看到它显示了每个进程的 **PID**(进程 ID)(它只是一个数字)。 - -你可以看到: - -* Gunicorn **进程管理器** 以 PID `19499` 开头(在您的情况下,它将是一个不同的数字)。 -* 然后它开始`Listening at: http://0.0.0.0:80`。 -* 然后它检测到它必须使用 `uvicorn.workers.UvicornWorker` 处的worker类。 -* 然后它启动**4个worker**,每个都有自己的PID:`19511`、`19513`、`19514`和`19515`。 - -Gunicorn 还将负责管理**死进程**和**重新启动**新进程(如果需要保持worker数量)。 因此,这在一定程度上有助于上面列表中**重启**的概念。 - -尽管如此,您可能还希望有一些外部的东西,以确保在必要时**重新启动 Gunicorn**,并且**在启动时运行它**等。 - -## Uvicorn with Workers - -Uvicorn 也有一个选项可以启动和运行多个 **worker进程**。 - -然而,到目前为止,Uvicorn 处理worker进程的能力比 Gunicorn 更有限。 因此,如果您想拥有这个级别(Python 级别)的进程管理器,那么最好尝试使用 Gunicorn 作为进程管理器。 - -无论如何,您都可以像这样运行它: +如果您更想要直接使用 `uvicorn` 命令:
@@ -151,13 +105,15 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
+//// + 这里唯一的新选项是 `--workers` 告诉 Uvicorn 启动 4 个工作进程。 -您还可以看到它显示了每个进程的 **PID**,父进程(这是 **进程管理器**)的 PID 为`27365`,每个工作进程的 PID 为:`27368`、`27369`, `27370`和`27367`。 +您还可以看到它显示了每个进程的 **PID**,父进程(这是**进程管理器**)的 PID 为`27365`,每个工作进程的 PID 为:`27368`、`27369`, `27370`和`27367`。 ## 部署概念 -在这里,您了解了如何使用 **Gunicorn**(或 Uvicorn)管理 **Uvicorn 工作进程**来**并行**应用程序的执行,利用 CPU 中的 **多核**,并 能够满足**更多请求**。 +在这里,您学习了如何使用多个**工作进程(workers)**来让应用程序的执行**并行化**,充分利用 CPU 的**多核性能**,并能够处理**更多的请求**。 从上面的部署概念列表来看,使用worker主要有助于**复制**部分,并对**重新启动**有一点帮助,但您仍然需要照顾其他部分: @@ -170,15 +126,13 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 ## 容器和 Docker -在关于 [容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank} 的下一章中,我将介绍一些可用于处理其他 **部署概念** 的策略。 +在关于 [容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank} 的下一章中,我将介绍一些可用于处理其他**部署概念**的策略。 -我还将向您展示 **官方 Docker 镜像**,其中包括 **Gunicorn 和 Uvicorn worker** 以及一些对简单情况有用的默认配置。 - -在那里,我还将向您展示如何 **从头开始构建自己的镜像** 以运行单个 Uvicorn 进程(没有 Gunicorn)。 这是一个简单的过程,并且可能是您在使用像 **Kubernetes** 这样的分布式容器管理系统时想要做的事情。 +我将向您展示如何**从零开始构建自己的镜像**,以运行一个单独的 Uvicorn 进程。这个过程相对简单,并且在使用 **Kubernetes** 等分布式容器管理系统时,这通常是您需要采取的方法。 ## 回顾 -您可以使用**Gunicorn**(或Uvicorn)作为Uvicorn工作进程的进程管理器,以利用**多核CPU**,**并行运行多个进程**。 +您可以在使用 `fastapi` 或 `uvicorn` 命令时,通过 `--workers` CLI 选项启用多个工作进程(workers),以充分利用**多核 CPU**,以**并行运行多个进程**。 如果您要设置**自己的部署系统**,同时自己处理其他部署概念,则可以使用这些工具和想法。 diff --git a/docs/zh/docs/environment-variables.md b/docs/zh/docs/environment-variables.md new file mode 100644 index 0000000000..812278051a --- /dev/null +++ b/docs/zh/docs/environment-variables.md @@ -0,0 +1,298 @@ +# 环境变量 + +/// tip + +如果你已经知道什么是“环境变量”并且知道如何使用它们,你可以放心跳过这一部分。 + +/// + +环境变量(也称为“**env var**”)是一个独立于 Python 代码**之外**的变量,它存在于**操作系统**中,可以被你的 Python 代码(或其他程序)读取。 + +环境变量对于处理应用程序**设置**、作为 Python **安装**的一部分等方面非常有用。 + +## 创建和使用环境变量 + +你在 **shell(终端)**中就可以**创建**和使用环境变量,并不需要用到 Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// 你可以使用以下命令创建一个名为 MY_NAME 的环境变量 +$ export MY_NAME="Wade Wilson" + +// 然后,你可以在其他程序中使用它,例如 +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | 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 程序: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// 这里我们还没有设置环境变量 +$ python main.py + +// 因为我们没有设置环境变量,所以我们得到的是默认值 + +Hello World from Python + +// 但是如果我们事先创建过一个环境变量 +$ export MY_NAME="Wade Wilson" + +// 然后再次调用程序 +$ python main.py + +// 现在就可以读取到环境变量了 + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// 这里我们还没有设置环境变量 +$ python main.py + +// 因为我们没有设置环境变量,所以我们得到的是默认值 + +Hello World from Python + +// 但是如果我们事先创建过一个环境变量 +$ $Env: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 + +你可以在 The Twelve-Factor App: 配置中了解更多信息。 + +/// + +## 类型和验证 + +这些环境变量只能处理**文本字符串**,因为它们是处于 Python 范畴之外的,必须与其他程序和操作系统的其余部分兼容(甚至与不同的操作系统兼容,如 Linux、Windows、macOS)。 + +这意味着从环境变量中读取的**任何值**在 Python 中都将是一个 `str`,任何类型转换或验证都必须在代码中完成。 + +你将在[高级用户指南 - 设置和环境变量](./advanced/settings.md)中了解更多关于使用环境变量处理**应用程序设置**的信息。 + +## `PATH` 环境变量 + +有一个**特殊的**环境变量称为 **`PATH`**,操作系统(Linux、macOS、Windows)用它来查找要运行的程序。 + +`PATH` 变量的值是一个长字符串,由 Linux 和 macOS 上的冒号 `:` 分隔的目录组成,而在 Windows 上则是由分号 `;` 分隔的。 + +例如,`PATH` 环境变量可能如下所示: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +这意味着系统应该在以下目录中查找程序: + +- `/usr/local/bin` +- `/usr/bin` +- `/bin` +- `/usr/sbin` +- `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +这意味着系统应该在以下目录中查找程序: + +- `C:\Program Files\Python312\Scripts` +- `C:\Program Files\Python312` +- `C:\Windows\System32` + +//// + +当你在终端中输入一个**命令**时,操作系统会在 `PATH` 环境变量中列出的**每个目录**中**查找**程序。 + +例如,当你在终端中输入 `python` 时,操作系统会在该列表中的**第一个目录**中查找名为 `python` 的程序。 + +如果找到了,那么操作系统将**使用它**;否则,操作系统会继续在**其他目录**中查找。 + +### 安装 Python 和更新 `PATH` + +安装 Python 时,可能会询问你是否要更新 `PATH` 环境变量。 + +//// tab | Linux, macOS + +假设你安装 Python 并最终将其安装在了目录 `/opt/custompython/bin` 中。 + +如果你同意更新 `PATH` 环境变量,那么安装程序将会将 `/opt/custompython/bin` 添加到 `PATH` 环境变量中。 + +它看起来大概会像这样: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +如此一来,当你在终端中输入 `python` 时,系统会在 `/opt/custompython/bin` 中找到 Python 程序(最后一个目录)并使用它。 + +//// + +//// tab | Windows + +假设你安装 Python 并最终将其安装在了目录 `C:\opt\custompython\bin` 中。 + +如果你同意更新 `PATH` 环境变量 (在 Python 安装程序中,这个操作是名为 `Add Python x.xx to PATH` 的复选框 —— 译者注),那么安装程序将会将 `C:\opt\custompython\bin` 添加到 `PATH` 环境变量中。 + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +如此一来,当你在终端中输入 `python` 时,系统会在 `C:\opt\custompython\bin` 中找到 Python 程序(最后一个目录)并使用它。 + +//// + +因此,如果你输入: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +系统会在 `/opt/custompython/bin` 中**找到** `python` 程序并运行它。 + +这和输入以下命令大致等价: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +系统会在 `C:\opt\custompython\bin\python` 中**找到** `python` 程序并运行它。 + +这和输入以下命令大致等价: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +当学习[虚拟环境](virtual-environments.md)时,这些信息将会很有用。 + +## 结论 + +通过这个教程,你应该对**环境变量**是什么以及如何在 Python 中使用它们有了基本的了解。 + +你也可以在环境变量 - 维基百科 (Wikipedia for Environment Variable) 中了解更多关于它们的信息。 + +在许多情况下,环境变量的用途和适用性并不是很明显。但是在开发过程中,它们会在许多不同的场景中出现,因此了解它们是很有必要的。 + +例如,你将在下一节关于[虚拟环境](virtual-environments.md)中需要这些信息。 diff --git a/docs/zh/docs/fastapi-cli.md b/docs/zh/docs/fastapi-cli.md index 00235bd022..3b67eb6645 100644 --- a/docs/zh/docs/fastapi-cli.md +++ b/docs/zh/docs/fastapi-cli.md @@ -9,47 +9,39 @@
```console -$ fastapi dev main.py -INFO Using path main.py -INFO Resolved absolute path /home/user/code/awesomeapp/main.py -INFO Searching for package file structure from directories with __init__.py files -INFO Importing from /home/user/code/awesomeapp +$ fastapi dev main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting development server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with the + following code: - ╭────────── FastAPI CLI - Development mode ───────────╮ - │ │ - │ Serving at: http://127.0.0.1:8000 │ - │ │ - │ API docs: http://127.0.0.1:8000/docs │ - │ │ - │ Running in development mode, for production use: │ - │ │ - fastapi run - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [2265862] using WatchFiles -INFO: Started server process [2265873] -INFO: Waiting for application startup. -INFO: Application startup complete. + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to + quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. ```
@@ -60,7 +52,7 @@ FastAPI CLI 接收你的 Python 程序路径,自动检测包含 FastAPI 的变 在生产环境中,你应该使用 `fastapi run` 命令。🚀 -在内部,**FastAPI CLI** 使用了 Uvicorn,这是一个高性能、适用于生产环境的 ASGI 服务器。😎 +在内部,**FastAPI CLI** 使用了 Uvicorn,这是一个高性能、适用于生产环境的 ASGI 服务器。😎 ## `fastapi dev` @@ -80,7 +72,7 @@ FastAPI CLI 接收你的 Python 程序路径,自动检测包含 FastAPI 的变 在大多数情况下,你会(且应该)有一个“终止代理”在上层为你处理 HTTPS,这取决于你如何部署应用程序,你的服务提供商可能会为你处理此事,或者你可能需要自己设置。 -/// tip | "提示" +/// tip | 提示 你可以在 [deployment documentation](deployment/index.md){.internal-link target=_blank} 获得更多信息。 diff --git a/docs/zh/docs/features.md b/docs/zh/docs/features.md index 24dc3e8ce0..eaf8daff7e 100644 --- a/docs/zh/docs/features.md +++ b/docs/zh/docs/features.md @@ -165,7 +165,7 @@ FastAPI 有一个使用非常简单,但是非常强大的Tweet about **FastAPI** 让我和大家知道您为什么喜欢 FastAPI。🎉 +Tweet about **FastAPI** 让我和大家知道您为什么喜欢 FastAPI。🎉 知道有人使用 **FastAPI**,我会很开心,我也想知道您为什么喜欢 FastAPI,以及您在什么项目/哪些公司使用 FastAPI,等等。 @@ -108,7 +108,7 @@ 快加入 👥 Discord 聊天服务器 👥 和 FastAPI 社区里的小伙伴一起哈皮吧。 -/// tip | "提示" +/// tip | 提示 如有问题,请在 GitHub Issues 里提问,在这里更容易得到 [FastAPI 专家](fastapi-people.md#_3){.internal-link target=_blank}的帮助。 diff --git a/docs/zh/docs/history-design-future.md b/docs/zh/docs/history-design-future.md index 48cfef524a..4db5c84724 100644 --- a/docs/zh/docs/history-design-future.md +++ b/docs/zh/docs/history-design-future.md @@ -57,7 +57,7 @@ 我甚至为它做了不少贡献,让它完美兼容了 JSON Schema,支持多种方式定义约束声明,并基于多个编辑器,改进了它对编辑器支持(类型检查、自动补全)。 -在开发期间,我还为 **Starlette** 做了不少贡献,这是另一个关键需求项。 +在开发期间,我还为 **Starlette** 做了不少贡献,这是另一个关键需求项。 ## 开发 diff --git a/docs/zh/docs/how-to/configure-swagger-ui.md b/docs/zh/docs/how-to/configure-swagger-ui.md index c0d09f943f..108e0cb95a 100644 --- a/docs/zh/docs/how-to/configure-swagger-ui.md +++ b/docs/zh/docs/how-to/configure-swagger-ui.md @@ -1,6 +1,6 @@ # 配置 Swagger UI -你可以配置一些额外的 Swagger UI 参数. +你可以配置一些额外的 Swagger UI 参数. 如果需要配置它们,可以在创建 `FastAPI()` 应用对象时或调用 `get_swagger_ui_html()` 函数时传递 `swagger_ui_parameters` 参数。 @@ -18,9 +18,7 @@ FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因 但是你可以通过设置 `syntaxHighlight` 为 `False` 来禁用 Swagger UI 中的语法高亮: -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial001.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} ...在此之后,Swagger UI 将不会高亮代码: @@ -30,9 +28,7 @@ FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因 同样地,你也可以通过设置键 `"syntaxHighlight.theme"` 来设置语法高亮主题(注意中间有一个点): -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial002.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} 这个配置会改变语法高亮主题: @@ -44,21 +40,17 @@ FastAPI 包含了一些默认配置参数,适用于大多数用例。 其包括这些默认配置参数: -```Python -{!../../../fastapi/openapi/docs.py[ln:7-23]!} -``` +{* ../../fastapi/openapi/docs.py ln[7:23] *} 你可以通过在 `swagger_ui_parameters` 中设置不同的值来覆盖它们。 比如,如果要禁用 `deepLinking`,你可以像这样传递设置到 `swagger_ui_parameters` 中: -```Python hl_lines="3" -{!../../../docs_src/configure_swagger_ui/tutorial003.py!} -``` +{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} ## 其他 Swagger UI 参数 -查看其他 Swagger UI 参数,请阅读 docs for Swagger UI parameters。 +查看其他 Swagger UI 参数,请阅读 docs for Swagger UI parameters。 ## JavaScript-only 配置 diff --git a/docs/zh/docs/how-to/index.md b/docs/zh/docs/how-to/index.md index 262dcfaee9..ac097618be 100644 --- a/docs/zh/docs/how-to/index.md +++ b/docs/zh/docs/how-to/index.md @@ -6,7 +6,7 @@ 如果某些内容看起来对你的项目有用,请继续查阅,否则请直接跳过它们。 -/// 小技巧 +/// tip | 小技巧 如果你想以系统的方式**学习 FastAPI**(推荐),请阅读 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 的每一章节。 diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md index 777240ec28..9f4b6d3d56 100644 --- a/docs/zh/docs/index.md +++ b/docs/zh/docs/index.md @@ -11,11 +11,11 @@ FastAPI 框架,高性能,易于学习,高效编码,生产可用

- - Test + + Test - - Coverage + + Coverage Package version @@ -88,13 +88,13 @@ FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框 「_**FastAPI** 让我兴奋的欣喜若狂。它太棒了!_」 -

+
Brian Okken - Python Bytes 播客主持人 (ref)
--- 「_老实说,你的作品看起来非常可靠和优美。在很多方面,这就是我想让 **Hug** 成为的样子 - 看到有人实现了它真的很鼓舞人心。_」 -
Timothy Crosley - Hug 作者 (ref)
+
Timothy Crosley - Hug 作者 (ref)
--- @@ -102,7 +102,7 @@ FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框 「_我们已经将 **API** 服务切换到了 **FastAPI** [...] 我认为你会喜欢它的 [...]_」 -
Ines Montani - Matthew Honnibal - Explosion AI 创始人 - spaCy 作者 (ref) - (ref)
+
Ines Montani - Matthew Honnibal - Explosion AI 创始人 - spaCy 作者 (ref) - (ref)
--- @@ -120,7 +120,7 @@ Python 及更高版本 FastAPI 站在以下巨人的肩膀之上: -* Starlette 负责 web 部分。 +* Starlette 负责 web 部分。 * Pydantic 负责数据部分。 ## 安装 @@ -135,7 +135,7 @@ $ pip install fastapi
-你还会需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn。 +你还会需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn
@@ -459,7 +459,7 @@ item: Item 用于 FastAPI / Starlette: -* uvicorn - 用于加载和运行你的应用程序的服务器。 +* uvicorn - 用于加载和运行你的应用程序的服务器。 * orjson - 使用 `ORJSONResponse` 时安装。 * ujson - 使用 `UJSONResponse` 时安装。 diff --git a/docs/zh/docs/project-generation.md b/docs/zh/docs/project-generation.md index 0655cb0a9c..48eb990df3 100644 --- a/docs/zh/docs/project-generation.md +++ b/docs/zh/docs/project-generation.md @@ -1,84 +1,28 @@ -# 项目生成 - 模板 +# FastAPI全栈模板 -项目生成器一般都会提供很多初始设置、安全措施、数据库,甚至还准备好了第一个 API 端点,能帮助您快速上手。 +模板通常带有特定的设置,而且被设计为灵活和可定制的。这允许您根据项目的需求修改和调整它们,使它们成为一个很好的起点。🏁 -项目生成器的设置通常都很主观,您可以按需更新或修改,但对于您的项目来说,它是非常好的起点。 +您可以使用此模板开始,因为它包含了许多已经为您完成的初始设置、安全性、数据库和一些API端点。 -## 全栈 FastAPI + PostgreSQL +代码仓: Full Stack FastAPI Template -GitHub:https://github.com/tiangolo/full-stack-fastapi-postgresql +## FastAPI全栈模板 - 技术栈和特性 -### 全栈 FastAPI + PostgreSQL - 功能 - -* 完整的 **Docker** 集成(基于 Docker) -* Docker Swarm 开发模式 -* **Docker Compose** 本地开发集成与优化 -* **生产可用**的 Python 网络服务器,使用 Uvicorn 或 Gunicorn -* Python **FastAPI** 后端: -* * **速度快**:可与 **NodeJS** 和 **Go** 比肩的极高性能(归功于 Starlette 和 Pydantic) - * **直观**:强大的编辑器支持,处处皆可自动补全,减少调试时间 - * **简单**:易学、易用,阅读文档所需时间更短 - * **简短**:代码重复最小化,每次参数声明都可以实现多个功能 - * **健壮**: 生产级别的代码,还有自动交互文档 - * **基于标准**:完全兼容并基于 API 开放标准:OpenAPIJSON Schema - * **更多功能**包括自动验证、序列化、交互文档、OAuth2 JWT 令牌身份验证等 -* **安全密码**,默认使用密码哈希 -* **JWT 令牌**身份验证 -* **SQLAlchemy** 模型(独立于 Flask 扩展,可直接用于 Celery Worker) -* 基础的用户模型(可按需修改或删除) -* **Alembic** 迁移 -* **CORS**(跨域资源共享) -* **Celery** Worker 可从后端其它部分有选择地导入并使用模型和代码 -* REST 后端测试基于 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** - * 基于 **Nginx** 的 Docker 服务器(优化了 Vue-router 配置) - * Docker 多阶段构建,无需保存或提交编译的代码 - * 在构建时运行前端测试(可禁用) - * 尽量模块化,开箱即用,但仍可使用 Vue CLI 重新生成或创建所需项目,或复用所需内容 -* 使用 **PGAdmin** 管理 PostgreSQL 数据库,可轻松替换为 PHPMyAdmin 或 MySQL -* 使用 **Flower** 监控 Celery 任务 -* 使用 **Traefik** 处理前后端负载平衡,可把前后端放在同一个域下,按路径分隔,但在不同容器中提供服务 -* Traefik 集成,包括自动生成 Let's Encrypt **HTTPS** 凭证 -* GitLab **CI**(持续集成),包括前后端测试 - -## 全栈 FastAPI + Couchbase - -GitHub:https://github.com/tiangolo/full-stack-fastapi-couchbase - -⚠️ **警告** ⚠️ - -如果您想从头开始创建新项目,建议使用以下备选方案。 - -例如,项目生成器全栈 FastAPI + PostgreSQL 会更适用,这个项目的维护积极,用的人也多,还包括了所有新功能和改进内容。 - -当然,您也可以放心使用这个基于 Couchbase 的生成器,它也能正常使用。就算用它生成项目也没有任何问题(为了更好地满足需求,您可以自行更新这个项目)。 - -详见资源仓库中的文档。 - -## 全栈 FastAPI + MongoDB - -……敬请期待,得看我有没有时间做这个项目。😅 🎉 - -## FastAPI + spaCy 机器学习模型 - -GitHub:https://github.com/microsoft/cookiecutter-spacy-fastapi - -### FastAPI + spaCy 机器学习模型 - 功能 - -* 集成 **spaCy** NER 模型 -* 内置 **Azure 认知搜索**请求格式 -* **生产可用**的 Python 网络服务器,使用 Uvicorn 与 Gunicorn -* 内置 **Azure DevOps** Kubernetes (AKS) CI/CD 开发 -* **多语**支持,可在项目设置时选择 spaCy 内置的语言 -* 不仅局限于 spaCy,可**轻松扩展**至其它模型框架(Pytorch、TensorFlow) +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) 用于Python后端API. + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 用于Python和SQL数据库的集成(ORM)。 + - 🔍 [Pydantic](https://docs.pydantic.dev) FastAPI的依赖项之一,用于数据验证和配置管理。 + - 💾 [PostgreSQL](https://www.postgresql.org) 作为SQL数据库。 +- 🚀 [React](https://react.dev) 用于前端。 + - 💃 使用了TypeScript、hooks、[Vite](https://vitejs.dev)和其他一些现代化的前端技术栈。 + - 🎨 [Chakra UI](https://chakra-ui.com) 用于前端组件。 + - 🤖 一个自动化生成的前端客户端。 + - 🧪 [Playwright](https://playwright.dev)用于端到端测试。 + - 🦇 支持暗黑主题(Dark mode)。 +- 🐋 [Docker Compose](https://www.docker.com) 用于开发环境和生产环境。 +- 🔒 默认使用密码哈希来保证安全。 +- 🔑 JWT令牌用于权限验证。 +- 📫 使用邮箱来进行密码恢复。 +- ✅ 单元测试用了[Pytest](https://pytest.org). +- 📞 [Traefik](https://traefik.io) 用于反向代理和负载均衡。 +- 🚢 部署指南(Docker Compose)包含了如何起一个Traefik前端代理来自动化HTTPS认证。 +- 🏭 CI(持续集成)和 CD(持续部署)基于GitHub Actions。 diff --git a/docs/zh/docs/python-types.md b/docs/zh/docs/python-types.md index c788525391..a7f76d97fa 100644 --- a/docs/zh/docs/python-types.md +++ b/docs/zh/docs/python-types.md @@ -22,9 +22,8 @@ 让我们从一个简单的例子开始: -```Python -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py *} + 运行这段程序将输出: @@ -38,9 +37,8 @@ John Doe * 通过 `title()` 将每个参数的第一个字母转换为大写形式。 * 中间用一个空格来拼接它们。 -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial001.py!} -``` +{* ../../docs_src/python_types/tutorial001.py hl[2] *} + ### 修改示例 @@ -82,9 +80,8 @@ John Doe 这些就是"类型提示": -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial002.py!} -``` +{* ../../docs_src/python_types/tutorial002.py hl[1] *} + 这和声明默认值是不同的,例如: @@ -112,9 +109,8 @@ John Doe 下面是一个已经有类型提示的函数: -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial003.py!} -``` +{* ../../docs_src/python_types/tutorial003.py hl[1] *} + 因为编辑器已经知道了这些变量的类型,所以不仅能对代码进行补全,还能检查其中的错误: @@ -122,9 +118,8 @@ John Doe 现在你知道了必须先修复这个问题,通过 `str(age)` 把 `age` 转换成字符串: -```Python hl_lines="2" -{!../../../docs_src/python_types/tutorial004.py!} -``` +{* ../../docs_src/python_types/tutorial004.py hl[2] *} + ## 声明类型 @@ -143,9 +138,8 @@ John Doe * `bool` * `bytes` -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial005.py!} -``` +{* ../../docs_src/python_types/tutorial005.py hl[1] *} + ### 嵌套类型 @@ -161,9 +155,8 @@ John Doe 从 `typing` 模块导入 `List`(注意是大写的 `L`): -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial006.py!} -``` +{* ../../docs_src/python_types/tutorial006.py hl[1] *} + 同样以冒号(`:`)来声明这个变量。 @@ -171,9 +164,8 @@ John Doe 由于列表是带有"子类型"的类型,所以我们把子类型放在方括号中: -```Python hl_lines="4" -{!../../../docs_src/python_types/tutorial006.py!} -``` +{* ../../docs_src/python_types/tutorial006.py hl[4] *} + 这表示:"变量 `items` 是一个 `list`,并且这个列表里的每一个元素都是 `str`"。 @@ -191,9 +183,8 @@ John Doe 声明 `tuple` 和 `set` 的方法也是一样的: -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial007.py!} -``` +{* ../../docs_src/python_types/tutorial007.py hl[1,4] *} + 这表示: @@ -208,9 +199,8 @@ John Doe 第二个子类型声明 `dict` 的所有值: -```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial008.py!} -``` +{* ../../docs_src/python_types/tutorial008.py hl[1,4] *} + 这表示: @@ -224,15 +214,13 @@ John Doe 假设你有一个名为 `Person` 的类,拥有 name 属性: -```Python hl_lines="1-3" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} + 接下来,你可以将一个变量声明为 `Person` 类型: -```Python hl_lines="6" -{!../../../docs_src/python_types/tutorial010.py!} -``` +{* ../../docs_src/python_types/tutorial010.py hl[6] *} + 然后,你将再次获得所有的编辑器支持: @@ -240,7 +228,7 @@ John Doe ## Pydantic 模型 -Pydantic 是一个用来用来执行数据校验的 Python 库。 +Pydantic 是一个用来执行数据校验的 Python 库。 你可以将数据的"结构"声明为具有属性的类。 @@ -252,10 +240,31 @@ John Doe 下面的例子来自 Pydantic 官方文档: +//// tab | Python 3.10+ + ```Python -{!../../../docs_src/python_types/tutorial010.py!} +{!> ../../docs_src/python_types/tutorial011_py310.py!} ``` +//// + +//// tab | Python 3.9+ + +```Python +{!> ../../docs_src/python_types/tutorial011_py39.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python +{!> ../../docs_src/python_types/tutorial011.py!} +``` + +//// + + /// info 想进一步了解 Pydantic,请阅读其文档. diff --git a/docs/zh/docs/tutorial/background-tasks.md b/docs/zh/docs/tutorial/background-tasks.md index 95fd7b6b57..b9becd8bff 100644 --- a/docs/zh/docs/tutorial/background-tasks.md +++ b/docs/zh/docs/tutorial/background-tasks.md @@ -15,9 +15,7 @@ 首先导入 `BackgroundTasks` 并在 *路径操作函数* 中使用类型声明 `BackgroundTasks` 定义一个参数: -```Python hl_lines="1 13" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[1, 13] *} **FastAPI** 会创建一个 `BackgroundTasks` 类型的对象并作为该参数传入。 @@ -33,17 +31,13 @@ 由于写操作不使用 `async` 和 `await`,我们用普通的 `def` 定义函数: -```Python hl_lines="6-9" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} ## 添加后台任务 在你的 *路径操作函数* 里,用 `.add_task()` 方法将任务函数传到 *后台任务* 对象中: -```Python hl_lines="14" -{!../../../docs_src/background_tasks/tutorial001.py!} -``` +{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} `.add_task()` 接收以下参数: @@ -59,25 +53,19 @@ //// tab | Python 3.10+ -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002_an_py310.py!} -``` +{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13, 15, 22, 25] *} //// //// tab | Python 3.9+ -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002_an_py39.py!} -``` +{* ../../docs_src/background_tasks/tutorial002_an_py39.py hl[13, 15, 22, 25] *} //// //// tab | Python 3.8+ -```Python hl_lines="14 16 23 26" -{!> ../../../docs_src/background_tasks/tutorial002_an.py!} -``` +{* ../../docs_src/background_tasks/tutorial002_an.py hl[14, 16, 23, 26] *} //// @@ -89,9 +77,7 @@ /// -```Python hl_lines="11 13 20 23" -{!> ../../../docs_src/background_tasks/tutorial002_py310.py!} -``` +{* ../../docs_src/background_tasks/tutorial002_py310.py hl[11, 13, 20, 23] *} //// @@ -103,9 +89,7 @@ /// -```Python hl_lines="13 15 22 25" -{!> ../../../docs_src/background_tasks/tutorial002.py!} -``` +{* ../../docs_src/background_tasks/tutorial002.py hl[13, 15, 22, 25] *} //// @@ -117,7 +101,7 @@ ## 技术细节 -`BackgroundTasks` 类直接来自 `starlette.background`。 +`BackgroundTasks` 类直接来自 `starlette.background`。 它被直接导入/包含到FastAPI以便你可以从 `fastapi` 导入,并避免意外从 `starlette.background` 导入备用的 `BackgroundTask` (后面没有 `s`)。 @@ -125,7 +109,7 @@ 在FastAPI中仍然可以单独使用 `BackgroundTask`,但您必须在代码中创建对象,并返回包含它的Starlette `Response`。 -更多细节查看 Starlette's official docs for Background Tasks. +更多细节查看 Starlette's official docs for Background Tasks. ## 告诫 @@ -133,8 +117,6 @@ 它们往往需要更复杂的配置,即消息/作业队列管理器,如RabbitMQ或Redis,但它们允许您在多个进程中运行后台任务,甚至是在多个服务器中。 -要查看示例,查阅 [Project Generators](../project-generation.md){.internal-link target=_blank},它们都包括已经配置的Celery。 - 但是,如果您需要从同一个**FastAPI**应用程序访问变量和对象,或者您需要执行小型后台任务(如发送电子邮件通知),您只需使用 `BackgroundTasks` 即可。 ## 回顾 diff --git a/docs/zh/docs/tutorial/bigger-applications.md b/docs/zh/docs/tutorial/bigger-applications.md index a0c7095e93..554bc654f0 100644 --- a/docs/zh/docs/tutorial/bigger-applications.md +++ b/docs/zh/docs/tutorial/bigger-applications.md @@ -52,7 +52,7 @@ from app.routers import items * 还有一个子目录 `app/internal/` 包含另一个 `__init__.py` 文件,因此它是又一个「Python 子包」:`app.internal`。 * `app/internal/admin.py` 是另一个子模块:`app.internal.admin`。 - + 带有注释的同一文件结构: @@ -86,7 +86,7 @@ from app.routers import items 你可以导入它并通过与 `FastAPI` 类相同的方式创建一个「实例」: ```Python hl_lines="1 3" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` ### 使用 `APIRouter` 的*路径操作* @@ -96,7 +96,7 @@ from app.routers import items 使用方式与 `FastAPI` 类相同: ```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../../docs_src/bigger_applications/app/routers/users.py!} +{!../../docs_src/bigger_applications/app/routers/users.py!} ``` 你可以将 `APIRouter` 视为一个「迷你 `FastAPI`」类。 @@ -122,7 +122,7 @@ from app.routers import items 现在我们将使用一个简单的依赖项来读取一个自定义的 `X-Token` 请求首部: ```Python hl_lines="1 4-6" title="app/dependencies.py" -{!../../../docs_src/bigger_applications/app/dependencies.py!} +{!../../docs_src/bigger_applications/app/dependencies.py!} ``` /// tip @@ -156,7 +156,7 @@ from app.routers import items 因此,我们可以将其添加到 `APIRouter` 中,而不是将其添加到每个路径操作中。 ```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` 由于每个*路径操作*的路径都必须以 `/` 开头,例如: @@ -217,7 +217,7 @@ async def read_item(item_id: str): 因此,我们通过 `..` 对依赖项使用了相对导入: ```Python hl_lines="3" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` #### 相对导入如何工作 @@ -244,7 +244,7 @@ from .dependencies import get_token_header 请记住我们的程序/文件结构是怎样的: - + --- @@ -290,7 +290,7 @@ from ...dependencies import get_token_header 但是我们仍然可以添加*更多*将会应用于特定的*路径操作*的 `tags`,以及一些特定于该*路径操作*的额外 `responses`: ```Python hl_lines="30-31" title="app/routers/items.py" -{!../../../docs_src/bigger_applications/app/routers/items.py!} +{!../../docs_src/bigger_applications/app/routers/items.py!} ``` /// tip @@ -318,7 +318,7 @@ from ...dependencies import get_token_header 我们甚至可以声明[全局依赖项](dependencies/global-dependencies.md){.internal-link target=_blank},它会和每个 `APIRouter` 的依赖项组合在一起: ```Python hl_lines="1 3 7" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` ### 导入 `APIRouter` @@ -326,7 +326,7 @@ from ...dependencies import get_token_header 现在,我们导入具有 `APIRouter` 的其他子模块: ```Python hl_lines="5" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` 由于文件 `app/routers/users.py` 和 `app/routers/items.py` 是同一 Python 包 `app` 一个部分的子模块,因此我们可以使用单个点 ` .` 通过「相对导入」来导入它们。 @@ -391,7 +391,7 @@ from .routers.users import router 因此,为了能够在同一个文件中使用它们,我们直接导入子模块: ```Python hl_lines="5" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` ### 包含 `users` 和 `items` 的 `APIRouter` @@ -399,7 +399,7 @@ from .routers.users import router 现在,让我们来包含来自 `users` 和 `items` 子模块的 `router`。 ```Python hl_lines="10-11" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` /// info @@ -414,7 +414,7 @@ from .routers.users import router 它将包含来自该路由器的所有路由作为其一部分。 -/// note | "技术细节" +/// note | 技术细节 实际上,它将在内部为声明在 `APIRouter` 中的每个*路径操作*创建一个*路径操作*。 @@ -441,7 +441,7 @@ from .routers.users import router 对于此示例,它将非常简单。但是假设由于它是与组织中的其他项目所共享的,因此我们无法对其进行修改,以及直接在 `APIRouter` 中添加 `prefix`、`dependencies`、`tags` 等: ```Python hl_lines="3" title="app/internal/admin.py" -{!../../../docs_src/bigger_applications/app/internal/admin.py!} +{!../../docs_src/bigger_applications/app/internal/admin.py!} ``` 但是我们仍然希望在包含 `APIRouter` 时设置一个自定义的 `prefix`,以便其所有*路径操作*以 `/admin` 开头,我们希望使用本项目已经有的 `dependencies` 保护它,并且我们希望它包含自定义的 `tags` 和 `responses`。 @@ -449,7 +449,7 @@ from .routers.users import router 我们可以通过将这些参数传递给 `app.include_router()` 来完成所有的声明,而不必修改原始的 `APIRouter`: ```Python hl_lines="14-17" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` 这样,原始的 `APIRouter` 将保持不变,因此我们仍然可以与组织中的其他项目共享相同的 `app/internal/admin.py` 文件。 @@ -472,12 +472,12 @@ from .routers.users import router 这里我们这样做了...只是为了表明我们可以做到🤷: ```Python hl_lines="21-23" title="app/main.py" -{!../../../docs_src/bigger_applications/app/main.py!} +{!../../docs_src/bigger_applications/app/main.py!} ``` 它将与通过 `app.include_router()` 添加的所有其他*路径操作*一起正常运行。 -/// info | "特别的技术细节" +/// info | 特别的技术细节 **注意**:这是一个非常技术性的细节,你也许可以**直接跳过**。 diff --git a/docs/zh/docs/tutorial/body-fields.md b/docs/zh/docs/tutorial/body-fields.md index 6e4c385dd6..4cff58bfc5 100644 --- a/docs/zh/docs/tutorial/body-fields.md +++ b/docs/zh/docs/tutorial/body-fields.md @@ -6,59 +6,9 @@ 首先,从 Pydantic 中导入 `Field`: -//// tab | Python 3.10+ +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *} -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="2" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="4" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// - -/// warning | "警告" +/// warning | 警告 注意,与从 `fastapi` 导入 `Query`,`Path`、`Body` 不同,要直接从 `pydantic` 导入 `Field` 。 @@ -68,61 +18,11 @@ 然后,使用 `Field` 定义模型的属性: -//// tab | Python 3.10+ - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="12-15" -{!> ../../../docs_src/body_fields/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="9-12" -{!> ../../../docs_src/body_fields/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="11-14" -{!> ../../../docs_src/body_fields/tutorial001.py!} -``` - -//// +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *} `Field` 的工作方式和 `Query`、`Path`、`Body` 相同,参数也相同。 -/// note | "技术细节" +/// note | 技术细节 实际上,`Query`、`Path` 都是 `Params` 的子类,而 `Params` 类又是 Pydantic 中 `FieldInfo` 的子类。 @@ -134,7 +34,7 @@ Pydantic 的 `Field` 返回也是 `FieldInfo` 的类实例。 /// -/// tip | "提示" +/// tip | 提示 注意,模型属性的类型、默认值及 `Field` 的代码结构与*路径操作函数*的参数相同,只不过是用 `Field` 替换了`Path`、`Query`、`Body`。 diff --git a/docs/zh/docs/tutorial/body-multiple-params.md b/docs/zh/docs/tutorial/body-multiple-params.md index fe951e5447..b4356fdcb4 100644 --- a/docs/zh/docs/tutorial/body-multiple-params.md +++ b/docs/zh/docs/tutorial/body-multiple-params.md @@ -8,57 +8,7 @@ 你还可以通过将默认值设置为 `None` 来将请求体参数声明为可选参数: -//// tab | Python 3.10+ - -```Python hl_lines="18-20" -{!> ../../../docs_src/body_multiple_params/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="18-20" -{!> ../../../docs_src/body_multiple_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="19-21" -{!> ../../../docs_src/body_multiple_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="17-19" -{!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="19-21" -{!> ../../../docs_src/body_multiple_params/tutorial001.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} /// note @@ -81,21 +31,7 @@ 但是你也可以声明多个请求体参数,例如 `item` 和 `user`: -//// tab | Python 3.10+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial002.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} 在这种情况下,**FastAPI** 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。 @@ -137,57 +73,7 @@ 但是你可以使用 `Body` 指示 **FastAPI** 将其作为请求体的另一个键进行处理。 -//// tab | Python 3.10+ - -```Python hl_lines="23" -{!> ../../../docs_src/body_multiple_params/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="23" -{!> ../../../docs_src/body_multiple_params/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24" -{!> ../../../docs_src/body_multiple_params/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="20" -{!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="22" -{!> ../../../docs_src/body_multiple_params/tutorial003.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} 在这种情况下,**FastAPI** 将期望像这样的请求体: @@ -222,57 +108,7 @@ q: str = None 比如: -//// tab | Python 3.10+ - -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="28" -{!> ../../../docs_src/body_multiple_params/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="25" -{!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="27" -{!> ../../../docs_src/body_multiple_params/tutorial004.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *} /// info @@ -294,57 +130,7 @@ item: Item = Body(embed=True) 比如: -//// tab | Python 3.10+ - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body_multiple_params/tutorial005_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="15" -{!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="17" -{!> ../../../docs_src/body_multiple_params/tutorial005.py!} -``` - -//// +{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} 在这种情况下,**FastAPI** 将期望像这样的请求体: diff --git a/docs/zh/docs/tutorial/body-nested-models.md b/docs/zh/docs/tutorial/body-nested-models.md index 26837abc6d..df96d96b4b 100644 --- a/docs/zh/docs/tutorial/body-nested-models.md +++ b/docs/zh/docs/tutorial/body-nested-models.md @@ -6,21 +6,7 @@ 你可以将一个属性定义为拥有子元素的类型。例如 Python `list`: -//// tab | Python 3.10+ - -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial001.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} 这将使 `tags` 成为一个由元素组成的列表。不过它没有声明每个元素的类型。 @@ -32,9 +18,7 @@ 首先,从 Python 的标准库 `typing` 模块中导入 `List`: -```Python hl_lines="1" -{!> ../../../docs_src/body_nested_models/tutorial002.py!} -``` +{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} ### 声明具有子类型的 List @@ -55,29 +39,7 @@ my_list: List[str] 因此,在我们的示例中,我们可以将 `tags` 明确地指定为一个「字符串列表」: -//// tab | Python 3.10+ - -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial002.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} ## Set 类型 @@ -87,29 +49,7 @@ Python 具有一种特殊的数据类型来保存一组唯一的元素,即 `se 然后我们可以导入 `Set` 并将 `tag` 声明为一个由 `str` 组成的 `set`: -//// tab | Python 3.10+ - -```Python hl_lines="12" -{!> ../../../docs_src/body_nested_models/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="14" -{!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 14" -{!> ../../../docs_src/body_nested_models/tutorial003.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} 这样,即使你收到带有重复数据的请求,这些数据也会被转换为一组唯一项。 @@ -131,57 +71,13 @@ Pydantic 模型的每个属性都具有类型。 例如,我们可以定义一个 `Image` 模型: -//// tab | Python 3.10+ - -```Python hl_lines="7-9" -{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9-11" -{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9-11" -{!> ../../../docs_src/body_nested_models/tutorial004.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} ### 将子模型用作类型 然后我们可以将其用作一个属性的类型: -//// tab | Python 3.10+ - -```Python hl_lines="18" -{!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial004.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *} 这意味着 **FastAPI** 将期望类似于以下内容的请求体: @@ -214,29 +110,7 @@ Pydantic 模型的每个属性都具有类型。 例如,在 `Image` 模型中我们有一个 `url` 字段,我们可以把它声明为 Pydantic 的 `HttpUrl`,而不是 `str`: -//// tab | Python 3.10+ - -```Python hl_lines="2 8" -{!> ../../../docs_src/body_nested_models/tutorial005_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 10" -{!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 10" -{!> ../../../docs_src/body_nested_models/tutorial005.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *} 该字符串将被检查是否为有效的 URL,并在 JSON Schema / OpenAPI 文档中进行记录。 @@ -244,29 +118,7 @@ Pydantic 模型的每个属性都具有类型。 你还可以将 Pydantic 模型用作 `list`、`set` 等的子类型: -//// tab | Python 3.10+ - -```Python hl_lines="18" -{!> ../../../docs_src/body_nested_models/tutorial006_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="20" -{!> ../../../docs_src/body_nested_models/tutorial006.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} 这将期望(转换,校验,记录文档等)下面这样的 JSON 请求体: @@ -304,29 +156,7 @@ Pydantic 模型的每个属性都具有类型。 你可以定义任意深度的嵌套模型: -//// tab | Python 3.10+ - -```Python hl_lines="7 12 18 21 25" -{!> ../../../docs_src/body_nested_models/tutorial007_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9 14 20 23 27" -{!> ../../../docs_src/body_nested_models/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 14 20 23 27" -{!> ../../../docs_src/body_nested_models/tutorial007.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} /// info @@ -344,21 +174,7 @@ images: List[Image] 例如: -//// tab | Python 3.9+ - -```Python hl_lines="13" -{!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15" -{!> ../../../docs_src/body_nested_models/tutorial008.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} ## 无处不在的编辑器支持 @@ -388,21 +204,7 @@ images: List[Image] 在下面的例子中,你将接受任意键为 `int` 类型并且值为 `float` 类型的 `dict`: -//// tab | Python 3.9+ - -```Python hl_lines="7" -{!> ../../../docs_src/body_nested_models/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/body_nested_models/tutorial009.py!} -``` - -//// +{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} /// tip diff --git a/docs/zh/docs/tutorial/body-updates.md b/docs/zh/docs/tutorial/body-updates.md index 6c74d12e06..87f88f2553 100644 --- a/docs/zh/docs/tutorial/body-updates.md +++ b/docs/zh/docs/tutorial/body-updates.md @@ -6,9 +6,7 @@ 把输入数据转换为以 JSON 格式存储的数据(比如,使用 NoSQL 数据库时),可以使用 `jsonable_encoder`。例如,把 `datetime` 转换为 `str`。 -```Python hl_lines="30-35" -{!../../../docs_src/body_updates/tutorial001.py!} -``` +{* ../../docs_src/body_updates/tutorial001.py hl[30:35] *} `PUT` 用于接收替换现有数据的数据。 @@ -34,7 +32,7 @@ 即,只发送要更新的数据,其余数据保持不变。 -/// note | "笔记" +/// note | 笔记 `PATCH` 没有 `PUT` 知名,也怎么不常用。 @@ -56,9 +54,7 @@ 然后再用它生成一个只含已设置(在请求中所发送)数据,且省略了默认值的 `dict`: -```Python hl_lines="34" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +{* ../../docs_src/body_updates/tutorial002.py hl[34] *} ### 使用 Pydantic 的 `update` 参数 @@ -66,9 +62,7 @@ 例如,`stored_item_model.copy(update=update_data)`: -```Python hl_lines="35" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +{* ../../docs_src/body_updates/tutorial002.py hl[35] *} ### 更新部分数据小结 @@ -85,18 +79,16 @@ * 把数据保存至数据库; * 返回更新后的模型。 -```Python hl_lines="30-37" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +{* ../../docs_src/body_updates/tutorial002.py hl[30:37] *} -/// tip | "提示" +/// tip | 提示 实际上,HTTP `PUT` 也可以完成相同的操作。 但本节以 `PATCH` 为例的原因是,该操作就是为了这种用例创建的。 /// -/// note | "笔记" +/// note | 笔记 注意,输入模型仍需验证。 diff --git a/docs/zh/docs/tutorial/body.md b/docs/zh/docs/tutorial/body.md index c47abec774..3820fc7477 100644 --- a/docs/zh/docs/tutorial/body.md +++ b/docs/zh/docs/tutorial/body.md @@ -8,7 +8,7 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请 使用 Pydantic 模型声明**请求体**,能充分利用它的功能和优点。 -/// info | "说明" +/// info | 说明 发送数据使用 `POST`(最常用)、`PUT`、`DELETE`、`PATCH` 等操作。 @@ -22,21 +22,7 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请 从 `pydantic` 中导入 `BaseModel`: -//// tab | Python 3.10+ - -```Python hl_lines="2" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// +{* ../../docs_src/body/tutorial001_py310.py hl[2] *} ## 创建数据模型 @@ -44,21 +30,7 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请 使用 Python 标准类型声明所有属性: -//// tab | Python 3.10+ - -```Python hl_lines="5-9" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="7-11" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// +{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} 与声明查询参数一样,包含默认值的模型属性是可选的,否则就是必选的。默认值为 `None` 的模型属性也是可选的。 @@ -86,21 +58,7 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请 使用与声明路径和查询参数相同的方式声明请求体,把请求体添加至*路径操作*: -//// tab | Python 3.10+ - -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial001.py!} -``` - -//// +{* ../../docs_src/body/tutorial001_py310.py hl[16] *} ……此处,请求体参数的类型为 `Item` 模型。 @@ -149,7 +107,7 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文 -/// tip | "提示" +/// tip | 提示 使用 PyCharm 编辑器时,推荐安装 Pydantic PyCharm 插件。 @@ -167,21 +125,7 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文 在*路径操作*函数内部直接访问模型对象的属性: -//// tab | Python 3.10+ - -```Python hl_lines="19" -{!> ../../../docs_src/body/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="21" -{!> ../../../docs_src/body/tutorial002.py!} -``` - -//// +{* ../../docs_src/body/tutorial002_py310.py hl[19] *} ## 请求体 + 路径参数 @@ -189,21 +133,7 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文 **FastAPI** 能识别与**路径参数**匹配的函数参数,还能识别从**请求体**中获取的类型为 Pydantic 模型的函数参数。 -//// tab | Python 3.10+ - -```Python hl_lines="15-16" -{!> ../../../docs_src/body/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17-18" -{!> ../../../docs_src/body/tutorial003.py!} -``` - -//// +{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} ## 请求体 + 路径参数 + 查询参数 @@ -211,21 +141,7 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文 **FastAPI** 能够正确识别这三种参数,并从正确的位置获取数据。 -//// tab | Python 3.10+ - -```Python hl_lines="16" -{!> ../../../docs_src/body/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/body/tutorial004.py!} -``` - -//// +{* ../../docs_src/body/tutorial004_py310.py hl[16] *} 函数参数按如下规则进行识别: @@ -233,7 +149,7 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文 - 类型是(`int`、`float`、`str`、`bool` 等)**单类型**的参数,是**查询**参数 - 类型是 **Pydantic 模型**的参数,是**请求体** -/// note | "笔记" +/// note | 笔记 因为默认值是 `None`, FastAPI 会把 `q` 当作可选参数。 diff --git a/docs/zh/docs/tutorial/cookie-param-models.md b/docs/zh/docs/tutorial/cookie-param-models.md new file mode 100644 index 0000000000..6a7b09e257 --- /dev/null +++ b/docs/zh/docs/tutorial/cookie-param-models.md @@ -0,0 +1,76 @@ +# Cookie 参数模型 + +如果您有一组相关的 **cookie**,您可以创建一个 **Pydantic 模型**来声明它们。🍪 + +这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证方式和元数据。😎 + +/// note + +自 FastAPI 版本 `0.115.0` 起支持此功能。🤓 + +/// + +/// tip + +此技术同样适用于 `Query` 、 `Cookie` 和 `Header` 。😎 + +/// + +## 带有 Pydantic 模型的 Cookie + +在 **Pydantic** 模型中声明所需的 **cookie** 参数,然后将参数声明为 `Cookie` : + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +**FastAPI** 将从请求中接收到的 **cookie** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。 + +## 查看文档 + +您可以在文档 UI 的 `/docs` 中查看定义的 cookie: + +
+ +
+ +/// info + +请记住,由于**浏览器**以特殊方式**处理 cookie**,并在后台进行操作,因此它们**不会**轻易允许 **JavaScript** 访问这些 cookie。 + +如果您访问 `/docs` 的 **API 文档 UI**,您将能够查看您*路径操作*的 cookie **文档**。 + +但是即使您**填写数据**并点击“执行”,由于文档界面使用 **JavaScript**,cookie 将不会被发送。而您会看到一条**错误**消息,就好像您没有输入任何值一样。 + +/// + +## 禁止额外的 Cookie + +在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 cookie。 + +您的 API 现在可以控制自己的 cookie 同意。🤪🍪 + +您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段: + +{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} + +如果客户尝试发送一些**额外的 cookie**,他们将收到**错误**响应。 + +可怜的 cookie 通知条,费尽心思为了获得您的同意,却被API 拒绝了。🍪 + +例如,如果客户端尝试发送一个值为 `good-list-please` 的 `santa_tracker` cookie,客户端将收到一个**错误**响应,告知他们 `santa_tracker` cookie 是不允许的: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## 总结 + +您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 **cookie**。😎 diff --git a/docs/zh/docs/tutorial/cookie-params.md b/docs/zh/docs/tutorial/cookie-params.md index 7ca77696ee..4956008149 100644 --- a/docs/zh/docs/tutorial/cookie-params.md +++ b/docs/zh/docs/tutorial/cookie-params.md @@ -6,57 +6,7 @@ 首先,导入 `Cookie`: -//// tab | Python 3.10+ - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="3" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` - -//// +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} ## 声明 `Cookie` 参数 @@ -65,59 +15,9 @@ 第一个值是默认值,还可以传递所有验证参数或注释参数: -//// tab | Python 3.10+ +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/cookie_params/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/cookie_params/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="9" -{!> ../../../docs_src/cookie_params/tutorial001.py!} -``` - -//// - -/// note | "技术细节" +/// note | 技术细节 `Cookie` 、`Path` 、`Query` 是**兄弟类**,都继承自共用的 `Param` 类。 @@ -125,7 +25,7 @@ /// -/// info | "说明" +/// info | 说明 必须使用 `Cookie` 声明 cookie 参数,否则该参数会被解释为查询参数。 diff --git a/docs/zh/docs/tutorial/cors.md b/docs/zh/docs/tutorial/cors.md index de880f3471..a4f15f6478 100644 --- a/docs/zh/docs/tutorial/cors.md +++ b/docs/zh/docs/tutorial/cors.md @@ -46,9 +46,7 @@ * 特定的 HTTP 方法(`POST`,`PUT`)或者使用通配符 `"*"` 允许所有方法。 * 特定的 HTTP headers 或者使用通配符 `"*"` 允许所有 headers。 -```Python hl_lines="2 6-11 13-19" -{!../../../docs_src/cors/tutorial001.py!} -``` +{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} 默认情况下,这个 `CORSMiddleware` 实现所使用的默认参数较为保守,所以你需要显式地启用特定的源、方法或者 headers,以便浏览器能够在跨域上下文中使用它们。 @@ -78,7 +76,7 @@ 更多关于 CORS 的信息,请查看 Mozilla CORS 文档。 -/// note | "技术细节" +/// note | 技术细节 你也可以使用 `from starlette.middleware.cors import CORSMiddleware`。 diff --git a/docs/zh/docs/tutorial/debugging.md b/docs/zh/docs/tutorial/debugging.md index 9450942072..734b855653 100644 --- a/docs/zh/docs/tutorial/debugging.md +++ b/docs/zh/docs/tutorial/debugging.md @@ -6,9 +6,7 @@ 在你的 FastAPI 应用中直接导入 `uvicorn` 并运行: -```Python hl_lines="1 15" -{!../../../docs_src/debugging/tutorial001.py!} -``` +{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} ### 关于 `__name__ == "__main__"` diff --git a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md index 9932f90fc2..f07280790c 100644 --- a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md @@ -6,21 +6,7 @@ 在前面的例子中, 我们从依赖项 ("可依赖对象") 中返回了一个 `dict`: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_py310.py hl[7] *} 但是后面我们在路径操作函数的参数 `commons` 中得到了一个 `dict`。 @@ -83,57 +69,15 @@ fluffy = Cat(name="Mr Fluffy") 所以,我们可以将上面的依赖项 "可依赖对象" `common_parameters` 更改为类 `CommonQueryParams`: -//// tab | Python 3.10+ - -```Python hl_lines="9-13" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11-15" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_py310.py hl[9:13] *} 注意用于创建类实例的 `__init__` 方法: -//// tab | Python 3.10+ - -```Python hl_lines="10" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.6+ - -```Python hl_lines="12" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_py310.py hl[10] *} ...它与我们以前的 `common_parameters` 具有相同的参数: -//// tab | Python 3.10+ - -```Python hl_lines="6" -{!> ../../../docs_src/dependencies/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.6+ - -```Python hl_lines="9" -{!> ../../../docs_src/dependencies/tutorial001.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial001_py310.py hl[6] *} 这些参数就是 **FastAPI** 用来 "处理" 依赖项的。 @@ -149,21 +93,7 @@ fluffy = Cat(name="Mr Fluffy") 现在,您可以使用这个类来声明你的依赖项了。 -//// tab | Python 3.10+ - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.6+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial002.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial002_py310.py hl[17] *} **FastAPI** 调用 `CommonQueryParams` 类。这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。 @@ -203,21 +133,7 @@ commons = Depends(CommonQueryParams) ..就像: -//// tab | Python 3.10+ - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.6+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial003.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial003_py310.py hl[17] *} 但是声明类型是被鼓励的,因为那样你的编辑器就会知道将传递什么作为参数 `commons` ,然后它可以帮助你完成代码,类型检查,等等: @@ -251,21 +167,7 @@ commons: CommonQueryParams = Depends() 同样的例子看起来像这样: -//// tab | Python 3.10+ - -```Python hl_lines="17" -{!> ../../../docs_src/dependencies/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.6+ - -```Python hl_lines="19" -{!> ../../../docs_src/dependencies/tutorial004.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial004_py310.py hl[17] *} ... **FastAPI** 会知道怎么处理。 diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index e6bbd47c14..51b3e9fc39 100644 --- a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,13 +14,11 @@ 该参数的值是由 `Depends()` 组成的 `list`: -```Python hl_lines="17" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[17] *} 路径操作装饰器依赖项(以下简称为**“路径装饰器依赖项”**)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。 -/// tip | "提示" +/// tip | 提示 有些编辑器会检查代码中没使用过的函数参数,并显示错误提示。 @@ -30,7 +28,7 @@ /// -/// info | "说明" +/// info | 说明 本例中,使用的是自定义响应头 `X-Key` 和 `X-Token`。 @@ -46,17 +44,13 @@ 路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项: -```Python hl_lines="6 11" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *} ### 触发异常 路径装饰器依赖项与正常的依赖项一样,可以 `raise` 异常: -```Python hl_lines="8 13" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *} ### 返回值 @@ -64,9 +58,7 @@ 因此,可以复用在其他位置使用过的、(能返回值的)普通依赖项,即使没有使用这个值,也会执行该依赖项: -```Python hl_lines="9 14" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *} ## 为一组路径操作定义依赖项 diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md index 6058f78781..a863bb861e 100644 --- a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md @@ -10,7 +10,7 @@ FastAPI支持在完成后执行一些 ../../../docs_src/dependencies/tutorial008_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="5 13 21" -{!> ../../../docs_src/dependencies/tutorial008_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | 提示 - -如果可以,请尽量使用 `Annotated` 版本。 - -/// - -```Python hl_lines="4 12 20" -{!> ../../../docs_src/dependencies/tutorial008.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} 所有这些依赖都可以使用 `yield`。 @@ -110,35 +74,7 @@ FastAPI支持在完成后执行一些上下文管理器完成的。 @@ -170,35 +106,7 @@ FastAPI支持在完成后执行一些 ../../../docs_src/dependencies/tutorial008c_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14-15" -{!> ../../../docs_src/dependencies/tutorial008c_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | 提示 - -如果可以,请尽量使用 `Annotated` 版本。 - -/// - -```Python hl_lines="13-14" -{!> ../../../docs_src/dependencies/tutorial008c.py!} -``` - -//// +{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} 在示例代码的情况下,客户端将会收到 *HTTP 500 Internal Server Error* 的响应,因为我们没有抛出 `HTTPException` 或者类似的异常,并且服务器也 **不会有任何日志** 或者其他提示来告诉我们错误是什么。😱 @@ -244,35 +124,7 @@ FastAPI支持在完成后执行一些添加专有自定义请求头. -但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 Starlette's CORS docs文档中. +但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 Starlette's CORS docs文档中. /// -/// note | "技术细节" +/// note | 技术细节 你也可以使用 `from starlette.requests import Request`. @@ -59,9 +57,7 @@ 例如你可以添加自定义请求头 `X-Process-Time` 包含以秒为单位的接收请求和生成响应的时间: -```Python hl_lines="10 12-13" -{!../../../docs_src/middleware/tutorial001.py!} -``` +{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} ## 其他中间件 diff --git a/docs/zh/docs/tutorial/path-operation-configuration.md b/docs/zh/docs/tutorial/path-operation-configuration.md index ac0177128d..adeca2d3fd 100644 --- a/docs/zh/docs/tutorial/path-operation-configuration.md +++ b/docs/zh/docs/tutorial/path-operation-configuration.md @@ -2,7 +2,7 @@ *路径操作装饰器*支持多种配置参数。 -/// warning | "警告" +/// warning | 警告 注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。 @@ -16,13 +16,11 @@ 如果记不住数字码的涵义,也可以用 `status` 的快捷常量: -```Python hl_lines="3 17" -{!../../../docs_src/path_operation_configuration/tutorial001.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *} 状态码在响应中使用,并会被添加到 OpenAPI 概图。 -/// note | "技术细节" +/// note | 技术细节 也可以使用 `from starlette import status` 导入状态码。 @@ -34,9 +32,7 @@ `tags` 参数的值是由 `str` 组成的 `list` (一般只有一个 `str` ),`tags` 用于为*路径操作*添加标签: -```Python hl_lines="17 22 27" -{!../../../docs_src/path_operation_configuration/tutorial002.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *} OpenAPI 概图会自动添加标签,供 API 文档接口使用: @@ -46,9 +42,7 @@ OpenAPI 概图会自动添加标签,供 API 文档接口使用: 路径装饰器还支持 `summary` 和 `description` 这两个参数: -```Python hl_lines="20-21" -{!../../../docs_src/path_operation_configuration/tutorial003.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *} ## 文档字符串(`docstring`) @@ -56,9 +50,7 @@ OpenAPI 概图会自动添加标签,供 API 文档接口使用: 文档字符串支持 Markdown,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。 -```Python hl_lines="19-27" -{!../../../docs_src/path_operation_configuration/tutorial004.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} 下图为 Markdown 文本在 API 文档中的显示效果: @@ -68,17 +60,15 @@ OpenAPI 概图会自动添加标签,供 API 文档接口使用: `response_description` 参数用于定义响应的描述说明: -```Python hl_lines="21" -{!../../../docs_src/path_operation_configuration/tutorial005.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *} -/// info | "说明" +/// info | 说明 注意,`response_description` 只用于描述响应,`description` 一般则用于描述*路径操作*。 /// -/// check | "检查" +/// check | 检查 OpenAPI 规定每个*路径操作*都要有响应描述。 @@ -92,9 +82,7 @@ OpenAPI 规定每个*路径操作*都要有响应描述。 `deprecated` 参数可以把*路径操作*标记为弃用,无需直接删除: -```Python hl_lines="16" -{!../../../docs_src/path_operation_configuration/tutorial006.py!} -``` +{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} API 文档会把该路径操作标记为弃用: diff --git a/docs/zh/docs/tutorial/path-params-numeric-validations.md b/docs/zh/docs/tutorial/path-params-numeric-validations.md index 6310ad8d24..ff62428359 100644 --- a/docs/zh/docs/tutorial/path-params-numeric-validations.md +++ b/docs/zh/docs/tutorial/path-params-numeric-validations.md @@ -6,57 +6,7 @@ 首先,从 `fastapi` 导入 `Path`: -//// tab | Python 3.10+ - -```Python hl_lines="1 3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3-4" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="1" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="3" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` - -//// +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *} ## 声明元数据 @@ -64,57 +14,7 @@ 例如,要声明路径参数 `item_id`的 `title` 元数据值,你可以输入: -//// tab | Python 3.10+ - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="11" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="8" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="10" -{!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` - -//// +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} /// note @@ -142,19 +42,7 @@ 因此,你可以将函数声明为: -//// tab | Python 3.8 non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="7" -{!> ../../../docs_src/path_params_numeric_validations/tutorial002.py!} -``` - -//// +{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} ## 按需对参数排序的技巧 @@ -164,9 +52,7 @@ Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs,来调用。即使它们没有默认值。 -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial003.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} ## 数值校验:大于等于 @@ -174,9 +60,7 @@ Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参 像下面这样,添加 `ge=1` 后,`item_id` 将必须是一个大于(`g`reater than)或等于(`e`qual)`1` 的整数。 -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} ## 数值校验:大于和小于等于 @@ -185,9 +69,7 @@ Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参 * `gt`:大于(`g`reater `t`han) * `le`:小于等于(`l`ess than or `e`qual) -```Python hl_lines="9" -{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *} ## 数值校验:浮点数、大于和小于 @@ -199,9 +81,7 @@ Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参 对于 lt 也是一样的。 -```Python hl_lines="11" -{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} -``` +{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} ## 总结 @@ -222,7 +102,7 @@ Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参 /// -/// note | "技术细节" +/// note | 技术细节 当你从 `fastapi` 导入 `Query`、`Path` 和其他同类对象时,它们实际上是函数。 diff --git a/docs/zh/docs/tutorial/path-params.md b/docs/zh/docs/tutorial/path-params.md index 091dcbeb0d..ac9df08317 100644 --- a/docs/zh/docs/tutorial/path-params.md +++ b/docs/zh/docs/tutorial/path-params.md @@ -2,9 +2,7 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**变量**): -```Python hl_lines="6-7" -{!../../../docs_src/path_params/tutorial001.py!} -``` +{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} 这段代码把路径参数 `item_id` 的值传递给路径函数的参数 `item_id`。 @@ -18,13 +16,11 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** 使用 Python 标准类型注解,声明路径操作函数中路径参数的类型。 -```Python hl_lines="7" -{!../../../docs_src/path_params/tutorial002.py!} -``` +{* ../../docs_src/path_params/tutorial002.py hl[7] *} 本例把 `item_id` 的类型声明为 `int`。 -/// check | "检查" +/// check | 检查 类型声明将为函数提供错误检查、代码补全等编辑器支持。 @@ -38,7 +34,7 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** {"item_id":3} ``` -/// check | "检查" +/// check | 检查 注意,函数接收并返回的值是 `3`( `int`),不是 `"3"`(`str`)。 @@ -69,7 +65,7 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** 值的类型不是 `int ` 而是浮点数(`float`)时也会显示同样的错误,比如: http://127.0.0.1:8000/items/4.2。 -/// check | "检查" +/// check | 检查 **FastAPI** 使用 Python 类型声明实现了数据校验。 @@ -85,7 +81,7 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** -/// check | "检查" +/// check | 检查 还是使用 Python 类型声明,**FastAPI** 提供了(集成 Swagger UI 的)API 文档。 @@ -121,9 +117,7 @@ FastAPI 充分地利用了 枚举(即 enums)。 +Python 3.4 及之后版本支持枚举(即 enums)。 /// -/// tip | "提示" +/// tip | 提示 **AlexNet**、**ResNet**、**LeNet** 是机器学习模型。 @@ -159,9 +151,7 @@ Python 3.4 及之后版本支持 + +
+ +## 禁止额外的查询参数 + +在一些特殊的使用场景中(可能不是很常见),你可能希望**限制**你要接收的查询参数。 + +你可以使用 Pydantic 的模型配置来 `forbid`(意为禁止 —— 译者注)任何 `extra`(意为额外的 —— 译者注)字段: + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +假设有一个客户端尝试在**查询参数**中发送一些**额外的**数据,它将会收到一个**错误**响应。 + +例如,如果客户端尝试发送一个值为 `plumbus` 的 `tool` 查询参数,如: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +他们将收到一个**错误**响应,告诉他们查询参数 `tool` 是不允许的: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## 总结 + +你可以使用 **Pydantic 模型**在 **FastAPI** 中声明**查询参数**。😎 + +/// tip + +剧透警告:你也可以使用 Pydantic 模型来声明 cookie 和 headers,但你将在本教程的后面部分阅读到这部分内容。🤫 + +/// diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md index cb4beb0ca3..c2f9a7e9f0 100644 --- a/docs/zh/docs/tutorial/query-params-str-validations.md +++ b/docs/zh/docs/tutorial/query-params-str-validations.md @@ -4,21 +4,7 @@ 让我们以下面的应用程序为例: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params_str_validations/tutorial001.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} 查询参数 `q` 的类型为 `str`,默认值为 `None`,因此它是可选的。 @@ -30,17 +16,13 @@ 为此,首先从 `fastapi` 导入 `Query`: -```Python hl_lines="1" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[1] *} ## 使用 `Query` 作为默认值 现在,将 `Query` 用作查询参数的默认值,并将它的 `max_length` 参数设置为 50: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} 由于我们必须用 `Query(default=None)` 替换默认值 `None`,`Query` 的第一个参数同样也是用于定义默认值。 @@ -70,17 +52,13 @@ q: Union[str, None] = Query(default=None, max_length=50) 你还可以添加 `min_length` 参数: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial003.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *} ## 添加正则表达式 你可以定义一个参数值必须匹配的正则表达式: -```Python hl_lines="11" -{!../../../docs_src/query_params_str_validations/tutorial004.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *} 这个指定的正则表达式通过以下规则检查接收到的参数值: @@ -98,9 +76,7 @@ q: Union[str, None] = Query(default=None, max_length=50) 假设你想要声明查询参数 `q`,使其 `min_length` 为 `3`,并且默认值为 `fixedquery`: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial005.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} /// note @@ -130,26 +106,7 @@ q: Union[str, None] = Query(default=None, min_length=3) 因此,当你在使用 `Query` 且需要声明一个值是必需的时,只需不声明默认参数: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial006.py!} -``` - -### 使用省略号(`...`)声明必需参数 - -有另一种方法可以显式的声明一个值是必需的,即将默认参数的默认值设为 `...` : - -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial006b.py!} -``` - -/// info - -如果你之前没见过 `...` 这种用法:它是一个特殊的单独值,它是 Python 的一部分并且被称为「省略号」。 -Pydantic 和 FastAPI 使用它来显式的声明需要一个值。 - -/// - -这将使 **FastAPI** 知道此查询参数是必需的。 +{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} ### 使用`None`声明必需参数 @@ -157,9 +114,7 @@ Pydantic 和 FastAPI 使用它来显式的声明需要一个值。 为此,你可以声明`None`是一个有效的类型,并仍然使用`default=...`: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial006c.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *} /// tip @@ -167,29 +122,13 @@ Pydantic 是 FastAPI 中所有数据验证和序列化的核心,当你在没 /// -### 使用Pydantic中的`Required`代替省略号(`...`) - -如果你觉得使用 `...` 不舒服,你也可以从 Pydantic 导入并使用 `Required`: - -```Python hl_lines="2 8" -{!../../../docs_src/query_params_str_validations/tutorial006d.py!} -``` - -/// tip - -请记住,在大多数情况下,当你需要某些东西时,可以简单地省略 `default` 参数,因此你通常不必使用 `...` 或 `Required` - -/// - ## 查询参数列表 / 多个值 当你使用 `Query` 显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。 例如,要声明一个可在 URL 中出现多次的查询参数 `q`,你可以这样写: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial011.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} 然后,输入如下网址: @@ -224,9 +163,7 @@ http://localhost:8000/items/?q=foo&q=bar 你还可以定义在没有任何给定值时的默认 `list` 值: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial012.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} 如果你访问: @@ -249,9 +186,7 @@ http://localhost:8000/items/ 你也可以直接使用 `list` 代替 `List [str]`: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial013.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} /// note @@ -277,15 +212,11 @@ http://localhost:8000/items/ 你可以添加 `title`: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial007.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *} 以及 `description`: -```Python hl_lines="13" -{!../../../docs_src/query_params_str_validations/tutorial008.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} ## 别名参数 @@ -305,9 +236,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 这时你可以用 `alias` 参数声明一个别名,该别名将用于在 URL 中查找查询参数值: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial009.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} ## 弃用参数 @@ -317,9 +246,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 那么将参数 `deprecated=True` 传入 `Query`: -```Python hl_lines="18" -{!../../../docs_src/query_params_str_validations/tutorial010.py!} -``` +{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} 文档将会像下面这样展示它: diff --git a/docs/zh/docs/tutorial/query-params.md b/docs/zh/docs/tutorial/query-params.md index 6853e3899b..cc2e74b9ea 100644 --- a/docs/zh/docs/tutorial/query-params.md +++ b/docs/zh/docs/tutorial/query-params.md @@ -2,9 +2,7 @@ 声明的参数不是路径参数时,路径操作函数会把该参数自动解释为**查询**参数。 -```Python hl_lines="9" -{!../../../docs_src/query_params/tutorial001.py!} -``` +{* ../../docs_src/query_params/tutorial001.py hl[9] *} 查询字符串是键值对的集合,这些键值对位于 URL 的 `?` 之后,以 `&` 分隔。 @@ -63,31 +61,17 @@ http://127.0.0.1:8000/items/?skip=20 同理,把默认值设为 `None` 即可声明**可选的**查询参数: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial002.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} 本例中,查询参数 `q` 是可选的,默认值为 `None`。 -/// check | "检查" +/// check | 检查 注意,**FastAPI** 可以识别出 `item_id` 是路径参数,`q` 不是路径参数,而是查询参数。 /// -/// note | "笔记" +/// note | 笔记 因为默认值为 `= None`,FastAPI 把 `q` 识别为可选参数。 @@ -100,21 +84,7 @@ FastAPI 不使用 `Optional[str]` 中的 `Optional`(只使用 `str`),但 ` 参数还可以声明为 `bool` 类型,FastAPI 会自动转换参数类型: -//// tab | Python 3.10+ - -```Python hl_lines="7" -{!> ../../../docs_src/query_params/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/query_params/tutorial003.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} 本例中,访问: @@ -157,21 +127,7 @@ http://127.0.0.1:8000/items/foo?short=yes FastAPI 通过参数名进行检测: -//// tab | Python 3.10+ - -```Python hl_lines="6 8" -{!> ../../../docs_src/query_params/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8 10" -{!> ../../../docs_src/query_params/tutorial004.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} ## 必选查询参数 @@ -181,9 +137,7 @@ FastAPI 通过参数名进行检测: 如果要把查询参数设置为**必选**,就不要声明默认值: -```Python hl_lines="6-7" -{!../../../docs_src/query_params/tutorial005.py!} -``` +{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} 这里的查询参数 `needy` 是类型为 `str` 的必选查询参数。 @@ -227,21 +181,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy 当然,把一些参数定义为必选,为另一些参数设置默认值,再把其它参数定义为可选,这些操作都是可以的: -//// tab | Python 3.10+ - -```Python hl_lines="8" -{!> ../../../docs_src/query_params/tutorial006_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10" -{!> ../../../docs_src/query_params/tutorial006.py!} -``` - -//// +{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} 本例中有 3 个查询参数: @@ -249,7 +189,7 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy * `skip`,默认值为 `0` 的 `int` 类型参数 * `limit`,可选的 `int` 类型参数 -/// tip | "提示" +/// tip | 提示 还可以像在[路径参数](path-params.md#_8){.internal-link target=_blank} 中那样使用 `Enum`。 diff --git a/docs/zh/docs/tutorial/request-files.md b/docs/zh/docs/tutorial/request-files.md index d5d0fb671c..81ddc7238a 100644 --- a/docs/zh/docs/tutorial/request-files.md +++ b/docs/zh/docs/tutorial/request-files.md @@ -2,7 +2,7 @@ `File` 用于定义客户端的上传文件。 -/// info | "说明" +/// info | 说明 因为上传文件以「表单数据」形式发送。 @@ -16,19 +16,15 @@ 从 `fastapi` 导入 `File` 和 `UploadFile`: -```Python hl_lines="1" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[1] *} ## 定义 `File` 参数 创建文件(`File`)参数的方式与 `Body` 和 `Form` 一样: -```Python hl_lines="7" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[7] *} -/// info | "说明" +/// info | 说明 `File` 是直接继承自 `Form` 的类。 @@ -36,7 +32,7 @@ /// -/// tip | "提示" +/// tip | 提示 声明文件体必须使用 `File`,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 @@ -54,9 +50,7 @@ 定义文件参数时使用 `UploadFile`: -```Python hl_lines="12" -{!../../../docs_src/request_files/tutorial001.py!} -``` +{* ../../docs_src/request_files/tutorial001.py hl[12] *} `UploadFile` 与 `bytes` 相比有更多优势: @@ -98,13 +92,13 @@ contents = await myfile.read() contents = myfile.file.read() ``` -/// note | "`async` 技术细节" +/// note | `async` 技术细节 使用 `async` 方法时,**FastAPI** 在线程池中执行文件方法,并 `await` 操作完成。 /// -/// note | "Starlette 技术细节" +/// note | Starlette 技术细节 **FastAPI** 的 `UploadFile` 直接继承自 **Starlette** 的 `UploadFile`,但添加了一些必要功能,使之与 **Pydantic** 及 FastAPI 的其它部件兼容。 @@ -116,7 +110,7 @@ contents = myfile.file.read() **FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。 -/// note | "技术细节" +/// note | 技术细节 不包含文件时,表单数据一般用 `application/x-www-form-urlencoded`「媒体类型」编码。 @@ -126,7 +120,7 @@ contents = myfile.file.read() /// -/// warning | "警告" +/// warning | 警告 可在一个*路径操作*中声明多个 `File` 和 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `multipart/form-data`,不是 `application/json`。 @@ -138,29 +132,13 @@ contents = myfile.file.read() 您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选: -//// tab | Python 3.9+ - -```Python hl_lines="7 14" -{!> ../../../docs_src/request_files/tutorial001_02_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 17" -{!> ../../../docs_src/request_files/tutorial001_02.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial001_02_py310.py hl[7,14] *} ## 带有额外元数据的 `UploadFile` 您也可以将 `File()` 与 `UploadFile` 一起使用,例如,设置额外的元数据: -```Python hl_lines="13" -{!../../../docs_src/request_files/tutorial001_03.py!} -``` +{* ../../docs_src/request_files/tutorial001_03.py hl[13] *} ## 多文件上传 @@ -170,26 +148,12 @@ FastAPI 支持同时上传多个文件。 上传多个文件时,要声明含 `bytes` 或 `UploadFile` 的列表(`List`): -//// tab | Python 3.9+ - -```Python hl_lines="8 13" -{!> ../../../docs_src/request_files/tutorial002_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="10 15" -{!> ../../../docs_src/request_files/tutorial002.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial002_py39.py hl[8,13] *} 接收的也是含 `bytes` 或 `UploadFile` 的列表(`list`)。 -/// note | "技术细节" +/// note | 技术细节 也可以使用 `from starlette.responses import HTMLResponse`。 @@ -201,21 +165,7 @@ FastAPI 支持同时上传多个文件。 和之前的方式一样, 您可以为 `File()` 设置额外参数, 即使是 `UploadFile`: -//// tab | Python 3.9+ - -```Python hl_lines="16" -{!> ../../../docs_src/request_files/tutorial003_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="18" -{!> ../../../docs_src/request_files/tutorial003.py!} -``` - -//// +{* ../../docs_src/request_files/tutorial003_py39.py hl[16] *} ## 小结 diff --git a/docs/zh/docs/tutorial/request-form-models.md b/docs/zh/docs/tutorial/request-form-models.md new file mode 100644 index 0000000000..e639e4b9fc --- /dev/null +++ b/docs/zh/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# 表单模型 + +您可以使用 **Pydantic 模型**在 FastAPI 中声明**表单字段**。 + +/// info + +要使用表单,需预先安装 `python-multipart` 。 + +确保您创建、激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}后再安装。 + +```console +$ pip install python-multipart +``` + +/// + +/// note + +自 FastAPI 版本 `0.113.0` 起支持此功能。🤓 + +/// + +## 表单的 Pydantic 模型 + +您只需声明一个 **Pydantic 模型**,其中包含您希望接收的**表单字段**,然后将参数声明为 `Form` : + +{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} + +**FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。 + +## 检查文档 + +您可以在文档 UI 中验证它,地址为 `/docs` : + +
+ +
+ +## 禁止额外的表单字段 + +在某些特殊使用情况下(可能并不常见),您可能希望将表单字段**限制**为仅在 Pydantic 模型中声明过的字段,并**禁止**任何**额外**的字段。 + +/// note + +自 FastAPI 版本 `0.114.0` 起支持此功能。🤓 + +/// + +您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段: + +{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} + +如果客户端尝试发送一些额外的数据,他们将收到**错误**响应。 + +例如,如果客户端尝试发送这样的表单字段: + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +他们将收到一条错误响应,表明字段 `extra` 是不被允许的: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## 总结 + +您可以使用 Pydantic 模型在 FastAPI 中声明表单字段。😎 diff --git a/docs/zh/docs/tutorial/request-forms-and-files.md b/docs/zh/docs/tutorial/request-forms-and-files.md index 723cf5b18c..f72e5047a9 100644 --- a/docs/zh/docs/tutorial/request-forms-and-files.md +++ b/docs/zh/docs/tutorial/request-forms-and-files.md @@ -2,7 +2,7 @@ FastAPI 支持同时使用 `File` 和 `Form` 定义文件和表单字段。 -/// info | "说明" +/// info | 说明 接收上传文件或表单数据,要预先安装 `python-multipart`。 @@ -12,23 +12,19 @@ FastAPI 支持同时使用 `File` 和 `Form` 定义文件和表单字段。 ## 导入 `File` 与 `Form` -```Python hl_lines="1" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *} ## 定义 `File` 与 `Form` 参数 创建文件和表单参数的方式与 `Body` 和 `Query` 一样: -```Python hl_lines="8" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *} 文件和表单字段作为表单数据上传与接收。 声明文件可以使用 `bytes` 或 `UploadFile` 。 -/// warning | "警告" +/// warning | 警告 可在一个*路径操作*中声明多个 `File` 与 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码为 `multipart/form-data`,不是 `application/json`。 diff --git a/docs/zh/docs/tutorial/request-forms.md b/docs/zh/docs/tutorial/request-forms.md index 6cc472ac19..ee03e82a7f 100644 --- a/docs/zh/docs/tutorial/request-forms.md +++ b/docs/zh/docs/tutorial/request-forms.md @@ -2,7 +2,7 @@ 接收的不是 JSON,而是表单字段时,要使用 `Form`。 -/// info | "说明" +/// info | 说明 要使用表单,需预先安装 `python-multipart`。 @@ -14,17 +14,13 @@ 从 `fastapi` 导入 `Form`: -```Python hl_lines="1" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[1] *} ## 定义 `Form` 参数 创建表单(`Form`)参数的方式与 `Body` 和 `Query` 一样: -```Python hl_lines="7" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +{* ../../docs_src/request_forms/tutorial001.py hl[7] *} 例如,OAuth2 规范的 "密码流" 模式规定要通过表单字段发送 `username` 和 `password`。 @@ -32,13 +28,13 @@ 使用 `Form` 可以声明与 `Body` (及 `Query`、`Path`、`Cookie`)相同的元数据和验证。 -/// info | "说明" +/// info | 说明 `Form` 是直接继承自 `Body` 的类。 /// -/// tip | "提示" +/// tip | 提示 声明表单体要显式使用 `Form` ,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 @@ -50,7 +46,7 @@ **FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。 -/// note | "技术细节" +/// note | 技术细节 表单数据的「媒体类型」编码一般为 `application/x-www-form-urlencoded`。 @@ -60,7 +56,7 @@ /// -/// warning | "警告" +/// warning | 警告 可在一个*路径操作*中声明多个 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `application/x-www-form-urlencoded`,不是 `application/json`。 diff --git a/docs/zh/docs/tutorial/response-model.md b/docs/zh/docs/tutorial/response-model.md index 3c196c9648..049cd12238 100644 --- a/docs/zh/docs/tutorial/response-model.md +++ b/docs/zh/docs/tutorial/response-model.md @@ -8,29 +8,7 @@ * `@app.delete()` * 等等。 -//// tab | Python 3.10+ - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="17 22 24-27" -{!> ../../../docs_src/response_model/tutorial001.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} /// note @@ -51,7 +29,7 @@ FastAPI 将使用此 `response_model` 来: * 会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。 -/// note | "技术细节" +/// note | 技术细节 响应模型在参数中被声明,而不是作为函数返回类型的注解,这是因为路径函数可能不会真正返回该响应模型,而是返回一个 `dict`、数据库对象或其他模型,然后再使用 `response_model` 来执行字段约束和序列化。 @@ -61,15 +39,11 @@ FastAPI 将使用此 `response_model` 来: 现在我们声明一个 `UserIn` 模型,它将包含一个明文密码属性。 -```Python hl_lines="9 11" -{!../../../docs_src/response_model/tutorial002.py!} -``` +{* ../../docs_src/response_model/tutorial002.py hl[9,11] *} 我们正在使用此模型声明输入数据,并使用同一模型声明输出数据: -```Python hl_lines="17-18" -{!../../../docs_src/response_model/tutorial002.py!} -``` +{* ../../docs_src/response_model/tutorial002.py hl[17:18] *} 现在,每当浏览器使用一个密码创建用户时,API 都会在响应中返回相同的密码。 @@ -87,57 +61,15 @@ FastAPI 将使用此 `response_model` 来: 相反,我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型: -//// tab | Python 3.10+ - -```Python hl_lines="9 11 16" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9 11 16" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} 这样,即便我们的*路径操作函数*将会返回包含密码的相同输入用户: -//// tab | Python 3.10+ - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} ...我们已经将 `response_model` 声明为了不包含密码的 `UserOut` 模型: -//// tab | Python 3.10+ - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="22" -{!> ../../../docs_src/response_model/tutorial003.py!} -``` - -//// +{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} 因此,**FastAPI** 将会负责过滤掉未在输出模型中声明的所有数据(使用 Pydantic)。 @@ -155,9 +87,7 @@ FastAPI 将使用此 `response_model` 来: 你的响应模型可以具有默认值,例如: -```Python hl_lines="11 13-14" -{!../../../docs_src/response_model/tutorial004.py!} -``` +{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *} * `description: Union[str, None] = None` 具有默认值 `None`。 * `tax: float = 10.5` 具有默认值 `10.5`. @@ -171,9 +101,7 @@ FastAPI 将使用此 `response_model` 来: 你可以设置*路径操作装饰器*的 `response_model_exclude_unset=True` 参数: -```Python hl_lines="24" -{!../../../docs_src/response_model/tutorial004.py!} -``` +{* ../../docs_src/response_model/tutorial004.py hl[24] *} 然后响应中将不会包含那些默认值,而是仅有实际设置的值。 @@ -262,9 +190,7 @@ FastAPI 通过 Pydantic 模型的 `.dict()` 配合 `http.HTTPStatus`。 @@ -33,7 +31,7 @@ -/// note | "笔记" +/// note | 笔记 某些响应状态码表示响应没有响应体(参阅下一章)。 @@ -43,7 +41,7 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 ## 关于 HTTP 状态码 -/// note | "笔记" +/// note | 笔记 如果已经了解 HTTP 状态码,请跳到下一章。 @@ -66,7 +64,7 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 * 对于来自客户端的一般错误,可以只使用 `400` * `500` 及以上的状态码用于表示服务器端错误。几乎永远不会直接使用这些状态码。应用代码或服务器出现问题时,会自动返回这些状态代码 -/// tip | "提示" +/// tip | 提示 状态码及适用场景的详情,请参阅 MDN 的 HTTP 状态码文档。 @@ -76,9 +74,7 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 再看下之前的例子: -```Python hl_lines="6" -{!../../../docs_src/response_status_code/tutorial001.py!} -``` +{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} `201` 表示**已创建**的状态码。 @@ -86,15 +82,13 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 可以使用 `fastapi.status` 中的快捷变量。 -```Python hl_lines="1 6" -{!../../../docs_src/response_status_code/tutorial002.py!} -``` +{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} 这只是一种快捷方式,具有相同的数字代码,但它可以使用编辑器的自动补全功能: -/// note | "技术细节" +/// note | 技术细节 也可以使用 `from starlette import status`。 diff --git a/docs/zh/docs/tutorial/schema-extra-example.md b/docs/zh/docs/tutorial/schema-extra-example.md index 6063bd731e..6c132eed3f 100644 --- a/docs/zh/docs/tutorial/schema-extra-example.md +++ b/docs/zh/docs/tutorial/schema-extra-example.md @@ -10,21 +10,7 @@ 您可以使用 `Config` 和 `schema_extra` 为Pydantic模型声明一个示例,如Pydantic 文档:定制 Schema 中所述: -//// tab | Python 3.10+ - -```Python hl_lines="13-21" -{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15-23" -{!> ../../../docs_src/schema_extra_example/tutorial001.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:21] *} 这些额外的信息将按原样添加到输出的JSON模式中。 @@ -32,21 +18,7 @@ 在 `Field`, `Path`, `Query`, `Body` 和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 `example`: -//// tab | Python 3.10+ - -```Python hl_lines="2 8-11" -{!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 10-13" -{!> ../../../docs_src/schema_extra_example/tutorial002.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} /// warning @@ -60,57 +32,7 @@ 比如,你可以将请求体的一个 `example` 传递给 `Body`: -//// tab | Python 3.10+ - -```Python hl_lines="22-27" -{!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="22-27" -{!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23-28" -{!> ../../../docs_src/schema_extra_example/tutorial003_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="18-23" -{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python hl_lines="20-25" -{!> ../../../docs_src/schema_extra_example/tutorial003.py!} -``` - -//// +{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *} ## 文档 UI 中的例子 diff --git a/docs/zh/docs/tutorial/security/first-steps.md b/docs/zh/docs/tutorial/security/first-steps.md index 266a5fcdf8..225eb26951 100644 --- a/docs/zh/docs/tutorial/security/first-steps.md +++ b/docs/zh/docs/tutorial/security/first-steps.md @@ -20,39 +20,11 @@ 把下面的示例代码复制到 `main.py`: -//// tab | Python 3.9+ - -```Python -{!> ../../../docs_src/security/tutorial001_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../../docs_src/security/tutorial001_an.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -```Python -{!> ../../../docs_src/security/tutorial001.py!} -``` - -//// +{* ../../docs_src/security/tutorial001_an_py39.py *} ## 运行 -/// info | "说明" +/// info | 说明 先安装 `python-multipart`。 @@ -82,7 +54,7 @@ $ uvicorn main:app --reload -/// check | "Authorize 按钮!" +/// check | Authorize 按钮! 页面右上角出现了一个「**Authorize**」按钮。 @@ -94,7 +66,7 @@ $ uvicorn main:app --reload -/// note | "笔记" +/// note | 笔记 目前,在表单中输入内容不会有任何反应,后文会介绍相关内容。 @@ -140,7 +112,7 @@ OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户 本例使用 **OAuth2** 的 **Password** 流以及 **Bearer** 令牌(`Token`)。为此要使用 `OAuth2PasswordBearer` 类。 -/// info | "说明" +/// info | 说明 `Bearer` 令牌不是唯一的选择。 @@ -154,11 +126,9 @@ OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户 创建 `OAuth2PasswordBearer` 的类实例时,要传递 `tokenUrl` 参数。该参数包含客户端(用户浏览器中运行的前端) 的 URL,用于发送 `username` 与 `password`,并获取令牌。 -```Python hl_lines="6" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[6] *} -/// tip | "提示" +/// tip | 提示 在此,`tokenUrl="token"` 指向的是暂未创建的相对 URL `token`。这个相对 URL 相当于 `./token`。 @@ -172,7 +142,7 @@ OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户 接下来,学习如何创建实际的路径操作。 -/// info | "说明" +/// info | 说明 严苛的 **Pythonista** 可能不喜欢用 `tokenUrl` 这种命名风格代替 `token_url`。 @@ -194,15 +164,13 @@ oauth2_scheme(some, parameters) 接下来,使用 `Depends` 把 `oauth2_scheme` 传入依赖项。 -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} 该依赖项使用字符串(`str`)接收*路径操作函数*的参数 `token` 。 **FastAPI** 使用依赖项在 OpenAPI 概图(及 API 文档)中定义**安全方案**。 -/// info | "技术细节" +/// info | 技术细节 **FastAPI** 使用(在依赖项中声明的)类 `OAuth2PasswordBearer` 在 OpenAPI 中定义安全方案,这是因为它继承自 `fastapi.security.oauth2.OAuth2`,而该类又是继承自`fastapi.security.base.SecurityBase`。 diff --git a/docs/zh/docs/tutorial/security/get-current-user.md b/docs/zh/docs/tutorial/security/get-current-user.md index f8094e86a8..1f254a1037 100644 --- a/docs/zh/docs/tutorial/security/get-current-user.md +++ b/docs/zh/docs/tutorial/security/get-current-user.md @@ -2,9 +2,7 @@ 上一章中,(基于依赖注入系统的)安全系统向*路径操作函数*传递了 `str` 类型的 `token`: -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +{* ../../docs_src/security/tutorial001.py hl[10] *} 但这并不实用。 @@ -17,9 +15,7 @@ 与使用 Pydantic 声明请求体相同,并且可在任何位置使用: -```Python hl_lines="5 12-16" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[5,12:16] *} ## 创建 `get_current_user` 依赖项 @@ -31,31 +27,25 @@ 与之前直接在路径操作中的做法相同,新的 `get_current_user` 依赖项从子依赖项 `oauth2_scheme` 中接收 `str` 类型的 `token`: -```Python hl_lines="25" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[25] *} ## 获取用户 `get_current_user` 使用创建的(伪)工具函数,该函数接收 `str` 类型的令牌,并返回 Pydantic 的 `User` 模型: -```Python hl_lines="19-22 26-27" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *} ## 注入当前用户 在*路径操作* 的 `Depends` 中使用 `get_current_user`: -```Python hl_lines="31" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[31] *} 注意,此处把 `current_user` 的类型声明为 Pydantic 的 `User` 模型。 这有助于在函数内部使用代码补全和类型检查。 -/// tip | "提示" +/// tip | 提示 还记得请求体也是使用 Pydantic 模型声明的吧。 @@ -63,7 +53,7 @@ /// -/// check | "检查" +/// check | 检查 依赖系统的这种设计方式可以支持不同的依赖项返回同一个 `User` 模型。 @@ -104,9 +94,7 @@ 所有*路径操作*只需 3 行代码就可以了: -```Python hl_lines="30-32" -{!../../../docs_src/security/tutorial002.py!} -``` +{* ../../docs_src/security/tutorial002.py hl[30:32] *} ## 小结 diff --git a/docs/zh/docs/tutorial/security/index.md b/docs/zh/docs/tutorial/security/index.md index e888a4fe9b..1484b99fd8 100644 --- a/docs/zh/docs/tutorial/security/index.md +++ b/docs/zh/docs/tutorial/security/index.md @@ -22,7 +22,7 @@ OAuth2是一个规范,它定义了几种处理身份认证和授权的方法 它包括了使用「第三方」进行身份认证的方法。 -这就是所有带有「使用 Facebook,Google,Twitter,GitHub 登录」的系统背后所使用的机制。 +这就是所有带有「使用 Facebook,Google,X (Twitter),GitHub 登录」的系统背后所使用的机制。 ### OAuth 1 @@ -79,7 +79,7 @@ OpenAPI 定义了以下安全方案: * HTTP Basic 认证方式。 * HTTP Digest,等等。 * `oauth2`:所有的 OAuth2 处理安全性的方式(称为「流程」)。 - *以下几种流程适合构建 OAuth 2.0 身份认证的提供者(例如 Google,Facebook,Twitter,GitHub 等): + *以下几种流程适合构建 OAuth 2.0 身份认证的提供者(例如 Google,Facebook,X (Twitter),GitHub 等): * `implicit` * `clientCredentials` * `authorizationCode` @@ -91,7 +91,7 @@ OpenAPI 定义了以下安全方案: /// tip -集成其他身份认证/授权提供者(例如Google,Facebook,Twitter,GitHub等)也是可能的,而且较为容易。 +集成其他身份认证/授权提供者(例如Google,Facebook,X (Twitter),GitHub等)也是可能的,而且较为容易。 最复杂的问题是创建一个像这样的身份认证/授权提供程序,但是 **FastAPI** 为你提供了轻松完成任务的工具,同时为你解决了重活。 diff --git a/docs/zh/docs/tutorial/security/oauth2-jwt.md b/docs/zh/docs/tutorial/security/oauth2-jwt.md index 690bd1789b..7d338419b5 100644 --- a/docs/zh/docs/tutorial/security/oauth2-jwt.md +++ b/docs/zh/docs/tutorial/security/oauth2-jwt.md @@ -40,7 +40,7 @@ $ pip install pyjwt -/// info | "说明" +/// info | 说明 如果您打算使用类似 RSA 或 ECDSA 的数字签名算法,您应该安装加密库依赖项 `pyjwt[crypto]`。 @@ -82,7 +82,7 @@ $ pip install passlib[bcrypt] -/// tip | "提示" +/// tip | 提示 `passlib` 甚至可以读取 Django、Flask 的安全插件等工具创建的密码。 @@ -98,7 +98,7 @@ $ pip install passlib[bcrypt] 创建用于密码哈希和身份校验的 PassLib **上下文**。 -/// tip | "提示" +/// tip | 提示 PassLib 上下文还支持使用不同哈希算法的功能,包括只能校验的已弃用旧算法等。 @@ -114,59 +114,9 @@ PassLib 上下文还支持使用不同哈希算法的功能,包括只能校验 第三个函数用于身份验证,并返回用户。 -//// tab | Python 3.10+ +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} -```Python hl_lines="8 49 56-57 60-61 70-76" -{!> ../../../docs_src/security/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="8 49 56-57 60-61 70-76" -{!> ../../../docs_src/security/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="8 50 57-58 61-62 71-77" -{!> ../../../docs_src/security/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="7 48 55-56 59-60 69-75" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="8 49 56-57 60-61 70-76" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// - -/// note | "笔记" +/// note | 笔记 查看新的(伪)数据库 `fake_users_db`,就能看到哈希后的密码:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`。 @@ -200,9 +150,7 @@ $ openssl rand -hex 32 创建生成新的访问令牌的工具函数。 -```Python hl_lines="6 12-14 28-30 78-86" -{!../../../docs_src/security/tutorial004.py!} -``` +{* ../../docs_src/security/tutorial004.py hl[6,12:14,28:30,78:86] *} ## 更新依赖项 @@ -212,57 +160,7 @@ $ openssl rand -hex 32 如果令牌无效,则直接返回 HTTP 错误。 -//// tab | Python 3.10+ - -```Python hl_lines="4 7 13-15 29-31 79-87" -{!> ../../../docs_src/security/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="4 7 13-15 29-31 79-87" -{!> ../../../docs_src/security/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="4 7 14-16 30-32 80-88" -{!> ../../../docs_src/security/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="3 6 12-14 28-30 78-86" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="4 7 13-15 29-31 79-87" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} ## 更新 `/token` *路径操作* @@ -270,57 +168,7 @@ Prefer to use the `Annotated` version if possible. 创建并返回真正的 JWT 访问令牌。 -//// tab | Python 3.10+ - -```Python hl_lines="118-133" -{!> ../../../docs_src/security/tutorial004_an_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="118-133" -{!> ../../../docs_src/security/tutorial004_an_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="119-134" -{!> ../../../docs_src/security/tutorial004_an.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="115-130" -{!> ../../../docs_src/security/tutorial004_py310.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="116-131" -{!> ../../../docs_src/security/tutorial004.py!} -``` - -//// +{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} ### JWT `sub` 的技术细节 @@ -358,7 +206,7 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。 用户名: `johndoe` 密码: `secret` -/// check | "检查" +/// check | 检查 注意,代码中没有明文密码**`secret`**,只保存了它的哈希值。 @@ -383,7 +231,7 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。 -/// note | "笔记" +/// note | 笔记 注意,请求中 `Authorization` 响应头的值以 `Bearer` 开头。 diff --git a/docs/zh/docs/tutorial/security/simple-oauth2.md b/docs/zh/docs/tutorial/security/simple-oauth2.md index 261421dad0..f70678df8f 100644 --- a/docs/zh/docs/tutorial/security/simple-oauth2.md +++ b/docs/zh/docs/tutorial/security/simple-oauth2.md @@ -32,7 +32,7 @@ OAuth2 还支持客户端发送**`scope`**表单字段。 * 脸书和 Instagram 使用 `instagram_basic` * 谷歌使用 `https://www.googleapis.com/auth/drive` -/// info | "说明" +/// info | 说明 OAuth2 中,**作用域**只是声明指定权限的字符串。 @@ -52,9 +52,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 首先,导入 `OAuth2PasswordRequestForm`,然后,在 `/token` *路径操作* 中,用 `Depends` 把该类作为依赖项。 -```Python hl_lines="4 76" -{!../../../docs_src/security/tutorial003.py!} -``` +{* ../../docs_src/security/tutorial003.py hl[4,76] *} `OAuth2PasswordRequestForm` 是用以下几项内容声明表单请求体的类依赖项: @@ -63,7 +61,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 * 可选的 `scope` 字段,由多个空格分隔的字符串组成的长字符串 * 可选的 `grant_type` -/// tip | "提示" +/// tip | 提示 实际上,OAuth2 规范*要求* `grant_type` 字段使用固定值 `password`,但 `OAuth2PasswordRequestForm` 没有作强制约束。 @@ -74,7 +72,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 * 可选的 `client_id`(本例未使用) * 可选的 `client_secret`(本例未使用) -/// info | "说明" +/// info | 说明 `OAuth2PasswordRequestForm` 与 `OAuth2PasswordBearer` 一样,都不是 FastAPI 的特殊类。 @@ -88,7 +86,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 ### 使用表单数据 -/// tip | "提示" +/// tip | 提示 `OAuth2PasswordRequestForm` 类依赖项的实例没有以空格分隔的长字符串属性 `scope`,但它支持 `scopes` 属性,由已发送的 scope 字符串列表组成。 @@ -102,9 +100,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 本例使用 `HTTPException` 异常显示此错误: -```Python hl_lines="3 77-79" -{!../../../docs_src/security/tutorial003.py!} -``` +{* ../../docs_src/security/tutorial003.py hl[3,77:79] *} ### 校验密码 @@ -130,9 +126,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 这样一来,窃贼就无法在其它应用中使用窃取的密码,要知道,很多用户在所有系统中都使用相同的密码,风险超大。 -```Python hl_lines="80-83" -{!../../../docs_src/security/tutorial003.py!} -``` +{* ../../docs_src/security/tutorial003.py hl[80:83] *} #### 关于 `**user_dict` @@ -150,7 +144,7 @@ UserInDB( ) ``` -/// info | "说明" +/// info | 说明 `user_dict` 的说明,详见[**更多模型**一章](../extra-models.md#user_indict){.internal-link target=_blank}。 @@ -166,7 +160,7 @@ UserInDB( 本例只是简单的演示,返回的 Token 就是 `username`,但这种方式极不安全。 -/// tip | "提示" +/// tip | 提示 下一章介绍使用哈希密码和 JWT Token 的真正安全机制。 @@ -174,11 +168,9 @@ UserInDB( /// -```Python hl_lines="85" -{!../../../docs_src/security/tutorial003.py!} -``` +{* ../../docs_src/security/tutorial003.py hl[85] *} -/// tip | "提示" +/// tip | 提示 按规范的要求,应像本示例一样,返回带有 `access_token` 和 `token_type` 的 JSON 对象。 @@ -202,11 +194,9 @@ UserInDB( 因此,在端点中,只有当用户存在、通过身份验证、且状态为激活时,才能获得该用户: -```Python hl_lines="58-67 69-72 90" -{!../../../docs_src/security/tutorial003.py!} -``` +{* ../../docs_src/security/tutorial003.py hl[58:67,69:72,90] *} -/// info | "说明" +/// info | 说明 此处返回值为 `Bearer` 的响应头 `WWW-Authenticate` 也是规范的一部分。 diff --git a/docs/zh/docs/tutorial/sql-databases.md b/docs/zh/docs/tutorial/sql-databases.md index 45ec71f7ca..fbdf3be6cc 100644 --- a/docs/zh/docs/tutorial/sql-databases.md +++ b/docs/zh/docs/tutorial/sql-databases.md @@ -1,908 +1,360 @@ -# SQL (关系型) 数据库 +# SQL(关系型)数据库 -/// info +**FastAPI** 并不要求您使用 SQL(关系型)数据库。您可以使用**任何**想用的数据库。 -这些文档即将被更新。🎉 +这里,我们来看一个使用 SQLModel 的示例。 -当前版本假设Pydantic v1和SQLAlchemy版本小于2。 +**SQLModel** 是基于 SQLAlchemy 和 Pydantic 构建的。它由 **FastAPI** 的同一作者制作,旨在完美匹配需要使用 **SQL 数据库**的 FastAPI 应用程序。 -新的文档将包括Pydantic v2以及 SQLModel(也是基于SQLAlchemy),一旦SQLModel更新为为使用Pydantic v2。 +/// tip + +您可以使用任何其他您想要的 SQL 或 NoSQL 数据库(在某些情况下称为 “ORM”),FastAPI 不会强迫您使用任何东西。😎 /// -**FastAPI**不需要你使用SQL(关系型)数据库。 - -但是您可以使用任何您想要的关系型数据库。 - -在这里,让我们看一个使用着[SQLAlchemy](https://www.sqlalchemy.org/)的示例。 - -您可以很容易地将其调整为任何SQLAlchemy支持的数据库,如: +由于 SQLModel 基于 SQLAlchemy,因此您可以轻松使用任何由 SQLAlchemy **支持的数据库**(这也让它们被 SQLModel 支持),例如: * PostgreSQL * MySQL * SQLite * Oracle -* Microsoft SQL Server,等等其它数据库 +* Microsoft SQL Server 等. -在此示例中,我们将使用**SQLite**,因为它使用单个文件并且 在Python中具有集成支持。因此,您可以复制此示例并按原样来运行它。 +在这个例子中,我们将使用 **SQLite**,因为它使用单个文件,并且 Python 对其有集成支持。因此,您可以直接复制这个例子并运行。 -稍后,对于您的产品级别的应用程序,您可能会要使用像**PostgreSQL**这样的数据库服务器。 +之后,对于您的生产应用程序,您可能会想要使用像 PostgreSQL 这样的数据库服务器。 /// tip -这儿有一个**FastAPI**和**PostgreSQL**的官方项目生成器,全部基于**Docker**,包括前端和更多工具:https://github.com/tiangolo/full-stack-fastapi-postgresql +有一个使用 **FastAPI** 和 **PostgreSQL** 的官方的项目生成器,其中包括了前端和更多工具: https://github.com/fastapi/full-stack-fastapi-template /// -/// note +这是一个非常简单和简短的教程。如果您想了解一般的数据库、SQL 或更高级的功能,请查看 SQLModel 文档。 -请注意,大部分代码是`SQLAlchemy`的标准代码,您可以用于任何框架。FastAPI特定的代码和往常一样少。 +## 安装 `SQLModel` -/// - -## ORMs(对象关系映射) - -**FastAPI**可与任何数据库在任何样式的库中一起与 数据库进行通信。 - -一种常见的模式是使用“ORM”:对象关系映射。 - -ORM 具有在代码和数据库表(“*关系型”)中的**对象**之间转换(“*映射*”)的工具。 - -使用 ORM,您通常会在 SQL 数据库中创建一个代表映射的类,该类的每个属性代表一个列,具有名称和类型。 - -例如,一个类`Pet`可以表示一个 SQL 表`pets`。 - -该类的每个*实例对象都代表数据库中的一行数据。* - -又例如,一个对象`orion_cat`(`Pet`的一个实例)可以有一个属性`orion_cat.type`, 对标数据库中的`type`列。并且该属性的值可以是其它,例如`"cat"`。 - -这些 ORM 还具有在表或实体之间建立关系的工具(比如创建多表关系)。 - -这样,您还可以拥有一个属性`orion_cat.owner`,它包含该宠物所有者的数据,这些数据取自另外一个表。 - -因此,`orion_cat.owner.name`可能是该宠物主人的姓名(来自表`owners`中的列`name`)。 - -它可能有一个像`"Arquilian"`(一种业务逻辑)。 - -当您尝试从您的宠物对象访问它时,ORM 将完成所有工作以从相应的表*所有者那里再获取信息。* - -常见的 ORM 例如:Django-ORM(Django 框架的一部分)、SQLAlchemy ORM(SQLAlchemy 的一部分,独立于框架)和 Peewee(独立于框架)等。 - -在这里,我们将看到如何使用**SQLAlchemy ORM**。 - -以类似的方式,您也可以使用任何其他 ORM。 - -/// tip - -在文档中也有一篇使用 Peewee 的等效的文章。 - -/// - -## 文件结构 - -对于这些示例,假设您有一个名为的目录`my_super_project`,其中包含一个名为的子目录`sql_app`,其结构如下: - -``` -. -└── sql_app - ├── __init__.py - ├── crud.py - ├── database.py - ├── main.py - ├── models.py - └── schemas.py -``` - -该文件`__init__.py`只是一个空文件,但它告诉 Python `sql_app` 是一个包。 - -现在让我们看看每个文件/模块的作用。 - -## 安装 SQLAlchemy - -首先你需要安装`SQLAlchemy`: +首先,确保您创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装了 `sqlmodel` :
```console -$ pip install sqlalchemy - +$ pip install sqlmodel ---> 100% ```
-## 创建 SQLAlchemy 部件 +## 创建含有单一模型的应用程序 -让我们转到文件`sql_app/database.py`。 +我们首先创建应用程序的最简单的第一个版本,只有一个 **SQLModel** 模型。 -### 导入 SQLAlchemy 部件 +稍后我们将通过下面的**多个模型**提高其安全性和多功能性。🤓 -```Python hl_lines="1-3" -{!../../../docs_src/sql_databases/sql_app/database.py!} -``` +### 创建模型 -### 为 SQLAlchemy 定义数据库 URL地址 +导入 `SQLModel` 并创建一个数据库模型: -```Python hl_lines="5-6" -{!../../../docs_src/sql_databases/sql_app/database.py!} -``` +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} -在这个例子中,我们正在“连接”到一个 SQLite 数据库(用 SQLite 数据库打开一个文件)。 +`Hero` 类与 Pydantic 模型非常相似(实际上,从底层来看,它确实*就是一个 Pydantic 模型*)。 -该文件将位于文件中的同一目录中`sql_app.db`。 +有一些区别: -这就是为什么最后一部分是`./sql_app.db`. +* `table=True` 会告诉 SQLModel 这是一个*表模型*,它应该表示 SQL 数据库中的一个*表*,而不仅仅是一个*数据模型*(就像其他常规的 Pydantic 类一样)。 -如果您使用的是**PostgreSQL**数据库,则只需取消注释该行: +* `Field(primary_key=True)` 会告诉 SQLModel `id` 是 SQL 数据库中的**主键**(您可以在 SQLModel 文档中了解更多关于 SQL 主键的信息)。 -```Python -SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" -``` + 把类型设置为 `int | None` ,SQLModel 就能知道该列在 SQL 数据库中应该是 `INTEGER` 类型,并且应该是 `NULLABLE` 。 -...并根据您的数据库数据和相关凭据(也适用于 MySQL、MariaDB 或任何其他)对其进行调整。 +* `Field(index=True)` 会告诉 SQLModel 应该为此列创建一个 **SQL 索引**,这样在读取按此列过滤的数据时,程序能在数据库中进行更快的查找。 + + SQLModel 会知道声明为 `str` 的内容将是类型为 `TEXT` (或 `VARCHAR` ,具体取决于数据库)的 SQL 列。 + +### 创建引擎(Engine) + +SQLModel 的引擎 `engine`(实际上它是一个 SQLAlchemy `engine` )是用来与数据库**保持连接**的。 + +您只需构建**一个 `engine`**,来让您的所有代码连接到同一个数据库。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +使用 `check_same_thread=False` 可以让 FastAPI 在不同线程中使用同一个 SQLite 数据库。这很有必要,因为**单个请求**可能会使用**多个线程**(例如在依赖项中)。 + +不用担心,我们会按照代码结构确保**每个请求使用一个单独的 SQLModel *会话***,这实际上就是 `check_same_thread` 想要实现的。 + +### 创建表 + +然后,我们来添加一个函数,使用 `SQLModel.metadata.create_all(engine)` 为所有*表模型***创建表**。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### 创建会话(Session)依赖项 + +**`Session`** 会存储**内存中的对象**并跟踪数据中所需更改的内容,然后它**使用 `engine`** 与数据库进行通信。 + +我们会使用 `yield` 创建一个 FastAPI **依赖项**,为每个请求提供一个新的 `Session` 。这确保我们每个请求使用一个单独的会话。🤓 + +然后我们创建一个 `Annotated` 的依赖项 `SessionDep` 来简化其他也会用到此依赖的代码。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### 在启动时创建数据库表 + +我们会在应用程序启动时创建数据库表。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +此处,在应用程序启动事件中,我们创建了表。 + +而对于生产环境,您可能会用一个能够在启动应用程序之前运行的迁移脚本。🤓 /// tip -如果您想使用不同的数据库,这是就是您必须修改的地方。 +SQLModel 将会拥有封装 Alembic 的迁移工具,但目前您可以直接使用 Alembic。 /// -### 创建 SQLAlchemy 引擎 +### 创建 Hero 类 -第一步,创建一个 SQLAlchemy的“引擎”。 +因为每个 SQLModel 模型同时也是一个 Pydantic 模型,所以您可以在与 Pydantic 模型相同的**类型注释**中使用它。 -我们稍后会将这个`engine`在其他地方使用。 +例如,如果您声明一个类型为 `Hero` 的参数,它将从 **JSON 主体**中读取数据。 -```Python hl_lines="8-10" -{!../../../docs_src/sql_databases/sql_app/database.py!} -``` +同样,您可以将其声明为函数的**返回类型**,然后数据的结构就会显示在自动生成的 API 文档界面中。 -#### 注意 +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} -参数: + -```Python -connect_args={"check_same_thread": False} -``` +这里,我们使用 `SessionDep` 依赖项(一个 `Session` )将新的 `Hero` 添加到 `Session` 实例中,提交更改到数据库,刷新 hero 中的数据,并返回它。 -...仅用于`SQLite`,在其他数据库不需要它。 +### 读取 Hero 类 -/// info | "技术细节" +我们可以使用 `select()` 从数据库中**读取** `Hero` 类,并利用 `limit` 和 `offset` 来对结果进行分页。 -默认情况下,SQLite 只允许一个线程与其通信,假设有多个线程的话,也只将处理一个独立的请求。 +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} -这是为了防止意外地为不同的事物(不同的请求)共享相同的连接。 +### 读取单个 Hero -但是在 FastAPI 中,使用普通函数(def)时,多个线程可以为同一个请求与数据库交互,所以我们需要使用`connect_args={"check_same_thread": False}`来让SQLite允许这样。 +我们可以**读取**单个 `Hero` 。 -此外,我们将确保每个请求都在依赖项中获得自己的数据库连接会话,因此不需要该默认机制。 +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} -/// +### 删除单个 Hero -### 创建一个`SessionLocal`类 +我们也可以**删除**单个 `Hero` 。 -每个`SessionLocal`类的实例都会是一个数据库会话。当然该类本身还不是数据库会话。 +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} -但是一旦我们创建了一个`SessionLocal`类的实例,这个实例将是实际的数据库会话。 - -我们将它命名为`SessionLocal`是为了将它与我们从 SQLAlchemy 导入的`Session`区别开来。 - -稍后我们将使用`Session`(从 SQLAlchemy 导入的那个)。 - -要创建`SessionLocal`类,请使用函数`sessionmaker`: - -```Python hl_lines="11" -{!../../../docs_src/sql_databases/sql_app/database.py!} -``` - -### 创建一个`Base`类 - -现在我们将使用`declarative_base()`返回一个类。 - -稍后我们将继承这个类,来创建每个数据库模型或类(ORM 模型): - -```Python hl_lines="13" -{!../../../docs_src/sql_databases/sql_app/database.py!} -``` - -## 创建数据库模型 - -现在让我们看看文件`sql_app/models.py`。 - -### 用`Base`类来创建 SQLAlchemy 模型 - -我们将使用我们之前创建的`Base`类来创建 SQLAlchemy 模型。 - -/// tip - -SQLAlchemy 使用的“**模型**”这个术语 来指代与数据库交互的这些类和实例。 - -而 Pydantic 也使用“模型”这个术语 来指代不同的东西,即数据验证、转换以及文档类和实例。 - -/// - -从`database`(来自上面的`database.py`文件)导入`Base`。 - -创建从它继承的类。 - -这些类就是 SQLAlchemy 模型。 - -```Python hl_lines="4 7-8 18-19" -{!../../../docs_src/sql_databases/sql_app/models.py!} -``` - -这个`__tablename__`属性是用来告诉 SQLAlchemy 要在数据库中为每个模型使用的数据库表的名称。 - -### 创建模型属性/列 - -现在创建所有模型(类)的属性。 - -这些属性中的每一个都代表其相应数据库表中的一列。 - -我们使用`Column`来表示 SQLAlchemy 中的默认值。 - -我们传递一个 SQLAlchemy “类型”,如`Integer`、`String`和`Boolean`,它定义了数据库中的类型,作为参数。 - -```Python hl_lines="1 10-13 21-24" -{!../../../docs_src/sql_databases/sql_app/models.py!} -``` - -### 创建关系 - -现在创建关系。 - -为此,我们使用SQLAlchemy ORM提供的`relationship`。 - -这将或多或少会成为一种“神奇”属性,其中表示该表与其他相关的表中的值。 - -```Python hl_lines="2 15 26" -{!../../../docs_src/sql_databases/sql_app/models.py!} -``` - -当访问 user 中的属性`items`时,如 中`my_user.items`,它将有一个`Item`SQLAlchemy 模型列表(来自`items`表),这些模型具有指向`users`表中此记录的外键。 - -当您访问`my_user.items`时,SQLAlchemy 实际上会从`items`表中的获取一批记录并在此处填充进去。 - -同样,当访问 Item中的属性`owner`时,它将包含表中的`User`SQLAlchemy 模型`users`。使用`owner_id`属性/列及其外键来了解要从`users`表中获取哪条记录。 - -## 创建 Pydantic 模型 - -现在让我们查看一下文件`sql_app/schemas.py`。 - -/// tip - -为了避免 SQLAlchemy*模型*和 Pydantic*模型*之间的混淆,我们将有`models.py`(SQLAlchemy 模型的文件)和`schemas.py`( Pydantic 模型的文件)。 - -这些 Pydantic 模型或多或少地定义了一个“schema”(一个有效的数据形状)。 - -因此,这将帮助我们在使用两者时避免混淆。 - -/// - -### 创建初始 Pydantic*模型*/模式 - -创建一个`ItemBase`和`UserBase`Pydantic*模型*(或者我们说“schema”),他们拥有创建或读取数据时具有的共同属性。 - -然后创建一个继承自他们的`ItemCreate`和`UserCreate`,并添加创建时所需的其他数据(或属性)。 - -因此在创建时也应当有一个`password`属性。 - -但是为了安全起见,`password`不会出现在其他同类 Pydantic*模型*中,例如通过API读取一个用户数据时,它不应当包含在内。 - -//// tab | Python 3.10+ - -```Python hl_lines="1 4-6 9-10 21-22 25-26" -{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3 6-8 11-12 23-24 27-28" -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3 6-8 11-12 23-24 27-28" -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -#### SQLAlchemy 风格和 Pydantic 风格 - -请注意,SQLAlchemy*模型*使用 `=`来定义属性,并将类型作为参数传递给`Column`,例如: - -```Python -name = Column(String) -``` - -虽然 Pydantic*模型*使用`:` 声明类型,但新的类型注释语法/类型提示是: - -```Python -name: str -``` - -请牢记这一点,这样您在使用`:`还是`=`时就不会感到困惑。 - -### 创建用于读取/返回的Pydantic*模型/模式* - -现在创建当从 API 返回数据时、将在读取数据时使用的Pydantic*模型(schemas)。* - -例如,在创建一个项目之前,我们不知道分配给它的 ID 是什么,但是在读取它时(从 API 返回时)我们已经知道它的 ID。 - -同样,当读取用户时,我们现在可以声明`items`,将包含属于该用户的项目。 - -不仅是这些项目的 ID,还有我们在 Pydantic*模型*中定义的用于读取项目的所有数据:`Item`. - -//// tab | Python 3.10+ - -```Python hl_lines="13-15 29-32" -{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="15-17 31-34" -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15-17 31-34" -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -/// tip - -请注意,读取用户(从 API 返回)时将使用不包括`password`的`User` Pydantic*模型*。 - -/// - -### 使用 Pydantic 的`orm_mode` - -现在,在用于查询的 Pydantic*模型*`Item`中`User`,添加一个内部`Config`类。 - -此类[`Config`](https://docs.pydantic.dev/latest/api/config/)用于为 Pydantic 提供配置。 - -在`Config`类中,设置属性`orm_mode = True`。 - -//// tab | Python 3.10+ - -```Python hl_lines="13 17-18 29 34-35" -{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="15 19-20 31 36-37" -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15 19-20 31 36-37" -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -/// tip - -请注意,它使用`=`分配一个值,例如: - -`orm_mode = True` - -它不使用之前的`:`来类型声明。 - -这是设置配置值,而不是声明类型。 - -/// - -Pydantic`orm_mode`将告诉 Pydantic*模型*读取数据,即它不是一个`dict`,而是一个 ORM 模型(或任何其他具有属性的任意对象)。 - -这样,而不是仅仅试图从`dict`上 `id` 中获取值,如下所示: - -```Python -id = data["id"] -``` - -它还会尝试从属性中获取它,如: - -```Python -id = data.id -``` - -有了这个,Pydantic*模型*与 ORM 兼容,您只需在*路径操作*`response_model`的参数中声明它即可。 - -您将能够返回一个数据库模型,它将从中读取数据。 - -#### ORM 模式的技术细节 - -SQLAlchemy 和许多其他默认情况下是“延迟加载”。 - -这意味着,例如,除非您尝试访问包含该数据的属性,否则它们不会从数据库中获取关系数据。 - -例如,访问属性`items`: - -```Python -current_user.items -``` - -将使 SQLAlchemy 转到`items`表并获取该用户的项目,在调用`.items`之前不会去查询数据库。 - -没有`orm_mode`,如果您从*路径操作*返回一个 SQLAlchemy 模型,它不会包含关系数据。 - -即使您在 Pydantic 模型中声明了这些关系,也没有用处。 - -但是在 ORM 模式下,由于 Pydantic 本身会尝试从属性访问它需要的数据(而不是假设为 `dict`),你可以声明你想要返回的特定数据,它甚至可以从 ORM 中获取它。 - -## CRUD工具 - -现在让我们看看文件`sql_app/crud.py`。 - -在这个文件中,我们将编写可重用的函数用来与数据库中的数据进行交互。 - -**CRUD**分别为:增加(**C**reate)、查询(**R**ead)、更改(**U**pdate)、删除(**D**elete),即增删改查。 - -...虽然在这个例子中我们只是新增和查询。 - -### 读取数据 - -从 `sqlalchemy.orm`中导入`Session`,这将允许您声明`db`参数的类型,并在您的函数中进行更好的类型检查和完成。 - -导入之前的`models`(SQLAlchemy 模型)和`schemas`(Pydantic*模型*/模式)。 - -创建一些工具函数来完成: - -* 通过 ID 和电子邮件查询单个用户。 -* 查询多个用户。 -* 查询多个项目。 - -```Python hl_lines="1 3 6-7 10-11 14-15 27-28" -{!../../../docs_src/sql_databases/sql_app/crud.py!} -``` - -/// tip - -通过创建仅专用于与数据库交互(获取用户或项目)的函数,独立于*路径操作函数*,您可以更轻松地在多个部分中重用它们,并为它们添加单元测试。 - -/// - -### 创建数据 - -现在创建工具函数来创建数据。 - -它的步骤是: - -* 使用您的数据创建一个 SQLAlchemy 模型*实例。* -* 使用`add`来将该实例对象添加到数据库会话。 -* 使用`commit`来将更改提交到数据库(以便保存它们)。 -* 使用`refresh`来刷新您的实例对象(以便它包含来自数据库的任何新数据,例如生成的 ID)。 - -```Python hl_lines="18-24 31-36" -{!../../../docs_src/sql_databases/sql_app/crud.py!} -``` - -/// tip - -SQLAlchemy 模型`User`包含一个`hashed_password`,它应该是一个包含散列的安全密码。 - -但由于 API 客户端提供的是原始密码,因此您需要将其提取并在应用程序中生成散列密码。 - -然后将hashed_password参数与要保存的值一起传递。 - -/// - -/// warning - -此示例不安全,密码未经过哈希处理。 - -在现实生活中的应用程序中,您需要对密码进行哈希处理,并且永远不要以明文形式保存它们。 - -有关更多详细信息,请返回教程中的安全部分。 - -在这里,我们只关注数据库的工具和机制。 - -/// - -/// tip - -这里不是将每个关键字参数传递给Item并从Pydantic模型中读取每个参数,而是先生成一个字典,其中包含Pydantic模型的数据: - -`item.dict()` - -然后我们将dict的键值对 作为关键字参数传递给 SQLAlchemy `Item`: - -`Item(**item.dict())` - -然后我们传递 Pydantic模型未提供的额外关键字参数`owner_id`: - -`Item(**item.dict(), owner_id=user_id)` - -/// - -## 主**FastAPI**应用程序 - -现在在`sql_app/main.py`文件中 让我们集成和使用我们之前创建的所有其他部分。 - -### 创建数据库表 - -以非常简单的方式创建数据库表: - -//// tab | Python 3.9+ - -```Python hl_lines="7" -{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -#### Alembic 注意 - -通常你可能会使用 Alembic,来进行格式化数据库(创建表等)。 - -而且您还可以将 Alembic 用于“迁移”(这是它的主要工作)。 - -“迁移”是每当您更改 SQLAlchemy 模型的结构、添加新属性等以在数据库中复制这些更改、添加新列、新表等时所需的一组步骤。 - -您可以在[Full Stack FastAPI Template](https://fastapi.tiangolo.com/zh/project-generation/)的模板中找到一个 FastAPI 项目中的 Alembic 示例。具体在[`alembic`代码目录中](https://github.com/tiangolo/full-stack-fastapi-template/tree/master/backend/app/alembic)。 - -### 创建依赖项 - -现在使用我们在`sql_app/database.py`文件中创建的`SessionLocal`来创建依赖项。 - -我们需要每个请求有一个独立的数据库会话/连接(`SessionLocal`),在整个请求中使用相同的会话,然后在请求完成后关闭它。 - -然后将为下一个请求创建一个新会话。 - -为此,我们将创建一个包含`yield`的依赖项,正如前面关于[Dependencies with`yield`](https://fastapi.tiangolo.com/zh/tutorial/dependencies/dependencies-with-yield/)的部分中所解释的那样。 - -我们的依赖项将创建一个新的 SQLAlchemy `SessionLocal`,它将在单个请求中使用,然后在请求完成后关闭它。 - -//// tab | Python 3.9+ - -```Python hl_lines="13-18" -{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15-20" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -/// info - -我们将`SessionLocal()`请求的创建和处理放在一个`try`块中。 - -然后我们在finally块中关闭它。 - -通过这种方式,我们确保数据库会话在请求后始终关闭。即使在处理请求时出现异常。 - -但是您不能从退出代码中引发另一个异常(在yield之后)。可以查阅 [Dependencies with yield and HTTPException](https://fastapi.tiangolo.com/zh/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-httpexception) - -/// - -*然后,当在路径操作函数*中使用依赖项时,我们使用`Session`,直接从 SQLAlchemy 导入的类型声明它。 - -*这将为我们在路径操作函数*中提供更好的编辑器支持,因为编辑器将知道`db`参数的类型`Session`: - -//// tab | Python 3.9+ - -```Python hl_lines="22 30 36 45 51" -{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24 32 38 47 53" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -/// info | "技术细节" - -参数`db`实际上是 type `SessionLocal`,但是这个类(用 创建`sessionmaker()`)是 SQLAlchemy 的“代理” `Session`,所以,编辑器并不真正知道提供了哪些方法。 - -但是通过将类型声明为Session,编辑器现在可以知道可用的方法(.add()、.query()、.commit()等)并且可以提供更好的支持(比如完成)。类型声明不影响实际对象。 - -/// - -### 创建您的**FastAPI** *路径操作* - -现在,到了最后,编写标准的**FastAPI** *路径操作*代码。 - -//// tab | Python 3.9+ - -```Python hl_lines="21-26 29-32 35-40 43-47 50-53" -{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23-28 31-34 37-42 45-49 52-55" -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -我们在依赖项中的每个请求之前利用`yield`创建数据库会话,然后关闭它。 - -所以我们就可以在*路径操作函数*中创建需要的依赖,就能直接获取会话。 - -这样,我们就可以直接从*路径操作函数*内部调用`crud.get_user`并使用该会话,来进行对数据库操作。 - -/// tip - -请注意,您返回的值是 SQLAlchemy 模型或 SQLAlchemy 模型列表。 - -但是由于所有路径操作的response_model都使用 Pydantic模型/使用orm_mode模式,因此您的 Pydantic 模型中声明的数据将从它们中提取并返回给客户端,并进行所有正常的过滤和验证。 - -/// - -/// tip - -另请注意,`response_models`应当是标准 Python 类型,例如`List[schemas.Item]`. - -但是由于它的内容/参数List是一个 使用orm_mode模式的Pydantic模型,所以数据将被正常检索并返回给客户端,所以没有问题。 - -/// - -### 关于 `def` 对比 `async def` - -*在这里,我们在路径操作函数*和依赖项中都使用着 SQLAlchemy 模型,它将与外部数据库进行通信。 - -这会需要一些“等待时间”。 - -但是由于 SQLAlchemy 不具有`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 - -如果您需要异步连接到关系数据库,请参阅[Async SQL (Relational) Databases](https://fastapi.tiangolo.com/zh/advanced/async-sql-databases/) - -/// - -/// note | "Very Technical Details" - -如果您很好奇并且拥有深厚的技术知识,您可以在[Async](https://fastapi.tiangolo.com/zh/async/#very-technical-details)文档中查看有关如何处理 `async def`于`def`差别的技术细节。 - -/// - -## 迁移 - -因为我们直接使用 SQLAlchemy,并且我们不需要任何类型的插件来使用**FastAPI**,所以我们可以直接将数据库迁移至[Alembic](https://alembic.sqlalchemy.org/)进行集成。 - -由于与 SQLAlchemy 和 SQLAlchemy 模型相关的代码位于单独的独立文件中,您甚至可以使用 Alembic 执行迁移,而无需安装 FastAPI、Pydantic 或其他任何东西。 - -同样,您将能够在与**FastAPI**无关的代码的其他部分中使用相同的 SQLAlchemy 模型和实用程序。 - -例如,在具有[Celery](https://docs.celeryq.dev/)、[RQ](https://python-rq.org/)或[ARQ](https://arq-docs.helpmanual.io/)的后台任务工作者中。 - -## 审查所有文件 - -最后回顾整个案例,您应该有一个名为的目录`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`: - -//// tab | Python 3.10+ - -```Python -{!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -* `sql_app/crud.py`: - -```Python -{!../../../docs_src/sql_databases/sql_app/crud.py!} -``` - -* `sql_app/main.py`: - -//// tab | Python 3.9+ - -```Python -{!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -## 执行项目 - -您可以复制这些代码并按原样使用它。 - -/// info - -事实上,这里的代码只是大多数测试代码的一部分。 - -/// - -你可以用 Uvicorn 运行它: +### 运行应用程序 +您可以运行这个应用程序:
```console -$ uvicorn sql_app.main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-打开浏览器进入 http://127.0.0.1:8000/docs。 - -您将能够与您的**FastAPI**应用程序交互,从真实数据库中读取数据: +然后在 `/docs` UI 中,您能够看到 **FastAPI** 会用这些**模型**来**记录** API,并且还会用它们来**序列化**和**验证**数据。 +
+
-## 直接与数据库交互 +## 更新应用程序以支持多个模型 -如果您想独立于 FastAPI 直接浏览 SQLite 数据库(文件)以调试其内容、添加表、列、记录、修改数据等,您可以使用[SQLite 的 DB Browser](https://sqlitebrowser.org/) +现在让我们稍微**重构**一下这个应用,以提高**安全性**和**多功能性**。 -它看起来像这样: +如果您查看之前的应用程序,您可以在 UI 界面中看到,到目前为止,由客户端决定要创建的 `Hero` 的 `id` 值。😱 - +我们不应该允许这样做,因为他们可能会覆盖我们在数据库中已经分配的 `id` 。决定 `id` 的行为应该由**后端**或**数据库**来完成,**而非客户端**。 -您还可以使用[SQLite Viewer](https://inloop.github.io/sqlite-viewer/)或[ExtendsClass](https://extendsclass.com/sqlite-browser.html)等在线 SQLite 浏览器。 +此外,我们为 hero 创建了一个 `secret_name` ,但到目前为止,我们在各处都返回了它,这就不太**秘密**了……😅 -## 中间件替代数据库会话 +我们将通过添加一些**额外的模型**来解决这些问题,而 SQLModel 将在这里大放异彩。✨ -如果你不能使用带有`yield`的依赖项——例如,如果你没有使用**Python 3.7**并且不能安装上面提到的**Python 3.6**的“backports” ——你可以使用类似的方法在“中间件”中设置会话。 +### 创建多个模型 -“中间件”基本上是一个对每个请求都执行的函数,其中一些代码在端点函数之前执行,另一些代码在端点函数之后执行。 +在 **SQLModel** 中,任何含有 `table=True` 属性的模型类都是一个**表模型**。 -### 创建中间件 +任何不含有 `table=True` 属性的模型类都是**数据模型**,这些实际上只是 Pydantic 模型(附带一些小的额外功能)。🤓 -我们要添加的中间件(只是一个函数)将为每个请求创建一个新的 SQLAlchemy`SessionLocal`,将其添加到请求中,然后在请求完成后关闭它。 +有了 SQLModel,我们就可以利用**继承**来在所有情况下**避免重复**所有字段。 -//// tab | Python 3.9+ +#### `HeroBase` - 基类 -```Python hl_lines="12-20" -{!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!} -``` +我们从一个 `HeroBase` 模型开始,该模型具有所有模型**共享的字段**: -//// +* `name` +* `age` -//// tab | Python 3.8+ +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} -```Python hl_lines="14-22" -{!> ../../../docs_src/sql_databases/sql_app/alt_main.py!} -``` +#### `Hero` - *表模型* -//// +接下来,我们创建 `Hero` ,实际的*表模型*,并添加那些不总是在其他模型中的**额外字段**: -/// info +* `id` +* `secret_name` -我们将`SessionLocal()`请求的创建和处理放在一个`try`块中。 +因为 `Hero` 继承自 HeroBase ,所以它**也**包含了在 `HeroBase` 中声明过的**字段**。因此 `Hero` 的所有字段为: -然后我们在finally块中关闭它。 +* `id` +* `name` +* `age` +* `secret_name` -通过这种方式,我们确保数据库会话在请求后始终关闭,即使在处理请求时出现异常也会关闭。 +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} -/// +#### `HeroPublic` - 公共*数据模型* -### 关于`request.state` +接下来,我们创建一个 `HeroPublic` 模型,这是将**返回**给 API 客户端的模型。 -`request.state`是每个`Request`对象的属性。它用于存储附加到请求本身的任意对象,例如本例中的数据库会话。您可以在[Starlette 的关于`Request`state](https://www.starlette.io/requests/#other-state)的文档中了解更多信息。 +它包含与 `HeroBase` 相同的字段,因此不会包括 `secret_name` 。 -对于这种情况下,它帮助我们确保在整个请求中使用单个数据库会话,然后关闭(在中间件中)。 +终于,我们英雄(hero)的身份得到了保护! 🥷 -### 使用`yield`依赖项与使用中间件的区别 - -在此处添加**中间件**与`yield`的依赖项的作用效果类似,但也有一些区别: - -* 中间件需要更多的代码并且更复杂一些。 -* 中间件必须是一个`async`函数。 - * 如果其中有代码必须“等待”网络,它可能会在那里“阻止”您的应用程序并稍微降低性能。 - * 尽管这里的`SQLAlchemy`工作方式可能不是很成问题。 - * 但是,如果您向等待大量I/O的中间件添加更多代码,则可能会出现问题。 -* *每个*请求都会运行一个中间件。 - * 将为每个请求创建一个连接。 - * 即使处理该请求的*路径操作*不需要数据库。 +它还重新声明了 `id: int` 。这样我们便与 API 客户端建立了一种**约定**,使他们始终可以期待 `id` 存在并且是一个整数 `int`(永远不会是 `None` )。 /// tip -最好使用带有yield的依赖项,如果这足够满足用例需求 +确保返回模型始终提供一个值并且始终是 `int` (而不是 `None` )对 API 客户端非常有用,他们可以在这种确定性下编写更简单的代码。 + +此外,**自动生成的客户端**将拥有更简洁的接口,这样与您的 API 交互的开发者就能更轻松地使用您的 API。😎 /// -/// info +`HeroPublic` 中的所有字段都与 `HeroBase` 中的相同,其中 `id` 声明为 `int` (不是 `None` ): -带有`yield`的依赖项是最近刚加入**FastAPI**中的。 +* `id` +* `name` +* `age` +* `secret_name` -所以本教程的先前版本只有带有中间件的示例,并且可能有多个应用程序使用中间件进行数据库会话管理。 +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - 用于创建 hero 的*数据模型* + +现在我们创建一个 `HeroCreate` 模型,这是用于**验证**客户数据的模型。 + +它不仅拥有与 `HeroBase` 相同的字段,还有 `secret_name` 。 + +现在,当客户端**创建一个新的 hero** 时,他们会发送 `secret_name` ,它会被存储到数据库中,但这些 `secret_name` 不会通过 API 返回给客户端。 + +/// tip + +这应当是**密码**被处理的方式:接收密码,但不要通过 API 返回它们。 + +在存储密码之前,您还应该对密码的值进行**哈希**处理,**绝不要以明文形式存储它们**。 /// + +`HeroCreate` 的字段包括: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - 用于更新 hero 的*数据模型* + +在之前的应用程序中,我们没有办法**更新 hero**,但现在有了**多个模型**,我们便能做到这一点了。🎉 + +`HeroUpdate` *数据模型*有些特殊,它包含创建新 hero 所需的**所有相同字段**,但所有字段都是**可选的**(它们都有默认值)。这样,当您更新一个 hero 时,您可以只发送您想要更新的字段。 + +因为所有**字段实际上**都发生了**变化**(类型现在包括 `None` ,并且它们现在有一个默认值 `None` ),我们需要**重新声明**它们。 + +我们会重新声明所有字段,因此我们并不是真的需要从 `HeroBase` 继承。我会让它继承只是为了保持一致,但这并不必要。这更多是个人喜好的问题。🤷 + +`HeroUpdate` 的字段包括: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### 使用 `HeroCreate` 创建并返回 `HeroPublic` + +既然我们有了**多个模型**,我们就可以对使用它们的应用程序部分进行更新。 + +我们在请求中接收到一个 `HeroCreate` *数据模型*,然后从中创建一个 `Hero` *表模型*。 + +这个新的*表模型* `Hero` 会包含客户端发送的字段,以及一个由数据库生成的 `id` 。 + +然后我们将与函数中相同的*表模型* `Hero` 原样返回。但是由于我们使用 `HeroPublic` *数据模型*声明了 `response_model` ,**FastAPI** 会使用 `HeroPublic` 来验证和序列化数据。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip + +现在我们使用 `response_model=HeroPublic` 来代替**返回类型注释** `-> HeroPublic` ,因为我们返回的值实际上**并不是** `HeroPublic` 类型。 + +如果我们声明了 `-> HeroPublic` ,您的编辑器和代码检查工具会抱怨(但也确实理所应当)您返回了一个 `Hero` 而不是一个 `HeroPublic` 。 + +通过 `response_model` 的声明,我们让 **FastAPI** 按照它自己的方式处理,而不会干扰类型注解以及编辑器和其他工具提供的帮助。 + +/// + +### 用 `HeroPublic` 读取 Hero + +我们可以像之前一样**读取** `Hero` 。同样,使用 `response_model=list[HeroPublic]` 确保正确地验证和序列化数据。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### 用 `HeroPublic` 读取单个 Hero + +我们可以**读取**单个 `hero` 。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### 用 `HeroUpdate` 更新单个 Hero + +我们可以**更新**单个 `hero` 。为此,我们会使用 HTTP 的 `PATCH` 操作。 + +在代码中,我们会得到一个 `dict` ,其中包含客户端发送的所有数据,**只有客户端发送的数据**,并排除了任何一个仅仅作为默认值存在的值。为此,我们使用 `exclude_unset=True` 。这是最主要的技巧。🪄 + +然后我们会使用 `hero_db.sqlmodel_update(hero_data)` ,来利用 `hero_data` 的数据更新 `hero_db` 。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### (又一次)删除单个 Hero + +**删除**一个 hero 基本保持不变。 + +我们不会满足在这一部分中重构一切的愿望。😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### (又一次)运行应用程序 + +您可以再运行一次应用程序: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +您会在 `/docs` API UI 看到它现在已经更新,并且在进行创建 hero 等操作时,它不会再期望从客户端接收 `id` 数据。 + +
+ +
+ +## 总结 + +您可以使用 **SQLModel** 与 SQL 数据库进行交互,并通过*数据模型*和*表模型*简化代码。 + +您可以在 SQLModel 的文档中学习到更多内容,其中有一个更详细的关于如何将 SQLModel 与 FastAPI 一起使用的教程。🚀 diff --git a/docs/zh/docs/tutorial/static-files.md b/docs/zh/docs/tutorial/static-files.md index 3d14f34d87..1a0d4504cc 100644 --- a/docs/zh/docs/tutorial/static-files.md +++ b/docs/zh/docs/tutorial/static-files.md @@ -7,11 +7,9 @@ * 导入`StaticFiles`。 * "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。 -```Python hl_lines="2 6" -{!../../../docs_src/static_files/tutorial001.py!} -``` +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} -/// note | "技术细节" +/// note | 技术细节 你也可以用 `from starlette.staticfiles import StaticFiles`。 @@ -39,4 +37,4 @@ ## 更多信息 -更多细节和选择查阅 Starlette's docs about Static Files. +更多细节和选择查阅 Starlette's docs about Static Files. diff --git a/docs/zh/docs/tutorial/testing.md b/docs/zh/docs/tutorial/testing.md index 18c35e8c67..3877adbac6 100644 --- a/docs/zh/docs/tutorial/testing.md +++ b/docs/zh/docs/tutorial/testing.md @@ -1,6 +1,6 @@ # 测试 -感谢 Starlette,测试**FastAPI** 应用轻松又愉快。 +感谢 Starlette,测试**FastAPI** 应用轻松又愉快。 它基于 HTTPX, 而HTTPX又是基于Requests设计的,所以很相似且易懂。 @@ -8,7 +8,7 @@ ## 使用 `TestClient` -/// info | "信息" +/// info | 信息 要使用 `TestClient`,先要安装 `httpx`. @@ -26,11 +26,9 @@ 为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。 -```Python hl_lines="2 12 15-18" -{!../../../docs_src/app_testing/tutorial001.py!} -``` +{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} -/// tip | "提示" +/// tip | 提示 注意测试函数是普通的 `def`,不是 `async def`。 @@ -40,7 +38,7 @@ /// -/// note | "技术细节" +/// note | 技术细节 你也可以用 `from starlette.testclient import TestClient`。 @@ -48,7 +46,7 @@ /// -/// tip | "提示" +/// tip | 提示 除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。 @@ -74,9 +72,7 @@ 在 `main.py` 文件中你有一个 **FastAPI** app: -```Python -{!../../../docs_src/app_testing/main.py!} -``` +{* ../../docs_src/app_testing/main.py *} ### 测试文件 @@ -92,9 +88,7 @@ 因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象: -```Python hl_lines="3" -{!../../../docs_src/app_testing/test_main.py!} -``` +{* ../../docs_src/app_testing/test_main.py hl[3] *} ...然后测试代码和之前一样的。 @@ -125,7 +119,7 @@ //// tab | Python 3.10+ ```Python -{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} +{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} ``` //// @@ -133,7 +127,7 @@ //// tab | Python 3.9+ ```Python -{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} +{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} ``` //// @@ -141,35 +135,35 @@ //// tab | Python 3.8+ ```Python -{!> ../../../docs_src/app_testing/app_b_an/main.py!} +{!> ../../docs_src/app_testing/app_b_an/main.py!} ``` //// //// tab | Python 3.10+ non-Annotated -/// tip | "提示" +/// tip | 提示 Prefer to use the `Annotated` version if possible. /// ```Python -{!> ../../../docs_src/app_testing/app_b_py310/main.py!} +{!> ../../docs_src/app_testing/app_b_py310/main.py!} ``` //// //// tab | Python 3.8+ non-Annotated -/// tip | "提示" +/// tip | 提示 Prefer to use the `Annotated` version if possible. /// ```Python -{!> ../../../docs_src/app_testing/app_b/main.py!} +{!> ../../docs_src/app_testing/app_b/main.py!} ``` //// @@ -178,9 +172,7 @@ Prefer to use the `Annotated` version if possible. 然后您可以使用扩展后的测试更新`test_main.py`: -```Python -{!> ../../../docs_src/app_testing/app_b/test_main.py!} -``` +{* ../../docs_src/app_testing/app_b/test_main.py *} 每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。 @@ -196,7 +188,7 @@ Prefer to use the `Annotated` version if possible. 关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 HTTPX 文档. -/// info | "信息" +/// info | 信息 注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。 diff --git a/docs/zh/docs/virtual-environments.md b/docs/zh/docs/virtual-environments.md new file mode 100644 index 0000000000..9b3c0340a6 --- /dev/null +++ b/docs/zh/docs/virtual-environments.md @@ -0,0 +1,844 @@ +# 虚拟环境 + +当你在 Python 工程中工作时,你可能会有必要用到一个**虚拟环境**(或类似的机制)来隔离你为每个工程安装的包。 + +/// info + +如果你已经了解虚拟环境,知道如何创建和使用它们,你可以考虑跳过这一部分。🤓 + +/// + +/// tip + +**虚拟环境**和**环境变量**是不同的。 + +**环境变量**是系统中的一个变量,可以被程序使用。 + +**虚拟环境**是一个包含一些文件的目录。 + +/// + +/// info + +这个页面将教你如何使用**虚拟环境**以及了解它们的工作原理。 + +如果你计划使用一个**可以为你管理一切的工具**(包括安装 Python),试试 uv。 + +/// + +## 创建一个工程 + +首先,为你的工程创建一个目录。 + +我 (指原作者 —— 译者注) 通常会在我的主目录下创建一个名为 `code` 的目录。 + +在这个目录下,我再为每个工程创建一个目录。 + +
+ +```console +// 进入主目录 +$ cd +// 创建一个用于存放所有代码工程的目录 +$ mkdir code +// 进入 code 目录 +$ cd code +// 创建一个用于存放这个工程的目录 +$ mkdir awesome-project +// 进入这个工程的目录 +$ cd awesome-project +``` + +
+ +## 创建一个虚拟环境 + +在开始一个 Python 工程的**第一时间**,**在你的工程内部**创建一个虚拟环境。 + +/// tip + +你只需要 **在每个工程中操作一次**,而不是每次工作时都操作。 + +/// + +//// tab | `venv` + +你可以使用 Python 自带的 `venv` 模块来创建一个虚拟环境。 + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | 上述命令的含义 + +* `python`: 使用名为 `python` 的程序 +* `-m`: 以脚本的方式调用一个模块,我们将告诉它接下来使用哪个模块 +* `venv`: 使用名为 `venv` 的模块,这个模块通常随 Python 一起安装 +* `.venv`: 在新目录 `.venv` 中创建虚拟环境 + +/// + +//// + +//// tab | `uv` + +如果你安装了 `uv`,你也可以使用它来创建一个虚拟环境。 + +
+ +```console +$ uv venv +``` + +
+ +/// tip + +默认情况下,`uv` 会在一个名为 `.venv` 的目录中创建一个虚拟环境。 + +但你可以通过传递一个额外的参数来自定义它,指定目录的名称。 + +/// + +//// + +这个命令会在一个名为 `.venv` 的目录中创建一个新的虚拟环境。 + +/// details | `.venv`,或是其他名称 + +你可以在不同的目录下创建虚拟环境,但通常我们会把它命名为 `.venv`。 + +/// + +## 激活虚拟环境 + +激活新的虚拟环境来确保你运行的任何 Python 命令或安装的包都能使用到它。 + +/// tip + +**每次**开始一个 **新的终端会话** 来工作在这个工程时,你都需要执行这个操作。 + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +或者,如果你在 Windows 上使用 Bash(例如 Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip + +每次你在这个环境中安装一个 **新的包** 时,都需要 **重新激活** 这个环境。 + +这么做确保了当你使用一个由这个包安装的 **终端(CLI)程序** 时,你使用的是你的虚拟环境中的程序,而不是全局安装、可能版本不同的程序。 + +/// + +## 检查虚拟环境是否激活 + +检查虚拟环境是否激活 (前面的命令是否生效)。 + +/// tip + +这是 **可选的**,但这是一个很好的方法,可以 **检查** 一切是否按预期工作,以及你是否使用了你打算使用的虚拟环境。 + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +如果它显示了在你工程 (在这个例子中是 `awesome-project`) 的 `.venv/bin/python` 中的 `python` 二进制文件,那么它就生效了。🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +如果它显示了在你工程 (在这个例子中是 `awesome-project`) 的 `.venv\Scripts\python` 中的 `python` 二进制文件,那么它就生效了。🎉 + +//// + +## 升级 `pip` + +/// tip + +如果你使用 `uv` 来安装内容,而不是 `pip`,那么你就不需要升级 `pip`。😎 + +/// + +如果你使用 `pip` 来安装包(它是 Python 的默认组件),你应该将它 **升级** 到最新版本。 + +在安装包时出现的许多奇怪的错误都可以通过先升级 `pip` 来解决。 + +/// tip + +通常你只需要在创建虚拟环境后 **执行一次** 这个操作。 + +/// + +确保虚拟环境是激活的 (使用上面的命令),然后运行: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## 添加 `.gitignore` + +如果你使用 **Git** (这是你应该使用的),添加一个 `.gitignore` 文件来排除你的 `.venv` 中的所有内容。 + +/// tip + +如果你使用 `uv` 来创建虚拟环境,它会自动为你完成这个操作,你可以跳过这一步。😎 + +/// + +/// tip + +通常你只需要在创建虚拟环境后 **执行一次** 这个操作。 + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | 上述命令的含义 + +* `echo "*"`: 将在终端中 "打印" 文本 `*`(接下来的部分会对这个操作进行一些修改) +* `>`: 使左边的命令打印到终端的任何内容实际上都不会被打印,而是会被写入到右边的文件中 +* `.gitignore`: 被写入文本的文件的名称 + +而 `*` 对于 Git 来说意味着 "所有内容"。所以,它会忽略 `.venv` 目录中的所有内容。 + +该命令会创建一个名为 `.gitignore` 的文件,内容如下: + +```gitignore +* +``` + +/// + +## 安装软件包 + +在激活虚拟环境后,你可以在其中安装软件包。 + +/// tip + +当你需要安装或升级软件包时,执行本操作**一次**; + +如果你需要再升级版本或添加新软件包,你可以**再次执行此操作**。 + +/// + +### 直接安装包 + +如果你急于安装,不想使用文件来声明工程的软件包依赖,您可以直接安装它们。 + +/// tip + +将程序所需的软件包及其版本放在文件中(例如 `requirements.txt` 或 `pyproject.toml`)是个好(并且非常好)的主意。 + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +如果你有 `uv`: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### 从 `requirements.txt` 安装 + +如果你有一个 `requirements.txt` 文件,你可以使用它来安装其中的软件包。 + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +如果你有 `uv`: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | 关于 `requirements.txt` + +一个包含一些软件包的 `requirements.txt` 文件看起来应该是这样的: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## 运行程序 + +在你激活虚拟环境后,你可以运行你的程序,它将使用虚拟环境中的 Python 和你在其中安装的软件包。 + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## 配置编辑器 + +你可能会用到编辑器(即 IDE —— 译者注),请确保配置它使用与你创建的相同的虚拟环境(它可能会自动检测到),以便你可以获得自动补全和内联错误提示。 + +例如: + +* VS Code +* PyCharm + +/// tip + +通常你只需要在创建虚拟环境时执行此操作**一次**。 + +/// + +## 退出虚拟环境 + +当你完成工作后,你可以**退出**虚拟环境。 + +
+ +```console +$ deactivate +``` + +
+ +这样,当你运行 `python` 时,它不会尝试从安装了软件包的虚拟环境中运行。(即,它将不再会尝试从虚拟环境中运行,也不会使用其中安装的软件包。—— 译者注) + +## 开始工作 + +现在你已经准备好开始你的工作了。 + + + +/// tip + +你想要理解上面的所有内容吗? + +继续阅读。👇🤓 + +/// + +## 为什么要使用虚拟环境 + +你需要安装 Python 才能使用 FastAPI。 + +之后,你需要**安装** FastAPI 和你想要使用的任何其他**软件包**。 + +要安装软件包,你通常会使用随 Python 一起提供的 `pip` 命令(或类似的替代方案)。 + +然而,如果你直接使用 `pip`,软件包将被安装在你的**全局 Python 环境**中(即 Python 的全局安装)。 + +### 存在的问题 + +那么,在全局 Python 环境中安装软件包有什么问题呢? + +有些时候,你可能会编写许多不同的程序,这些程序依赖于**不同的软件包**;你所做的一些工程也会依赖于**同一软件包的不同版本**。😱 + +例如,你可能会创建一个名为 `philosophers-stone` 的工程,这个程序依赖于另一个名为 **`harry` 的软件包,使用版本 `1`**。因此,你需要安装 `harry`。 + +```mermaid +flowchart LR + stone(philosophers-stone) -->|需要| harry-1[harry v1] +``` + +然而在此之后,你又创建了另一个名为 `prisoner-of-azkaban` 的工程,这个工程也依赖于 `harry`,但是这个工程需要 **`harry` 版本 `3`**。 + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |需要| harry-3[harry v3] +``` + +那么现在的问题是,如果你将软件包安装在全局环境中而不是在本地**虚拟环境**中,你将不得不面临选择安装哪个版本的 `harry` 的问题。 + +如果你想运行 `philosophers-stone`,你需要首先安装 `harry` 版本 `1`,例如: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +然后你将在全局 Python 环境中安装 `harry` 版本 `1`。 + +```mermaid +flowchart LR + subgraph global[全局环境] + harry-1[harry v1] + end + subgraph stone-project[工程 philosophers-stone] + stone(philosophers-stone) -->|需要| harry-1 + end +``` + +但是如果你想运行 `prisoner-of-azkaban`,你需要卸载 `harry` 版本 `1` 并安装 `harry` 版本 `3`(或者说,只要你安装版本 `3` ,版本 `1` 就会自动卸载)。 + +
+ +```console +$ pip install "harry==3" +``` + +
+ +于是,你在你的全局 Python 环境中安装了 `harry` 版本 `3`。 + +如果你再次尝试运行 `philosophers-stone`,有可能它**无法正常工作**,因为它需要 `harry` 版本 `1`。 + +```mermaid +flowchart LR + subgraph global[全局环境] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[工程 philosophers-stone] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[工程 prisoner-of-azkaban] + azkaban(prisoner-of-azkaban) --> |需要| harry-3 + end +``` + +/// tip + +Python 包在推出**新版本**时通常会尽量**避免破坏性更改**,但最好还是要小心,要想清楚再安装新版本,而且在运行测试以确保一切能正常工作时再安装。 + +/// + +现在,想象一下,如果有**许多**其他**软件包**,它们都是你的**工程所依赖的**。这是非常难以管理的。你可能会发现,有些工程使用了一些**不兼容的软件包版本**,而不知道为什么某些东西无法正常工作。 + +此外,取决于你的操作系统(例如 Linux、Windows、macOS),它可能已经预先安装了 Python。在这种情况下,它可能已经预先安装了一些软件包,这些软件包的特定版本是**系统所需的**。如果你在全局 Python 环境中安装软件包,你可能会**破坏**一些随操作系统一起安装的程序。 + +## 软件包安装在哪里 + +当你安装 Python 时,它会在你的计算机上创建一些目录,并在这些目录中放一些文件。 + +其中一些目录负责存放你安装的所有软件包。 + +当你运行: + +
+ +```console +// 先别去运行这个命令,这只是一个示例 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +这将会从 PyPI 下载一个压缩文件,其中包含 FastAPI 代码。 + +它还会**下载** FastAPI 依赖的其他软件包的文件。 + +然后它会**解压**所有这些文件,并将它们放在你的计算机上的一个目录中。 + +默认情况下,它会将下载并解压的这些文件放在随 Python 安装的目录中,这就是**全局环境**。 + +## 什么是虚拟环境 + +解决软件包都安装在全局环境中的问题的方法是为你所做的每个工程使用一个**虚拟环境**。 + +虚拟环境是一个**目录**,与全局环境非常相似,你可以在其中专为某个工程安装软件包。 + +这样,每个工程都会有自己的虚拟环境(`.venv` 目录),其中包含自己的软件包。 + +```mermaid +flowchart TB + subgraph stone-project[工程 philosophers-stone] + stone(philosophers-stone) --->|需要| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[工程 prisoner-of-azkaban] + azkaban(prisoner-of-azkaban) --->|需要| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## 激活虚拟环境意味着什么 + +当你激活了一个虚拟环境,例如: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +或者如果你在 Windows 上使用 Bash(例如 Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +这个命令会创建或修改一些[环境变量](environment-variables.md){.internal-link target=_blank},这些环境变量将在接下来的命令中可用。 + +其中之一是 `PATH` 变量。 + +/// tip + +你可以在 [环境变量](environment-variables.md#path-environment-variable){.internal-link target=_blank} 部分了解更多关于 `PATH` 环境变量的内容。 + +/// + +激活虚拟环境会将其路径 `.venv/bin`(在 Linux 和 macOS 上)或 `.venv\Scripts`(在 Windows 上)添加到 `PATH` 环境变量中。 + +假设在激活环境之前,`PATH` 变量看起来像这样: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +这意味着系统会在以下目录中查找程序: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +这意味着系统会在以下目录中查找程序: + +* `C:\Windows\System32` + +//// + +激活虚拟环境后,`PATH` 变量会变成这样: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +这意味着系统现在会首先在以下目录中查找程序: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +然后再在其他目录中查找。 + +因此,当你在终端中输入 `python` 时,系统会在以下目录中找到 Python 程序: + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +并使用这个。 + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +这意味着系统现在会首先在以下目录中查找程序: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +然后再在其他目录中查找。 + +因此,当你在终端中输入 `python` 时,系统会在以下目录中找到 Python 程序: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +并使用这个。 + +//// + +一个重要的细节是,虚拟环境路径会被放在 `PATH` 变量的**开头**。系统会在找到任何其他可用的 Python **之前**找到它。这样,当你运行 `python` 时,它会使用**虚拟环境中**的 Python,而不是任何其他 `python`(例如,全局环境中的 `python`)。 + +激活虚拟环境还会改变其他一些东西,但这是它所做的最重要的事情之一。 + +## 检查虚拟环境 + +当你检查虚拟环境是否激活时,例如: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +这意味着将使用的 `python` 程序是**在虚拟环境中**的那个。 + +在 Linux 和 macOS 中使用 `which`,在 Windows PowerShell 中使用 `Get-Command`。 + +这个命令的工作方式是,它会在 `PATH` 环境变量中查找,按顺序**逐个路径**查找名为 `python` 的程序。一旦找到,它会**显示该程序的路径**。 + +最重要的部分是,当你调用 `python` 时,将执行的就是这个确切的 "`python`"。 + +因此,你可以确认你是否在正确的虚拟环境中。 + +/// tip + +激活一个虚拟环境,获取一个 Python,然后**转到另一个工程**是一件很容易的事情; + +但如果第二个工程**无法工作**,那是因为你使用了来自另一个工程的虚拟环境的、**不正确的 Python**。 + +因此,会检查正在使用的 `python` 是很有用的。🤓 + +/// + +## 为什么要停用虚拟环境 + +例如,你可能正在一个工程 `philosophers-stone` 上工作,**激活了该虚拟环境**,安装了包并使用了该环境, + +然后你想要在**另一个工程** `prisoner-of-azkaban` 上工作, + +你进入那个工程: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +如果你不去停用 `philosophers-stone` 的虚拟环境,当你在终端中运行 `python` 时,它会尝试使用 `philosophers-stone` 中的 Python。 + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// 导入 sirius 报错,它没有安装 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +但是如果你停用虚拟环境并激活 `prisoner-of-askaban` 的新虚拟环境,那么当你运行 `python` 时,它会使用 `prisoner-of-askaban` 中的虚拟环境中的 Python。 + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// 你不需要在旧目录中操作停用,你可以在任何地方操作停用,甚至在转到另一个工程之后 😎 +$ deactivate + +// 激活 prisoner-of-azkaban/.venv 中的虚拟环境 🚀 +$ source .venv/bin/activate + +// 现在当你运行 python 时,它会在这个虚拟环境中找到安装的 sirius 包 ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## 替代方案 + +这是一个简单的指南,可以帮助你入门并教会你如何理解一切**底层**的东西。 + +有许多**替代方案**来管理虚拟环境、包依赖(requirements)、工程。 + +一旦你准备好并想要使用一个工具来**管理整个工程**、包依赖、虚拟环境等,建议你尝试 uv。 + +`uv` 可以做很多事情,它可以: + +* 为你**安装 Python**,包括不同的版本 +* 为你的工程管理**虚拟环境** +* 安装**软件包** +* 为你的工程管理软件包的**依赖和版本** +* 确保你有一个**确切**的软件包和版本集合来安装,包括它们的依赖项,这样你就可以确保在生产中运行你的工程与在开发时在你的计算机上运行的工程完全相同,这被称为**锁定** +* 还有很多其他功能 + +## 结论 + +如果你读过并理解了所有这些,现在**你对虚拟环境的了解比很多开发者都要多**。🤓 + +在未来当你调试看起来复杂的东西时,了解这些细节很可能会有用,你会知道**它是如何在底层工作的**。😎 diff --git a/docs_src/app_testing/tutorial004.py b/docs_src/app_testing/tutorial004.py new file mode 100644 index 0000000000..f83ac9ae9a --- /dev/null +++ b/docs_src/app_testing/tutorial004.py @@ -0,0 +1,43 @@ +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +items = {} + + +@asynccontextmanager +async def lifespan(app: FastAPI): + items["foo"] = {"name": "Fighters"} + items["bar"] = {"name": "Tenders"} + yield + # clean up items + items.clear() + + +app = FastAPI(lifespan=lifespan) + + +@app.get("/items/{item_id}") +async def read_items(item_id: str): + return items[item_id] + + +def test_read_items(): + # Before the lifespan starts, "items" is still empty + assert items == {} + + with TestClient(app) as client: + # Inside the "with TestClient" block, the lifespan starts and items added + assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}} + + response = client.get("/items/foo") + assert response.status_code == 200 + assert response.json() == {"name": "Fighters"} + + # After the requests is done, the items are still there + assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}} + + # The end of the "with TestClient" block simulates terminating the app, so + # the lifespan ends and items are cleaned up + assert items == {} diff --git a/docs_src/async_sql_databases/tutorial001.py b/docs_src/async_sql_databases/tutorial001.py deleted file mode 100644 index cbf43d790f..0000000000 --- a/docs_src/async_sql_databases/tutorial001.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import List - -import databases -import sqlalchemy -from fastapi import FastAPI -from pydantic import BaseModel - -# SQLAlchemy specific code, as with any other app -DATABASE_URL = "sqlite:///./test.db" -# DATABASE_URL = "postgresql://user:password@postgresserver/db" - -database = databases.Database(DATABASE_URL) - -metadata = sqlalchemy.MetaData() - -notes = sqlalchemy.Table( - "notes", - metadata, - sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True), - sqlalchemy.Column("text", sqlalchemy.String), - sqlalchemy.Column("completed", sqlalchemy.Boolean), -) - - -engine = sqlalchemy.create_engine( - DATABASE_URL, connect_args={"check_same_thread": False} -) -metadata.create_all(engine) - - -class NoteIn(BaseModel): - text: str - completed: bool - - -class Note(BaseModel): - id: int - text: str - completed: bool - - -app = FastAPI() - - -@app.on_event("startup") -async def startup(): - await database.connect() - - -@app.on_event("shutdown") -async def shutdown(): - await database.disconnect() - - -@app.get("/notes/", response_model=List[Note]) -async def read_notes(): - query = notes.select() - return await database.fetch_all(query) - - -@app.post("/notes/", response_model=Note) -async def create_note(note: NoteIn): - query = notes.insert().values(text=note.text, completed=note.completed) - last_record_id = await database.execute(query) - return {**note.dict(), "id": last_record_id} diff --git a/docs_src/async_tests/test_main.py b/docs_src/async_tests/test_main.py index 9f1527d5f6..a57a31f7d8 100644 --- a/docs_src/async_tests/test_main.py +++ b/docs_src/async_tests/test_main.py @@ -1,12 +1,14 @@ import pytest -from httpx import AsyncClient +from httpx import ASGITransport, AsyncClient from .main import app @pytest.mark.anyio async def test_root(): - async with AsyncClient(app=app, base_url="http://test") as ac: + async with AsyncClient( + transport=ASGITransport(app=app), base_url="http://test" + ) as ac: response = await ac.get("/") assert response.status_code == 200 assert response.json() == {"message": "Tomato"} diff --git a/docs_src/behind_a_proxy/tutorial001_01.py b/docs_src/behind_a_proxy/tutorial001_01.py new file mode 100644 index 0000000000..52b114395b --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial001_01.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/") +def read_items(): + return ["plumbus", "portal gun"] diff --git a/docs_src/body/tutorial002.py b/docs_src/body/tutorial002.py index 7f51839082..5cd86216b3 100644 --- a/docs_src/body/tutorial002.py +++ b/docs_src/body/tutorial002.py @@ -17,7 +17,7 @@ app = FastAPI() @app.post("/items/") async def create_item(item: Item): item_dict = item.dict() - if item.tax: + if item.tax is not None: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) return item_dict diff --git a/docs_src/body/tutorial002_py310.py b/docs_src/body/tutorial002_py310.py index 8928b72b8d..454c45c886 100644 --- a/docs_src/body/tutorial002_py310.py +++ b/docs_src/body/tutorial002_py310.py @@ -15,7 +15,7 @@ app = FastAPI() @app.post("/items/") async def create_item(item: Item): item_dict = item.dict() - if item.tax: + if item.tax is not None: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) return item_dict diff --git a/docs_src/configure_swagger_ui/tutorial002.py b/docs_src/configure_swagger_ui/tutorial002.py index cc569ce450..cc75c21968 100644 --- a/docs_src/configure_swagger_ui/tutorial002.py +++ b/docs_src/configure_swagger_ui/tutorial002.py @@ -1,6 +1,6 @@ from fastapi import FastAPI -app = FastAPI(swagger_ui_parameters={"syntaxHighlight.theme": "obsidian"}) +app = FastAPI(swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}}) @app.get("/users/{username}") diff --git a/docs_src/cookie_param_models/tutorial001.py b/docs_src/cookie_param_models/tutorial001.py new file mode 100644 index 0000000000..cc65c43e1a --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001.py @@ -0,0 +1,17 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_an.py b/docs_src/cookie_param_models/tutorial001_an.py new file mode 100644 index 0000000000..e5839ffd54 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_an.py @@ -0,0 +1,18 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_an_py310.py b/docs_src/cookie_param_models/tutorial001_an_py310.py new file mode 100644 index 0000000000..24cc889a92 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_an_py310.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_an_py39.py b/docs_src/cookie_param_models/tutorial001_an_py39.py new file mode 100644 index 0000000000..3d90c2007b --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_an_py39.py @@ -0,0 +1,17 @@ +from typing import Annotated, Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial001_py310.py b/docs_src/cookie_param_models/tutorial001_py310.py new file mode 100644 index 0000000000..7cdee5a923 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial001_py310.py @@ -0,0 +1,15 @@ +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002.py b/docs_src/cookie_param_models/tutorial002.py new file mode 100644 index 0000000000..9679e890f6 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002.py @@ -0,0 +1,19 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_an.py b/docs_src/cookie_param_models/tutorial002_an.py new file mode 100644 index 0000000000..ce5644b7bd --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_an.py @@ -0,0 +1,20 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_an_py310.py b/docs_src/cookie_param_models/tutorial002_an_py310.py new file mode 100644 index 0000000000..7fa70fe927 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_an_py310.py @@ -0,0 +1,19 @@ +from typing import Annotated + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_an_py39.py b/docs_src/cookie_param_models/tutorial002_an_py39.py new file mode 100644 index 0000000000..a906ce6a1c --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_an_py39.py @@ -0,0 +1,19 @@ +from typing import Annotated, Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1.py b/docs_src/cookie_param_models/tutorial002_pv1.py new file mode 100644 index 0000000000..13f78b850e --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1.py @@ -0,0 +1,20 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an.py b/docs_src/cookie_param_models/tutorial002_pv1_an.py new file mode 100644 index 0000000000..ddfda9b6f5 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_an.py @@ -0,0 +1,21 @@ +from typing import Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py new file mode 100644 index 0000000000..ac00360b60 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py @@ -0,0 +1,20 @@ +from typing import Annotated + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py new file mode 100644 index 0000000000..573caea4b1 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py @@ -0,0 +1,20 @@ +from typing import Annotated, Union + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: Union[str, None] = None + googall_tracker: Union[str, None] = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_py310.py new file mode 100644 index 0000000000..2c59aad123 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_pv1_py310.py @@ -0,0 +1,18 @@ +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + class Config: + extra = "forbid" + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/cookie_param_models/tutorial002_py310.py b/docs_src/cookie_param_models/tutorial002_py310.py new file mode 100644 index 0000000000..f011aa1af4 --- /dev/null +++ b/docs_src/cookie_param_models/tutorial002_py310.py @@ -0,0 +1,17 @@ +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + model_config = {"extra": "forbid"} + + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Cookies = Cookie()): + return cookies diff --git a/docs_src/custom_docs_ui/tutorial001.py b/docs_src/custom_docs_ui/tutorial001.py index f7ceb0c2fc..1cfcce19aa 100644 --- a/docs_src/custom_docs_ui/tutorial001.py +++ b/docs_src/custom_docs_ui/tutorial001.py @@ -29,7 +29,7 @@ async def redoc_html(): return get_redoc_html( openapi_url=app.openapi_url, title=app.title + " - ReDoc", - redoc_js_url="https://unpkg.com/redoc@next/bundles/redoc.standalone.js", + redoc_js_url="https://unpkg.com/redoc@2/bundles/redoc.standalone.js", ) diff --git a/docs_src/dependencies/tutorial013_an_py310.py b/docs_src/dependencies/tutorial013_an_py310.py new file mode 100644 index 0000000000..0c2f62c4f9 --- /dev/null +++ b/docs_src/dependencies/tutorial013_an_py310.py @@ -0,0 +1,38 @@ +import time +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException +from fastapi.responses import StreamingResponse +from sqlmodel import Field, Session, SQLModel, create_engine + +engine = create_engine("postgresql+psycopg://postgres:postgres@localhost/db") + + +class User(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str + + +app = FastAPI() + + +def get_session(): + with Session(engine) as session: + yield session + + +def get_user(user_id: int, session: Annotated[Session, Depends(get_session)]): + user = session.get(User, user_id) + if not user: + raise HTTPException(status_code=403, detail="Not authorized") + + +def generate_stream(query: str): + for ch in query: + yield ch + time.sleep(0.1) + + +@app.get("/generate", dependencies=[Depends(get_user)]) +def generate(query: str): + return StreamingResponse(content=generate_stream(query)) diff --git a/docs_src/dependencies/tutorial014_an_py310.py b/docs_src/dependencies/tutorial014_an_py310.py new file mode 100644 index 0000000000..ed7c1809a5 --- /dev/null +++ b/docs_src/dependencies/tutorial014_an_py310.py @@ -0,0 +1,39 @@ +import time +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException +from fastapi.responses import StreamingResponse +from sqlmodel import Field, Session, SQLModel, create_engine + +engine = create_engine("postgresql+psycopg://postgres:postgres@localhost/db") + + +class User(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str + + +app = FastAPI() + + +def get_session(): + with Session(engine) as session: + yield session + + +def get_user(user_id: int, session: Annotated[Session, Depends(get_session)]): + user = session.get(User, user_id) + if not user: + raise HTTPException(status_code=403, detail="Not authorized") + session.close() + + +def generate_stream(query: str): + for ch in query: + yield ch + time.sleep(0.1) + + +@app.get("/generate", dependencies=[Depends(get_user)]) +def generate(query: str): + return StreamingResponse(content=generate_stream(query)) diff --git a/docs_src/graphql/tutorial001.py b/docs_src/graphql/tutorial001.py index 3b4ca99cf6..e92b2d71c4 100644 --- a/docs_src/graphql/tutorial001.py +++ b/docs_src/graphql/tutorial001.py @@ -1,6 +1,6 @@ import strawberry from fastapi import FastAPI -from strawberry.asgi import GraphQL +from strawberry.fastapi import GraphQLRouter @strawberry.type @@ -19,8 +19,7 @@ class Query: schema = strawberry.Schema(query=Query) -graphql_app = GraphQL(schema) +graphql_app = GraphQLRouter(schema) app = FastAPI() -app.add_route("/graphql", graphql_app) -app.add_websocket_route("/graphql", graphql_app) +app.include_router(graphql_app, prefix="/graphql") diff --git a/docs_src/handling_errors/tutorial005.py b/docs_src/handling_errors/tutorial005.py index 6e0b81d313..0e04fa0864 100644 --- a/docs_src/handling_errors/tutorial005.py +++ b/docs_src/handling_errors/tutorial005.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, Request, status +from fastapi import FastAPI, Request from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse @@ -10,7 +10,7 @@ app = FastAPI() @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + status_code=422, content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}), ) diff --git a/docs_src/header_param_models/tutorial001.py b/docs_src/header_param_models/tutorial001.py new file mode 100644 index 0000000000..4caaba87b9 --- /dev/null +++ b/docs_src/header_param_models/tutorial001.py @@ -0,0 +1,19 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial001_an.py b/docs_src/header_param_models/tutorial001_an.py new file mode 100644 index 0000000000..b55c6b56b1 --- /dev/null +++ b/docs_src/header_param_models/tutorial001_an.py @@ -0,0 +1,20 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial001_an_py310.py b/docs_src/header_param_models/tutorial001_an_py310.py new file mode 100644 index 0000000000..acfb6b9bf2 --- /dev/null +++ b/docs_src/header_param_models/tutorial001_an_py310.py @@ -0,0 +1,19 @@ +from typing import Annotated + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial001_an_py39.py b/docs_src/header_param_models/tutorial001_an_py39.py new file mode 100644 index 0000000000..51a5f94fc8 --- /dev/null +++ b/docs_src/header_param_models/tutorial001_an_py39.py @@ -0,0 +1,19 @@ +from typing import Annotated, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial001_py310.py b/docs_src/header_param_models/tutorial001_py310.py new file mode 100644 index 0000000000..7239c64cea --- /dev/null +++ b/docs_src/header_param_models/tutorial001_py310.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial001_py39.py b/docs_src/header_param_models/tutorial001_py39.py new file mode 100644 index 0000000000..4c1137813a --- /dev/null +++ b/docs_src/header_param_models/tutorial001_py39.py @@ -0,0 +1,19 @@ +from typing import Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002.py b/docs_src/header_param_models/tutorial002.py new file mode 100644 index 0000000000..3f9aac58d2 --- /dev/null +++ b/docs_src/header_param_models/tutorial002.py @@ -0,0 +1,21 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_an.py b/docs_src/header_param_models/tutorial002_an.py new file mode 100644 index 0000000000..771135d770 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_an.py @@ -0,0 +1,22 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_an_py310.py b/docs_src/header_param_models/tutorial002_an_py310.py new file mode 100644 index 0000000000..e9535f045f --- /dev/null +++ b/docs_src/header_param_models/tutorial002_an_py310.py @@ -0,0 +1,21 @@ +from typing import Annotated + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_an_py39.py b/docs_src/header_param_models/tutorial002_an_py39.py new file mode 100644 index 0000000000..ca5208c9d0 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_an_py39.py @@ -0,0 +1,21 @@ +from typing import Annotated, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1.py b/docs_src/header_param_models/tutorial002_pv1.py new file mode 100644 index 0000000000..7e56cd993b --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1.py @@ -0,0 +1,22 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an.py b/docs_src/header_param_models/tutorial002_pv1_an.py new file mode 100644 index 0000000000..236778231a --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_an.py @@ -0,0 +1,23 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py310.py b/docs_src/header_param_models/tutorial002_pv1_an_py310.py new file mode 100644 index 0000000000..e99e24ea55 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_an_py310.py @@ -0,0 +1,22 @@ +from typing import Annotated + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py39.py b/docs_src/header_param_models/tutorial002_pv1_an_py39.py new file mode 100644 index 0000000000..18398b726c --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_an_py39.py @@ -0,0 +1,22 @@ +from typing import Annotated, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_py310.py b/docs_src/header_param_models/tutorial002_pv1_py310.py new file mode 100644 index 0000000000..3dbff9d7bf --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_py310.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_py39.py b/docs_src/header_param_models/tutorial002_pv1_py39.py new file mode 100644 index 0000000000..86e19be0d1 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_pv1_py39.py @@ -0,0 +1,22 @@ +from typing import Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + class Config: + extra = "forbid" + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_py310.py b/docs_src/header_param_models/tutorial002_py310.py new file mode 100644 index 0000000000..3d22963454 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_py310.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial002_py39.py b/docs_src/header_param_models/tutorial002_py39.py new file mode 100644 index 0000000000..f8ce559a74 --- /dev/null +++ b/docs_src/header_param_models/tutorial002_py39.py @@ -0,0 +1,21 @@ +from typing import Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + model_config = {"extra": "forbid"} + + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header()): + return headers diff --git a/docs_src/header_param_models/tutorial003.py b/docs_src/header_param_models/tutorial003.py new file mode 100644 index 0000000000..dc2eb74bd1 --- /dev/null +++ b/docs_src/header_param_models/tutorial003.py @@ -0,0 +1,19 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): + return headers diff --git a/docs_src/header_param_models/tutorial003_an.py b/docs_src/header_param_models/tutorial003_an.py new file mode 100644 index 0000000000..e3edb11891 --- /dev/null +++ b/docs_src/header_param_models/tutorial003_an.py @@ -0,0 +1,22 @@ +from typing import List, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: List[str] = [] + + +@app.get("/items/") +async def read_items( + headers: Annotated[CommonHeaders, Header(convert_underscores=False)], +): + return headers diff --git a/docs_src/header_param_models/tutorial003_an_py310.py b/docs_src/header_param_models/tutorial003_an_py310.py new file mode 100644 index 0000000000..07bfa83bfe --- /dev/null +++ b/docs_src/header_param_models/tutorial003_an_py310.py @@ -0,0 +1,21 @@ +from typing import Annotated + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items( + headers: Annotated[CommonHeaders, Header(convert_underscores=False)], +): + return headers diff --git a/docs_src/header_param_models/tutorial003_an_py39.py b/docs_src/header_param_models/tutorial003_an_py39.py new file mode 100644 index 0000000000..8be6b01d0e --- /dev/null +++ b/docs_src/header_param_models/tutorial003_an_py39.py @@ -0,0 +1,21 @@ +from typing import Annotated, Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items( + headers: Annotated[CommonHeaders, Header(convert_underscores=False)], +): + return headers diff --git a/docs_src/header_param_models/tutorial003_py310.py b/docs_src/header_param_models/tutorial003_py310.py new file mode 100644 index 0000000000..65e92a28cf --- /dev/null +++ b/docs_src/header_param_models/tutorial003_py310.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: str | None = None + traceparent: str | None = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): + return headers diff --git a/docs_src/header_param_models/tutorial003_py39.py b/docs_src/header_param_models/tutorial003_py39.py new file mode 100644 index 0000000000..848c341119 --- /dev/null +++ b/docs_src/header_param_models/tutorial003_py39.py @@ -0,0 +1,19 @@ +from typing import Union + +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + + +class CommonHeaders(BaseModel): + host: str + save_data: bool + if_modified_since: Union[str, None] = None + traceparent: Union[str, None] = None + x_tag: list[str] = [] + + +@app.get("/items/") +async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): + return headers diff --git a/docs_src/middleware/tutorial001.py b/docs_src/middleware/tutorial001.py index 6bab3410a6..e65a7dade1 100644 --- a/docs_src/middleware/tutorial001.py +++ b/docs_src/middleware/tutorial001.py @@ -7,8 +7,8 @@ app = FastAPI() @app.middleware("http") async def add_process_time_header(request: Request, call_next): - start_time = time.time() + start_time = time.perf_counter() response = await call_next(request) - process_time = time.time() - start_time + process_time = time.perf_counter() - start_time response.headers["X-Process-Time"] = str(process_time) return response diff --git a/docs_src/nosql_databases/tutorial001.py b/docs_src/nosql_databases/tutorial001.py deleted file mode 100644 index 91893e5281..0000000000 --- a/docs_src/nosql_databases/tutorial001.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Union - -from couchbase import LOCKMODE_WAIT -from couchbase.bucket import Bucket -from couchbase.cluster import Cluster, PasswordAuthenticator -from fastapi import FastAPI -from pydantic import BaseModel - -USERPROFILE_DOC_TYPE = "userprofile" - - -def get_bucket(): - cluster = Cluster( - "couchbase://couchbasehost:8091?fetch_mutation_tokens=1&operation_timeout=30&n1ql_timeout=300" - ) - authenticator = PasswordAuthenticator("username", "password") - cluster.authenticate(authenticator) - bucket: Bucket = cluster.open_bucket("bucket_name", lockmode=LOCKMODE_WAIT) - bucket.timeout = 30 - bucket.n1ql_timeout = 300 - return bucket - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - type: str = USERPROFILE_DOC_TYPE - hashed_password: str - - -def get_user(bucket: Bucket, username: str): - doc_id = f"userprofile::{username}" - result = bucket.get(doc_id, quiet=True) - if not result.value: - return None - user = UserInDB(**result.value) - return user - - -# FastAPI specific code -app = FastAPI() - - -@app.get("/users/{username}", response_model=User) -def read_user(username: str): - bucket = get_bucket() - user = get_user(bucket=bucket, username=username) - return user diff --git a/docs_src/path_params_numeric_validations/tutorial006.py b/docs_src/path_params_numeric_validations/tutorial006.py index 0ea32694ae..f07629aa0a 100644 --- a/docs_src/path_params_numeric_validations/tutorial006.py +++ b/docs_src/path_params_numeric_validations/tutorial006.py @@ -13,4 +13,6 @@ async def read_items( results = {"item_id": item_id} if q: results.update({"q": q}) + if size: + results.update({"size": size}) return results diff --git a/docs_src/path_params_numeric_validations/tutorial006_an.py b/docs_src/path_params_numeric_validations/tutorial006_an.py index 22a1436236..ac47325732 100644 --- a/docs_src/path_params_numeric_validations/tutorial006_an.py +++ b/docs_src/path_params_numeric_validations/tutorial006_an.py @@ -14,4 +14,6 @@ async def read_items( results = {"item_id": item_id} if q: results.update({"q": q}) + if size: + results.update({"size": size}) 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 index 804751893c..426ec37764 100644 --- a/docs_src/path_params_numeric_validations/tutorial006_an_py39.py +++ b/docs_src/path_params_numeric_validations/tutorial006_an_py39.py @@ -15,4 +15,6 @@ async def read_items( results = {"item_id": item_id} if q: results.update({"q": q}) + if size: + results.update({"size": size}) return results diff --git a/docs_src/pydantic_v1_in_v2/tutorial001_an.py b/docs_src/pydantic_v1_in_v2/tutorial001_an.py new file mode 100644 index 0000000000..62a4b2c210 --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial001_an.py @@ -0,0 +1,9 @@ +from typing import Union + +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + size: float diff --git a/docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py new file mode 100644 index 0000000000..a8ec729b32 --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py @@ -0,0 +1,7 @@ +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + size: float diff --git a/docs_src/pydantic_v1_in_v2/tutorial002_an.py b/docs_src/pydantic_v1_in_v2/tutorial002_an.py new file mode 100644 index 0000000000..3c6a060807 --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial002_an.py @@ -0,0 +1,18 @@ +from typing import Union + +from fastapi import FastAPI +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + size: float + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Item) -> Item: + return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py new file mode 100644 index 0000000000..4934e70041 --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + size: float + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Item) -> Item: + return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial003_an.py b/docs_src/pydantic_v1_in_v2/tutorial003_an.py new file mode 100644 index 0000000000..117d6f7a4c --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial003_an.py @@ -0,0 +1,25 @@ +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel as BaseModelV2 +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + size: float + + +class ItemV2(BaseModelV2): + name: str + description: Union[str, None] = None + size: float + + +app = FastAPI() + + +@app.post("/items/", response_model=ItemV2) +async def create_item(item: Item): + return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py new file mode 100644 index 0000000000..6e3013644c --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py @@ -0,0 +1,23 @@ +from fastapi import FastAPI +from pydantic import BaseModel as BaseModelV2 +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + size: float + + +class ItemV2(BaseModelV2): + name: str + description: str | None = None + size: float + + +app = FastAPI() + + +@app.post("/items/", response_model=ItemV2) +async def create_item(item: Item): + return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial004_an.py b/docs_src/pydantic_v1_in_v2/tutorial004_an.py new file mode 100644 index 0000000000..cca8a9ea80 --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial004_an.py @@ -0,0 +1,20 @@ +from typing import Union + +from fastapi import FastAPI +from fastapi.temp_pydantic_v1_params import Body +from pydantic.v1 import BaseModel +from typing_extensions import Annotated + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + size: float + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item: + return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py b/docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py new file mode 100644 index 0000000000..c251311e0b --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py @@ -0,0 +1,19 @@ +from typing import Annotated + +from fastapi import FastAPI +from fastapi.temp_pydantic_v1_params import Body +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + size: float + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item: + return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py new file mode 100644 index 0000000000..150ab20ae5 --- /dev/null +++ b/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py @@ -0,0 +1,19 @@ +from typing import Annotated, Union + +from fastapi import FastAPI +from fastapi.temp_pydantic_v1_params import Body +from pydantic.v1 import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + size: float + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item: + return item diff --git a/docs_src/query_param_models/tutorial001.py b/docs_src/query_param_models/tutorial001.py new file mode 100644 index 0000000000..0c0ab315e8 --- /dev/null +++ b/docs_src/query_param_models/tutorial001.py @@ -0,0 +1,19 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_an.py b/docs_src/query_param_models/tutorial001_an.py new file mode 100644 index 0000000000..28375057c1 --- /dev/null +++ b/docs_src/query_param_models/tutorial001_an.py @@ -0,0 +1,19 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_an_py310.py b/docs_src/query_param_models/tutorial001_an_py310.py new file mode 100644 index 0000000000..71427acae1 --- /dev/null +++ b/docs_src/query_param_models/tutorial001_an_py310.py @@ -0,0 +1,18 @@ +from typing import Annotated, Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_an_py39.py b/docs_src/query_param_models/tutorial001_an_py39.py new file mode 100644 index 0000000000..ba690d3e3f --- /dev/null +++ b/docs_src/query_param_models/tutorial001_an_py39.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_py310.py b/docs_src/query_param_models/tutorial001_py310.py new file mode 100644 index 0000000000..3ebf9f4d70 --- /dev/null +++ b/docs_src/query_param_models/tutorial001_py310.py @@ -0,0 +1,18 @@ +from typing import Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial001_py39.py b/docs_src/query_param_models/tutorial001_py39.py new file mode 100644 index 0000000000..54b52a054c --- /dev/null +++ b/docs_src/query_param_models/tutorial001_py39.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002.py b/docs_src/query_param_models/tutorial002.py new file mode 100644 index 0000000000..1633bc4644 --- /dev/null +++ b/docs_src/query_param_models/tutorial002.py @@ -0,0 +1,21 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_an.py b/docs_src/query_param_models/tutorial002_an.py new file mode 100644 index 0000000000..69705d4b4b --- /dev/null +++ b/docs_src/query_param_models/tutorial002_an.py @@ -0,0 +1,21 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_an_py310.py b/docs_src/query_param_models/tutorial002_an_py310.py new file mode 100644 index 0000000000..9759565023 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_an_py310.py @@ -0,0 +1,20 @@ +from typing import Annotated, Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_an_py39.py b/docs_src/query_param_models/tutorial002_an_py39.py new file mode 100644 index 0000000000..2d4c1a62b5 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_an_py39.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1.py b/docs_src/query_param_models/tutorial002_pv1.py new file mode 100644 index 0000000000..71ccd961d3 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1.py @@ -0,0 +1,22 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an.py b/docs_src/query_param_models/tutorial002_pv1_an.py new file mode 100644 index 0000000000..1dd29157a4 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_an.py @@ -0,0 +1,22 @@ +from typing import List + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: List[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py310.py b/docs_src/query_param_models/tutorial002_pv1_an_py310.py new file mode 100644 index 0000000000..d635aae88f --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_an_py310.py @@ -0,0 +1,21 @@ +from typing import Annotated, Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py39.py b/docs_src/query_param_models/tutorial002_pv1_an_py39.py new file mode 100644 index 0000000000..494fef11fc --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_an_py39.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_py310.py b/docs_src/query_param_models/tutorial002_pv1_py310.py new file mode 100644 index 0000000000..9ffdeefc06 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_py310.py @@ -0,0 +1,21 @@ +from typing import Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_py39.py b/docs_src/query_param_models/tutorial002_pv1_py39.py new file mode 100644 index 0000000000..7fa456a791 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_pv1_py39.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + class Config: + extra = "forbid" + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_py310.py b/docs_src/query_param_models/tutorial002_py310.py new file mode 100644 index 0000000000..6ec4184991 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_py310.py @@ -0,0 +1,20 @@ +from typing import Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_param_models/tutorial002_py39.py b/docs_src/query_param_models/tutorial002_py39.py new file mode 100644 index 0000000000..f9bba028c2 --- /dev/null +++ b/docs_src/query_param_models/tutorial002_py39.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +from typing_extensions import Literal + +app = FastAPI() + + +class FilterParams(BaseModel): + model_config = {"extra": "forbid"} + + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: FilterParams = Query()): + return filter_query diff --git a/docs_src/query_params_str_validations/tutorial004_an_py310_regex.py b/docs_src/query_params_str_validations/tutorial004_regex_an_py310.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial004_an_py310_regex.py rename to docs_src/query_params_str_validations/tutorial004_regex_an_py310.py diff --git a/docs_src/query_params_str_validations/tutorial006b.py b/docs_src/query_params_str_validations/tutorial006b.py deleted file mode 100644 index a8d69c8899..0000000000 --- a/docs_src/query_params_str_validations/tutorial006b.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str = Query(default=..., min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006b_an.py b/docs_src/query_params_str_validations/tutorial006b_an.py deleted file mode 100644 index ea3b02583a..0000000000 --- a/docs_src/query_params_str_validations/tutorial006b_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006b_an_py39.py b/docs_src/query_params_str_validations/tutorial006b_an_py39.py deleted file mode 100644 index 687a9f5446..0000000000 --- a/docs_src/query_params_str_validations/tutorial006b_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006c.py b/docs_src/query_params_str_validations/tutorial006c.py index 2ac148c94f..0a0e820da3 100644 --- a/docs_src/query_params_str_validations/tutorial006c.py +++ b/docs_src/query_params_str_validations/tutorial006c.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Union[str, None] = Query(default=..., min_length=3)): +async def read_items(q: Union[str, None] = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py index 10bf26a577..55c4f4adca 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an.py +++ b/docs_src/query_params_str_validations/tutorial006c_an.py @@ -7,7 +7,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...): +async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py310.py b/docs_src/query_params_str_validations/tutorial006c_an_py310.py index 1ab0a7d53d..2995d9c979 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py310.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py310.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[str | None, Query(min_length=3)] = ...): +async def read_items(q: Annotated[str | None, Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py39.py b/docs_src/query_params_str_validations/tutorial006c_an_py39.py index ac12733318..76a1cd49ac 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py39.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...): +async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_py310.py b/docs_src/query_params_str_validations/tutorial006c_py310.py index 82dd9e5d7c..88b499c7af 100644 --- a/docs_src/query_params_str_validations/tutorial006c_py310.py +++ b/docs_src/query_params_str_validations/tutorial006c_py310.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str | None = Query(default=..., min_length=3)): +async def read_items(q: str | None = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006d.py b/docs_src/query_params_str_validations/tutorial006d.py deleted file mode 100644 index 42c5bf4ebb..0000000000 --- a/docs_src/query_params_str_validations/tutorial006d.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index bc8283e153..0000000000 --- a/docs_src/query_params_str_validations/tutorial006d_an.py +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 035d9e3bdd..0000000000 --- a/docs_src/query_params_str_validations/tutorial006d_an_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -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/tutorial015_an.py b/docs_src/query_params_str_validations/tutorial015_an.py new file mode 100644 index 0000000000..f2ec6db123 --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial015_an.py @@ -0,0 +1,31 @@ +import random +from typing import Union + +from fastapi import FastAPI +from pydantic import AfterValidator +from typing_extensions import Annotated + +app = FastAPI() + +data = { + "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", + "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", + "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", +} + + +def check_valid_id(id: str): + if not id.startswith(("isbn-", "imdb-")): + raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') + return id + + +@app.get("/items/") +async def read_items( + id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None, +): + if id: + item = data.get(id) + else: + id, item = random.choice(list(data.items())) + return {"id": id, "name": item} diff --git a/docs_src/query_params_str_validations/tutorial015_an_py310.py b/docs_src/query_params_str_validations/tutorial015_an_py310.py new file mode 100644 index 0000000000..35f3680949 --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial015_an_py310.py @@ -0,0 +1,30 @@ +import random +from typing import Annotated + +from fastapi import FastAPI +from pydantic import AfterValidator + +app = FastAPI() + +data = { + "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", + "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", + "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", +} + + +def check_valid_id(id: str): + if not id.startswith(("isbn-", "imdb-")): + raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') + return id + + +@app.get("/items/") +async def read_items( + id: Annotated[str | None, AfterValidator(check_valid_id)] = None, +): + if id: + item = data.get(id) + else: + id, item = random.choice(list(data.items())) + return {"id": id, "name": item} diff --git a/docs_src/query_params_str_validations/tutorial015_an_py39.py b/docs_src/query_params_str_validations/tutorial015_an_py39.py new file mode 100644 index 0000000000..989b6d2c25 --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial015_an_py39.py @@ -0,0 +1,30 @@ +import random +from typing import Annotated, Union + +from fastapi import FastAPI +from pydantic import AfterValidator + +app = FastAPI() + +data = { + "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", + "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", + "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", +} + + +def check_valid_id(id: str): + if not id.startswith(("isbn-", "imdb-")): + raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') + return id + + +@app.get("/items/") +async def read_items( + id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None, +): + if id: + item = data.get(id) + else: + id, item = random.choice(list(data.items())) + return {"id": id, "name": item} diff --git a/docs_src/request_form_models/tutorial001.py b/docs_src/request_form_models/tutorial001.py new file mode 100644 index 0000000000..98feff0b9f --- /dev/null +++ b/docs_src/request_form_models/tutorial001.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + +@app.post("/login/") +async def login(data: FormData = Form()): + return data diff --git a/docs_src/request_form_models/tutorial001_an.py b/docs_src/request_form_models/tutorial001_an.py new file mode 100644 index 0000000000..30483d4455 --- /dev/null +++ b/docs_src/request_form_models/tutorial001_an.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/request_form_models/tutorial001_an_py39.py b/docs_src/request_form_models/tutorial001_an_py39.py new file mode 100644 index 0000000000..7cc81aae95 --- /dev/null +++ b/docs_src/request_form_models/tutorial001_an_py39.py @@ -0,0 +1,16 @@ +from typing import Annotated + +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/request_form_models/tutorial002.py b/docs_src/request_form_models/tutorial002.py new file mode 100644 index 0000000000..59b329e8d8 --- /dev/null +++ b/docs_src/request_form_models/tutorial002.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + model_config = {"extra": "forbid"} + + +@app.post("/login/") +async def login(data: FormData = Form()): + return data diff --git a/docs_src/request_form_models/tutorial002_an.py b/docs_src/request_form_models/tutorial002_an.py new file mode 100644 index 0000000000..bcb0227959 --- /dev/null +++ b/docs_src/request_form_models/tutorial002_an.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + model_config = {"extra": "forbid"} + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/request_form_models/tutorial002_an_py39.py b/docs_src/request_form_models/tutorial002_an_py39.py new file mode 100644 index 0000000000..3004e08524 --- /dev/null +++ b/docs_src/request_form_models/tutorial002_an_py39.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + model_config = {"extra": "forbid"} + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/request_form_models/tutorial002_pv1.py b/docs_src/request_form_models/tutorial002_pv1.py new file mode 100644 index 0000000000..d5f7db2a67 --- /dev/null +++ b/docs_src/request_form_models/tutorial002_pv1.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + class Config: + extra = "forbid" + + +@app.post("/login/") +async def login(data: FormData = Form()): + return data diff --git a/docs_src/request_form_models/tutorial002_pv1_an.py b/docs_src/request_form_models/tutorial002_pv1_an.py new file mode 100644 index 0000000000..fe9dbc344b --- /dev/null +++ b/docs_src/request_form_models/tutorial002_pv1_an.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + class Config: + extra = "forbid" + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/request_form_models/tutorial002_pv1_an_py39.py b/docs_src/request_form_models/tutorial002_pv1_an_py39.py new file mode 100644 index 0000000000..942d5d4118 --- /dev/null +++ b/docs_src/request_form_models/tutorial002_pv1_an_py39.py @@ -0,0 +1,19 @@ +from typing import Annotated + +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + class Config: + extra = "forbid" + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/schema_extra_example/tutorial001_py310_pv1.py b/docs_src/schema_extra_example/tutorial001_pv1_py310.py similarity index 100% rename from docs_src/schema_extra_example/tutorial001_py310_pv1.py rename to docs_src/schema_extra_example/tutorial001_pv1_py310.py diff --git a/docs_src/security/tutorial004.py b/docs_src/security/tutorial004.py index 91d161b8af..130dc699a0 100644 --- a/docs_src/security/tutorial004.py +++ b/docs_src/security/tutorial004.py @@ -5,7 +5,7 @@ import jwt from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel # to get a string like this run: @@ -20,7 +20,7 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, } } @@ -46,7 +46,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -54,11 +54,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -95,7 +95,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py index df50754afc..018234e300 100644 --- a/docs_src/security/tutorial004_an.py +++ b/docs_src/security/tutorial004_an.py @@ -5,7 +5,7 @@ import jwt from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel from typing_extensions import Annotated @@ -21,7 +21,7 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, } } @@ -47,7 +47,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -55,11 +55,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -96,7 +96,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py index eff54ef01e..18ea96bc5c 100644 --- a/docs_src/security/tutorial004_an_py310.py +++ b/docs_src/security/tutorial004_an_py310.py @@ -5,7 +5,7 @@ import jwt from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel # to get a string like this run: @@ -20,7 +20,7 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, } } @@ -46,7 +46,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -54,11 +54,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py index 0455b500cd..d3fd29e5a5 100644 --- a/docs_src/security/tutorial004_an_py39.py +++ b/docs_src/security/tutorial004_an_py39.py @@ -5,7 +5,7 @@ import jwt from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel # to get a string like this run: @@ -20,7 +20,7 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, } } @@ -46,7 +46,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -54,11 +54,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py index 78bee22a3f..cd1dcff460 100644 --- a/docs_src/security/tutorial004_py310.py +++ b/docs_src/security/tutorial004_py310.py @@ -4,7 +4,7 @@ import jwt from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel # to get a string like this run: @@ -19,7 +19,7 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, } } @@ -45,7 +45,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -53,11 +53,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -94,7 +94,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial005.py b/docs_src/security/tutorial005.py index ccad079694..fdd73bcd8a 100644 --- a/docs_src/security/tutorial005.py +++ b/docs_src/security/tutorial005.py @@ -9,7 +9,7 @@ from fastapi.security import ( SecurityScopes, ) from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel, ValidationError # to get a string like this run: @@ -24,14 +24,14 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, }, "alice": { "username": "alice", "full_name": "Alice Chains", "email": "alicechains@example.com", - "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", "disabled": True, }, } @@ -58,7 +58,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", @@ -69,11 +69,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -119,7 +119,8 @@ async def get_current_user( username: str = payload.get("sub") if username is None: raise credentials_exception - token_scopes = payload.get("scopes", []) + scope: str = payload.get("scope", "") + token_scopes = scope.split(" ") token_data = TokenData(scopes=token_scopes, username=username) except (InvalidTokenError, ValidationError): raise credentials_exception @@ -153,7 +154,7 @@ async def login_for_access_token( 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}, + data={"sub": user.username, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires, ) return Token(access_token=access_token, token_type="bearer") diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py index 5b67cb145d..e1d7b4f62a 100644 --- a/docs_src/security/tutorial005_an.py +++ b/docs_src/security/tutorial005_an.py @@ -9,7 +9,7 @@ from fastapi.security import ( SecurityScopes, ) from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel, ValidationError from typing_extensions import Annotated @@ -25,14 +25,14 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, }, "alice": { "username": "alice", "full_name": "Alice Chains", "email": "alicechains@example.com", - "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", "disabled": True, }, } @@ -59,7 +59,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", @@ -70,11 +70,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -117,10 +117,11 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception - token_scopes = payload.get("scopes", []) + scope: str = payload.get("scope", "") + token_scopes = scope.split(" ") token_data = TokenData(scopes=token_scopes, username=username) except (InvalidTokenError, ValidationError): raise credentials_exception @@ -154,7 +155,7 @@ async def login_for_access_token( 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}, + data={"sub": user.username, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires, ) return Token(access_token=access_token, token_type="bearer") diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py index 297193e351..df55951c07 100644 --- a/docs_src/security/tutorial005_an_py310.py +++ b/docs_src/security/tutorial005_an_py310.py @@ -9,7 +9,7 @@ from fastapi.security import ( SecurityScopes, ) from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel, ValidationError # to get a string like this run: @@ -24,14 +24,14 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, }, "alice": { "username": "alice", "full_name": "Alice Chains", "email": "alicechains@example.com", - "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", "disabled": True, }, } @@ -58,7 +58,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", @@ -69,11 +69,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -116,10 +116,11 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception - token_scopes = payload.get("scopes", []) + scope: str = payload.get("scope", "") + token_scopes = scope.split(" ") token_data = TokenData(scopes=token_scopes, username=username) except (InvalidTokenError, ValidationError): raise credentials_exception @@ -153,7 +154,7 @@ async def login_for_access_token( 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}, + data={"sub": user.username, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires, ) return Token(access_token=access_token, token_type="bearer") diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py index 1acf47bdcf..983c1c22cf 100644 --- a/docs_src/security/tutorial005_an_py39.py +++ b/docs_src/security/tutorial005_an_py39.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta, timezone -from typing import Annotated, List, Union +from typing import Annotated, Union import jwt from fastapi import Depends, FastAPI, HTTPException, Security, status @@ -9,7 +9,7 @@ from fastapi.security import ( SecurityScopes, ) from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel, ValidationError # to get a string like this run: @@ -24,14 +24,14 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, }, "alice": { "username": "alice", "full_name": "Alice Chains", "email": "alicechains@example.com", - "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", "disabled": True, }, } @@ -44,7 +44,7 @@ class Token(BaseModel): class TokenData(BaseModel): username: Union[str, None] = None - scopes: List[str] = [] + scopes: list[str] = [] class User(BaseModel): @@ -58,7 +58,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", @@ -69,11 +69,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -116,10 +116,11 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception - token_scopes = payload.get("scopes", []) + scope: str = payload.get("scope", "") + token_scopes = scope.split(" ") token_data = TokenData(scopes=token_scopes, username=username) except (InvalidTokenError, ValidationError): raise credentials_exception @@ -153,7 +154,7 @@ async def login_for_access_token( 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}, + data={"sub": user.username, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires, ) return Token(access_token=access_token, token_type="bearer") diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py index b244ef08e9..d08e2c59f3 100644 --- a/docs_src/security/tutorial005_py310.py +++ b/docs_src/security/tutorial005_py310.py @@ -8,7 +8,7 @@ from fastapi.security import ( SecurityScopes, ) from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel, ValidationError # to get a string like this run: @@ -23,14 +23,14 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, }, "alice": { "username": "alice", "full_name": "Alice Chains", "email": "alicechains@example.com", - "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", "disabled": True, }, } @@ -57,7 +57,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", @@ -68,11 +68,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -118,7 +118,8 @@ async def get_current_user( username: str = payload.get("sub") if username is None: raise credentials_exception - token_scopes = payload.get("scopes", []) + scope: str = payload.get("scope", "") + token_scopes = scope.split(" ") token_data = TokenData(scopes=token_scopes, username=username) except (InvalidTokenError, ValidationError): raise credentials_exception @@ -152,7 +153,7 @@ async def login_for_access_token( 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}, + data={"sub": user.username, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires, ) return Token(access_token=access_token, token_type="bearer") diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py index 8f0e93376a..5bde47ef47 100644 --- a/docs_src/security/tutorial005_py39.py +++ b/docs_src/security/tutorial005_py39.py @@ -9,7 +9,7 @@ from fastapi.security import ( SecurityScopes, ) from jwt.exceptions import InvalidTokenError -from passlib.context import CryptContext +from pwdlib import PasswordHash from pydantic import BaseModel, ValidationError # to get a string like this run: @@ -24,14 +24,14 @@ fake_users_db = { "username": "johndoe", "full_name": "John Doe", "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", "disabled": False, }, "alice": { "username": "alice", "full_name": "Alice Chains", "email": "alicechains@example.com", - "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm", + "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", "disabled": True, }, } @@ -58,7 +58,7 @@ class UserInDB(User): hashed_password: str -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +password_hash = PasswordHash.recommended() oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", @@ -69,11 +69,11 @@ app = FastAPI() def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) + return password_hash.verify(plain_password, hashed_password) def get_password_hash(password): - return pwd_context.hash(password) + return password_hash.hash(password) def get_user(db, username: str): @@ -119,7 +119,8 @@ async def get_current_user( username: str = payload.get("sub") if username is None: raise credentials_exception - token_scopes = payload.get("scopes", []) + scope: str = payload.get("scope", "") + token_scopes = scope.split(" ") token_data = TokenData(scopes=token_scopes, username=username) except (InvalidTokenError, ValidationError): raise credentials_exception @@ -153,7 +154,7 @@ async def login_for_access_token( 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}, + data={"sub": user.username, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires, ) return Token(access_token=access_token, token_type="bearer") diff --git a/docs_src/sql_databases/sql_app/alt_main.py b/docs_src/sql_databases/sql_app/alt_main.py deleted file mode 100644 index f7206bcb41..0000000000 --- a/docs_src/sql_databases/sql_app/alt_main.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import List - -from fastapi import Depends, FastAPI, HTTPException, Request, Response -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -@app.middleware("http") -async def db_session_middleware(request: Request, call_next): - response = Response("Internal server error", status_code=500) - try: - request.state.db = SessionLocal() - response = await call_next(request) - finally: - request.state.db.close() - return response - - -# Dependency -def get_db(request: Request): - return request.state.db - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=List[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@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) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=List[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app/crud.py b/docs_src/sql_databases/sql_app/crud.py deleted file mode 100644 index 679acdb5ce..0000000000 --- a/docs_src/sql_databases/sql_app/crud.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy.orm import Session - -from . import models, schemas - - -def get_user(db: Session, user_id: int): - return db.query(models.User).filter(models.User.id == user_id).first() - - -def get_user_by_email(db: Session, email: str): - return db.query(models.User).filter(models.User.email == email).first() - - -def get_users(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.User).offset(skip).limit(limit).all() - - -def create_user(db: Session, user: schemas.UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = models.User(email=user.email, hashed_password=fake_hashed_password) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def get_items(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.Item).offset(skip).limit(limit).all() - - -def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int): - db_item = models.Item(**item.dict(), owner_id=user_id) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item diff --git a/docs_src/sql_databases/sql_app/database.py b/docs_src/sql_databases/sql_app/database.py deleted file mode 100644 index 45a8b9f694..0000000000 --- a/docs_src/sql_databases/sql_app/database.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" -# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() diff --git a/docs_src/sql_databases/sql_app/main.py b/docs_src/sql_databases/sql_app/main.py deleted file mode 100644 index e7508c59d4..0000000000 --- a/docs_src/sql_databases/sql_app/main.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import List - -from fastapi import Depends, FastAPI, HTTPException -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -# Dependency -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=List[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@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) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=List[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app/models.py b/docs_src/sql_databases/sql_app/models.py deleted file mode 100644 index 09ae2a8077..0000000000 --- a/docs_src/sql_databases/sql_app/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from .database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True) - email = Column(String, unique=True, index=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) - - items = relationship("Item", back_populates="owner") - - -class Item(Base): - __tablename__ = "items" - - id = Column(Integer, primary_key=True) - title = Column(String, index=True) - description = Column(String, index=True) - owner_id = Column(Integer, ForeignKey("users.id")) - - owner = relationship("User", back_populates="items") diff --git a/docs_src/sql_databases/sql_app/schemas.py b/docs_src/sql_databases/sql_app/schemas.py deleted file mode 100644 index c49beba882..0000000000 --- a/docs_src/sql_databases/sql_app/schemas.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import List, Union - -from pydantic import BaseModel - - -class ItemBase(BaseModel): - title: str - description: Union[str, None] = None - - -class ItemCreate(ItemBase): - pass - - -class Item(ItemBase): - id: int - owner_id: int - - class Config: - orm_mode = True - - -class UserBase(BaseModel): - email: str - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - id: int - is_active: bool - items: List[Item] = [] - - class Config: - orm_mode = True 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 deleted file mode 100644 index 5f55add0a9..0000000000 --- a/docs_src/sql_databases/sql_app/tests/test_sql_app.py +++ /dev/null @@ -1,50 +0,0 @@ -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://" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, - connect_args={"check_same_thread": False}, - poolclass=StaticPool, -) -TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -Base.metadata.create_all(bind=engine) - - -def override_get_db(): - try: - db = TestingSessionLocal() - yield db - finally: - db.close() - - -app.dependency_overrides[get_db] = override_get_db - -client = TestClient(app) - - -def test_create_user(): - response = client.post( - "/users/", - json={"email": "deadpool@example.com", "password": "chimichangas4life"}, - ) - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert "id" in data - user_id = data["id"] - - response = client.get(f"/users/{user_id}") - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert data["id"] == user_id diff --git a/docs_src/sql_databases/sql_app_py310/alt_main.py b/docs_src/sql_databases/sql_app_py310/alt_main.py deleted file mode 100644 index 5de88ec3a1..0000000000 --- a/docs_src/sql_databases/sql_app_py310/alt_main.py +++ /dev/null @@ -1,60 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException, Request, Response -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -@app.middleware("http") -async def db_session_middleware(request: Request, call_next): - response = Response("Internal server error", status_code=500) - try: - request.state.db = SessionLocal() - response = await call_next(request) - finally: - request.state.db.close() - return response - - -# Dependency -def get_db(request: Request): - return request.state.db - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@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) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py310/crud.py b/docs_src/sql_databases/sql_app_py310/crud.py deleted file mode 100644 index 679acdb5ce..0000000000 --- a/docs_src/sql_databases/sql_app_py310/crud.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy.orm import Session - -from . import models, schemas - - -def get_user(db: Session, user_id: int): - return db.query(models.User).filter(models.User.id == user_id).first() - - -def get_user_by_email(db: Session, email: str): - return db.query(models.User).filter(models.User.email == email).first() - - -def get_users(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.User).offset(skip).limit(limit).all() - - -def create_user(db: Session, user: schemas.UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = models.User(email=user.email, hashed_password=fake_hashed_password) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def get_items(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.Item).offset(skip).limit(limit).all() - - -def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int): - db_item = models.Item(**item.dict(), owner_id=user_id) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item diff --git a/docs_src/sql_databases/sql_app_py310/database.py b/docs_src/sql_databases/sql_app_py310/database.py deleted file mode 100644 index 45a8b9f694..0000000000 --- a/docs_src/sql_databases/sql_app_py310/database.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" -# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() diff --git a/docs_src/sql_databases/sql_app_py310/main.py b/docs_src/sql_databases/sql_app_py310/main.py deleted file mode 100644 index a9856d0b68..0000000000 --- a/docs_src/sql_databases/sql_app_py310/main.py +++ /dev/null @@ -1,53 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -# Dependency -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@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) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py310/models.py b/docs_src/sql_databases/sql_app_py310/models.py deleted file mode 100644 index 09ae2a8077..0000000000 --- a/docs_src/sql_databases/sql_app_py310/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from .database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True) - email = Column(String, unique=True, index=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) - - items = relationship("Item", back_populates="owner") - - -class Item(Base): - __tablename__ = "items" - - id = Column(Integer, primary_key=True) - title = Column(String, index=True) - description = Column(String, index=True) - owner_id = Column(Integer, ForeignKey("users.id")) - - owner = relationship("User", back_populates="items") diff --git a/docs_src/sql_databases/sql_app_py310/schemas.py b/docs_src/sql_databases/sql_app_py310/schemas.py deleted file mode 100644 index aea2e3f101..0000000000 --- a/docs_src/sql_databases/sql_app_py310/schemas.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel - - -class ItemBase(BaseModel): - title: str - description: str | None = None - - -class ItemCreate(ItemBase): - pass - - -class Item(ItemBase): - id: int - owner_id: int - - class Config: - orm_mode = True - - -class UserBase(BaseModel): - email: str - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - id: int - is_active: bool - items: list[Item] = [] - - class Config: - orm_mode = True diff --git a/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py deleted file mode 100644 index c60c3356f8..0000000000 --- a/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py +++ /dev/null @@ -1,47 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -from ..database import Base -from ..main import app, get_db - -SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -Base.metadata.create_all(bind=engine) - - -def override_get_db(): - try: - db = TestingSessionLocal() - yield db - finally: - db.close() - - -app.dependency_overrides[get_db] = override_get_db - -client = TestClient(app) - - -def test_create_user(): - response = client.post( - "/users/", - json={"email": "deadpool@example.com", "password": "chimichangas4life"}, - ) - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert "id" in data - user_id = data["id"] - - response = client.get(f"/users/{user_id}") - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert data["id"] == user_id diff --git a/docs_src/sql_databases/sql_app_py39/alt_main.py b/docs_src/sql_databases/sql_app_py39/alt_main.py deleted file mode 100644 index 5de88ec3a1..0000000000 --- a/docs_src/sql_databases/sql_app_py39/alt_main.py +++ /dev/null @@ -1,60 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException, Request, Response -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -@app.middleware("http") -async def db_session_middleware(request: Request, call_next): - response = Response("Internal server error", status_code=500) - try: - request.state.db = SessionLocal() - response = await call_next(request) - finally: - request.state.db.close() - return response - - -# Dependency -def get_db(request: Request): - return request.state.db - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@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) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py39/crud.py b/docs_src/sql_databases/sql_app_py39/crud.py deleted file mode 100644 index 679acdb5ce..0000000000 --- a/docs_src/sql_databases/sql_app_py39/crud.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy.orm import Session - -from . import models, schemas - - -def get_user(db: Session, user_id: int): - return db.query(models.User).filter(models.User.id == user_id).first() - - -def get_user_by_email(db: Session, email: str): - return db.query(models.User).filter(models.User.email == email).first() - - -def get_users(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.User).offset(skip).limit(limit).all() - - -def create_user(db: Session, user: schemas.UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = models.User(email=user.email, hashed_password=fake_hashed_password) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def get_items(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.Item).offset(skip).limit(limit).all() - - -def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int): - db_item = models.Item(**item.dict(), owner_id=user_id) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item diff --git a/docs_src/sql_databases/sql_app_py39/database.py b/docs_src/sql_databases/sql_app_py39/database.py deleted file mode 100644 index 45a8b9f694..0000000000 --- a/docs_src/sql_databases/sql_app_py39/database.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" -# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() diff --git a/docs_src/sql_databases/sql_app_py39/main.py b/docs_src/sql_databases/sql_app_py39/main.py deleted file mode 100644 index a9856d0b68..0000000000 --- a/docs_src/sql_databases/sql_app_py39/main.py +++ /dev/null @@ -1,53 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -# Dependency -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@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) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py39/models.py b/docs_src/sql_databases/sql_app_py39/models.py deleted file mode 100644 index 09ae2a8077..0000000000 --- a/docs_src/sql_databases/sql_app_py39/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from .database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True) - email = Column(String, unique=True, index=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) - - items = relationship("Item", back_populates="owner") - - -class Item(Base): - __tablename__ = "items" - - id = Column(Integer, primary_key=True) - title = Column(String, index=True) - description = Column(String, index=True) - owner_id = Column(Integer, ForeignKey("users.id")) - - owner = relationship("User", back_populates="items") diff --git a/docs_src/sql_databases/sql_app_py39/schemas.py b/docs_src/sql_databases/sql_app_py39/schemas.py deleted file mode 100644 index dadc403d93..0000000000 --- a/docs_src/sql_databases/sql_app_py39/schemas.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Union - -from pydantic import BaseModel - - -class ItemBase(BaseModel): - title: str - description: Union[str, None] = None - - -class ItemCreate(ItemBase): - pass - - -class Item(ItemBase): - id: int - owner_id: int - - class Config: - orm_mode = True - - -class UserBase(BaseModel): - email: str - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - id: int - is_active: bool - items: list[Item] = [] - - class Config: - orm_mode = True diff --git a/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py deleted file mode 100644 index c60c3356f8..0000000000 --- a/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py +++ /dev/null @@ -1,47 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -from ..database import Base -from ..main import app, get_db - -SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -Base.metadata.create_all(bind=engine) - - -def override_get_db(): - try: - db = TestingSessionLocal() - yield db - finally: - db.close() - - -app.dependency_overrides[get_db] = override_get_db - -client = TestClient(app) - - -def test_create_user(): - response = client.post( - "/users/", - json={"email": "deadpool@example.com", "password": "chimichangas4life"}, - ) - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert "id" in data - user_id = data["id"] - - response = client.get(f"/users/{user_id}") - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert data["id"] == user_id diff --git a/docs_src/sql_databases/tutorial001.py b/docs_src/sql_databases/tutorial001.py new file mode 100644 index 0000000000..be86ec0eeb --- /dev/null +++ b/docs_src/sql_databases/tutorial001.py @@ -0,0 +1,71 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +) -> List[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_an.py b/docs_src/sql_databases/tutorial001_an.py new file mode 100644 index 0000000000..8c000d31c7 --- /dev/null +++ b/docs_src/sql_databases/tutorial001_an.py @@ -0,0 +1,74 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select +from typing_extensions import Annotated + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: SessionDep) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +) -> List[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: SessionDep) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_an_py310.py b/docs_src/sql_databases/tutorial001_an_py310.py new file mode 100644 index 0000000000..de1fb81faf --- /dev/null +++ b/docs_src/sql_databases/tutorial001_an_py310.py @@ -0,0 +1,73 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: SessionDep) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: SessionDep) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_an_py39.py b/docs_src/sql_databases/tutorial001_an_py39.py new file mode 100644 index 0000000000..5958927461 --- /dev/null +++ b/docs_src/sql_databases/tutorial001_an_py39.py @@ -0,0 +1,73 @@ +from typing import Annotated, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: SessionDep) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: SessionDep) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_py310.py b/docs_src/sql_databases/tutorial001_py310.py new file mode 100644 index 0000000000..b58462e6a5 --- /dev/null +++ b/docs_src/sql_databases/tutorial001_py310.py @@ -0,0 +1,69 @@ +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_py39.py b/docs_src/sql_databases/tutorial001_py39.py new file mode 100644 index 0000000000..410a52d0c0 --- /dev/null +++ b/docs_src/sql_databases/tutorial001_py39.py @@ -0,0 +1,71 @@ +from typing import Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002.py b/docs_src/sql_databases/tutorial002.py new file mode 100644 index 0000000000..4350d19c61 --- /dev/null +++ b/docs_src/sql_databases/tutorial002.py @@ -0,0 +1,104 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=List[HeroPublic]) +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero( + hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) +): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an.py b/docs_src/sql_databases/tutorial002_an.py new file mode 100644 index 0000000000..15e3d7c3a5 --- /dev/null +++ b/docs_src/sql_databases/tutorial002_an.py @@ -0,0 +1,104 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select +from typing_extensions import Annotated + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=List[HeroPublic]) +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an_py310.py b/docs_src/sql_databases/tutorial002_an_py310.py new file mode 100644 index 0000000000..64c554b8a2 --- /dev/null +++ b/docs_src/sql_databases/tutorial002_an_py310.py @@ -0,0 +1,103 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: int | None = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: str | None = None + age: int | None = None + secret_name: str | None = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an_py39.py b/docs_src/sql_databases/tutorial002_an_py39.py new file mode 100644 index 0000000000..a8a0721ff6 --- /dev/null +++ b/docs_src/sql_databases/tutorial002_an_py39.py @@ -0,0 +1,103 @@ +from typing import Annotated, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_py310.py b/docs_src/sql_databases/tutorial002_py310.py new file mode 100644 index 0000000000..ec3d68db53 --- /dev/null +++ b/docs_src/sql_databases/tutorial002_py310.py @@ -0,0 +1,102 @@ +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: int | None = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: str | None = None + age: int | None = None + secret_name: str | None = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero( + hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) +): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_py39.py b/docs_src/sql_databases/tutorial002_py39.py new file mode 100644 index 0000000000..d8f5dd0901 --- /dev/null +++ b/docs_src/sql_databases/tutorial002_py39.py @@ -0,0 +1,104 @@ +from typing import Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero( + hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) +): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases_peewee/sql_app/__init__.py b/docs_src/sql_databases_peewee/sql_app/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs_src/sql_databases_peewee/sql_app/crud.py b/docs_src/sql_databases_peewee/sql_app/crud.py deleted file mode 100644 index 56c5875595..0000000000 --- a/docs_src/sql_databases_peewee/sql_app/crud.py +++ /dev/null @@ -1,30 +0,0 @@ -from . import models, schemas - - -def get_user(user_id: int): - return models.User.filter(models.User.id == user_id).first() - - -def get_user_by_email(email: str): - return models.User.filter(models.User.email == email).first() - - -def get_users(skip: int = 0, limit: int = 100): - return list(models.User.select().offset(skip).limit(limit)) - - -def create_user(user: schemas.UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = models.User(email=user.email, hashed_password=fake_hashed_password) - db_user.save() - return db_user - - -def get_items(skip: int = 0, limit: int = 100): - return list(models.Item.select().offset(skip).limit(limit)) - - -def create_user_item(item: schemas.ItemCreate, user_id: int): - db_item = models.Item(**item.dict(), owner_id=user_id) - db_item.save() - return db_item diff --git a/docs_src/sql_databases_peewee/sql_app/database.py b/docs_src/sql_databases_peewee/sql_app/database.py deleted file mode 100644 index 6938fe8263..0000000000 --- a/docs_src/sql_databases_peewee/sql_app/database.py +++ /dev/null @@ -1,24 +0,0 @@ -from contextvars import ContextVar - -import peewee - -DATABASE_NAME = "test.db" -db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None} -db_state = ContextVar("db_state", default=db_state_default.copy()) - - -class PeeweeConnectionState(peewee._ConnectionState): - def __init__(self, **kwargs): - super().__setattr__("_state", db_state) - super().__init__(**kwargs) - - def __setattr__(self, name, value): - self._state.get()[name] = value - - def __getattr__(self, name): - return self._state.get()[name] - - -db = peewee.SqliteDatabase(DATABASE_NAME, check_same_thread=False) - -db._state = PeeweeConnectionState() diff --git a/docs_src/sql_databases_peewee/sql_app/main.py b/docs_src/sql_databases_peewee/sql_app/main.py deleted file mode 100644 index 8fbd2075d6..0000000000 --- a/docs_src/sql_databases_peewee/sql_app/main.py +++ /dev/null @@ -1,79 +0,0 @@ -import time -from typing import List - -from fastapi import Depends, FastAPI, HTTPException - -from . import crud, database, models, schemas -from .database import db_state_default - -database.db.connect() -database.db.create_tables([models.User, models.Item]) -database.db.close() - -app = FastAPI() - -sleep_time = 10 - - -async def reset_db_state(): - database.db._state._state.set(db_state_default.copy()) - database.db._state.reset() - - -def get_db(db_state=Depends(reset_db_state)): - try: - database.db.connect() - yield - finally: - if not database.db.is_closed(): - database.db.close() - - -@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)]) -def create_user(user: schemas.UserCreate): - db_user = crud.get_user_by_email(email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(user=user) - - -@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)]) -def read_users(skip: int = 0, limit: int = 100): - users = crud.get_users(skip=skip, limit=limit) - return users - - -@app.get( - "/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)] -) -def read_user(user_id: int): - db_user = crud.get_user(user_id=user_id) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post( - "/users/{user_id}/items/", - response_model=schemas.Item, - dependencies=[Depends(get_db)], -) -def create_item_for_user(user_id: int, item: schemas.ItemCreate): - return crud.create_user_item(item=item, user_id=user_id) - - -@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)]) -def read_items(skip: int = 0, limit: int = 100): - items = crud.get_items(skip=skip, limit=limit) - return items - - -@app.get( - "/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)] -) -def read_slow_users(skip: int = 0, limit: int = 100): - global sleep_time - sleep_time = max(0, sleep_time - 1) - time.sleep(sleep_time) # Fake long processing request - users = crud.get_users(skip=skip, limit=limit) - return users diff --git a/docs_src/sql_databases_peewee/sql_app/models.py b/docs_src/sql_databases_peewee/sql_app/models.py deleted file mode 100644 index 46bdcd009e..0000000000 --- a/docs_src/sql_databases_peewee/sql_app/models.py +++ /dev/null @@ -1,21 +0,0 @@ -import peewee - -from .database import db - - -class User(peewee.Model): - email = peewee.CharField(unique=True, index=True) - hashed_password = peewee.CharField() - is_active = peewee.BooleanField(default=True) - - class Meta: - database = db - - -class Item(peewee.Model): - title = peewee.CharField(index=True) - description = peewee.CharField(index=True) - owner = peewee.ForeignKeyField(User, backref="items") - - class Meta: - database = db diff --git a/docs_src/sql_databases_peewee/sql_app/schemas.py b/docs_src/sql_databases_peewee/sql_app/schemas.py deleted file mode 100644 index d8775cb30c..0000000000 --- a/docs_src/sql_databases_peewee/sql_app/schemas.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import Any, List, Union - -import peewee -from pydantic import BaseModel -from pydantic.utils import GetterDict - - -class PeeweeGetterDict(GetterDict): - def get(self, key: Any, default: Any = None): - res = getattr(self._obj, key, default) - if isinstance(res, peewee.ModelSelect): - return list(res) - return res - - -class ItemBase(BaseModel): - title: str - description: Union[str, None] = None - - -class ItemCreate(ItemBase): - pass - - -class Item(ItemBase): - id: int - owner_id: int - - class Config: - orm_mode = True - getter_dict = PeeweeGetterDict - - -class UserBase(BaseModel): - email: str - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - id: int - is_active: bool - items: List[Item] = [] - - class Config: - orm_mode = True - getter_dict = PeeweeGetterDict diff --git a/fastapi/__init__.py b/fastapi/__init__.py index ac2508d89d..a7164d18f2 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.112.2" +__version__ = "0.119.1" from starlette import status as status diff --git a/fastapi/_compat.py b/fastapi/_compat.py deleted file mode 100644 index 06b847b4f3..0000000000 --- a/fastapi/_compat.py +++ /dev/null @@ -1,636 +0,0 @@ -from collections import deque -from copy import copy -from dataclasses import dataclass, is_dataclass -from enum import Enum -from typing import ( - Any, - Callable, - Deque, - Dict, - FrozenSet, - List, - Mapping, - Sequence, - Set, - Tuple, - Type, - Union, -) - -from fastapi.exceptions import RequestErrorModel -from fastapi.types import IncEx, ModelNameMap, UnionType -from pydantic import BaseModel, create_model -from pydantic.version import VERSION as P_VERSION -from starlette.datastructures import UploadFile -from typing_extensions import Annotated, Literal, get_args, get_origin - -# Reassign variable to make it reexported for mypy -PYDANTIC_VERSION = P_VERSION -PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.") - - -sequence_annotation_to_type = { - Sequence: list, - List: list, - list: list, - Tuple: tuple, - tuple: tuple, - Set: set, - set: set, - FrozenSet: frozenset, - frozenset: frozenset, - Deque: deque, - deque: deque, -} - -sequence_types = tuple(sequence_annotation_to_type.keys()) - -if PYDANTIC_V2: - from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError - from pydantic import TypeAdapter - from pydantic import ValidationError as ValidationError - from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] - GetJsonSchemaHandler as GetJsonSchemaHandler, - ) - from pydantic._internal._typing_extra import eval_type_lenient - from pydantic._internal._utils import lenient_issubclass as lenient_issubclass - from pydantic.fields import FieldInfo - from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema - from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue - from pydantic_core import CoreSchema as CoreSchema - from pydantic_core import PydanticUndefined, PydanticUndefinedType - from pydantic_core import Url as Url - - try: - from pydantic_core.core_schema import ( - with_info_plain_validator_function as with_info_plain_validator_function, - ) - except ImportError: # pragma: no cover - from pydantic_core.core_schema import ( - general_plain_validator_function as with_info_plain_validator_function, # noqa: F401 - ) - - Required = PydanticUndefined - Undefined = PydanticUndefined - UndefinedType = PydanticUndefinedType - evaluate_forwardref = eval_type_lenient - Validator = Any - - class BaseConfig: - pass - - class ErrorWrapper(Exception): - pass - - @dataclass - class ModelField: - field_info: FieldInfo - name: str - mode: Literal["validation", "serialization"] = "validation" - - @property - def alias(self) -> str: - a = self.field_info.alias - return a if a is not None else self.name - - @property - def required(self) -> bool: - return self.field_info.is_required() - - @property - def default(self) -> Any: - return self.get_default() - - @property - def type_(self) -> Any: - return self.field_info.annotation - - def __post_init__(self) -> None: - self._type_adapter: TypeAdapter[Any] = TypeAdapter( - Annotated[self.field_info.annotation, self.field_info] - ) - - def get_default(self) -> Any: - if self.field_info.is_required(): - return Undefined - return self.field_info.get_default(call_default_factory=True) - - def validate( - self, - value: Any, - values: Dict[str, Any] = {}, # noqa: B006 - *, - loc: Tuple[Union[int, str], ...] = (), - ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: - try: - return ( - self._type_adapter.validate_python(value, from_attributes=True), - None, - ) - except ValidationError as exc: - return None, _regenerate_error_with_loc( - errors=exc.errors(include_url=False), loc_prefix=loc - ) - - def serialize( - self, - value: Any, - *, - mode: Literal["json", "python"] = "json", - include: Union[IncEx, None] = None, - exclude: Union[IncEx, None] = None, - by_alias: bool = True, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - ) -> Any: - # What calls this code passes a value that already called - # self._type_adapter.validate_python(value) - return self._type_adapter.dump_python( - value, - mode=mode, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - def __hash__(self) -> int: - # Each ModelField is unique for our purposes, to allow making a dict from - # ModelField to its JSON Schema. - return id(self) - - def get_annotation_from_field_info( - annotation: Any, field_info: FieldInfo, field_name: str - ) -> Any: - return annotation - - def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: - return errors # type: ignore[return-value] - - def _model_rebuild(model: Type[BaseModel]) -> None: - model.model_rebuild() - - def _model_dump( - model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any - ) -> Any: - return model.model_dump(mode=mode, **kwargs) - - def _get_model_config(model: BaseModel) -> Any: - return model.model_config - - def get_schema_from_model_field( - *, - field: ModelField, - schema_generator: GenerateJsonSchema, - model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], - separate_input_output_schemas: bool = True, - ) -> Dict[str, Any]: - override_mode: Union[Literal["validation"], None] = ( - None if separate_input_output_schemas else "validation" - ) - # This expects that GenerateJsonSchema was already used to generate the definitions - json_schema = field_mapping[(field, override_mode or field.mode)] - if "$ref" not in json_schema: - # TODO remove when deprecating Pydantic v1 - # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 - json_schema["title"] = ( - field.field_info.title or field.alias.title().replace("_", " ") - ) - return json_schema - - def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: - return {} - - def get_definitions( - *, - fields: List[ModelField], - schema_generator: GenerateJsonSchema, - model_name_map: ModelNameMap, - separate_input_output_schemas: bool = True, - ) -> Tuple[ - Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], - Dict[str, Dict[str, Any]], - ]: - override_mode: Union[Literal["validation"], None] = ( - None if separate_input_output_schemas else "validation" - ) - inputs = [ - (field, override_mode or field.mode, field._type_adapter.core_schema) - for field in fields - ] - field_mapping, definitions = schema_generator.generate_definitions( - inputs=inputs - ) - return field_mapping, definitions # type: ignore[return-value] - - def is_scalar_field(field: ModelField) -> bool: - from fastapi import params - - return field_annotation_is_scalar( - field.field_info.annotation - ) and not isinstance(field.field_info, params.Body) - - def is_sequence_field(field: ModelField) -> bool: - return field_annotation_is_sequence(field.field_info.annotation) - - def is_scalar_sequence_field(field: ModelField) -> bool: - return field_annotation_is_scalar_sequence(field.field_info.annotation) - - def is_bytes_field(field: ModelField) -> bool: - return is_bytes_or_nonable_bytes_annotation(field.type_) - - def is_bytes_sequence_field(field: ModelField) -> bool: - return is_bytes_sequence_annotation(field.type_) - - def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: - cls = type(field_info) - merged_field_info = cls.from_annotation(annotation) - new_field_info = copy(field_info) - new_field_info.metadata = merged_field_info.metadata - new_field_info.annotation = merged_field_info.annotation - return new_field_info - - def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: - origin_type = ( - get_origin(field.field_info.annotation) or field.field_info.annotation - ) - assert issubclass(origin_type, sequence_types) # type: ignore[arg-type] - return sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return] - - def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: - error = ValidationError.from_exception_data( - "Field required", [{"type": "missing", "loc": loc, "input": {}}] - ).errors(include_url=False)[0] - error["input"] = None - return error # type: ignore[return-value] - - def create_body_model( - *, fields: Sequence[ModelField], model_name: str - ) -> Type[BaseModel]: - field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields} - BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload] - return BodyModel - -else: - from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX - from pydantic import AnyUrl as Url # noqa: F401 - from pydantic import ( # type: ignore[assignment] - BaseConfig as BaseConfig, # noqa: F401 - ) - from pydantic import ValidationError as ValidationError # noqa: F401 - from pydantic.class_validators import ( # type: ignore[no-redef] - Validator as Validator, # noqa: F401 - ) - from pydantic.error_wrappers import ( # type: ignore[no-redef] - ErrorWrapper as ErrorWrapper, # noqa: F401 - ) - from pydantic.errors import MissingError - from pydantic.fields import ( # type: ignore[attr-defined] - SHAPE_FROZENSET, - SHAPE_LIST, - SHAPE_SEQUENCE, - SHAPE_SET, - SHAPE_SINGLETON, - SHAPE_TUPLE, - SHAPE_TUPLE_ELLIPSIS, - ) - from pydantic.fields import FieldInfo as FieldInfo - from pydantic.fields import ( # type: ignore[no-redef,attr-defined] - ModelField as ModelField, # noqa: F401 - ) - from pydantic.fields import ( # type: ignore[no-redef,attr-defined] - Required as Required, # noqa: F401 - ) - from pydantic.fields import ( # type: ignore[no-redef,attr-defined] - Undefined as Undefined, - ) - from pydantic.fields import ( # type: ignore[no-redef, attr-defined] - UndefinedType as UndefinedType, # noqa: F401 - ) - from pydantic.schema import ( - field_schema, - get_flat_models_from_fields, - get_model_name_map, - model_process_schema, - ) - from pydantic.schema import ( # type: ignore[no-redef] # noqa: F401 - get_annotation_from_field_info as get_annotation_from_field_info, - ) - from pydantic.typing import ( # type: ignore[no-redef] - evaluate_forwardref as evaluate_forwardref, # noqa: F401 - ) - from pydantic.utils import ( # type: ignore[no-redef] - lenient_issubclass as lenient_issubclass, # noqa: F401 - ) - - GetJsonSchemaHandler = Any # type: ignore[assignment,misc] - JsonSchemaValue = Dict[str, Any] # type: ignore[misc] - CoreSchema = Any # type: ignore[assignment,misc] - - sequence_shapes = { - SHAPE_LIST, - SHAPE_SET, - SHAPE_FROZENSET, - SHAPE_TUPLE, - SHAPE_SEQUENCE, - SHAPE_TUPLE_ELLIPSIS, - } - sequence_shape_to_type = { - SHAPE_LIST: list, - SHAPE_SET: set, - SHAPE_TUPLE: tuple, - SHAPE_SEQUENCE: list, - SHAPE_TUPLE_ELLIPSIS: list, - } - - @dataclass - class GenerateJsonSchema: # type: ignore[no-redef] - ref_template: str - - class PydanticSchemaGenerationError(Exception): # type: ignore[no-redef] - pass - - def with_info_plain_validator_function( # type: ignore[misc] - function: Callable[..., Any], - *, - ref: Union[str, None] = None, - metadata: Any = None, - serialization: Any = None, - ) -> Any: - return {} - - def get_model_definitions( - *, - flat_models: Set[Union[Type[BaseModel], Type[Enum]]], - model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], - ) -> Dict[str, Any]: - definitions: Dict[str, Dict[str, Any]] = {} - for model in flat_models: - m_schema, m_definitions, m_nested_models = model_process_schema( - model, model_name_map=model_name_map, ref_prefix=REF_PREFIX - ) - 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 - - def is_pv1_scalar_field(field: ModelField) -> bool: - from fastapi import params - - field_info = field.field_info - if not ( - field.shape == SHAPE_SINGLETON # type: ignore[attr-defined] - and not lenient_issubclass(field.type_, BaseModel) - and not lenient_issubclass(field.type_, dict) - and not field_annotation_is_sequence(field.type_) - and not is_dataclass(field.type_) - and not isinstance(field_info, params.Body) - ): - return False - if field.sub_fields: # type: ignore[attr-defined] - if not all( - is_pv1_scalar_field(f) - for f in field.sub_fields # type: ignore[attr-defined] - ): - return False - return True - - def is_pv1_scalar_sequence_field(field: ModelField) -> bool: - if (field.shape in sequence_shapes) and not lenient_issubclass( # type: ignore[attr-defined] - field.type_, BaseModel - ): - if field.sub_fields is not None: # type: ignore[attr-defined] - for sub_field in field.sub_fields: # type: ignore[attr-defined] - if not is_pv1_scalar_field(sub_field): - return False - return True - if _annotation_is_sequence(field.type_): - return True - return False - - def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: - use_errors: List[Any] = [] - for error in errors: - if isinstance(error, ErrorWrapper): - new_errors = ValidationError( # type: ignore[call-arg] - errors=[error], model=RequestErrorModel - ).errors() - use_errors.extend(new_errors) - elif isinstance(error, list): - use_errors.extend(_normalize_errors(error)) - else: - use_errors.append(error) - return use_errors - - def _model_rebuild(model: Type[BaseModel]) -> None: - model.update_forward_refs() - - def _model_dump( - model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any - ) -> Any: - return model.dict(**kwargs) - - def _get_model_config(model: BaseModel) -> Any: - return model.__config__ # type: ignore[attr-defined] - - def get_schema_from_model_field( - *, - field: ModelField, - schema_generator: GenerateJsonSchema, - model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], - separate_input_output_schemas: bool = True, - ) -> Dict[str, Any]: - # This expects that GenerateJsonSchema was already used to generate the definitions - return field_schema( # type: ignore[no-any-return] - field, model_name_map=model_name_map, ref_prefix=REF_PREFIX - )[0] - - def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: - models = get_flat_models_from_fields(fields, known_models=set()) - return get_model_name_map(models) # type: ignore[no-any-return] - - def get_definitions( - *, - fields: List[ModelField], - schema_generator: GenerateJsonSchema, - model_name_map: ModelNameMap, - separate_input_output_schemas: bool = True, - ) -> Tuple[ - Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], - Dict[str, Dict[str, Any]], - ]: - models = get_flat_models_from_fields(fields, known_models=set()) - return {}, get_model_definitions( - flat_models=models, model_name_map=model_name_map - ) - - def is_scalar_field(field: ModelField) -> bool: - return is_pv1_scalar_field(field) - - def is_sequence_field(field: ModelField) -> bool: - return field.shape in sequence_shapes or _annotation_is_sequence(field.type_) # type: ignore[attr-defined] - - def is_scalar_sequence_field(field: ModelField) -> bool: - return is_pv1_scalar_sequence_field(field) - - def is_bytes_field(field: ModelField) -> bool: - return lenient_issubclass(field.type_, bytes) - - def is_bytes_sequence_field(field: ModelField) -> bool: - return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes) # type: ignore[attr-defined] - - def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: - return copy(field_info) - - def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: - return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return,attr-defined] - - def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: - missing_field_error = ErrorWrapper(MissingError(), loc=loc) # type: ignore[call-arg] - new_error = ValidationError([missing_field_error], RequestErrorModel) - return new_error.errors()[0] # type: ignore[return-value] - - def create_body_model( - *, fields: Sequence[ModelField], model_name: str - ) -> Type[BaseModel]: - BodyModel = create_model(model_name) - for f in fields: - BodyModel.__fields__[f.name] = f # type: ignore[index] - return BodyModel - - -def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] -) -> List[Dict[str, Any]]: - updated_loc_errors: List[Any] = [ - {**err, "loc": loc_prefix + err.get("loc", ())} - for err in _normalize_errors(errors) - ] - - return updated_loc_errors - - -def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: - if lenient_issubclass(annotation, (str, bytes)): - return False - return lenient_issubclass(annotation, sequence_types) - - -def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: - return _annotation_is_sequence(annotation) or _annotation_is_sequence( - get_origin(annotation) - ) - - -def value_is_sequence(value: Any) -> bool: - return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type] - - -def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: - return ( - lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile)) - or _annotation_is_sequence(annotation) - or is_dataclass(annotation) - ) - - -def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) - - return ( - _annotation_is_complex(annotation) - or _annotation_is_complex(origin) - or hasattr(origin, "__pydantic_core_schema__") - or hasattr(origin, "__get_pydantic_core_schema__") - ) - - -def field_annotation_is_scalar(annotation: Any) -> bool: - # handle Ellipsis here to make tuple[int, ...] work nicely - return annotation is Ellipsis or not field_annotation_is_complex(annotation) - - -def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - at_least_one_scalar_sequence = False - for arg in get_args(annotation): - if field_annotation_is_scalar_sequence(arg): - at_least_one_scalar_sequence = True - continue - elif not field_annotation_is_scalar(arg): - return False - return at_least_one_scalar_sequence - return field_annotation_is_sequence(annotation) and all( - field_annotation_is_scalar(sub_annotation) - for sub_annotation in get_args(annotation) - ) - - -def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool: - if lenient_issubclass(annotation, bytes): - return True - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - for arg in get_args(annotation): - if lenient_issubclass(arg, bytes): - return True - return False - - -def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool: - if lenient_issubclass(annotation, UploadFile): - return True - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - for arg in get_args(annotation): - if lenient_issubclass(arg, UploadFile): - return True - return False - - -def is_bytes_sequence_annotation(annotation: Any) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - at_least_one = False - for arg in get_args(annotation): - if is_bytes_sequence_annotation(arg): - at_least_one = True - continue - return at_least_one - return field_annotation_is_sequence(annotation) and all( - is_bytes_or_nonable_bytes_annotation(sub_annotation) - for sub_annotation in get_args(annotation) - ) - - -def is_uploadfile_sequence_annotation(annotation: Any) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - at_least_one = False - for arg in get_args(annotation): - if is_uploadfile_sequence_annotation(arg): - at_least_one = True - continue - return at_least_one - return field_annotation_is_sequence(annotation) and all( - is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation) - for sub_annotation in get_args(annotation) - ) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py new file mode 100644 index 0000000000..0aadd68de2 --- /dev/null +++ b/fastapi/_compat/__init__.py @@ -0,0 +1,50 @@ +from .main import BaseConfig as BaseConfig +from .main import PydanticSchemaGenerationError as PydanticSchemaGenerationError +from .main import RequiredParam as RequiredParam +from .main import Undefined as Undefined +from .main import UndefinedType as UndefinedType +from .main import Url as Url +from .main import Validator as Validator +from .main import _get_model_config as _get_model_config +from .main import _is_error_wrapper as _is_error_wrapper +from .main import _is_model_class as _is_model_class +from .main import _is_model_field as _is_model_field +from .main import _is_undefined as _is_undefined +from .main import _model_dump as _model_dump +from .main import _model_rebuild as _model_rebuild +from .main import copy_field_info as copy_field_info +from .main import create_body_model as create_body_model +from .main import evaluate_forwardref as evaluate_forwardref +from .main import get_annotation_from_field_info as get_annotation_from_field_info +from .main import get_cached_model_fields as get_cached_model_fields +from .main import get_compat_model_name_map as get_compat_model_name_map +from .main import get_definitions as get_definitions +from .main import get_missing_field_error as get_missing_field_error +from .main import get_schema_from_model_field as get_schema_from_model_field +from .main import is_bytes_field as is_bytes_field +from .main import is_bytes_sequence_field as is_bytes_sequence_field +from .main import is_scalar_field as is_scalar_field +from .main import is_scalar_sequence_field as is_scalar_sequence_field +from .main import is_sequence_field as is_sequence_field +from .main import serialize_sequence_value as serialize_sequence_value +from .main import ( + with_info_plain_validator_function as with_info_plain_validator_function, +) +from .may_v1 import CoreSchema as CoreSchema +from .may_v1 import GetJsonSchemaHandler as GetJsonSchemaHandler +from .may_v1 import JsonSchemaValue as JsonSchemaValue +from .may_v1 import _normalize_errors as _normalize_errors +from .model_field import ModelField as ModelField +from .shared import PYDANTIC_V2 as PYDANTIC_V2 +from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE +from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1 +from .shared import field_annotation_is_scalar as field_annotation_is_scalar +from .shared import ( + is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation, +) +from .shared import ( + is_uploadfile_sequence_annotation as is_uploadfile_sequence_annotation, +) +from .shared import lenient_issubclass as lenient_issubclass +from .shared import sequence_types as sequence_types +from .shared import value_is_sequence as value_is_sequence diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py new file mode 100644 index 0000000000..e5275950e8 --- /dev/null +++ b/fastapi/_compat/main.py @@ -0,0 +1,362 @@ +import sys +from functools import lru_cache +from typing import ( + Any, + Dict, + List, + Sequence, + Tuple, + Type, +) + +from fastapi._compat import may_v1 +from fastapi._compat.shared import PYDANTIC_V2, lenient_issubclass +from fastapi.types import ModelNameMap +from pydantic import BaseModel +from typing_extensions import Literal + +from .model_field import ModelField + +if PYDANTIC_V2: + from .v2 import BaseConfig as BaseConfig + from .v2 import FieldInfo as FieldInfo + from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError + from .v2 import RequiredParam as RequiredParam + from .v2 import Undefined as Undefined + from .v2 import UndefinedType as UndefinedType + from .v2 import Url as Url + from .v2 import Validator as Validator + from .v2 import evaluate_forwardref as evaluate_forwardref + from .v2 import get_missing_field_error as get_missing_field_error + from .v2 import ( + with_info_plain_validator_function as with_info_plain_validator_function, + ) +else: + from .v1 import BaseConfig as BaseConfig # type: ignore[assignment] + from .v1 import FieldInfo as FieldInfo + from .v1 import ( # type: ignore[assignment] + PydanticSchemaGenerationError as PydanticSchemaGenerationError, + ) + from .v1 import RequiredParam as RequiredParam + from .v1 import Undefined as Undefined + from .v1 import UndefinedType as UndefinedType + from .v1 import Url as Url # type: ignore[assignment] + from .v1 import Validator as Validator + from .v1 import evaluate_forwardref as evaluate_forwardref + from .v1 import get_missing_field_error as get_missing_field_error + from .v1 import ( # type: ignore[assignment] + with_info_plain_validator_function as with_info_plain_validator_function, + ) + + +@lru_cache +def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: + if lenient_issubclass(model, may_v1.BaseModel): + from fastapi._compat import v1 + + return v1.get_model_fields(model) + else: + from . import v2 + + return v2.get_model_fields(model) # type: ignore[return-value] + + +def _is_undefined(value: object) -> bool: + if isinstance(value, may_v1.UndefinedType): + return True + elif PYDANTIC_V2: + from . import v2 + + return isinstance(value, v2.UndefinedType) + return False + + +def _get_model_config(model: BaseModel) -> Any: + if isinstance(model, may_v1.BaseModel): + from fastapi._compat import v1 + + return v1._get_model_config(model) + elif PYDANTIC_V2: + from . import v2 + + return v2._get_model_config(model) + + +def _model_dump( + model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any +) -> Any: + if isinstance(model, may_v1.BaseModel): + from fastapi._compat import v1 + + return v1._model_dump(model, mode=mode, **kwargs) + elif PYDANTIC_V2: + from . import v2 + + return v2._model_dump(model, mode=mode, **kwargs) + + +def _is_error_wrapper(exc: Exception) -> bool: + if isinstance(exc, may_v1.ErrorWrapper): + return True + elif PYDANTIC_V2: + from . import v2 + + return isinstance(exc, v2.ErrorWrapper) + return False + + +def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: + if isinstance(field_info, may_v1.FieldInfo): + from fastapi._compat import v1 + + return v1.copy_field_info(field_info=field_info, annotation=annotation) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.copy_field_info(field_info=field_info, annotation=annotation) + + +def create_body_model( + *, fields: Sequence[ModelField], model_name: str +) -> Type[BaseModel]: + if fields and isinstance(fields[0], may_v1.ModelField): + from fastapi._compat import v1 + + return v1.create_body_model(fields=fields, model_name=model_name) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.create_body_model(fields=fields, model_name=model_name) # type: ignore[arg-type] + + +def get_annotation_from_field_info( + annotation: Any, field_info: FieldInfo, field_name: str +) -> Any: + if isinstance(field_info, may_v1.FieldInfo): + from fastapi._compat import v1 + + return v1.get_annotation_from_field_info( + annotation=annotation, field_info=field_info, field_name=field_name + ) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.get_annotation_from_field_info( + annotation=annotation, field_info=field_info, field_name=field_name + ) + + +def is_bytes_field(field: ModelField) -> bool: + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + + return v1.is_bytes_field(field) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.is_bytes_field(field) # type: ignore[arg-type] + + +def is_bytes_sequence_field(field: ModelField) -> bool: + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + + return v1.is_bytes_sequence_field(field) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.is_bytes_sequence_field(field) # type: ignore[arg-type] + + +def is_scalar_field(field: ModelField) -> bool: + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + + return v1.is_scalar_field(field) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.is_scalar_field(field) # type: ignore[arg-type] + + +def is_scalar_sequence_field(field: ModelField) -> bool: + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + + return v1.is_scalar_sequence_field(field) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.is_scalar_sequence_field(field) # type: ignore[arg-type] + + +def is_sequence_field(field: ModelField) -> bool: + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + + return v1.is_sequence_field(field) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.is_sequence_field(field) # type: ignore[arg-type] + + +def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + + return v1.serialize_sequence_value(field=field, value=value) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type] + + +def _model_rebuild(model: Type[BaseModel]) -> None: + if lenient_issubclass(model, may_v1.BaseModel): + from fastapi._compat import v1 + + v1._model_rebuild(model) + elif PYDANTIC_V2: + from . import v2 + + v2._model_rebuild(model) + + +def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: + v1_model_fields = [ + field for field in fields if isinstance(field, may_v1.ModelField) + ] + if v1_model_fields: + from fastapi._compat import v1 + + v1_flat_models = v1.get_flat_models_from_fields( + v1_model_fields, known_models=set() + ) + all_flat_models = v1_flat_models + else: + all_flat_models = set() + if PYDANTIC_V2: + from . import v2 + + v2_model_fields = [ + field for field in fields if isinstance(field, v2.ModelField) + ] + v2_flat_models = v2.get_flat_models_from_fields( + v2_model_fields, known_models=set() + ) + all_flat_models = all_flat_models.union(v2_flat_models) + + model_name_map = v2.get_model_name_map(all_flat_models) + return model_name_map + from fastapi._compat import v1 + + model_name_map = v1.get_model_name_map(all_flat_models) + return model_name_map + + +def get_definitions( + *, + fields: List[ModelField], + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, +) -> Tuple[ + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], + may_v1.JsonSchemaValue, + ], + Dict[str, Dict[str, Any]], +]: + if sys.version_info < (3, 14): + v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)] + v1_field_maps, v1_definitions = may_v1.get_definitions( + fields=v1_fields, + model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, + ) + if not PYDANTIC_V2: + return v1_field_maps, v1_definitions + else: + from . import v2 + + v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] + v2_field_maps, v2_definitions = v2.get_definitions( + fields=v2_fields, + model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, + ) + all_definitions = {**v1_definitions, **v2_definitions} + all_field_maps = {**v1_field_maps, **v2_field_maps} + return all_field_maps, all_definitions + + # Pydantic v1 is not supported since Python 3.14 + else: + from . import v2 + + v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] + v2_field_maps, v2_definitions = v2.get_definitions( + fields=v2_fields, + model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, + ) + return v2_field_maps, v2_definitions + + +def get_schema_from_model_field( + *, + field: ModelField, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], + may_v1.JsonSchemaValue, + ], + separate_input_output_schemas: bool = True, +) -> Dict[str, Any]: + if isinstance(field, may_v1.ModelField): + from fastapi._compat import v1 + + return v1.get_schema_from_model_field( + field=field, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + else: + assert PYDANTIC_V2 + from . import v2 + + return v2.get_schema_from_model_field( + field=field, # type: ignore[arg-type] + model_name_map=model_name_map, + field_mapping=field_mapping, # type: ignore[arg-type] + separate_input_output_schemas=separate_input_output_schemas, + ) + + +def _is_model_field(value: Any) -> bool: + if isinstance(value, may_v1.ModelField): + return True + elif PYDANTIC_V2: + from . import v2 + + return isinstance(value, v2.ModelField) + return False + + +def _is_model_class(value: Any) -> bool: + if lenient_issubclass(value, may_v1.BaseModel): + return True + elif PYDANTIC_V2: + from . import v2 + + return lenient_issubclass(value, v2.BaseModel) # type: ignore[attr-defined] + return False diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py new file mode 100644 index 0000000000..beea4d167f --- /dev/null +++ b/fastapi/_compat/may_v1.py @@ -0,0 +1,123 @@ +import sys +from typing import Any, Dict, List, Literal, Sequence, Tuple, Type, Union + +from fastapi.types import ModelNameMap + +if sys.version_info >= (3, 14): + + class AnyUrl: + pass + + class BaseConfig: + pass + + class BaseModel: + pass + + class Color: + pass + + class CoreSchema: + pass + + class ErrorWrapper: + pass + + class FieldInfo: + pass + + class GetJsonSchemaHandler: + pass + + class JsonSchemaValue: + pass + + class ModelField: + pass + + class NameEmail: + pass + + class RequiredParam: + pass + + class SecretBytes: + pass + + class SecretStr: + pass + + class Undefined: + pass + + class UndefinedType: + pass + + class Url: + pass + + from .v2 import ValidationError, create_model + + def get_definitions( + *, + fields: List[ModelField], + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, + ) -> Tuple[ + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + Dict[str, Dict[str, Any]], + ]: + return {}, {} # pragma: no cover + + +else: + from .v1 import AnyUrl as AnyUrl + from .v1 import BaseConfig as BaseConfig + from .v1 import BaseModel as BaseModel + from .v1 import Color as Color + from .v1 import CoreSchema as CoreSchema + from .v1 import ErrorWrapper as ErrorWrapper + from .v1 import FieldInfo as FieldInfo + from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler + from .v1 import JsonSchemaValue as JsonSchemaValue + from .v1 import ModelField as ModelField + from .v1 import NameEmail as NameEmail + from .v1 import RequiredParam as RequiredParam + from .v1 import SecretBytes as SecretBytes + from .v1 import SecretStr as SecretStr + from .v1 import Undefined as Undefined + from .v1 import UndefinedType as UndefinedType + from .v1 import Url as Url + from .v1 import ValidationError, create_model + from .v1 import get_definitions as get_definitions + + +RequestErrorModel: Type[BaseModel] = create_model("Request") + + +def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: + use_errors: List[Any] = [] + for error in errors: + if isinstance(error, ErrorWrapper): + new_errors = ValidationError( # type: ignore[call-arg] + errors=[error], model=RequestErrorModel + ).errors() + use_errors.extend(new_errors) + elif isinstance(error, list): + use_errors.extend(_normalize_errors(error)) + else: + use_errors.append(error) + return use_errors + + +def _regenerate_error_with_loc( + *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] +) -> List[Dict[str, Any]]: + updated_loc_errors: List[Any] = [ + {**err, "loc": loc_prefix + err.get("loc", ())} + for err in _normalize_errors(errors) + ] + + return updated_loc_errors diff --git a/fastapi/_compat/model_field.py b/fastapi/_compat/model_field.py new file mode 100644 index 0000000000..fa2008c5e0 --- /dev/null +++ b/fastapi/_compat/model_field.py @@ -0,0 +1,53 @@ +from typing import ( + Any, + Dict, + List, + Tuple, + Union, +) + +from fastapi.types import IncEx +from pydantic.fields import FieldInfo +from typing_extensions import Literal, Protocol + + +class ModelField(Protocol): + field_info: "FieldInfo" + name: str + mode: Literal["validation", "serialization"] = "validation" + _version: Literal["v1", "v2"] = "v1" + + @property + def alias(self) -> str: ... + + @property + def required(self) -> bool: ... + + @property + def default(self) -> Any: ... + + @property + def type_(self) -> Any: ... + + def get_default(self) -> Any: ... + + def validate( + self, + value: Any, + values: Dict[str, Any] = {}, # noqa: B006 + *, + loc: Tuple[Union[int, str], ...] = (), + ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: ... + + def serialize( + self, + value: Any, + *, + mode: Literal["json", "python"] = "json", + include: Union[IncEx, None] = None, + exclude: Union[IncEx, None] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> Any: ... diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py new file mode 100644 index 0000000000..cabf482283 --- /dev/null +++ b/fastapi/_compat/shared.py @@ -0,0 +1,211 @@ +import sys +import types +import typing +from collections import deque +from dataclasses import is_dataclass +from typing import ( + Any, + Deque, + FrozenSet, + List, + Mapping, + Sequence, + Set, + Tuple, + Type, + Union, +) + +from fastapi._compat import may_v1 +from fastapi.types import UnionType +from pydantic import BaseModel +from pydantic.version import VERSION as PYDANTIC_VERSION +from starlette.datastructures import UploadFile +from typing_extensions import Annotated, get_args, get_origin + +# Copy from Pydantic v2, compatible with v1 +if sys.version_info < (3, 9): + # Pydantic no longer supports Python 3.8, this might be incorrect, but the code + # this is used for is also never reached in this codebase, as it's a copy of + # Pydantic's lenient_issubclass, just for compatibility with v1 + # TODO: remove when dropping support for Python 3.8 + WithArgsTypes: Tuple[Any, ...] = () +elif sys.version_info < (3, 10): + WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined] +else: + WithArgsTypes: tuple[Any, ...] = ( + typing._GenericAlias, # type: ignore[attr-defined] + types.GenericAlias, + types.UnionType, + ) # pyright: ignore[reportAttributeAccessIssue] + +PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) +PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 + + +sequence_annotation_to_type = { + Sequence: list, + List: list, + list: list, + Tuple: tuple, + tuple: tuple, + Set: set, + set: set, + FrozenSet: frozenset, + frozenset: frozenset, + Deque: deque, + deque: deque, +} + +sequence_types = tuple(sequence_annotation_to_type.keys()) + +Url: Type[Any] + + +# Copy of Pydantic v2, compatible with v1 +def lenient_issubclass( + cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None] +) -> bool: + try: + return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] + except TypeError: # pragma: no cover + if isinstance(cls, WithArgsTypes): + return False + raise # pragma: no cover + + +def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: + if lenient_issubclass(annotation, (str, bytes)): + return False + return lenient_issubclass(annotation, sequence_types) # type: ignore[arg-type] + + +def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if field_annotation_is_sequence(arg): + return True + return False + return _annotation_is_sequence(annotation) or _annotation_is_sequence( + get_origin(annotation) + ) + + +def value_is_sequence(value: Any) -> bool: + return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type] + + +def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: + return ( + lenient_issubclass( + annotation, (BaseModel, may_v1.BaseModel, Mapping, UploadFile) + ) + or _annotation_is_sequence(annotation) + or is_dataclass(annotation) + ) + + +def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) + + if origin is Annotated: + return field_annotation_is_complex(get_args(annotation)[0]) + + return ( + _annotation_is_complex(annotation) + or _annotation_is_complex(origin) + or hasattr(origin, "__pydantic_core_schema__") + or hasattr(origin, "__get_pydantic_core_schema__") + ) + + +def field_annotation_is_scalar(annotation: Any) -> bool: + # handle Ellipsis here to make tuple[int, ...] work nicely + return annotation is Ellipsis or not field_annotation_is_complex(annotation) + + +def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one_scalar_sequence = False + for arg in get_args(annotation): + if field_annotation_is_scalar_sequence(arg): + at_least_one_scalar_sequence = True + continue + elif not field_annotation_is_scalar(arg): + return False + return at_least_one_scalar_sequence + return field_annotation_is_sequence(annotation) and all( + field_annotation_is_scalar(sub_annotation) + for sub_annotation in get_args(annotation) + ) + + +def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool: + if lenient_issubclass(annotation, bytes): + return True + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if lenient_issubclass(arg, bytes): + return True + return False + + +def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool: + if lenient_issubclass(annotation, UploadFile): + return True + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if lenient_issubclass(arg, UploadFile): + return True + return False + + +def is_bytes_sequence_annotation(annotation: Any) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one = False + for arg in get_args(annotation): + if is_bytes_sequence_annotation(arg): + at_least_one = True + continue + return at_least_one + return field_annotation_is_sequence(annotation) and all( + is_bytes_or_nonable_bytes_annotation(sub_annotation) + for sub_annotation in get_args(annotation) + ) + + +def is_uploadfile_sequence_annotation(annotation: Any) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one = False + for arg in get_args(annotation): + if is_uploadfile_sequence_annotation(arg): + at_least_one = True + continue + return at_least_one + return field_annotation_is_sequence(annotation) and all( + is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation) + for sub_annotation in get_args(annotation) + ) + + +def annotation_is_pydantic_v1(annotation: Any) -> bool: + if lenient_issubclass(annotation, may_v1.BaseModel): + return True + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if lenient_issubclass(arg, may_v1.BaseModel): + return True + if field_annotation_is_sequence(annotation): + for sub_annotation in get_args(annotation): + if annotation_is_pydantic_v1(sub_annotation): + return True + return False diff --git a/fastapi/_compat/v1.py b/fastapi/_compat/v1.py new file mode 100644 index 0000000000..e17ce8beaf --- /dev/null +++ b/fastapi/_compat/v1.py @@ -0,0 +1,312 @@ +from copy import copy +from dataclasses import dataclass, is_dataclass +from enum import Enum +from typing import ( + Any, + Callable, + Dict, + List, + Sequence, + Set, + Tuple, + Type, + Union, +) + +from fastapi._compat import shared +from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX +from fastapi.types import ModelNameMap +from pydantic.version import VERSION as PYDANTIC_VERSION +from typing_extensions import Literal + +PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) +PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 +# Keeping old "Required" functionality from Pydantic V1, without +# shadowing typing.Required. +RequiredParam: Any = Ellipsis + +if not PYDANTIC_V2: + from pydantic import BaseConfig as BaseConfig + from pydantic import BaseModel as BaseModel + from pydantic import ValidationError as ValidationError + from pydantic import create_model as create_model + from pydantic.class_validators import Validator as Validator + from pydantic.color import Color as Color + from pydantic.error_wrappers import ErrorWrapper as ErrorWrapper + from pydantic.errors import MissingError + from pydantic.fields import ( # type: ignore[attr-defined] + SHAPE_FROZENSET, + SHAPE_LIST, + SHAPE_SEQUENCE, + SHAPE_SET, + SHAPE_SINGLETON, + SHAPE_TUPLE, + SHAPE_TUPLE_ELLIPSIS, + ) + from pydantic.fields import FieldInfo as FieldInfo + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined] + from pydantic.fields import Undefined as Undefined # type: ignore[attr-defined] + from pydantic.fields import ( # type: ignore[attr-defined] + UndefinedType as UndefinedType, + ) + from pydantic.networks import AnyUrl as AnyUrl + from pydantic.networks import NameEmail as NameEmail + from pydantic.schema import TypeModelSet as TypeModelSet + from pydantic.schema import ( + field_schema, + model_process_schema, + ) + from pydantic.schema import ( + get_annotation_from_field_info as get_annotation_from_field_info, + ) + from pydantic.schema import get_flat_models_from_field as get_flat_models_from_field + from pydantic.schema import ( + get_flat_models_from_fields as get_flat_models_from_fields, + ) + from pydantic.schema import get_model_name_map as get_model_name_map + from pydantic.types import SecretBytes as SecretBytes + from pydantic.types import SecretStr as SecretStr + from pydantic.typing import evaluate_forwardref as evaluate_forwardref + from pydantic.utils import lenient_issubclass as lenient_issubclass + + +else: + from pydantic.v1 import BaseConfig as BaseConfig # type: ignore[assignment] + from pydantic.v1 import BaseModel as BaseModel # type: ignore[assignment] + from pydantic.v1 import ( # type: ignore[assignment] + ValidationError as ValidationError, + ) + from pydantic.v1 import create_model as create_model # type: ignore[no-redef] + from pydantic.v1.class_validators import Validator as Validator + from pydantic.v1.color import Color as Color # type: ignore[assignment] + from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper + from pydantic.v1.errors import MissingError + from pydantic.v1.fields import ( + SHAPE_FROZENSET, + SHAPE_LIST, + SHAPE_SEQUENCE, + SHAPE_SET, + SHAPE_SINGLETON, + SHAPE_TUPLE, + SHAPE_TUPLE_ELLIPSIS, + ) + from pydantic.v1.fields import FieldInfo as FieldInfo # type: ignore[assignment] + from pydantic.v1.fields import ModelField as ModelField + from pydantic.v1.fields import Undefined as Undefined + from pydantic.v1.fields import UndefinedType as UndefinedType + from pydantic.v1.networks import AnyUrl as AnyUrl + from pydantic.v1.networks import ( # type: ignore[assignment] + NameEmail as NameEmail, + ) + from pydantic.v1.schema import TypeModelSet as TypeModelSet + from pydantic.v1.schema import ( + field_schema, + model_process_schema, + ) + from pydantic.v1.schema import ( + get_annotation_from_field_info as get_annotation_from_field_info, + ) + from pydantic.v1.schema import ( + get_flat_models_from_field as get_flat_models_from_field, + ) + from pydantic.v1.schema import ( + get_flat_models_from_fields as get_flat_models_from_fields, + ) + from pydantic.v1.schema import get_model_name_map as get_model_name_map + from pydantic.v1.types import ( # type: ignore[assignment] + SecretBytes as SecretBytes, + ) + from pydantic.v1.types import ( # type: ignore[assignment] + SecretStr as SecretStr, + ) + from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref + from pydantic.v1.utils import lenient_issubclass as lenient_issubclass + + +GetJsonSchemaHandler = Any +JsonSchemaValue = Dict[str, Any] +CoreSchema = Any +Url = AnyUrl + +sequence_shapes = { + SHAPE_LIST, + SHAPE_SET, + SHAPE_FROZENSET, + SHAPE_TUPLE, + SHAPE_SEQUENCE, + SHAPE_TUPLE_ELLIPSIS, +} +sequence_shape_to_type = { + SHAPE_LIST: list, + SHAPE_SET: set, + SHAPE_TUPLE: tuple, + SHAPE_SEQUENCE: list, + SHAPE_TUPLE_ELLIPSIS: list, +} + + +@dataclass +class GenerateJsonSchema: + ref_template: str + + +class PydanticSchemaGenerationError(Exception): + pass + + +RequestErrorModel: Type[BaseModel] = create_model("Request") + + +def with_info_plain_validator_function( + function: Callable[..., Any], + *, + ref: Union[str, None] = None, + metadata: Any = None, + serialization: Any = None, +) -> Any: + return {} + + +def get_model_definitions( + *, + flat_models: Set[Union[Type[BaseModel], Type[Enum]]], + model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], +) -> Dict[str, Any]: + definitions: Dict[str, Dict[str, Any]] = {} + for model in flat_models: + m_schema, m_definitions, m_nested_models = model_process_schema( + model, model_name_map=model_name_map, ref_prefix=REF_PREFIX + ) + definitions.update(m_definitions) + model_name = model_name_map[model] + definitions[model_name] = m_schema + for m_schema in definitions.values(): + if "description" in m_schema: + m_schema["description"] = m_schema["description"].split("\f")[0] + return definitions + + +def is_pv1_scalar_field(field: ModelField) -> bool: + from fastapi import params + + field_info = field.field_info + if not ( + field.shape == SHAPE_SINGLETON + and not lenient_issubclass(field.type_, BaseModel) + and not lenient_issubclass(field.type_, dict) + and not shared.field_annotation_is_sequence(field.type_) + and not is_dataclass(field.type_) + and not isinstance(field_info, params.Body) + ): + return False + if field.sub_fields: + if not all(is_pv1_scalar_field(f) for f in field.sub_fields): + return False + return True + + +def is_pv1_scalar_sequence_field(field: ModelField) -> bool: + if (field.shape in sequence_shapes) and not lenient_issubclass( + field.type_, BaseModel + ): + if field.sub_fields is not None: + for sub_field in field.sub_fields: + if not is_pv1_scalar_field(sub_field): + return False + return True + if shared._annotation_is_sequence(field.type_): + return True + return False + + +def _model_rebuild(model: Type[BaseModel]) -> None: + model.update_forward_refs() + + +def _model_dump( + model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any +) -> Any: + return model.dict(**kwargs) + + +def _get_model_config(model: BaseModel) -> Any: + return model.__config__ # type: ignore[attr-defined] + + +def get_schema_from_model_field( + *, + field: ModelField, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + separate_input_output_schemas: bool = True, +) -> Dict[str, Any]: + return field_schema( # type: ignore[no-any-return] + field, model_name_map=model_name_map, ref_prefix=REF_PREFIX + )[0] + + +# def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: +# models = get_flat_models_from_fields(fields, known_models=set()) +# return get_model_name_map(models) # type: ignore[no-any-return] + + +def get_definitions( + *, + fields: List[ModelField], + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, +) -> Tuple[ + Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + Dict[str, Dict[str, Any]], +]: + models = get_flat_models_from_fields(fields, known_models=set()) + return {}, get_model_definitions(flat_models=models, model_name_map=model_name_map) + + +def is_scalar_field(field: ModelField) -> bool: + return is_pv1_scalar_field(field) + + +def is_sequence_field(field: ModelField) -> bool: + return field.shape in sequence_shapes or shared._annotation_is_sequence(field.type_) + + +def is_scalar_sequence_field(field: ModelField) -> bool: + return is_pv1_scalar_sequence_field(field) + + +def is_bytes_field(field: ModelField) -> bool: + return lenient_issubclass(field.type_, bytes) # type: ignore[no-any-return] + + +def is_bytes_sequence_field(field: ModelField) -> bool: + return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes) + + +def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: + return copy(field_info) + + +def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: + return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return] + + +def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: + missing_field_error = ErrorWrapper(MissingError(), loc=loc) + new_error = ValidationError([missing_field_error], RequestErrorModel) + return new_error.errors()[0] # type: ignore[return-value] + + +def create_body_model( + *, fields: Sequence[ModelField], model_name: str +) -> Type[BaseModel]: + BodyModel = create_model(model_name) + for f in fields: + BodyModel.__fields__[f.name] = f # type: ignore[index] + return BodyModel + + +def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: + return list(model.__fields__.values()) # type: ignore[attr-defined] diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py new file mode 100644 index 0000000000..fb2c691d87 --- /dev/null +++ b/fastapi/_compat/v2.py @@ -0,0 +1,459 @@ +import re +import warnings +from copy import copy, deepcopy +from dataclasses import dataclass +from enum import Enum +from typing import ( + Any, + Dict, + List, + Sequence, + Set, + Tuple, + Type, + Union, + cast, +) + +from fastapi._compat import may_v1, shared +from fastapi.openapi.constants import REF_TEMPLATE +from fastapi.types import IncEx, ModelNameMap +from pydantic import BaseModel, TypeAdapter, create_model +from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError +from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation +from pydantic import ValidationError as ValidationError +from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] + GetJsonSchemaHandler as GetJsonSchemaHandler, +) +from pydantic._internal._typing_extra import eval_type_lenient +from pydantic._internal._utils import lenient_issubclass as lenient_issubclass +from pydantic.fields import FieldInfo as FieldInfo +from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema +from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue +from pydantic_core import CoreSchema as CoreSchema +from pydantic_core import PydanticUndefined, PydanticUndefinedType +from pydantic_core import Url as Url +from typing_extensions import Annotated, Literal, get_args, get_origin + +try: + from pydantic_core.core_schema import ( + with_info_plain_validator_function as with_info_plain_validator_function, + ) +except ImportError: # pragma: no cover + from pydantic_core.core_schema import ( + general_plain_validator_function as with_info_plain_validator_function, # noqa: F401 + ) + +RequiredParam = PydanticUndefined +Undefined = PydanticUndefined +UndefinedType = PydanticUndefinedType +evaluate_forwardref = eval_type_lenient +Validator = Any + + +class BaseConfig: + pass + + +class ErrorWrapper(Exception): + pass + + +@dataclass +class ModelField: + field_info: FieldInfo + name: str + mode: Literal["validation", "serialization"] = "validation" + + @property + def alias(self) -> str: + a = self.field_info.alias + return a if a is not None else self.name + + @property + def required(self) -> bool: + return self.field_info.is_required() + + @property + def default(self) -> Any: + return self.get_default() + + @property + def type_(self) -> Any: + return self.field_info.annotation + + def __post_init__(self) -> None: + with warnings.catch_warnings(): + # Pydantic >= 2.12.0 warns about field specific metadata that is unused + # (e.g. `TypeAdapter(Annotated[int, Field(alias='b')])`). In some cases, we + # end up building the type adapter from a model field annotation so we + # need to ignore the warning: + if shared.PYDANTIC_VERSION_MINOR_TUPLE >= (2, 12): + from pydantic.warnings import UnsupportedFieldAttributeWarning + + warnings.simplefilter( + "ignore", category=UnsupportedFieldAttributeWarning + ) + self._type_adapter: TypeAdapter[Any] = TypeAdapter( + Annotated[self.field_info.annotation, self.field_info] + ) + + def get_default(self) -> Any: + if self.field_info.is_required(): + return Undefined + return self.field_info.get_default(call_default_factory=True) + + def validate( + self, + value: Any, + values: Dict[str, Any] = {}, # noqa: B006 + *, + loc: Tuple[Union[int, str], ...] = (), + ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: + try: + return ( + self._type_adapter.validate_python(value, from_attributes=True), + None, + ) + except ValidationError as exc: + return None, may_v1._regenerate_error_with_loc( + errors=exc.errors(include_url=False), loc_prefix=loc + ) + + def serialize( + self, + value: Any, + *, + mode: Literal["json", "python"] = "json", + include: Union[IncEx, None] = None, + exclude: Union[IncEx, None] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> Any: + # What calls this code passes a value that already called + # self._type_adapter.validate_python(value) + return self._type_adapter.dump_python( + value, + mode=mode, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + def __hash__(self) -> int: + # Each ModelField is unique for our purposes, to allow making a dict from + # ModelField to its JSON Schema. + return id(self) + + +def get_annotation_from_field_info( + annotation: Any, field_info: FieldInfo, field_name: str +) -> Any: + return annotation + + +def _model_rebuild(model: Type[BaseModel]) -> None: + model.model_rebuild() + + +def _model_dump( + model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any +) -> Any: + return model.model_dump(mode=mode, **kwargs) + + +def _get_model_config(model: BaseModel) -> Any: + return model.model_config + + +def get_schema_from_model_field( + *, + field: ModelField, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + separate_input_output_schemas: bool = True, +) -> Dict[str, Any]: + override_mode: Union[Literal["validation"], None] = ( + None if separate_input_output_schemas else "validation" + ) + # This expects that GenerateJsonSchema was already used to generate the definitions + json_schema = field_mapping[(field, override_mode or field.mode)] + if "$ref" not in json_schema: + # TODO remove when deprecating Pydantic v1 + # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 + json_schema["title"] = field.field_info.title or field.alias.title().replace( + "_", " " + ) + return json_schema + + +def get_definitions( + *, + fields: Sequence[ModelField], + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, +) -> Tuple[ + Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + Dict[str, Dict[str, Any]], +]: + schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) + override_mode: Union[Literal["validation"], None] = ( + None if separate_input_output_schemas else "validation" + ) + flat_models = get_flat_models_from_fields(fields, known_models=set()) + flat_model_fields = [ + ModelField(field_info=FieldInfo(annotation=model), name=model.__name__) + for model in flat_models + ] + input_types = {f.type_ for f in fields} + unique_flat_model_fields = { + f for f in flat_model_fields if f.type_ not in input_types + } + + inputs = [ + (field, override_mode or field.mode, field._type_adapter.core_schema) + for field in list(fields) + list(unique_flat_model_fields) + ] + field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs) + for item_def in cast(Dict[str, Dict[str, Any]], definitions).values(): + if "description" in item_def: + item_description = cast(str, item_def["description"]).split("\f")[0] + item_def["description"] = item_description + new_mapping, new_definitions = _remap_definitions_and_field_mappings( + model_name_map=model_name_map, + definitions=definitions, # type: ignore[arg-type] + field_mapping=field_mapping, + ) + return new_mapping, new_definitions + + +def _replace_refs( + *, + schema: Dict[str, Any], + old_name_to_new_name_map: Dict[str, str], +) -> Dict[str, Any]: + new_schema = deepcopy(schema) + for key, value in new_schema.items(): + if key == "$ref": + ref_name = schema["$ref"].split("/")[-1] + if ref_name in old_name_to_new_name_map: + new_name = old_name_to_new_name_map[ref_name] + new_schema["$ref"] = REF_TEMPLATE.format(model=new_name) + else: + new_schema["$ref"] = schema["$ref"] + continue + if isinstance(value, dict): + new_schema[key] = _replace_refs( + schema=value, + old_name_to_new_name_map=old_name_to_new_name_map, + ) + elif isinstance(value, list): + new_value = [] + for item in value: + if isinstance(item, dict): + new_item = _replace_refs( + schema=item, + old_name_to_new_name_map=old_name_to_new_name_map, + ) + new_value.append(new_item) + + else: + new_value.append(item) + new_schema[key] = new_value + return new_schema + + +def _remap_definitions_and_field_mappings( + *, + model_name_map: ModelNameMap, + definitions: Dict[str, Any], + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], +) -> Tuple[ + Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + Dict[str, Any], +]: + old_name_to_new_name_map = {} + for field_key, schema in field_mapping.items(): + model = field_key[0].type_ + if model not in model_name_map: + continue + new_name = model_name_map[model] + old_name = schema["$ref"].split("/")[-1] + if old_name in {f"{new_name}-Input", f"{new_name}-Output"}: + continue + old_name_to_new_name_map[old_name] = new_name + + new_field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ] = {} + for field_key, schema in field_mapping.items(): + new_schema = _replace_refs( + schema=schema, + old_name_to_new_name_map=old_name_to_new_name_map, + ) + new_field_mapping[field_key] = new_schema + + new_definitions = {} + for key, value in definitions.items(): + if key in old_name_to_new_name_map: + new_key = old_name_to_new_name_map[key] + else: + new_key = key + new_value = _replace_refs( + schema=value, + old_name_to_new_name_map=old_name_to_new_name_map, + ) + new_definitions[new_key] = new_value + return new_field_mapping, new_definitions + + +def is_scalar_field(field: ModelField) -> bool: + from fastapi import params + + return shared.field_annotation_is_scalar( + field.field_info.annotation + ) and not isinstance(field.field_info, params.Body) + + +def is_sequence_field(field: ModelField) -> bool: + return shared.field_annotation_is_sequence(field.field_info.annotation) + + +def is_scalar_sequence_field(field: ModelField) -> bool: + return shared.field_annotation_is_scalar_sequence(field.field_info.annotation) + + +def is_bytes_field(field: ModelField) -> bool: + return shared.is_bytes_or_nonable_bytes_annotation(field.type_) + + +def is_bytes_sequence_field(field: ModelField) -> bool: + return shared.is_bytes_sequence_annotation(field.type_) + + +def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: + cls = type(field_info) + merged_field_info = cls.from_annotation(annotation) + new_field_info = copy(field_info) + new_field_info.metadata = merged_field_info.metadata + new_field_info.annotation = merged_field_info.annotation + return new_field_info + + +def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: + origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation + assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type] + return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return] + + +def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: + error = ValidationError.from_exception_data( + "Field required", [{"type": "missing", "loc": loc, "input": {}}] + ).errors(include_url=False)[0] + error["input"] = None + return error # type: ignore[return-value] + + +def create_body_model( + *, fields: Sequence[ModelField], model_name: str +) -> Type[BaseModel]: + field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields} + BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload] + return BodyModel + + +def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: + return [ + ModelField(field_info=field_info, name=name) + for name, field_info in model.model_fields.items() + ] + + +# Duplicate of several schema functions from Pydantic v1 to make them compatible with +# Pydantic v2 and allow mixing the models + +TypeModelOrEnum = Union[Type["BaseModel"], Type[Enum]] +TypeModelSet = Set[TypeModelOrEnum] + + +def normalize_name(name: str) -> str: + return re.sub(r"[^a-zA-Z0-9.\-_]", "_", name) + + +def get_model_name_map(unique_models: TypeModelSet) -> Dict[TypeModelOrEnum, str]: + name_model_map = {} + conflicting_names: Set[str] = set() + for model in unique_models: + model_name = normalize_name(model.__name__) + if model_name in conflicting_names: + model_name = get_long_model_name(model) + name_model_map[model_name] = model + elif model_name in name_model_map: + conflicting_names.add(model_name) + conflicting_model = name_model_map.pop(model_name) + name_model_map[get_long_model_name(conflicting_model)] = conflicting_model + name_model_map[get_long_model_name(model)] = model + else: + name_model_map[model_name] = model + return {v: k for k, v in name_model_map.items()} + + +def get_flat_models_from_model( + model: Type["BaseModel"], known_models: Union[TypeModelSet, None] = None +) -> TypeModelSet: + known_models = known_models or set() + fields = get_model_fields(model) + get_flat_models_from_fields(fields, known_models=known_models) + return known_models + + +def get_flat_models_from_annotation( + annotation: Any, known_models: TypeModelSet +) -> TypeModelSet: + origin = get_origin(annotation) + if origin is not None: + for arg in get_args(annotation): + if lenient_issubclass(arg, (BaseModel, Enum)) and arg not in known_models: + known_models.add(arg) + if lenient_issubclass(arg, BaseModel): + get_flat_models_from_model(arg, known_models=known_models) + else: + get_flat_models_from_annotation(arg, known_models=known_models) + return known_models + + +def get_flat_models_from_field( + field: ModelField, known_models: TypeModelSet +) -> TypeModelSet: + field_type = field.type_ + if lenient_issubclass(field_type, BaseModel): + if field_type in known_models: + return known_models + known_models.add(field_type) + get_flat_models_from_model(field_type, known_models=known_models) + elif lenient_issubclass(field_type, Enum): + known_models.add(field_type) + else: + get_flat_models_from_annotation(field_type, known_models=known_models) + return known_models + + +def get_flat_models_from_fields( + fields: Sequence[ModelField], known_models: TypeModelSet +) -> TypeModelSet: + for field in fields: + get_flat_models_from_field(field, known_models=known_models) + return known_models + + +def get_long_model_name(model: TypeModelOrEnum) -> str: + return f"{model.__module__}__{model.__qualname__}".replace(".", "__") diff --git a/fastapi/applications.py b/fastapi/applications.py index 6d427cdc27..6db4b4e83d 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -22,6 +22,7 @@ from fastapi.exception_handlers import ( ) from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError from fastapi.logger import logger +from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware from fastapi.openapi.docs import ( get_redoc_html, get_swagger_ui_html, @@ -36,10 +37,12 @@ from starlette.datastructures import State 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, Lifespan, Receive, Scope, Send +from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send from typing_extensions import Annotated, Doc, deprecated AppType = TypeVar("AppType", bound="FastAPI") @@ -72,7 +75,7 @@ class FastAPI(Starlette): errors. Read more in the - [Starlette docs for Applications](https://www.starlette.io/applications/#instantiating-the-application). + [Starlette docs for Applications](https://www.starlette.dev/applications/#instantiating-the-application). """ ), ] = False, @@ -748,7 +751,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -810,6 +813,32 @@ class FastAPI(Starlette): """ ), ] = True, + openapi_external_docs: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + This field allows you to provide additional external documentation links. + If provided, it must be a dictionary containing: + + * `description`: A brief description of the external documentation. + * `url`: The URL pointing to the external documentation. The value **MUST** + be a valid URL format. + + **Example**: + + ```python + from fastapi import FastAPI + + external_docs = { + "description": "Detailed API Reference", + "url": "https://example.com/api-docs", + } + + app = FastAPI(openapi_external_docs=external_docs) + ``` + """ + ), + ] = None, **extra: Annotated[ Any, Doc( @@ -838,6 +867,7 @@ class FastAPI(Starlette): self.swagger_ui_parameters = swagger_ui_parameters self.servers = servers or [] self.separate_input_output_schemas = separate_input_output_schemas + self.openapi_external_docs = openapi_external_docs self.extra = extra self.openapi_version: Annotated[ str, @@ -908,7 +938,7 @@ class FastAPI(Starlette): This is simply inherited from Starlette. Read more about it in the - [Starlette docs for Applications](https://www.starlette.io/applications/#storing-state-on-the-app-instance). + [Starlette docs for Applications](https://www.starlette.dev/applications/#storing-state-on-the-app-instance). """ ), ] = State() @@ -963,6 +993,54 @@ class FastAPI(Starlette): self.middleware_stack: Union[ASGIApp, None] = None self.setup() + def build_middleware_stack(self) -> ASGIApp: + # Duplicate/override from Starlette to add AsyncExitStackMiddleware + # inside of ExceptionMiddleware, inside of custom user middlewares + debug = self.debug + error_handler = None + exception_handlers: dict[Any, ExceptionHandler] = {} + + for key, value in self.exception_handlers.items(): + if key in (500, Exception): + error_handler = value + else: + exception_handlers[key] = value + + middleware = ( + [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)] + + self.user_middleware + + [ + Middleware( + ExceptionMiddleware, handlers=exception_handlers, debug=debug + ), + # Add FastAPI-specific AsyncExitStackMiddleware for closing files. + # Before this was also used for closing dependencies with yield but + # those now have their own AsyncExitStack, to properly support + # streaming responses while keeping compatibility with the previous + # versions (as of writing 0.117.1) that allowed doing + # except HTTPException inside a dependency with yield. + # This needs to happen after user middlewares because those create a + # new contextvars context copy by using a new AnyIO task group. + # This AsyncExitStack preserves the context for contextvars, not + # strictly necessary for closing files but it was one of the original + # intentions. + # If the AsyncExitStack lived outside of the custom middlewares and + # contextvars were set, for example in a dependency with 'yield' + # in that internal contextvars context, the values would not be + # available in the outer context of the AsyncExitStack. + # By placing the middleware and the AsyncExitStack here, inside all + # user middlewares, the same context is used. + # This is currently not needed, only for closing files, but used to be + # important when dependencies with yield were closed here. + Middleware(AsyncExitStackMiddleware), + ] + ) + + app = self.router + for cls, args, kwargs in reversed(middleware): + app = cls(app, *args, **kwargs) + return app + def openapi(self) -> Dict[str, Any]: """ Generate the OpenAPI schema of the application. This is called by FastAPI @@ -992,6 +1070,7 @@ class FastAPI(Starlette): tags=self.openapi_tags, servers=self.servers, separate_input_output_schemas=self.separate_input_output_schemas, + external_docs=self.openapi_external_docs, ) return self.openapi_schema @@ -1720,7 +1799,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -2093,7 +2172,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -2471,7 +2550,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -2849,7 +2928,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -3222,7 +3301,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -3595,7 +3674,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -3968,7 +4047,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -4346,7 +4425,7 @@ class FastAPI(Starlette): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -4425,7 +4504,7 @@ class FastAPI(Starlette): app = FastAPI() - @app.put("/items/{item_id}") + @app.trace("/items/{item_id}") def trace_item(item_id: str): return None ``` @@ -4515,14 +4594,17 @@ class FastAPI(Starlette): ```python import time + from typing import Awaitable, Callable - from fastapi import FastAPI, Request + from fastapi import FastAPI, Request, Response app = FastAPI() @app.middleware("http") - async def add_process_time_header(request: Request, call_next): + async def add_process_time_header( + request: Request, call_next: Callable[[Request], Awaitable[Response]] + ) -> Response: start_time = time.time() response = await call_next(request) process_time = time.time() - start_time diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py index 894bd3ed11..3202c70789 100644 --- a/fastapi/concurrency.py +++ b/fastapi/concurrency.py @@ -1,7 +1,7 @@ from contextlib import asynccontextmanager as asynccontextmanager from typing import AsyncGenerator, ContextManager, TypeVar -import anyio +import anyio.to_thread 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 @@ -28,7 +28,7 @@ async def contextmanager_in_threadpool( except Exception as e: ok = bool( await anyio.to_thread.run_sync( - cm.__exit__, type(e), e, None, limiter=exit_limiter + cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter ) ) if not ok: diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index cf8406b0fc..34185b96aa 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -11,11 +11,9 @@ from typing import ( ) from fastapi._compat import ( - PYDANTIC_V2, CoreSchema, GetJsonSchemaHandler, JsonSchemaValue, - with_info_plain_validator_function, ) from starlette.datastructures import URL as URL # noqa: F401 from starlette.datastructures import Address as Address # noqa: F401 @@ -154,11 +152,10 @@ class UploadFile(StarletteUploadFile): raise ValueError(f"Expected UploadFile, received: {type(__input_value)}") return cast(UploadFile, __input_value) - if not PYDANTIC_V2: - - @classmethod - def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: - field_schema.update({"type": "string", "format": "binary"}) + # TODO: remove when deprecating Pydantic v1 + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update({"type": "string", "format": "binary"}) @classmethod def __get_pydantic_json_schema__( @@ -170,6 +167,8 @@ class UploadFile(StarletteUploadFile): def __get_pydantic_core_schema__( cls, source: Type[Any], handler: Callable[[Any], CoreSchema] ) -> CoreSchema: + from ._compat.v2 import with_info_plain_validator_function + return with_info_plain_validator_function(cls._validate) diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 61ef006387..418c117259 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -1,58 +1,37 @@ -from typing import Any, Callable, List, Optional, Sequence +from dataclasses import dataclass, field +from typing import Any, Callable, List, Optional, Sequence, Tuple from fastapi._compat import ModelField from fastapi.security.base import SecurityBase +@dataclass class SecurityRequirement: - def __init__( - self, security_scheme: SecurityBase, scopes: Optional[Sequence[str]] = None - ): - self.security_scheme = security_scheme - self.scopes = scopes + security_scheme: SecurityBase + scopes: Optional[Sequence[str]] = None +@dataclass class Dependant: - def __init__( - self, - *, - path_params: Optional[List[ModelField]] = None, - query_params: Optional[List[ModelField]] = None, - header_params: Optional[List[ModelField]] = None, - cookie_params: Optional[List[ModelField]] = None, - body_params: Optional[List[ModelField]] = None, - dependencies: Optional[List["Dependant"]] = None, - security_schemes: Optional[List[SecurityRequirement]] = None, - name: Optional[str] = None, - call: Optional[Callable[..., Any]] = None, - request_param_name: Optional[str] = None, - websocket_param_name: Optional[str] = None, - http_connection_param_name: Optional[str] = None, - response_param_name: Optional[str] = None, - background_tasks_param_name: Optional[str] = None, - security_scopes_param_name: Optional[str] = None, - security_scopes: Optional[List[str]] = None, - use_cache: bool = True, - path: Optional[str] = None, - ) -> None: - self.path_params = path_params or [] - self.query_params = query_params or [] - self.header_params = header_params or [] - self.cookie_params = cookie_params or [] - self.body_params = body_params or [] - self.dependencies = dependencies or [] - self.security_requirements = security_schemes or [] - self.request_param_name = request_param_name - self.websocket_param_name = websocket_param_name - self.http_connection_param_name = http_connection_param_name - self.response_param_name = response_param_name - self.background_tasks_param_name = background_tasks_param_name - self.security_scopes = security_scopes - self.security_scopes_param_name = security_scopes_param_name - self.name = name - self.call = call - self.use_cache = use_cache - # Store the path to be able to re-generate a dependable from it in overrides - self.path = path - # Save the cache key at creation to optimize performance + path_params: List[ModelField] = field(default_factory=list) + query_params: List[ModelField] = field(default_factory=list) + header_params: List[ModelField] = field(default_factory=list) + cookie_params: List[ModelField] = field(default_factory=list) + body_params: List[ModelField] = field(default_factory=list) + dependencies: List["Dependant"] = field(default_factory=list) + security_requirements: List[SecurityRequirement] = field(default_factory=list) + name: Optional[str] = None + call: Optional[Callable[..., Any]] = None + request_param_name: Optional[str] = None + websocket_param_name: Optional[str] = None + http_connection_param_name: Optional[str] = None + response_param_name: Optional[str] = None + background_tasks_param_name: Optional[str] = None + security_scopes_param_name: Optional[str] = None + security_scopes: Optional[List[str]] = None + use_cache: bool = True + path: Optional[str] = None + cache_key: Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] = field(init=False) + + def __post_init__(self) -> None: self.cache_key = (self.call, tuple(sorted(set(self.security_scopes or [])))) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 3e8e7b4101..aa06dd2a96 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,6 +1,8 @@ import inspect +import sys from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy +from dataclasses import dataclass from typing import ( Any, Callable, @@ -21,16 +23,17 @@ import anyio from fastapi import params from fastapi._compat import ( PYDANTIC_V2, - ErrorWrapper, ModelField, - Required, + RequiredParam, Undefined, - _regenerate_error_with_loc, + _is_error_wrapper, + _is_model_class, copy_field_info, create_body_model, evaluate_forwardref, field_annotation_is_scalar, get_annotation_from_field_info, + get_cached_model_fields, get_missing_field_error, is_bytes_field, is_bytes_sequence_field, @@ -40,10 +43,12 @@ from fastapi._compat import ( is_uploadfile_or_nonable_uploadfile_annotation, is_uploadfile_sequence_annotation, lenient_issubclass, + may_v1, sequence_types, serialize_sequence_value, value_is_sequence, ) +from fastapi._compat.shared import annotation_is_pydantic_v1 from fastapi.background import BackgroundTasks from fastapi.concurrency import ( asynccontextmanager, @@ -54,16 +59,30 @@ from fastapi.logger import logger from fastapi.security.base import SecurityBase from fastapi.security.oauth2 import OAuth2, SecurityScopes from fastapi.security.open_id_connect_url import OpenIdConnect -from fastapi.utils import create_response_field, get_path_param_names +from fastapi.utils import create_model_field, get_path_param_names +from pydantic import BaseModel from pydantic.fields import FieldInfo from starlette.background import BackgroundTasks as StarletteBackgroundTasks from starlette.concurrency import run_in_threadpool -from starlette.datastructures import FormData, Headers, QueryParams, UploadFile +from starlette.datastructures import ( + FormData, + Headers, + ImmutableMultiDict, + QueryParams, + UploadFile, +) from starlette.requests import HTTPConnection, Request from starlette.responses import Response from starlette.websockets import WebSocket from typing_extensions import Annotated, get_args, get_origin +from .. import temp_pydantic_v1_params + +if sys.version_info >= (3, 13): # pragma: no cover + from inspect import iscoroutinefunction +else: # pragma: no cover + from asyncio import iscoroutinefunction + multipart_not_installed_error = ( 'Form data requires "python-multipart" to be installed. \n' 'You can install "python-multipart" with: \n\n' @@ -79,17 +98,23 @@ multipart_incorrect_install_error = ( ) -def check_file_field(field: ModelField) -> None: - field_info = field.field_info - if isinstance(field_info, params.Form): +def ensure_multipart_is_installed() -> None: + try: + from python_multipart import __version__ + + # Import an attribute that can be mocked/deleted in testing + assert __version__ > "0.0.12" + except (ImportError, AssertionError): try: # __version__ is available in both multiparts, and can be mocked - from multipart import __version__ # type: ignore + from multipart import __version__ # type: ignore[no-redef,import-untyped] assert __version__ try: # parse_options_header is only available in the right multipart - from multipart.multipart import parse_options_header # type: ignore + from multipart.multipart import ( # type: ignore[import-untyped] + parse_options_header, + ) assert parse_options_header except ImportError: @@ -118,9 +143,9 @@ def get_param_sub_dependant( def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant: - assert callable( - depends.dependency - ), "A parameter-less dependency must have a callable dependency" + assert callable(depends.dependency), ( + "A parameter-less dependency must have a callable dependency" + ) return get_sub_dependant(depends=depends, dependency=depends.dependency, path=path) @@ -175,7 +200,7 @@ def get_flat_dependant( header_params=dependant.header_params.copy(), cookie_params=dependant.cookie_params.copy(), body_params=dependant.body_params.copy(), - security_schemes=dependant.security_requirements.copy(), + security_requirements=dependant.security_requirements.copy(), use_cache=dependant.use_cache, path=dependant.path, ) @@ -194,14 +219,23 @@ def get_flat_dependant( return flat_dependant +def _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]: + if not fields: + return fields + first_field = fields[0] + if len(fields) == 1 and _is_model_class(first_field.type_): + fields_to_extract = get_cached_model_fields(first_field.type_) + return fields_to_extract + return fields + + def get_flat_params(dependant: Dependant) -> List[ModelField]: flat_dependant = get_flat_dependant(dependant, skip_repeats=True) - return ( - flat_dependant.path_params - + flat_dependant.query_params - + flat_dependant.header_params - + flat_dependant.cookie_params - ) + path_params = _get_flat_fields_from_params(flat_dependant.path_params) + query_params = _get_flat_fields_from_params(flat_dependant.query_params) + header_params = _get_flat_fields_from_params(flat_dependant.header_params) + cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) + return path_params + query_params + header_params + cookie_params def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: @@ -224,6 +258,8 @@ def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: if isinstance(annotation, str): annotation = ForwardRef(annotation) annotation = evaluate_forwardref(annotation, globalns, globalns) + if annotation is type(None): + return None return annotation @@ -258,16 +294,16 @@ def get_dependant( ) for param_name, param in signature_params.items(): is_path_param = param_name in path_param_names - type_annotation, depends, param_field = analyze_param( + param_details = analyze_param( param_name=param_name, annotation=param.annotation, value=param.default, is_path_param=is_path_param, ) - if depends is not None: + if param_details.depends is not None: sub_dependant = get_param_sub_dependant( param_name=param_name, - depends=depends, + depends=param_details.depends, path=path, security_scopes=security_scopes, ) @@ -275,18 +311,20 @@ def get_dependant( continue if add_non_field_param_to_dependency( param_name=param_name, - type_annotation=type_annotation, + type_annotation=param_details.type_annotation, dependant=dependant, ): - assert ( - param_field is None - ), f"Cannot specify multiple FastAPI annotations for {param_name!r}" + assert param_details.field is None, ( + f"Cannot specify multiple FastAPI annotations for {param_name!r}" + ) continue - 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) + assert param_details.field is not None + if isinstance( + param_details.field.field_info, (params.Body, temp_pydantic_v1_params.Body) + ): + dependant.body_params.append(param_details.field) else: - add_param_to_fields(field=param_field, dependant=dependant) + add_param_to_fields(field=param_details.field, dependant=dependant) return dependant @@ -314,13 +352,20 @@ def add_non_field_param_to_dependency( return None +@dataclass +class ParamDetails: + type_annotation: Any + depends: Optional[params.Depends] + field: Optional[ModelField] + + def analyze_param( *, param_name: str, annotation: Any, value: Any, is_path_param: bool, -) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]: +) -> ParamDetails: field_info = None depends = None type_annotation: Any = Any @@ -328,31 +373,45 @@ def analyze_param( if annotation is not inspect.Signature.empty: use_annotation = annotation type_annotation = annotation + # Extract Annotated info if get_origin(use_annotation) is Annotated: 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)) + if isinstance(arg, (FieldInfo, may_v1.FieldInfo, params.Depends)) ] fastapi_specific_annotations = [ arg for arg in fastapi_annotations - if isinstance(arg, (params.Param, params.Body, params.Depends)) + if isinstance( + arg, + ( + params.Param, + temp_pydantic_v1_params.Param, + params.Body, + temp_pydantic_v1_params.Body, + params.Depends, + ), + ) ] if fastapi_specific_annotations: - fastapi_annotation: Union[FieldInfo, params.Depends, None] = ( - fastapi_specific_annotations[-1] - ) + fastapi_annotation: Union[ + FieldInfo, may_v1.FieldInfo, params.Depends, None + ] = fastapi_specific_annotations[-1] else: fastapi_annotation = None - if isinstance(fastapi_annotation, FieldInfo): + # Set default for Annotated FieldInfo + if isinstance(fastapi_annotation, (FieldInfo, may_v1.FieldInfo)): # Copy `field_info` because we mutate `field_info.default` below. field_info = copy_field_info( field_info=fastapi_annotation, annotation=use_annotation ) - assert field_info.default is Undefined or field_info.default is Required, ( + assert field_info.default in { + Undefined, + may_v1.Undefined, + } or field_info.default in {RequiredParam, may_v1.RequiredParam}, ( f"`{field_info.__class__.__name__}` default value cannot be set in" f" `Annotated` for {param_name!r}. Set the default value with `=` instead." ) @@ -360,10 +419,11 @@ def analyze_param( assert not is_path_param, "Path parameters cannot have default values" field_info.default = value else: - field_info.default = Required + field_info.default = RequiredParam + # Get Annotated Depends elif isinstance(fastapi_annotation, params.Depends): depends = fastapi_annotation - + # Get Depends from default value if isinstance(value, params.Depends): assert depends is None, ( "Cannot specify `Depends` in `Annotated` and default value" @@ -374,20 +434,24 @@ def analyze_param( f" default value together for {param_name!r}" ) depends = value - elif isinstance(value, FieldInfo): + # Get FieldInfo from default value + elif isinstance(value, (FieldInfo, may_v1.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 PYDANTIC_V2: - field_info.annotation = type_annotation + if isinstance(field_info, FieldInfo): + field_info.annotation = type_annotation + # Get Depends from type annotation if depends is not None and depends.dependency is None: # Copy `depends` before mutating it depends = copy(depends) depends.dependency = type_annotation + # Handle non-param type annotations like Request if lenient_issubclass( type_annotation, ( @@ -400,13 +464,14 @@ def analyze_param( ), ): 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}" + assert field_info is None, ( + f"Cannot specify FastAPI annotation for type {type_annotation!r}" + ) + # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value elif field_info is None and depends is None: - default_value = value if value is not inspect.Signature.empty else Required + default_value = value if value is not inspect.Signature.empty else RequiredParam if is_path_param: - # We might check here that `default_value is Required`, but the fact is that the same + # We might check here that `default_value is RequiredParam`, 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(annotation=use_annotation) @@ -415,19 +480,30 @@ def analyze_param( ) or is_uploadfile_sequence_annotation(type_annotation): field_info = params.File(annotation=use_annotation, default=default_value) elif not field_annotation_is_scalar(annotation=type_annotation): - field_info = params.Body(annotation=use_annotation, default=default_value) + if annotation_is_pydantic_v1(use_annotation): + field_info = temp_pydantic_v1_params.Body( + annotation=use_annotation, default=default_value + ) + else: + field_info = params.Body( + annotation=use_annotation, default=default_value + ) else: field_info = params.Query(annotation=use_annotation, default=default_value) field = None + # It's a field_info, not a dependency if field_info is not None: + # Handle field_info.in_ if is_path_param: - assert isinstance(field_info, params.Path), ( + assert isinstance( + field_info, (params.Path, temp_pydantic_v1_params.Path) + ), ( f"Cannot use `{field_info.__class__.__name__}` for path param" f" {param_name!r}" ) elif ( - isinstance(field_info, params.Param) + isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)) and getattr(field_info, "in_", None) is None ): field_info.in_ = params.ParamTypes.query @@ -436,40 +512,38 @@ def analyze_param( field_info, param_name, ) + if isinstance(field_info, (params.Form, temp_pydantic_v1_params.Form)): + ensure_multipart_is_installed() 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_info.alias = alias - field = create_response_field( + field = create_model_field( name=param_name, type_=use_annotation_from_field_info, default=field_info.default, alias=alias, - required=field_info.default in (Required, Undefined), + required=field_info.default + in (RequiredParam, may_v1.RequiredParam, Undefined), field_info=field_info, ) + if is_path_param: + assert is_scalar_field(field=field), ( + "Path params must be of one of the supported types" + ) + elif isinstance(field_info, (params.Query, temp_pydantic_v1_params.Query)): + assert ( + is_scalar_field(field) + or is_scalar_sequence_field(field) + or ( + _is_model_class(field.type_) + # For Pydantic v1 + and getattr(field, "shape", 1) == 1 + ) + ) - 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 + return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None: @@ -482,19 +556,19 @@ def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None: elif field_info_in == params.ParamTypes.header: dependant.header_params.append(field) else: - assert ( - field_info_in == params.ParamTypes.cookie - ), f"non-body parameters must be in path, query, header or cookie: {field.name}" + assert field_info_in == params.ParamTypes.cookie, ( + f"non-body parameters must be in path, query, header or cookie: {field.name}" + ) dependant.cookie_params.append(field) def is_coroutine_callable(call: Callable[..., Any]) -> bool: if inspect.isroutine(call): - return inspect.iscoroutinefunction(call) + return iscoroutinefunction(call) if inspect.isclass(call): return False dunder_call = getattr(call, "__call__", None) # noqa: B004 - return inspect.iscoroutinefunction(dunder_call) + return iscoroutinefunction(dunder_call) def is_async_gen_callable(call: Callable[..., Any]) -> bool: @@ -521,6 +595,15 @@ async def solve_generator( return await stack.enter_async_context(cm) +@dataclass +class SolvedDependency: + values: Dict[str, Any] + errors: List[Any] + background_tasks: Optional[StarletteBackgroundTasks] + response: Response + dependency_cache: Dict[Tuple[Callable[..., Any], Tuple[str]], Any] + + async def solve_dependencies( *, request: Union[Request, WebSocket], @@ -531,20 +614,16 @@ async def solve_dependencies( dependency_overrides_provider: Optional[Any] = None, dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None, async_exit_stack: AsyncExitStack, -) -> Tuple[ - Dict[str, Any], - List[Any], - Optional[StarletteBackgroundTasks], - Response, - Dict[Tuple[Callable[..., Any], Tuple[str]], Any], -]: + embed_body_fields: bool, +) -> SolvedDependency: values: Dict[str, Any] = {} errors: List[Any] = [] if response is None: response = Response() del response.headers["content-length"] response.status_code = None # type: ignore - dependency_cache = dependency_cache or {} + if dependency_cache is None: + dependency_cache = {} sub_dependant: Dependant for sub_dependant in dependant.dependencies: sub_dependant.call = cast(Callable[..., Any], sub_dependant.call) @@ -578,28 +657,22 @@ async def solve_dependencies( dependency_overrides_provider=dependency_overrides_provider, dependency_cache=dependency_cache, async_exit_stack=async_exit_stack, + embed_body_fields=embed_body_fields, ) - ( - sub_values, - sub_errors, - background_tasks, - _, # the subdependency returns the same response we have - sub_dependency_cache, - ) = solved_result - dependency_cache.update(sub_dependency_cache) - if sub_errors: - errors.extend(sub_errors) + background_tasks = solved_result.background_tasks + if solved_result.errors: + errors.extend(solved_result.errors) continue if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache: solved = dependency_cache[sub_dependant.cache_key] elif is_gen_callable(call) or is_async_gen_callable(call): solved = await solve_generator( - call=call, stack=async_exit_stack, sub_values=sub_values + call=call, stack=async_exit_stack, sub_values=solved_result.values ) elif is_coroutine_callable(call): - solved = await call(**sub_values) + solved = await call(**solved_result.values) else: - solved = await run_in_threadpool(call, **sub_values) + solved = await run_in_threadpool(call, **solved_result.values) if sub_dependant.name is not None: values[sub_dependant.name] = solved if sub_dependant.cache_key not in dependency_cache: @@ -626,7 +699,9 @@ async def solve_dependencies( body_values, body_errors, ) = await request_body_to_args( # body_params checked above - required_params=dependant.body_params, received_body=body + body_fields=dependant.body_params, + received_body=body, + embed_body_fields=embed_body_fields, ) values.update(body_values) errors.extend(body_errors) @@ -646,142 +721,289 @@ async def solve_dependencies( values[dependant.security_scopes_param_name] = SecurityScopes( scopes=dependant.security_scopes ) - return values, errors, background_tasks, response, dependency_cache + return SolvedDependency( + values=values, + errors=errors, + background_tasks=background_tasks, + response=response, + dependency_cache=dependency_cache, + ) + + +def _validate_value_with_model_field( + *, field: ModelField, value: Any, values: Dict[str, Any], loc: Tuple[str, ...] +) -> Tuple[Any, List[Any]]: + if value is None: + if field.required: + return None, [get_missing_field_error(loc=loc)] + else: + return deepcopy(field.default), [] + v_, errors_ = field.validate(value, values, loc=loc) + if _is_error_wrapper(errors_): # type: ignore[arg-type] + return None, [errors_] + elif isinstance(errors_, list): + new_errors = may_v1._regenerate_error_with_loc(errors=errors_, loc_prefix=()) + return None, new_errors + else: + return v_, [] + + +def _get_multidict_value( + field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None +) -> Any: + alias = alias or field.alias + if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)): + value = values.getlist(alias) + else: + value = values.get(alias, None) + if ( + value is None + or ( + isinstance(field.field_info, (params.Form, temp_pydantic_v1_params.Form)) + and isinstance(value, str) # For type checks + and value == "" + ) + or (is_sequence_field(field) and len(value) == 0) + ): + if field.required: + return + else: + return deepcopy(field.default) + return value def request_params_to_args( - required_params: Sequence[ModelField], + fields: Sequence[ModelField], received_params: Union[Mapping[str, Any], QueryParams, Headers], ) -> Tuple[Dict[str, Any], List[Any]]: - values = {} - errors = [] - for field in required_params: - if is_scalar_sequence_field(field) and isinstance( - received_params, (QueryParams, Headers) - ): - value = received_params.getlist(field.alias) or field.default - else: - value = received_params.get(field.alias) + values: Dict[str, Any] = {} + errors: List[Dict[str, Any]] = [] + + if not fields: + return values, errors + + first_field = fields[0] + fields_to_extract = fields + single_not_embedded_field = False + default_convert_underscores = True + if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): + fields_to_extract = get_cached_model_fields(first_field.type_) + single_not_embedded_field = True + # If headers are in a Pydantic model, the way to disable convert_underscores + # would be with Header(convert_underscores=False) at the Pydantic model level + default_convert_underscores = getattr( + first_field.field_info, "convert_underscores", True + ) + + params_to_process: Dict[str, Any] = {} + + processed_keys = set() + + for field in fields_to_extract: + alias = None + if isinstance(received_params, Headers): + # Handle fields extracted from a Pydantic Model for a header, each field + # doesn't have a FieldInfo of type Header with the default convert_underscores=True + convert_underscores = getattr( + field.field_info, "convert_underscores", default_convert_underscores + ) + if convert_underscores: + alias = ( + field.alias + if field.alias != field.name + else field.name.replace("_", "-") + ) + value = _get_multidict_value(field, received_params, alias=alias) + if value is not None: + params_to_process[field.name] = value + processed_keys.add(alias or field.alias) + processed_keys.add(field.name) + + for key, value in received_params.items(): + if key not in processed_keys: + params_to_process[key] = value + + if single_not_embedded_field: + field_info = first_field.field_info + assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), ( + "Params must be subclasses of Param" + ) + loc: Tuple[str, ...] = (field_info.in_.value,) + v_, errors_ = _validate_value_with_model_field( + field=first_field, value=params_to_process, values=values, loc=loc + ) + return {first_field.name: v_}, errors_ + + for field in fields: + value = _get_multidict_value(field, received_params) field_info = field.field_info - assert isinstance( - field_info, params.Param - ), "Params must be subclasses of Param" + assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), ( + "Params must be subclasses of Param" + ) loc = (field_info.in_.value, field.alias) - if value is None: - if field.required: - errors.append(get_missing_field_error(loc=loc)) - else: - values[field.name] = deepcopy(field.default) - continue - v_, errors_ = field.validate(value, values, loc=loc) - if isinstance(errors_, ErrorWrapper): - errors.append(errors_) - elif isinstance(errors_, list): - new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=()) - errors.extend(new_errors) + v_, errors_ = _validate_value_with_model_field( + field=field, value=value, values=values, loc=loc + ) + if errors_: + errors.extend(errors_) else: values[field.name] = v_ return values, errors -async def request_body_to_args( - required_params: List[ModelField], - received_body: Optional[Union[Dict[str, Any], FormData]], -) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: +def is_union_of_base_models(field_type: Any) -> bool: + """Check if field type is a Union where all members are BaseModel subclasses.""" + from fastapi.types import UnionType + + origin = get_origin(field_type) + + # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+) + if origin is not Union and origin is not UnionType: + return False + + union_args = get_args(field_type) + + for arg in union_args: + if not _is_model_class(arg): + return False + + return True + + +def _should_embed_body_fields(fields: List[ModelField]) -> bool: + if not fields: + return False + # More than one dependency could have the same field, it would show up as multiple + # fields but it's the same one, so count them by name + body_param_names_set = {field.name for field in fields} + # A top level field has to be a single field, not multiple + if len(body_param_names_set) > 1: + return True + first_field = fields[0] + # If it explicitly specifies it is embedded, it has to be embedded + if getattr(first_field.field_info, "embed", None): + return True + # If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level + # otherwise it has to be embedded, so that the key value pair can be extracted + if ( + isinstance(first_field.field_info, (params.Form, temp_pydantic_v1_params.Form)) + and not _is_model_class(first_field.type_) + and not is_union_of_base_models(first_field.type_) + ): + return True + return False + + +async def _extract_form_body( + body_fields: List[ModelField], + received_body: FormData, +) -> Dict[str, Any]: values = {} - errors: List[Dict[str, Any]] = [] - if required_params: - field = required_params[0] + + for field in body_fields: + value = _get_multidict_value(field, received_body) field_info = field.field_info - embed = getattr(field_info, "embed", None) - field_alias_omitted = len(required_params) == 1 and not embed - if field_alias_omitted: - received_body = {field.alias: received_body} + if ( + isinstance(field_info, (params.File, temp_pydantic_v1_params.File)) + and is_bytes_field(field) + and isinstance(value, UploadFile) + ): + value = await value.read() + elif ( + is_bytes_sequence_field(field) + and isinstance(field_info, (params.File, temp_pydantic_v1_params.File)) + and value_is_sequence(value) + ): + # For types + assert isinstance(value, sequence_types) # type: ignore[arg-type] + results: List[Union[bytes, str]] = [] - for field in required_params: - loc: Tuple[str, ...] - if field_alias_omitted: - loc = ("body",) - else: - loc = ("body", field.alias) + async def process_fn( + fn: Callable[[], Coroutine[Any, Any, Any]], + ) -> None: + result = await fn() + results.append(result) # noqa: B023 - value: Optional[Any] = None - if received_body is not None: - if (is_sequence_field(field)) and isinstance(received_body, FormData): - value = received_body.getlist(field.alias) - else: - try: - value = received_body.get(field.alias) - except AttributeError: - errors.append(get_missing_field_error(loc)) - continue - if ( - value is None - or (isinstance(field_info, params.Form) and value == "") - or ( - isinstance(field_info, params.Form) - and is_sequence_field(field) - and len(value) == 0 - ) - ): - if field.required: - errors.append(get_missing_field_error(loc)) - else: - values[field.name] = deepcopy(field.default) + async with anyio.create_task_group() as tg: + for sub_value in value: + tg.start_soon(process_fn, sub_value.read) + value = serialize_sequence_value(field=field, value=results) + if value is not None: + values[field.alias] = value + for key, value in received_body.items(): + if key not in values: + values[key] = value + return values + + +async def request_body_to_args( + body_fields: List[ModelField], + received_body: Optional[Union[Dict[str, Any], FormData]], + embed_body_fields: bool, +) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: + values: Dict[str, Any] = {} + errors: List[Dict[str, Any]] = [] + assert body_fields, "request_body_to_args() should be called with fields" + single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields + first_field = body_fields[0] + body_to_process = received_body + + fields_to_extract: List[ModelField] = body_fields + + if ( + single_not_embedded_field + and _is_model_class(first_field.type_) + and isinstance(received_body, FormData) + ): + fields_to_extract = get_cached_model_fields(first_field.type_) + + if isinstance(received_body, FormData): + body_to_process = await _extract_form_body(fields_to_extract, received_body) + + if single_not_embedded_field: + loc: Tuple[str, ...] = ("body",) + v_, errors_ = _validate_value_with_model_field( + field=first_field, value=body_to_process, values=values, loc=loc + ) + return {first_field.name: v_}, errors_ + for field in body_fields: + loc = ("body", field.alias) + value: Optional[Any] = None + if body_to_process is not None: + try: + value = body_to_process.get(field.alias) + # If the received body is a list, not a dict + except AttributeError: + errors.append(get_missing_field_error(loc)) continue - if ( - isinstance(field_info, params.File) - and is_bytes_field(field) - and isinstance(value, UploadFile) - ): - value = await value.read() - elif ( - is_bytes_sequence_field(field) - and isinstance(field_info, params.File) - and value_is_sequence(value) - ): - # For types - assert isinstance(value, sequence_types) # type: ignore[arg-type] - results: List[Union[bytes, str]] = [] - - async def process_fn( - fn: Callable[[], Coroutine[Any, Any, Any]], - ) -> None: - result = await fn() - results.append(result) # noqa: B023 - - async with anyio.create_task_group() as tg: - for sub_value in value: - tg.start_soon(process_fn, sub_value.read) - value = serialize_sequence_value(field=field, value=results) - - v_, errors_ = field.validate(value, values, loc=loc) - - if isinstance(errors_, list): - errors.extend(errors_) - elif errors_: - errors.append(errors_) - else: - values[field.name] = v_ + v_, errors_ = _validate_value_with_model_field( + field=field, value=value, values=values, loc=loc + ) + if errors_: + errors.extend(errors_) + else: + values[field.name] = v_ return values, errors -def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]: - flat_dependant = get_flat_dependant(dependant) +def get_body_field( + *, flat_dependant: Dependant, name: str, embed_body_fields: bool +) -> Optional[ModelField]: + """ + Get a ModelField representing the request body for a path operation, combining + all body parameters into a single field if necessary. + + Used to check if it's form data (with `isinstance(body_field, params.Form)`) + or JSON and to generate the JSON Schema for a request body. + + This is **not** used to validate/parse the request body, that's done with each + individual body parameter. + """ if not flat_dependant.body_params: return None first_param = flat_dependant.body_params[0] - field_info = first_param.field_info - embed = getattr(field_info, "embed", None) - body_param_names_set = {param.name for param in flat_dependant.body_params} - if len(body_param_names_set) == 1 and not embed: - check_file_field(first_param) + if not embed_body_fields: return first_param - # If one field requires to embed, all have to be embedded - # 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) # noqa: B010 model_name = "Body_" + name BodyModel = create_body_model( fields=flat_dependant.body_params, model_name=model_name @@ -795,24 +1017,36 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]: BodyFieldInfo_kwargs["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, temp_pydantic_v1_params.File) + for f in flat_dependant.body_params + ): + BodyFieldInfo: Type[temp_pydantic_v1_params.Body] = temp_pydantic_v1_params.File # type: ignore[no-redef] elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): BodyFieldInfo = params.Form + elif any( + isinstance(f.field_info, temp_pydantic_v1_params.Form) + for f in flat_dependant.body_params + ): + BodyFieldInfo = temp_pydantic_v1_params.Form # type: ignore[assignment] else: - BodyFieldInfo = params.Body + if annotation_is_pydantic_v1(BodyModel): + BodyFieldInfo = temp_pydantic_v1_params.Body # type: ignore[assignment] + else: + BodyFieldInfo = params.Body body_param_media_types = [ f.field_info.media_type for f in flat_dependant.body_params - if isinstance(f.field_info, params.Body) + if isinstance(f.field_info, (params.Body, temp_pydantic_v1_params.Body)) ] if len(set(body_param_media_types)) == 1: BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0] - final_field = create_response_field( + final_field = create_model_field( name="body", type_=BodyModel, required=required, alias="body", field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), ) - check_file_field(final_field) return final_field diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 451ea0760f..bba9c970e0 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -17,6 +17,7 @@ from types import GeneratorType from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union from uuid import UUID +from fastapi._compat import may_v1 from fastapi.types import IncEx from pydantic import BaseModel from pydantic.color import Color @@ -24,7 +25,7 @@ from pydantic.networks import AnyUrl, NameEmail from pydantic.types import SecretBytes, SecretStr from typing_extensions import Annotated, Doc -from ._compat import PYDANTIC_V2, UndefinedType, Url, _model_dump +from ._compat import Url, _is_undefined, _model_dump # Taken from Pydantic v1 as is @@ -58,6 +59,7 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { bytes: lambda o: o.decode(), Color: str, + may_v1.Color: str, datetime.date: isoformat, datetime.datetime: isoformat, datetime.time: isoformat, @@ -74,14 +76,19 @@ ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { IPv6Interface: str, IPv6Network: str, NameEmail: str, + may_v1.NameEmail: str, Path: str, Pattern: lambda o: o.pattern, SecretBytes: str, + may_v1.SecretBytes: str, SecretStr: str, + may_v1.SecretStr: str, set: list, UUID: str, Url: str, + may_v1.Url: str, AnyUrl: str, + may_v1.AnyUrl: str, } @@ -213,13 +220,13 @@ def jsonable_encoder( include = set(include) if exclude is not None and not isinstance(exclude, (set, dict)): exclude = set(exclude) - if isinstance(obj, BaseModel): + if isinstance(obj, (BaseModel, may_v1.BaseModel)): # TODO: remove when deprecating Pydantic v1 encoders: Dict[Any, Any] = {} - if not PYDANTIC_V2: + if isinstance(obj, may_v1.BaseModel): encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined] if custom_encoder: - encoders.update(custom_encoder) + encoders = {**encoders, **custom_encoder} obj_dict = _model_dump( obj, mode="json", @@ -241,6 +248,7 @@ def jsonable_encoder( sqlalchemy_safe=sqlalchemy_safe, ) if dataclasses.is_dataclass(obj): + assert not isinstance(obj, type) obj_dict = dataclasses.asdict(obj) return jsonable_encoder( obj_dict, @@ -259,7 +267,7 @@ def jsonable_encoder( return str(obj) if isinstance(obj, (str, int, float, type(None))): return obj - if isinstance(obj, UndefinedType): + if _is_undefined(obj): return None if isinstance(obj, dict): encoded_dict = {} diff --git a/fastapi/exception_handlers.py b/fastapi/exception_handlers.py index 6c2ba7fedf..475dd7bdd9 100644 --- a/fastapi/exception_handlers.py +++ b/fastapi/exception_handlers.py @@ -5,7 +5,7 @@ from fastapi.websockets import WebSocket from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.responses import JSONResponse, Response -from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, WS_1008_POLICY_VIOLATION +from starlette.status import WS_1008_POLICY_VIOLATION async def http_exception_handler(request: Request, exc: HTTPException) -> Response: @@ -21,7 +21,7 @@ async def request_validation_exception_handler( request: Request, exc: RequestValidationError ) -> JSONResponse: return JSONResponse( - status_code=HTTP_422_UNPROCESSABLE_ENTITY, + status_code=422, content={"detail": jsonable_encoder(exc.errors())}, ) diff --git a/fastapi/middleware/asyncexitstack.py b/fastapi/middleware/asyncexitstack.py new file mode 100644 index 0000000000..4ce3f5a625 --- /dev/null +++ b/fastapi/middleware/asyncexitstack.py @@ -0,0 +1,18 @@ +from contextlib import AsyncExitStack + +from starlette.types import ASGIApp, Receive, Scope, Send + + +# Used mainly to close files after the request is done, dependencies are closed +# in their own AsyncExitStack +class AsyncExitStackMiddleware: + def __init__( + self, app: ASGIApp, context_name: str = "fastapi_middleware_astack" + ) -> None: + self.app = app + self.context_name = context_name + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + async with AsyncExitStack() as stack: + scope[self.context_name] = stack + await self.app(scope, receive, send) diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index c2ec358d2f..f181b43c1b 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -188,7 +188,7 @@ def get_redoc_html( It is normally set to a CDN URL. """ ), - ] = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js", + ] = "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js", redoc_favicon_url: Annotated[ str, Doc( diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index ed07b40f57..81d276aed6 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -121,6 +121,12 @@ class ExternalDocumentation(BaseModelWithConfig): url: AnyUrl +# Ref JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation#name-type +SchemaType = Literal[ + "array", "boolean", "integer", "null", "number", "object", "string" +] + + class Schema(BaseModelWithConfig): # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu # Core Vocabulary @@ -145,7 +151,7 @@ class Schema(BaseModelWithConfig): dependentSchemas: Optional[Dict[str, "SchemaOrBool"]] = None prefixItems: Optional[List["SchemaOrBool"]] = None # TODO: uncomment and remove below when deprecating Pydantic v1 - # It generales a list of schemas for tuples, before prefixItems was available + # It generates a list of schemas for tuples, before prefixItems was available # items: Optional["SchemaOrBool"] = None items: Optional[Union["SchemaOrBool", List["SchemaOrBool"]]] = None contains: Optional["SchemaOrBool"] = None @@ -157,7 +163,7 @@ class Schema(BaseModelWithConfig): unevaluatedProperties: Optional["SchemaOrBool"] = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural # A Vocabulary for Structural Validation - type: Optional[str] = None + type: Optional[Union[SchemaType, List[SchemaType]]] = None enum: Optional[List[Any]] = None const: Optional[Any] = None multipleOf: Optional[float] = Field(default=None, gt=0) diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 79ad9f83f2..dbc93d2892 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -5,7 +5,6 @@ from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, from fastapi import routing from fastapi._compat import ( - GenerateJsonSchema, JsonSchemaValue, ModelField, Undefined, @@ -16,11 +15,15 @@ from fastapi._compat import ( ) from fastapi.datastructures import DefaultPlaceholder from fastapi.dependencies.models import Dependant -from fastapi.dependencies.utils import get_flat_dependant, get_flat_params +from fastapi.dependencies.utils import ( + _get_flat_fields_from_params, + get_flat_dependant, + get_flat_params, +) from fastapi.encoders import jsonable_encoder -from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX, REF_TEMPLATE +from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX from fastapi.openapi.models import OpenAPI -from fastapi.params import Body, Param +from fastapi.params import Body, ParamTypes from fastapi.responses import Response from fastapi.types import ModelNameMap from fastapi.utils import ( @@ -28,11 +31,13 @@ from fastapi.utils import ( generate_operation_id_for_path, is_body_allowed_for_status_code, ) +from pydantic import BaseModel from starlette.responses import JSONResponse from starlette.routing import BaseRoute -from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY from typing_extensions import Literal +from .._compat import _is_model_field + validation_error_definition = { "title": "ValidationError", "type": "object", @@ -87,10 +92,9 @@ def get_openapi_security_definitions( return security_definitions, operation_security -def get_openapi_operation_parameters( +def _get_openapi_operation_parameters( *, - all_route_params: Sequence[ModelField], - schema_generator: GenerateJsonSchema, + dependant: Dependant, model_name_map: ModelNameMap, field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue @@ -98,40 +102,72 @@ def get_openapi_operation_parameters( separate_input_output_schemas: bool = True, ) -> List[Dict[str, Any]]: parameters = [] - for param in all_route_params: - field_info = param.field_info - field_info = cast(Param, field_info) - if not field_info.include_in_schema: - continue - param_schema = get_schema_from_model_field( - field=param, - schema_generator=schema_generator, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - parameter = { - "name": param.alias, - "in": field_info.in_.value, - "required": param.required, - "schema": param_schema, - } - if field_info.description: - parameter["description"] = field_info.description - if field_info.openapi_examples: - parameter["examples"] = jsonable_encoder(field_info.openapi_examples) - elif field_info.example != Undefined: - parameter["example"] = jsonable_encoder(field_info.example) - if field_info.deprecated: - parameter["deprecated"] = True - parameters.append(parameter) + flat_dependant = get_flat_dependant(dependant, skip_repeats=True) + path_params = _get_flat_fields_from_params(flat_dependant.path_params) + query_params = _get_flat_fields_from_params(flat_dependant.query_params) + header_params = _get_flat_fields_from_params(flat_dependant.header_params) + cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) + parameter_groups = [ + (ParamTypes.path, path_params), + (ParamTypes.query, query_params), + (ParamTypes.header, header_params), + (ParamTypes.cookie, cookie_params), + ] + default_convert_underscores = True + if len(flat_dependant.header_params) == 1: + first_field = flat_dependant.header_params[0] + if lenient_issubclass(first_field.type_, BaseModel): + default_convert_underscores = getattr( + first_field.field_info, "convert_underscores", True + ) + for param_type, param_group in parameter_groups: + for param in param_group: + field_info = param.field_info + # field_info = cast(Param, field_info) + if not getattr(field_info, "include_in_schema", True): + continue + param_schema = get_schema_from_model_field( + field=param, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + name = param.alias + convert_underscores = getattr( + param.field_info, + "convert_underscores", + default_convert_underscores, + ) + if ( + param_type == ParamTypes.header + and param.alias == param.name + and convert_underscores + ): + name = param.name.replace("_", "-") + + parameter = { + "name": name, + "in": param_type.value, + "required": param.required, + "schema": param_schema, + } + if field_info.description: + parameter["description"] = field_info.description + openapi_examples = getattr(field_info, "openapi_examples", None) + example = getattr(field_info, "example", None) + if openapi_examples: + parameter["examples"] = jsonable_encoder(openapi_examples) + elif example != Undefined: + parameter["example"] = jsonable_encoder(example) + if getattr(field_info, "deprecated", None): + parameter["deprecated"] = True + parameters.append(parameter) return parameters def get_openapi_operation_request_body( *, body_field: Optional[ModelField], - schema_generator: GenerateJsonSchema, model_name_map: ModelNameMap, field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue @@ -140,10 +176,9 @@ def get_openapi_operation_request_body( ) -> Optional[Dict[str, Any]]: if not body_field: return None - assert isinstance(body_field, ModelField) + assert _is_model_field(body_field) body_schema = get_schema_from_model_field( field=body_field, - schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -216,7 +251,6 @@ def get_openapi_path( *, route: routing.APIRoute, operation_ids: Set[str], - schema_generator: GenerateJsonSchema, model_name_map: ModelNameMap, field_mapping: Dict[ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue @@ -247,10 +281,8 @@ def get_openapi_path( operation.setdefault("security", []).extend(operation_security) if security_definitions: security_schemes.update(security_definitions) - all_route_params = get_flat_params(route.dependant) - operation_parameters = get_openapi_operation_parameters( - all_route_params=all_route_params, - schema_generator=schema_generator, + operation_parameters = _get_openapi_operation_parameters( + dependant=route.dependant, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -272,7 +304,6 @@ def get_openapi_path( if method in METHODS_WITH_BODY: request_body_oai = get_openapi_operation_request_body( body_field=route.body_field, - schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -290,7 +321,6 @@ def get_openapi_path( ) = get_openapi_path( route=callback, operation_ids=operation_ids, - schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -321,7 +351,6 @@ def get_openapi_path( if route.response_field: response_schema = get_schema_from_model_field( field=route.response_field, - schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -347,15 +376,14 @@ def get_openapi_path( openapi_response = operation_responses.setdefault( status_code_key, {} ) - assert isinstance( - process_response, dict - ), "An additional response must be a dict" + assert isinstance(process_response, dict), ( + "An additional response must be a dict" + ) field = route.response_fields.get(additional_status_code) additional_field_schema: Optional[Dict[str, Any]] = None if field: additional_field_schema = get_schema_from_model_field( field=field, - schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -378,7 +406,8 @@ def get_openapi_path( ) deep_dict_update(openapi_response, process_response) openapi_response["description"] = description - http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) + http422 = "422" + all_route_params = get_flat_params(route.dependant) if (all_route_params or route.body_field) and not any( status in operation["responses"] for status in [http422, "4XX", "default"] @@ -416,9 +445,9 @@ def get_fields_from_routes( route, routing.APIRoute ): if route.body_field: - assert isinstance( - route.body_field, ModelField - ), "A request body must be a Pydantic Field" + assert _is_model_field(route.body_field), ( + "A request body must be a Pydantic Field" + ) body_fields_from_routes.append(route.body_field) if route.response_field: responses_from_routes.append(route.response_field) @@ -450,6 +479,7 @@ def get_openapi( contact: Optional[Dict[str, Union[str, Any]]] = None, license_info: Optional[Dict[str, Union[str, Any]]] = None, separate_input_output_schemas: bool = True, + external_docs: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: info: Dict[str, Any] = {"title": title, "version": version} if summary: @@ -471,10 +501,8 @@ def get_openapi( operation_ids: Set[str] = set() all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or [])) model_name_map = get_compat_model_name_map(all_fields) - schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) field_mapping, definitions = get_definitions( fields=all_fields, - schema_generator=schema_generator, model_name_map=model_name_map, separate_input_output_schemas=separate_input_output_schemas, ) @@ -483,7 +511,6 @@ def get_openapi( result = get_openapi_path( route=route, operation_ids=operation_ids, - schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -503,7 +530,6 @@ def get_openapi( result = get_openapi_path( route=webhook, operation_ids=operation_ids, - schema_generator=schema_generator, model_name_map=model_name_map, field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, @@ -527,4 +553,6 @@ def get_openapi( output["webhooks"] = webhook_paths if tags: output["tags"] = tags + if external_docs: + output["externalDocs"] = external_docs return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index 0d5f27af48..b3621626cf 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -1282,7 +1282,7 @@ def Body( # noqa: N802 ), ] = _Unset, embed: Annotated[ - bool, + Union[bool, None], Doc( """ When `embed` is `True`, the parameter will be expected in a JSON body as a @@ -1294,7 +1294,7 @@ def Body( # noqa: N802 [FastAPI docs for Body - Multiple Parameters](https://fastapi.tiangolo.com/tutorial/body-multiple-params/#embed-a-single-body-parameter). """ ), - ] = False, + ] = None, media_type: Annotated[ str, Doc( @@ -2298,7 +2298,7 @@ def Security( # noqa: N802 dependency. The term "scope" comes from the OAuth2 specification, it seems to be - intentionaly vague and interpretable. It normally refers to permissions, + intentionally vague and interpretable. It normally refers to permissions, in cases to roles. These scopes are integrated with OpenAPI (and the API docs at `/docs`). diff --git a/fastapi/params.py b/fastapi/params.py index cc2a5c13c0..e853750188 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -6,7 +6,11 @@ from fastapi.openapi.models import Example from pydantic.fields import FieldInfo from typing_extensions import Annotated, deprecated -from ._compat import PYDANTIC_V2, PYDANTIC_VERSION, Undefined +from ._compat import ( + PYDANTIC_V2, + PYDANTIC_VERSION_MINOR_TUPLE, + Undefined, +) _Unset: Any = Undefined @@ -18,7 +22,7 @@ class ParamTypes(Enum): cookie = "cookie" -class Param(FieldInfo): +class Param(FieldInfo): # type: ignore[misc] in_: ParamTypes def __init__( @@ -105,7 +109,7 @@ class Param(FieldInfo): stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra - if PYDANTIC_VERSION < "2.7.0": + if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): self.deprecated = deprecated else: kwargs["deprecated"] = deprecated @@ -132,7 +136,7 @@ class Param(FieldInfo): return f"{self.__class__.__name__}({self.default})" -class Path(Param): +class Path(Param): # type: ignore[misc] in_ = ParamTypes.path def __init__( @@ -218,7 +222,7 @@ class Path(Param): ) -class Query(Param): +class Query(Param): # type: ignore[misc] in_ = ParamTypes.query def __init__( @@ -302,7 +306,7 @@ class Query(Param): ) -class Header(Param): +class Header(Param): # type: ignore[misc] in_ = ParamTypes.header def __init__( @@ -388,7 +392,7 @@ class Header(Param): ) -class Cookie(Param): +class Cookie(Param): # type: ignore[misc] in_ = ParamTypes.cookie def __init__( @@ -472,14 +476,14 @@ class Cookie(Param): ) -class Body(FieldInfo): +class Body(FieldInfo): # type: ignore[misc] def __init__( self, default: Any = Undefined, *, default_factory: Union[Callable[[], Any], None] = _Unset, annotation: Optional[Any] = None, - embed: bool = False, + embed: Union[bool, None] = None, media_type: str = "application/json", alias: Optional[str] = None, alias_priority: Union[int, None] = _Unset, @@ -556,12 +560,12 @@ class Body(FieldInfo): kwargs["examples"] = examples if regex is not None: warnings.warn( - "`regex` has been depreacated, please use `pattern` instead", + "`regex` has been deprecated, please use `pattern` instead", category=DeprecationWarning, stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra - if PYDANTIC_VERSION < "2.7.0": + if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): self.deprecated = deprecated else: kwargs["deprecated"] = deprecated @@ -589,7 +593,7 @@ class Body(FieldInfo): return f"{self.__class__.__name__}({self.default})" -class Form(Body): +class Form(Body): # type: ignore[misc] def __init__( self, default: Any = Undefined, @@ -642,7 +646,6 @@ class Form(Body): default=default, default_factory=default_factory, annotation=annotation, - embed=True, media_type=media_type, alias=alias, alias_priority=alias_priority, @@ -674,7 +677,7 @@ class Form(Body): ) -class File(Form): +class File(Form): # type: ignore[misc] def __init__( self, default: Any = Undefined, diff --git a/fastapi/routing.py b/fastapi/routing.py index fe37eb548c..99b0962d7d 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1,15 +1,17 @@ -import asyncio import dataclasses import email.message import functools import inspect import json +import sys from contextlib import AsyncExitStack, asynccontextmanager from enum import Enum, IntEnum from typing import ( Any, AsyncIterator, + Awaitable, Callable, + Collection, Coroutine, Dict, List, @@ -24,7 +26,7 @@ from typing import ( import anyio from anyio import CapacityLimiter -from fastapi import params +from fastapi import params, temp_pydantic_v1_params from fastapi._compat import ( ModelField, Undefined, @@ -36,8 +38,10 @@ from fastapi._compat import ( from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.dependencies.models import Dependant from fastapi.dependencies.utils import ( + _should_embed_body_fields, get_body_field, get_dependant, + get_flat_dependant, get_parameterless_sub_dependant, get_typed_return_annotation, solve_dependencies, @@ -52,13 +56,15 @@ from fastapi.exceptions import ( from fastapi.types import DecoratedCallable, IncEx from fastapi.utils import ( create_cloned_field, - create_response_field, + create_model_field, generate_unique_id, get_value_or_default, is_body_allowed_for_status_code, ) from pydantic import BaseModel from starlette import routing +from starlette._exception_handler import wrap_app_handling_exceptions +from starlette._utils import is_async_callable from starlette.concurrency import run_in_threadpool from starlette.exceptions import HTTPException from starlette.requests import Request @@ -68,14 +74,84 @@ from starlette.routing import ( Match, compile_path, get_name, - request_response, - websocket_session, ) from starlette.routing import Mount as Mount # noqa -from starlette.types import AppType, ASGIApp, Lifespan, Scope +from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send from starlette.websockets import WebSocket from typing_extensions import Annotated, Doc, deprecated +if sys.version_info >= (3, 13): # pragma: no cover + from inspect import iscoroutinefunction +else: # pragma: no cover + from asyncio import iscoroutinefunction + + +# Copy of starlette.routing.request_response modified to include the +# dependencies' AsyncExitStack +def request_response( + func: Callable[[Request], Union[Awaitable[Response], Response]], +) -> ASGIApp: + """ + Takes a function or coroutine `func(request) -> response`, + and returns an ASGI application. + """ + f: Callable[[Request], Awaitable[Response]] = ( + func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type:ignore + ) + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + request = Request(scope, receive, send) + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + # Starts customization + response_awaited = False + async with AsyncExitStack() as stack: + scope["fastapi_inner_astack"] = stack + # Same as in Starlette + response = await f(request) + await response(scope, receive, send) + # Continues customization + response_awaited = True + if not response_awaited: + raise FastAPIError( + "Response not awaited. There's a high chance that the " + "application code is raising an exception and a dependency with yield " + "has a block with a bare except, or a block with except Exception, " + "and is not raising the exception again. Read more about it in the " + "docs: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-except" + ) + + # Same as in Starlette + await wrap_app_handling_exceptions(app, request)(scope, receive, send) + + return app + + +# Copy of starlette.routing.websocket_session modified to include the +# dependencies' AsyncExitStack +def websocket_session( + func: Callable[[WebSocket], Awaitable[None]], +) -> ASGIApp: + """ + Takes a coroutine `func(session)`, and returns an ASGI application. + """ + # assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async" + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + session = WebSocket(scope, receive=receive, send=send) + + async def app(scope: Scope, receive: Receive, send: Send) -> None: + # Starts customization + async with AsyncExitStack() as stack: + scope["fastapi_inner_astack"] = stack + # Same as in Starlette + await func(session) + + # Same as in Starlette + await wrap_app_handling_exceptions(app, session)(scope, receive, send) + + return app + def _prepare_response_content( res: Any, @@ -120,6 +196,7 @@ def _prepare_response_content( for k, v in res.items() } elif dataclasses.is_dataclass(res): + assert not isinstance(res, type) return dataclasses.asdict(res) return res @@ -231,10 +308,13 @@ def get_request_handler( response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, dependency_overrides_provider: Optional[Any] = None, + embed_body_fields: bool = False, ) -> Callable[[Request], Coroutine[Any, Any, Response]]: assert dependant.call is not None, "dependant.call must be a function" - is_coroutine = asyncio.iscoroutinefunction(dependant.call) - is_body_form = body_field and isinstance(body_field.field_info, params.Form) + is_coroutine = iscoroutinefunction(dependant.call) + is_body_form = body_field and isinstance( + body_field.field_info, (params.Form, temp_pydantic_v1_params.Form) + ) if isinstance(response_class, DefaultPlaceholder): actual_response_class: Type[Response] = response_class.value else: @@ -242,135 +322,148 @@ def get_request_handler( async def app(request: Request) -> Response: response: Union[Response, None] = None - async with AsyncExitStack() as file_stack: - try: - body: Any = None - if body_field: - if is_body_form: - body = await request.form() - file_stack.push_async_callback(body.close) - else: - body_bytes = await request.body() - if body_bytes: - json_body: Any = Undefined - content_type_value = request.headers.get("content-type") - if not content_type_value: - json_body = await request.json() - else: - message = email.message.Message() - message["content-type"] = content_type_value - if message.get_content_maintype() == "application": - subtype = message.get_content_subtype() - if subtype == "json" or subtype.endswith("+json"): - json_body = await request.json() - if json_body != Undefined: - body = json_body - else: - body = body_bytes - except json.JSONDecodeError as e: - validation_error = RequestValidationError( - [ - { - "type": "json_invalid", - "loc": ("body", e.pos), - "msg": "JSON decode error", - "input": {}, - "ctx": {"error": e.msg}, - } - ], - body=e.doc, - ) - raise validation_error from e - except HTTPException: - # If a middleware raises an HTTPException, it should be raised again - raise - except Exception as e: - http_error = HTTPException( - status_code=400, detail="There was an error parsing the body" - ) - raise http_error from e - errors: List[Any] = [] - async with AsyncExitStack() as async_exit_stack: - solved_result = await solve_dependencies( - request=request, - dependant=dependant, - body=body, - dependency_overrides_provider=dependency_overrides_provider, - async_exit_stack=async_exit_stack, - ) - values, errors, background_tasks, sub_response, _ = solved_result - if not errors: - raw_response = await run_endpoint_function( - dependant=dependant, values=values, is_coroutine=is_coroutine - ) - if isinstance(raw_response, Response): - if raw_response.background is None: - raw_response.background = background_tasks - response = raw_response - else: - 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, - exclude=response_model_exclude, - by_alias=response_model_by_alias, - exclude_unset=response_model_exclude_unset, - exclude_defaults=response_model_exclude_defaults, - exclude_none=response_model_exclude_none, - is_coroutine=is_coroutine, - ) - 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 errors: - validation_error = RequestValidationError( - _normalize_errors(errors), body=body - ) - raise validation_error - if response is None: - raise FastAPIError( - "No response object was returned. There's a high chance that the " - "application code is raising an exception and a dependency with yield " - "has a block with a bare except, or a block with except Exception, " - "and is not raising the exception again. Read more about it in the " - "docs: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-except" + file_stack = request.scope.get("fastapi_middleware_astack") + assert isinstance(file_stack, AsyncExitStack), ( + "fastapi_middleware_astack not found in request scope" + ) + + # Read body and auto-close files + try: + body: Any = None + if body_field: + if is_body_form: + body = await request.form() + file_stack.push_async_callback(body.close) + else: + body_bytes = await request.body() + if body_bytes: + json_body: Any = Undefined + content_type_value = request.headers.get("content-type") + if not content_type_value: + json_body = await request.json() + else: + message = email.message.Message() + message["content-type"] = content_type_value + if message.get_content_maintype() == "application": + subtype = message.get_content_subtype() + if subtype == "json" or subtype.endswith("+json"): + json_body = await request.json() + if json_body != Undefined: + body = json_body + else: + body = body_bytes + except json.JSONDecodeError as e: + validation_error = RequestValidationError( + [ + { + "type": "json_invalid", + "loc": ("body", e.pos), + "msg": "JSON decode error", + "input": {}, + "ctx": {"error": e.msg}, + } + ], + body=e.doc, ) + raise validation_error from e + except HTTPException: + # If a middleware raises an HTTPException, it should be raised again + raise + except Exception as e: + http_error = HTTPException( + status_code=400, detail="There was an error parsing the body" + ) + raise http_error from e + + # Solve dependencies and run path operation function, auto-closing dependencies + errors: List[Any] = [] + async_exit_stack = request.scope.get("fastapi_inner_astack") + assert isinstance(async_exit_stack, AsyncExitStack), ( + "fastapi_inner_astack not found in request scope" + ) + solved_result = await solve_dependencies( + request=request, + dependant=dependant, + body=body, + dependency_overrides_provider=dependency_overrides_provider, + async_exit_stack=async_exit_stack, + embed_body_fields=embed_body_fields, + ) + errors = solved_result.errors + if not errors: + raw_response = await run_endpoint_function( + dependant=dependant, + values=solved_result.values, + is_coroutine=is_coroutine, + ) + if isinstance(raw_response, Response): + if raw_response.background is None: + raw_response.background = solved_result.background_tasks + response = raw_response + else: + response_args: Dict[str, Any] = { + "background": solved_result.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 solved_result.response.status_code + ) + if current_status_code is not None: + response_args["status_code"] = current_status_code + if solved_result.response.status_code: + response_args["status_code"] = solved_result.response.status_code + content = await serialize_response( + field=response_field, + response_content=raw_response, + include=response_model_include, + exclude=response_model_exclude, + by_alias=response_model_by_alias, + exclude_unset=response_model_exclude_unset, + exclude_defaults=response_model_exclude_defaults, + exclude_none=response_model_exclude_none, + is_coroutine=is_coroutine, + ) + 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(solved_result.response.headers.raw) + if errors: + validation_error = RequestValidationError( + _normalize_errors(errors), body=body + ) + raise validation_error + + # Return response + assert response return response return app def get_websocket_app( - dependant: Dependant, dependency_overrides_provider: Optional[Any] = None + dependant: Dependant, + dependency_overrides_provider: Optional[Any] = None, + embed_body_fields: bool = False, ) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]: async def app(websocket: WebSocket) -> None: - async with AsyncExitStack() as async_exit_stack: - # TODO: remove this scope later, after a few releases - # This scope fastapi_astack is no longer used by FastAPI, kept for - # compatibility, just in case - websocket.scope["fastapi_astack"] = async_exit_stack - solved_result = await solve_dependencies( - request=websocket, - dependant=dependant, - dependency_overrides_provider=dependency_overrides_provider, - async_exit_stack=async_exit_stack, + async_exit_stack = websocket.scope.get("fastapi_inner_astack") + assert isinstance(async_exit_stack, AsyncExitStack), ( + "fastapi_inner_astack not found in request scope" + ) + solved_result = await solve_dependencies( + request=websocket, + dependant=dependant, + dependency_overrides_provider=dependency_overrides_provider, + async_exit_stack=async_exit_stack, + embed_body_fields=embed_body_fields, + ) + if solved_result.errors: + raise WebSocketRequestValidationError( + _normalize_errors(solved_result.errors) ) - values, errors, _, _2, _3 = solved_result - if errors: - raise WebSocketRequestValidationError(_normalize_errors(errors)) - assert dependant.call is not None, "dependant.call must be a function" - await dependant.call(**values) + assert dependant.call is not None, "dependant.call must be a function" + await dependant.call(**solved_result.values) return app @@ -396,11 +489,15 @@ class APIWebSocketRoute(routing.WebSocketRoute): 0, get_parameterless_sub_dependant(depends=depends, path=self.path_format), ) - + self._flat_dependant = get_flat_dependant(self.dependant) + self._embed_body_fields = _should_embed_body_fields( + self._flat_dependant.body_params + ) self.app = websocket_session( get_websocket_app( dependant=self.dependant, dependency_overrides_provider=dependency_overrides_provider, + embed_body_fields=self._embed_body_fields, ) ) @@ -490,11 +587,11 @@ class APIRoute(routing.Route): status_code = int(status_code) self.status_code = status_code if self.response_model: - assert is_body_allowed_for_status_code( - status_code - ), f"Status code {status_code} must not have a response 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( + self.response_field = create_model_field( name=response_name, type_=self.response_model, mode="serialization", @@ -523,11 +620,13 @@ class APIRoute(routing.Route): assert isinstance(response, dict), "An additional response must be a dict" model = response.get("model") if model: - assert is_body_allowed_for_status_code( - additional_status_code - ), f"Status code {additional_status_code} must not have a response 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) + response_field = create_model_field( + name=response_name, type_=model, mode="serialization" + ) response_fields[additional_status_code] = response_field if response_fields: self.response_fields: Dict[Union[int, str], ModelField] = response_fields @@ -541,7 +640,15 @@ class APIRoute(routing.Route): 0, get_parameterless_sub_dependant(depends=depends, path=self.path_format), ) - self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id) + self._flat_dependant = get_flat_dependant(self.dependant) + self._embed_body_fields = _should_embed_body_fields( + self._flat_dependant.body_params + ) + self.body_field = get_body_field( + flat_dependant=self._flat_dependant, + name=self.unique_id, + embed_body_fields=self._embed_body_fields, + ) self.app = request_response(self.get_route_handler()) def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]: @@ -558,6 +665,7 @@ class APIRoute(routing.Route): response_model_exclude_defaults=self.response_model_exclude_defaults, response_model_exclude_none=self.response_model_exclude_none, dependency_overrides_provider=self.dependency_overrides_provider, + embed_body_fields=self._embed_body_fields, ) def matches(self, scope: Scope) -> Tuple[Match, Scope]: @@ -789,7 +897,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -819,9 +927,9 @@ class APIRouter(routing.Router): ) if prefix: assert prefix.startswith("/"), "A path prefix must start with '/'" - assert not prefix.endswith( - "/" - ), "A path prefix must not end with '/', as the routes will start with '/'" + assert not prefix.endswith("/"), ( + "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 []) @@ -837,7 +945,7 @@ class APIRouter(routing.Router): def route( self, path: str, - methods: Optional[List[str]] = None, + methods: Optional[Collection[str]] = None, name: Optional[str] = None, include_in_schema: bool = True, ) -> Callable[[DecoratedCallable], DecoratedCallable]: @@ -1231,9 +1339,9 @@ class APIRouter(routing.Router): """ if prefix: assert prefix.startswith("/"), "A path prefix must start with '/'" - assert not prefix.endswith( - "/" - ), "A path prefix must not end with '/', as the routes will start with '/'" + assert not prefix.endswith("/"), ( + "A path prefix must not end with '/', as the routes will start with '/'" + ) else: for r in router.routes: path = getattr(r, "path") # noqa: B009 @@ -1601,7 +1709,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -1978,7 +2086,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -2360,7 +2468,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -2742,7 +2850,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -3119,7 +3227,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -3496,7 +3604,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -3878,7 +3986,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, @@ -4260,7 +4368,7 @@ class APIRouter(routing.Router): This affects the generated OpenAPI (e.g. visible at `/docs`). Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ ), ] = True, diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py index d68bdb037e..6d6dd01d91 100644 --- a/fastapi/security/api_key.py +++ b/fastapi/security/api_key.py @@ -9,7 +9,15 @@ from typing_extensions import Annotated, Doc class APIKeyBase(SecurityBase): - pass + @staticmethod + def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]: + if not api_key: + if auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + return None + return api_key class APIKeyQuery(APIKeyBase): @@ -92,7 +100,7 @@ class APIKeyQuery(APIKeyBase): ] = True, ): self.model: APIKey = APIKey( - **{"in": APIKeyIn.query}, # type: ignore[arg-type] + **{"in": APIKeyIn.query}, name=name, description=description, ) @@ -101,14 +109,7 @@ class APIKeyQuery(APIKeyBase): async def __call__(self, request: Request) -> Optional[str]: api_key = request.query_params.get(self.model.name) - if not api_key: - if self.auto_error: - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - else: - return None - return api_key + return self.check_api_key(api_key, self.auto_error) class APIKeyHeader(APIKeyBase): @@ -187,7 +188,7 @@ class APIKeyHeader(APIKeyBase): ] = True, ): self.model: APIKey = APIKey( - **{"in": APIKeyIn.header}, # type: ignore[arg-type] + **{"in": APIKeyIn.header}, name=name, description=description, ) @@ -196,14 +197,7 @@ class APIKeyHeader(APIKeyBase): async def __call__(self, request: Request) -> Optional[str]: api_key = request.headers.get(self.model.name) - if not api_key: - if self.auto_error: - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - else: - return None - return api_key + return self.check_api_key(api_key, self.auto_error) class APIKeyCookie(APIKeyBase): @@ -282,7 +276,7 @@ class APIKeyCookie(APIKeyBase): ] = True, ): self.model: APIKey = APIKey( - **{"in": APIKeyIn.cookie}, # type: ignore[arg-type] + **{"in": APIKeyIn.cookie}, name=name, description=description, ) @@ -291,11 +285,4 @@ class APIKeyCookie(APIKeyBase): async def __call__(self, request: Request) -> Optional[str]: api_key = request.cookies.get(self.model.name) - if not api_key: - if self.auto_error: - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - else: - return None - return api_key + return self.check_api_key(api_key, self.auto_error) diff --git a/fastapi/security/http.py b/fastapi/security/http.py index a142b135da..9ab2df3c98 100644 --- a/fastapi/security/http.py +++ b/fastapi/security/http.py @@ -277,7 +277,7 @@ class HTTPBearer(HTTPBase): bool, Doc( """ - By default, if the HTTP Bearer token not provided (in an + By default, if the HTTP Bearer token is not provided (in an `Authorization` header), `HTTPBearer` will automatically cancel the request and send the client an error. @@ -380,7 +380,7 @@ class HTTPDigest(HTTPBase): bool, Doc( """ - By default, if the HTTP Digest not provided, `HTTPDigest` will + By default, if the HTTP Digest is not provided, `HTTPDigest` will automatically cancel the request and send the client an error. If `auto_error` is set to `False`, when the HTTP Digest is not @@ -413,8 +413,11 @@ class HTTPDigest(HTTPBase): else: return None if scheme.lower() != "digest": - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, - detail="Invalid authentication credentials", - ) + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, + detail="Invalid authentication credentials", + ) + else: + return None return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py index 9720cace05..fdedbc2dad 100644 --- a/fastapi/security/oauth2.py +++ b/fastapi/security/oauth2.py @@ -52,7 +52,7 @@ class OAuth2PasswordRequestForm: ``` Note that for OAuth2 the scope `items:read` is a single scope in an opaque string. - You could have custom internal logic to separate it by colon caracters (`:`) or + You could have custom internal logic to separate it by colon characters (`:`) or similar, and get the two parts `items` and `read`. Many applications do that to group and organize permissions, you could do it as well in your application, just know that that it is application specific, it's not part of the specification. @@ -63,7 +63,7 @@ class OAuth2PasswordRequestForm: *, grant_type: Annotated[ Union[str, None], - Form(pattern="password"), + Form(pattern="^password$"), Doc( """ The OAuth2 spec says it is required and MUST be the fixed string @@ -85,11 +85,11 @@ class OAuth2PasswordRequestForm: ], password: Annotated[ str, - Form(), + Form(json_schema_extra={"format": "password"}), Doc( """ `password` string. The OAuth2 spec requires the exact field name - `password". + `password`. """ ), ], @@ -130,7 +130,7 @@ class OAuth2PasswordRequestForm: ] = None, client_secret: Annotated[ Union[str, None], - Form(), + Form(json_schema_extra={"format": "password"}), Doc( """ If there's a `client_password` (and a `client_id`), they can be sent @@ -194,7 +194,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): ``` Note that for OAuth2 the scope `items:read` is a single scope in an opaque string. - You could have custom internal logic to separate it by colon caracters (`:`) or + You could have custom internal logic to separate it by colon characters (`:`) or similar, and get the two parts `items` and `read`. Many applications do that to group and organize permissions, you could do it as well in your application, just know that that it is application specific, it's not part of the specification. @@ -217,7 +217,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): self, grant_type: Annotated[ str, - Form(pattern="password"), + Form(pattern="^password$"), Doc( """ The OAuth2 spec says it is required and MUST be the fixed string @@ -243,7 +243,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): Doc( """ `password` string. The OAuth2 spec requires the exact field name - `password". + `password`. """ ), ], @@ -457,11 +457,26 @@ class OAuth2PasswordBearer(OAuth2): """ ), ] = True, + refreshUrl: Annotated[ + Optional[str], + Doc( + """ + The URL to refresh the token and obtain a new one. + """ + ), + ] = None, ): if not scopes: scopes = {} flows = OAuthFlowsModel( - password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes}) + password=cast( + Any, + { + "tokenUrl": tokenUrl, + "refreshUrl": refreshUrl, + "scopes": scopes, + }, + ) ) super().__init__( flows=flows, diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py new file mode 100644 index 0000000000..e41d712308 --- /dev/null +++ b/fastapi/temp_pydantic_v1_params.py @@ -0,0 +1,724 @@ +import warnings +from typing import Any, Callable, Dict, List, Optional, Union + +from fastapi.openapi.models import Example +from fastapi.params import ParamTypes +from typing_extensions import Annotated, deprecated + +from ._compat.may_v1 import FieldInfo, Undefined +from ._compat.shared import PYDANTIC_VERSION_MINOR_TUPLE + +_Unset: Any = Undefined + + +class Param(FieldInfo): # type: ignore[misc] + in_: ParamTypes + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + if example is not _Unset: + warnings.warn( + "`example` has been deprecated, please use `examples` instead", + category=DeprecationWarning, + stacklevel=4, + ) + self.example = example + self.include_in_schema = include_in_schema + self.openapi_examples = openapi_examples + kwargs = dict( + default=default, + default_factory=default_factory, + alias=alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + discriminator=discriminator, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + **extra, + ) + if examples is not None: + kwargs["examples"] = examples + if regex is not None: + warnings.warn( + "`regex` has been deprecated, please use `pattern` instead", + category=DeprecationWarning, + stacklevel=4, + ) + current_json_schema_extra = json_schema_extra or extra + if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): + self.deprecated = deprecated + else: + kwargs["deprecated"] = deprecated + kwargs["regex"] = pattern or regex + kwargs.update(**current_json_schema_extra) + use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} + + super().__init__(**use_kwargs) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.default})" + + +class Path(Param): # type: ignore[misc] + in_ = ParamTypes.path + + def __init__( + self, + default: Any = ..., + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + assert default is ..., "Path parameters cannot have a default value" + self.in_ = self.in_ + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Query(Param): # type: ignore[misc] + in_ = ParamTypes.query + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Header(Param): # type: ignore[misc] + in_ = ParamTypes.header + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + convert_underscores: bool = True, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + self.convert_underscores = convert_underscores + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Cookie(Param): # type: ignore[misc] + in_ = ParamTypes.cookie + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Body(FieldInfo): # type: ignore[misc] + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + embed: Union[bool, None] = None, + media_type: str = "application/json", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + self.embed = embed + self.media_type = media_type + if example is not _Unset: + warnings.warn( + "`example` has been deprecated, please use `examples` instead", + category=DeprecationWarning, + stacklevel=4, + ) + self.example = example + self.include_in_schema = include_in_schema + self.openapi_examples = openapi_examples + kwargs = dict( + default=default, + default_factory=default_factory, + alias=alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + discriminator=discriminator, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + **extra, + ) + if examples is not None: + kwargs["examples"] = examples + if regex is not None: + warnings.warn( + "`regex` has been deprecated, please use `pattern` instead", + category=DeprecationWarning, + stacklevel=4, + ) + current_json_schema_extra = json_schema_extra or extra + if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): + self.deprecated = deprecated + else: + kwargs["deprecated"] = deprecated + kwargs["regex"] = pattern or regex + kwargs.update(**current_json_schema_extra) + + use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} + + super().__init__(**use_kwargs) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.default})" + + +class Form(Body): # type: ignore[misc] + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + media_type: str = "application/x-www-form-urlencoded", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class File(Form): # type: ignore[misc] + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + media_type: str = "multipart/form-data", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) diff --git a/fastapi/utils.py b/fastapi/utils.py index 5c2538faca..2e79ee6b19 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -23,10 +23,12 @@ from fastapi._compat import ( Undefined, UndefinedType, Validator, + annotation_is_pydantic_v1, lenient_issubclass, + may_v1, ) from fastapi.datastructures import DefaultPlaceholder, DefaultType -from pydantic import BaseModel, create_model +from pydantic import BaseModel from pydantic.fields import FieldInfo from typing_extensions import Literal @@ -60,53 +62,74 @@ def get_path_param_names(path: str) -> Set[str]: return set(re.findall("{(.*?)}", path)) -def create_response_field( +_invalid_args_message = ( + "Invalid args for response field! Hint: " + "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/" +) + + +def create_model_field( name: str, - type_: Type[Any], + type_: Any, class_validators: Optional[Dict[str, Validator]] = None, default: Optional[Any] = Undefined, required: Union[bool, UndefinedType] = Undefined, - model_config: Type[BaseConfig] = BaseConfig, + model_config: Union[Type[BaseConfig], None] = None, field_info: Optional[FieldInfo] = None, alias: Optional[str] = None, mode: Literal["validation", "serialization"] = "validation", + version: Literal["1", "auto"] = "auto", ) -> ModelField: - """ - Create a new response field. Raises if type_ is invalid. - """ class_validators = class_validators or {} - if PYDANTIC_V2: + + v1_model_config = may_v1.BaseConfig + v1_field_info = field_info or may_v1.FieldInfo() + v1_kwargs = { + "name": name, + "field_info": v1_field_info, + "type_": type_, + "class_validators": class_validators, + "default": default, + "required": required, + "model_config": v1_model_config, + "alias": alias, + } + + if ( + annotation_is_pydantic_v1(type_) + or isinstance(field_info, may_v1.FieldInfo) + or version == "1" + ): + from fastapi._compat import v1 + + try: + return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] + except RuntimeError: + raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None + elif PYDANTIC_V2: + from ._compat import v2 + field_info = field_info or FieldInfo( annotation=type_, default=default, alias=alias ) - else: - field_info = field_info or FieldInfo() - kwargs = {"name": name, "field_info": field_info} - if PYDANTIC_V2: - kwargs.update({"mode": mode}) - else: - kwargs.update( - { - "type_": type_, - "class_validators": class_validators, - "default": default, - "required": required, - "model_config": model_config, - "alias": alias, - } - ) + kwargs = {"mode": mode, "name": name, "field_info": field_info} + try: + return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type] + except PydanticSchemaGenerationError: + raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None + # Pydantic v2 is not installed, but it's not a Pydantic v1 ModelField, it could be + # a Pydantic v1 type, like a constrained int + from fastapi._compat import v1 + try: - return ModelField(**kwargs) # type: ignore[arg-type] - except (RuntimeError, PydanticSchemaGenerationError): - raise fastapi.exceptions.FastAPIError( - "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 + return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] + except RuntimeError: + raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None def create_cloned_field( @@ -115,7 +138,13 @@ def create_cloned_field( cloned_types: Optional[MutableMapping[Type[BaseModel], Type[BaseModel]]] = None, ) -> ModelField: if PYDANTIC_V2: - return field + from ._compat import v2 + + if isinstance(field, v2.ModelField): + return field + + from fastapi._compat import v1 + # cloned_types caches already cloned types to support recursive models and improve # performance by avoiding unnecessary cloning if cloned_types is None: @@ -125,21 +154,23 @@ def create_cloned_field( if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"): original_type = original_type.__pydantic_model__ use_type = original_type - if lenient_issubclass(original_type, BaseModel): - original_type = cast(Type[BaseModel], original_type) + if lenient_issubclass(original_type, v1.BaseModel): + original_type = cast(Type[v1.BaseModel], original_type) use_type = cloned_types.get(original_type) if use_type is None: - use_type = create_model(original_type.__name__, __base__=original_type) + use_type = v1.create_model(original_type.__name__, __base__=original_type) cloned_types[original_type] = use_type for f in original_type.__fields__.values(): use_type.__fields__[f.name] = create_cloned_field( - f, cloned_types=cloned_types + f, + cloned_types=cloned_types, ) - new_field = create_response_field(name=field.name, type_=use_type) + new_field = create_model_field(name=field.name, type_=use_type, version="1") new_field.has_alias = field.has_alias # type: ignore[attr-defined] new_field.alias = field.alias # type: ignore[misc] new_field.class_validators = field.class_validators # type: ignore[attr-defined] new_field.default = field.default # type: ignore[misc] + new_field.default_factory = field.default_factory # type: ignore[attr-defined] new_field.required = field.required # type: ignore[misc] new_field.model_config = field.model_config # type: ignore[attr-defined] new_field.field_info = field.field_info diff --git a/pyproject.toml b/pyproject.toml index bb87be470e..cac8059f47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ "Framework :: FastAPI", "Framework :: Pydantic", "Framework :: Pydantic :: 1", + "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3 :: Only", @@ -37,11 +38,13 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP", ] dependencies = [ - "starlette>=0.37.2,<0.39.0", + "starlette>=0.40.0,<0.49.0", "pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0", "typing-extensions>=4.8.0", ] @@ -56,13 +59,32 @@ Changelog = "https://fastapi.tiangolo.com/release-notes/" [project.optional-dependencies] standard = [ - "fastapi-cli[standard] >=0.0.5", + "fastapi-cli[standard] >=0.0.8", # For the test client - "httpx >=0.23.0", + "httpx >=0.23.0,<1.0.0", # For templates - "jinja2 >=2.11.2", + "jinja2 >=3.1.5", # For forms and file uploads - "python-multipart >=0.0.7", + "python-multipart >=0.0.18", + # To validate email fields + "email-validator >=2.0.0", + # Uvicorn with uvloop + "uvicorn[standard] >=0.12.0", + # TODO: this should be part of some pydantic optional extra dependencies + # # Settings management + # "pydantic-settings >=2.0.0", + # # Extra Pydantic data types + # "pydantic-extra-types >=2.0.0", +] + +standard-no-fastapi-cloud-cli = [ + "fastapi-cli[standard-no-fastapi-cloud-cli] >=0.0.8", + # For the test client + "httpx >=0.23.0,<1.0.0", + # For templates + "jinja2 >=3.1.5", + # For forms and file uploads + "python-multipart >=0.0.18", # To validate email fields "email-validator >=2.0.0", # Uvicorn with uvloop @@ -75,13 +97,13 @@ standard = [ ] all = [ - "fastapi-cli[standard] >=0.0.5", + "fastapi-cli[standard] >=0.0.8", # # For the test client - "httpx >=0.23.0", + "httpx >=0.23.0,<1.0.0", # For templates - "jinja2 >=2.11.2", + "jinja2 >=3.1.5", # For forms and file uploads - "python-multipart >=0.0.7", + "python-multipart >=0.0.18", # For Starlette's SessionMiddleware, not commonly used with FastAPI "itsdangerous >=1.1.0", # For Starlette's schema generation, would not be used with FastAPI @@ -121,6 +143,7 @@ source-includes = [ name = "fastapi-slim" [tool.mypy] +plugins = ["pydantic.mypy"] strict = true [[tool.mypy.overrides]] @@ -150,8 +173,6 @@ junit_family = "xunit2" filterwarnings = [ "error", 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette', - # 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", # TODO: remove after upgrading SQLAlchemy to a version that includes the following changes @@ -161,6 +182,8 @@ filterwarnings = [ # Ref: https://github.com/python-trio/trio/pull/3054 # Remove once there's a new version of Trio 'ignore:The `hash` argument is deprecated*:DeprecationWarning:trio', + # Ignore flaky coverage / pytest warning about SQLite connection, only applies to Python 3.13 and Pydantic v1 + 'ignore:Exception ignored in. =0.23.0,<0.25.0 +httpx >=0.23.0,<1.0.0 +# For linting and generating docs versions +ruff ==0.13.2 diff --git a/requirements-docs.txt b/requirements-docs.txt index ab2b0165be..0013f9f79d 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,18 +1,19 @@ -e . -r requirements-docs-tests.txt -mkdocs-material==9.5.18 +mkdocs-material==9.6.16 mdx-include >=1.4.1,<2.0.0 mkdocs-redirects>=1.2.1,<1.3.0 -typer == 0.12.3 +typer == 0.16.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==10.3.0 +pillow==11.3.0 # For image processing by Material for MkDocs -cairosvg==2.7.1 -mkdocstrings[python]==0.25.1 -griffe-typingdoc==0.2.6 +cairosvg==2.8.2 +mkdocstrings[python]==0.26.1 +griffe-typingdoc==0.2.9 # For griffe, it formats with black -black==24.3.0 -mkdocs-macros-plugin==1.0.5 +black==25.1.0 +mkdocs-macros-plugin==1.4.0 +markdown-include-variants==0.0.5 diff --git a/requirements-github-actions.txt b/requirements-github-actions.txt index 559dc06fb2..a6a733447d 100644 --- a/requirements-github-actions.txt +++ b/requirements-github-actions.txt @@ -1,4 +1,6 @@ PyGithub>=2.3.0,<3.0.0 pydantic>=2.5.3,<3.0.0 pydantic-settings>=2.1.0,<3.0.0 -httpx>=0.27.0,<0.28.0 +httpx>=0.27.0,<1.0.0 +pyyaml >=5.3.1,<7.0.0 +smokeshow diff --git a/requirements-tests.txt b/requirements-tests.txt index 08561d23a9..c5de4157e7 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,20 +1,16 @@ -e .[all] -r requirements-docs-tests.txt -pytest >=7.1.3,<8.0.0 +pytest >=7.1.3,<9.0.0 coverage[toml] >= 6.5.0,< 8.0 -mypy ==1.8.0 -ruff ==0.6.1 -dirty-equals ==0.6.0 -# TODO: once removing databases from tutorial, upgrade SQLAlchemy -# probably when including SQLModel -sqlalchemy >=1.3.18,<2.0.33 -databases[sqlite] >=0.3.2,<0.7.0 -flask >=1.1.2,<3.0.0 -anyio[trio] >=3.2.1,<4.0.0 -PyJWT==2.8.0 +mypy ==1.14.1 +dirty-equals ==0.9.0 +sqlmodel==0.0.27 +flask >=1.1.2,<4.0.0 +anyio[trio] >=3.2.1,<5.0.0 +PyJWT==2.9.0 pyyaml >=5.3.1,<7.0.0 -passlib[bcrypt] >=1.7.2,<2.0.0 - +pwdlib[argon2] >=0.2.1 +inline-snapshot>=0.21.1 # types -types-ujson ==5.7.0.1 +types-ujson ==5.10.0.20240515 types-orjson ==3.6.2 diff --git a/requirements-translations.txt b/requirements-translations.txt new file mode 100644 index 0000000000..a62ba3ac1c --- /dev/null +++ b/requirements-translations.txt @@ -0,0 +1,2 @@ +pydantic-ai==0.4.10 +GitPython==3.1.45 diff --git a/requirements.txt b/requirements.txt index 8e1fef3416..9180bf1be5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -e .[all] -r requirements-tests.txt -r requirements-docs.txt -pre-commit >=2.17.0,<4.0.0 +pre-commit >=2.17.0,<5.0.0 # For generating screenshots playwright diff --git a/scripts/contributors.py b/scripts/contributors.py new file mode 100644 index 0000000000..251558de7f --- /dev/null +++ b/scripts/contributors.py @@ -0,0 +1,315 @@ +import logging +import secrets +import subprocess +from collections import Counter +from datetime import datetime +from pathlib import Path +from typing import Any + +import httpx +import yaml +from github import Github +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings + +github_graphql_url = "https://api.github.com/graphql" + + +prs_query = """ +query Q($after: String) { + repository(name: "fastapi", owner: "fastapi") { + pullRequests(first: 100, after: $after) { + edges { + cursor + node { + number + labels(first: 100) { + nodes { + name + } + } + author { + login + avatarUrl + url + } + title + createdAt + lastEditedAt + updatedAt + state + reviews(first:100) { + nodes { + author { + login + avatarUrl + url + } + state + } + } + } + } + } + } +} +""" + + +class Author(BaseModel): + login: str + avatarUrl: str + url: str + + +class LabelNode(BaseModel): + name: str + + +class Labels(BaseModel): + nodes: list[LabelNode] + + +class ReviewNode(BaseModel): + author: Author | None = None + state: str + + +class Reviews(BaseModel): + nodes: list[ReviewNode] + + +class PullRequestNode(BaseModel): + number: int + labels: Labels + author: Author | None = None + title: str + createdAt: datetime + lastEditedAt: datetime | None = None + updatedAt: datetime | None = None + state: str + reviews: Reviews + + +class PullRequestEdge(BaseModel): + cursor: str + node: PullRequestNode + + +class PullRequests(BaseModel): + edges: list[PullRequestEdge] + + +class PRsRepository(BaseModel): + pullRequests: PullRequests + + +class PRsResponseData(BaseModel): + repository: PRsRepository + + +class PRsResponse(BaseModel): + data: PRsResponseData + + +class Settings(BaseSettings): + github_token: SecretStr + github_repository: str + httpx_timeout: int = 30 + + +def get_graphql_response( + *, + settings: Settings, + query: str, + after: str | None = None, +) -> dict[str, Any]: + headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} + variables = {"after": after} + response = httpx.post( + github_graphql_url, + headers=headers, + timeout=settings.httpx_timeout, + json={"query": query, "variables": variables, "operationName": "Q"}, + ) + if response.status_code != 200: + logging.error(f"Response was not 200, after: {after}") + logging.error(response.text) + raise RuntimeError(response.text) + data = response.json() + if "errors" in data: + logging.error(f"Errors in response, after: {after}") + logging.error(data["errors"]) + logging.error(response.text) + raise RuntimeError(response.text) + return data + + +def get_graphql_pr_edges( + *, settings: Settings, after: str | None = None +) -> list[PullRequestEdge]: + data = get_graphql_response(settings=settings, query=prs_query, after=after) + graphql_response = PRsResponse.model_validate(data) + return graphql_response.data.repository.pullRequests.edges + + +def get_pr_nodes(settings: Settings) -> list[PullRequestNode]: + pr_nodes: list[PullRequestNode] = [] + pr_edges = get_graphql_pr_edges(settings=settings) + + while pr_edges: + for edge in pr_edges: + pr_nodes.append(edge.node) + last_edge = pr_edges[-1] + pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor) + return pr_nodes + + +class ContributorsResults(BaseModel): + contributors: Counter[str] + translation_reviewers: Counter[str] + translators: Counter[str] + authors: dict[str, Author] + + +def get_contributors(pr_nodes: list[PullRequestNode]) -> ContributorsResults: + contributors = Counter[str]() + translation_reviewers = Counter[str]() + translators = Counter[str]() + authors: dict[str, Author] = {} + + for pr in pr_nodes: + if pr.author: + authors[pr.author.login] = pr.author + is_lang = False + for label in pr.labels.nodes: + if label.name == "lang-all": + is_lang = True + break + for review in pr.reviews.nodes: + if review.author: + authors[review.author.login] = review.author + if is_lang: + translation_reviewers[review.author.login] += 1 + if pr.state == "MERGED" and pr.author: + if is_lang: + translators[pr.author.login] += 1 + else: + contributors[pr.author.login] += 1 + return ContributorsResults( + contributors=contributors, + translation_reviewers=translation_reviewers, + translators=translators, + authors=authors, + ) + + +def get_users_to_write( + *, + counter: Counter[str], + authors: dict[str, Author], + min_count: int = 2, +) -> dict[str, Any]: + users: dict[str, Any] = {} + for user, count in counter.most_common(): + if count >= min_count: + author = authors[user] + users[user] = { + "login": user, + "count": count, + "avatarUrl": author.avatarUrl, + "url": author.url, + } + return users + + +def update_content(*, content_path: Path, new_content: Any) -> bool: + old_content = content_path.read_text(encoding="utf-8") + + new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True) + if old_content == new_content: + logging.info(f"The content hasn't changed for {content_path}") + return False + content_path.write_text(new_content, encoding="utf-8") + logging.info(f"Updated {content_path}") + return True + + +def main() -> None: + logging.basicConfig(level=logging.INFO) + settings = Settings() + logging.info(f"Using config: {settings.model_dump_json()}") + g = Github(settings.github_token.get_secret_value()) + repo = g.get_repo(settings.github_repository) + + pr_nodes = get_pr_nodes(settings=settings) + contributors_results = get_contributors(pr_nodes=pr_nodes) + authors = contributors_results.authors + + top_contributors = get_users_to_write( + counter=contributors_results.contributors, + authors=authors, + ) + + top_translators = get_users_to_write( + counter=contributors_results.translators, + authors=authors, + ) + top_translations_reviewers = get_users_to_write( + counter=contributors_results.translation_reviewers, + authors=authors, + ) + + # For local development + # contributors_path = Path("../docs/en/data/contributors.yml") + contributors_path = Path("./docs/en/data/contributors.yml") + # translators_path = Path("../docs/en/data/translators.yml") + translators_path = Path("./docs/en/data/translators.yml") + # translation_reviewers_path = Path("../docs/en/data/translation_reviewers.yml") + translation_reviewers_path = Path("./docs/en/data/translation_reviewers.yml") + + updated = [ + update_content(content_path=contributors_path, new_content=top_contributors), + update_content(content_path=translators_path, new_content=top_translators), + update_content( + content_path=translation_reviewers_path, + new_content=top_translations_reviewers, + ), + ] + + if not any(updated): + logging.info("The data hasn't changed, finishing.") + return + + logging.info("Setting up GitHub Actions git user") + subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run( + ["git", "config", "user.email", "github-actions@github.com"], check=True + ) + branch_name = f"fastapi-people-contributors-{secrets.token_hex(4)}" + logging.info(f"Creating a new branch {branch_name}") + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + logging.info("Adding updated file") + subprocess.run( + [ + "git", + "add", + str(contributors_path), + str(translators_path), + str(translation_reviewers_path), + ], + check=True, + ) + logging.info("Committing updated file") + message = "👥 Update FastAPI People - Contributors and Translators" + subprocess.run(["git", "commit", "-m", message], check=True) + logging.info("Pushing branch") + subprocess.run(["git", "push", "origin", branch_name], check=True) + logging.info("Creating PR") + pr = repo.create_pull(title=message, body=message, base="master", head=branch_name) + logging.info(f"Created PR: {pr.number}") + logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100755 index 0000000000..e07b51ec59 --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e +set -x + +coverage combine +coverage report +coverage html diff --git a/scripts/deploy_docs_status.py b/scripts/deploy_docs_status.py index 19dffbcb9a..e620b15baf 100644 --- a/scripts/deploy_docs_status.py +++ b/scripts/deploy_docs_status.py @@ -1,8 +1,9 @@ import logging import re +from typing import Literal -from github import Github -from pydantic import SecretStr +from github import Auth, Github +from pydantic import BaseModel, SecretStr from pydantic_settings import BaseSettings @@ -12,15 +13,21 @@ class Settings(BaseSettings): deploy_url: str | None = None commit_sha: str run_id: int - is_done: bool = False + state: Literal["pending", "success", "error"] = "pending" -def main(): +class LinkData(BaseModel): + previous_link: str + preview_link: str + en_link: str | None = None + + +def main() -> None: logging.basicConfig(level=logging.INFO) settings = Settings() logging.info(f"Using config: {settings.model_dump_json()}") - g = Github(settings.github_token.get_secret_value()) + g = Github(auth=Auth.Token(settings.github_token.get_secret_value())) repo = g.get_repo(settings.github_repository) use_pr = next( (pr for pr in repo.get_pulls() if pr.head.sha == settings.commit_sha), None @@ -31,16 +38,7 @@ def main(): commits = list(use_pr.get_commits()) current_commit = [c for c in commits if c.sha == settings.commit_sha][0] run_url = f"https://github.com/{settings.github_repository}/actions/runs/{settings.run_id}" - if settings.is_done and not settings.deploy_url: - current_commit.create_status( - state="success", - description="No Docs Changes", - context="deploy-docs", - target_url=run_url, - ) - logging.info("No docs changes found") - return - if not settings.deploy_url: + if settings.state == "pending": current_commit.create_status( state="pending", description="Deploying Docs", @@ -49,6 +47,26 @@ def main(): ) logging.info("No deploy URL available yet") return + if settings.state == "error": + current_commit.create_status( + state="error", + description="Error Deploying Docs", + context="deploy-docs", + target_url=run_url, + ) + logging.info("Error deploying docs") + return + assert settings.state == "success" + if not settings.deploy_url: + current_commit.create_status( + state="success", + description="No Docs Changes", + context="deploy-docs", + target_url=run_url, + ) + logging.info("No docs changes found") + return + assert settings.deploy_url current_commit.create_status( state="success", description="Docs Deployed", @@ -60,7 +78,7 @@ def main(): docs_files = [f for f in files if f.filename.startswith("docs/")] deploy_url = settings.deploy_url.rstrip("/") - lang_links: dict[str, list[str]] = {} + lang_links: dict[str, list[LinkData]] = {} for f in docs_files: match = re.match(r"docs/([^/]+)/docs/(.*)", f.filename) if not match: @@ -71,15 +89,22 @@ def main(): path = path.replace("index.md", "") else: path = path.replace(".md", "/") + en_path = path if lang == "en": - link = f"{deploy_url}/{path}" + use_path = en_path else: - link = f"{deploy_url}/{lang}/{path}" + use_path = f"{lang}/{path}" + link = LinkData( + previous_link=f"https://fastapi.tiangolo.com/{use_path}", + preview_link=f"{deploy_url}/{use_path}", + ) + if lang != "en": + link.en_link = f"https://fastapi.tiangolo.com/{en_path}" lang_links.setdefault(lang, []).append(link) - links: list[str] = [] + links: list[LinkData] = [] en_links = lang_links.get("en", []) - en_links.sort() + en_links.sort(key=lambda x: x.preview_link) links.extend(en_links) langs = list(lang_links.keys()) @@ -88,17 +113,34 @@ def main(): if lang == "en": continue current_lang_links = lang_links[lang] - current_lang_links.sort() + current_lang_links.sort(key=lambda x: x.preview_link) links.extend(current_lang_links) - message = f"📝 Docs preview for commit {settings.commit_sha} at: {deploy_url}" + header = "## 📝 Docs preview" + message = header + message += f"\n\nLast commit {settings.commit_sha} at: {deploy_url}" if links: message += "\n\n### Modified Pages\n\n" - message += "\n".join([f"* {link}" for link in links]) + for link in links: + message += f"* {link.preview_link}" + message += f" - ([before]({link.previous_link}))" + if link.en_link: + message += f" - ([English]({link.en_link}))" + message += "\n" print(message) - use_pr.as_issue().create_comment(message) + issue = use_pr.as_issue() + comments = list(issue.get_comments()) + for comment in comments: + if ( + comment.body.startswith(header) + and comment.user.login == "github-actions[bot]" + ): + comment.edit(message) + break + else: + issue.create_comment(message) logging.info("Finished") diff --git a/scripts/docs.py b/scripts/docs.py index f0c51f7a62..56ffb9d364 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -15,6 +15,7 @@ import mkdocs.utils import typer import yaml from jinja2 import Template +from ruff.__main__ import find_ruff_bin logging.basicConfig(level=logging.INFO) @@ -23,7 +24,7 @@ app = typer.Typer() mkdocs_name = "mkdocs.yml" missing_translation_snippet = """ -{!../../../docs/missing-translation.md!} +{!../../docs/missing-translation.md!} """ non_translated_sections = [ @@ -34,6 +35,7 @@ non_translated_sections = [ "newsletter.md", "management-tasks.md", "management.md", + "contributing.md", ] docs_path = Path("docs") @@ -42,6 +44,8 @@ en_config_path: Path = en_docs_path / mkdocs_name site_path = Path("site").absolute() build_site_path = Path("site_build").absolute() +header_with_permalink_pattern = re.compile(r"^(#{1,6}) (.+?)(\s*\{\s*#.*\s*\})\s*$") + @lru_cache def is_mkdocs_insiders() -> bool: @@ -152,9 +156,21 @@ index_sponsors_template = """ """ +def remove_header_permalinks(content: str): + lines: list[str] = [] + for line in content.split("\n"): + match = header_with_permalink_pattern.match(line) + if match: + hashes, title, *_ = match.groups() + line = f"{hashes} {title}" + lines.append(line) + return "\n".join(lines) + + def generate_readme_content() -> str: en_index = en_docs_path / "docs" / "index.md" content = en_index.read_text("utf-8") + content = remove_header_permalinks(content) # remove permalinks from headers match_pre = re.search(r"\n\n", content) match_start = re.search(r"", content) match_end = re.search(r"", content) @@ -382,5 +398,41 @@ def langs_json(): print(json.dumps(langs)) +@app.command() +def generate_docs_src_versions_for_file(file_path: Path) -> None: + target_versions = ["py39", "py310"] + base_content = file_path.read_text(encoding="utf-8") + previous_content = {base_content} + for target_version in target_versions: + version_result = subprocess.run( + [ + find_ruff_bin(), + "check", + "--target-version", + target_version, + "--fix", + "--unsafe-fixes", + "-", + ], + input=base_content.encode("utf-8"), + capture_output=True, + ) + content_target = version_result.stdout.decode("utf-8") + format_result = subprocess.run( + [find_ruff_bin(), "format", "-"], + input=content_target.encode("utf-8"), + capture_output=True, + ) + content_format = format_result.stdout.decode("utf-8") + if content_format in previous_content: + continue + previous_content.add(content_format) + version_file = file_path.with_name( + file_path.name.replace(".py", f"_{target_version}.py") + ) + logging.info(f"Writing to {version_file}") + version_file.write_text(content_format, encoding="utf-8") + + if __name__ == "__main__": app() diff --git a/scripts/label_approved.py b/scripts/label_approved.py new file mode 100644 index 0000000000..81de92efbe --- /dev/null +++ b/scripts/label_approved.py @@ -0,0 +1,60 @@ +import logging +from typing import Literal + +from github import Github +from github.PullRequestReview import PullRequestReview +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings + + +class LabelSettings(BaseModel): + await_label: str | None = None + number: int + + +default_config = {"approved-2": LabelSettings(await_label="awaiting-review", number=2)} + + +class Settings(BaseSettings): + github_repository: str + token: SecretStr + debug: bool | None = False + config: dict[str, LabelSettings] | Literal[""] = default_config + + +settings = Settings() +if settings.debug: + logging.basicConfig(level=logging.DEBUG) +else: + logging.basicConfig(level=logging.INFO) +logging.debug(f"Using config: {settings.model_dump_json()}") +g = Github(settings.token.get_secret_value()) +repo = g.get_repo(settings.github_repository) +for pr in repo.get_pulls(state="open"): + logging.info(f"Checking PR: #{pr.number}") + pr_labels = list(pr.get_labels()) + pr_label_by_name = {label.name: label for label in pr_labels} + reviews = list(pr.get_reviews()) + review_by_user: dict[str, PullRequestReview] = {} + for review in reviews: + if review.user.login in review_by_user: + stored_review = review_by_user[review.user.login] + if review.submitted_at >= stored_review.submitted_at: + review_by_user[review.user.login] = review + else: + review_by_user[review.user.login] = review + approved_reviews = [ + review for review in review_by_user.values() if review.state == "APPROVED" + ] + config = settings.config or default_config + for approved_label, conf in config.items(): + logging.debug(f"Processing config: {conf.model_dump_json()}") + if conf.await_label is None or (conf.await_label in pr_label_by_name): + logging.debug(f"Processable PR: {pr.number}") + if len(approved_reviews) >= conf.number: + logging.info(f"Adding label to PR: {pr.number}") + pr.add_to_labels(approved_label) + if conf.await_label: + logging.info(f"Removing label from PR: {pr.number}") + pr.remove_from_labels(conf.await_label) +logging.info("Finished") diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index 0bc4929a42..b9e4ff59ed 100644 --- a/scripts/mkdocs_hooks.py +++ b/scripts/mkdocs_hooks.py @@ -110,7 +110,7 @@ def generate_renamed_section_items( # 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.title = new_title.split("{ #")[0] item.children = new_children new_items.append(item) else: @@ -132,6 +132,15 @@ def on_pre_page(page: Page, *, config: MkDocsConfig, files: Files) -> Page: def on_page_markdown( markdown: str, *, page: Page, config: MkDocsConfig, files: Files ) -> str: + # Set matadata["social"]["cards_layout_options"]["title"] to clean title (without + # permalink) + title = page.title + clean_title = title.split("{ #")[0] + if clean_title: + page.meta.setdefault("social", {}) + page.meta["social"].setdefault("cards_layout_options", {}) + page.meta["social"]["cards_layout_options"]["title"] = clean_title + if isinstance(page.file, EnFile): for excluded_section in non_translated_sections: if page.file.src_path.startswith(excluded_section): diff --git a/.github/actions/notify-translations/app/main.py b/scripts/notify_translations.py similarity index 88% rename from .github/actions/notify-translations/app/main.py rename to scripts/notify_translations.py index 716232d49a..c300624db2 100644 --- a/.github/actions/notify-translations/app/main.py +++ b/scripts/notify_translations.py @@ -7,12 +7,13 @@ from typing import Any, Dict, List, Union, cast import httpx from github import Github -from pydantic import BaseModel, BaseSettings, SecretStr +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings awaiting_label = "awaiting-review" lang_all_label = "lang-all" approved_label = "approved-1" -translations_path = Path(__file__).parent / "translations.yml" + github_graphql_url = "https://api.github.com/graphql" questions_translations_category_id = "DIC_kwDOCZduT84CT5P9" @@ -175,20 +176,23 @@ class AllDiscussionsResponse(BaseModel): class Settings(BaseSettings): + model_config = {"env_ignore_empty": True} + github_repository: str - input_token: SecretStr + github_token: SecretStr github_event_path: Path github_event_name: Union[str, None] = None httpx_timeout: int = 30 - input_debug: Union[bool, None] = False + debug: Union[bool, None] = False + number: int | None = None class PartialGitHubEventIssue(BaseModel): - number: int + number: int | None = None class PartialGitHubEvent(BaseModel): - pull_request: PartialGitHubEventIssue + pull_request: PartialGitHubEventIssue | None = None def get_graphql_response( @@ -202,9 +206,7 @@ def get_graphql_response( comment_id: Union[str, None] = None, body: Union[str, None] = None, ) -> Dict[str, Any]: - headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} - # some fields are only used by one query, but GraphQL allows unused variables, so - # keep them here for simplicity + headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} variables = { "after": after, "category_id": category_id, @@ -228,37 +230,40 @@ def get_graphql_response( data = response.json() if "errors" in data: logging.error(f"Errors in response, after: {after}, category_id: {category_id}") + logging.error(data["errors"]) logging.error(response.text) raise RuntimeError(response.text) return cast(Dict[str, Any], data) -def get_graphql_translation_discussions(*, settings: Settings): +def get_graphql_translation_discussions( + *, settings: Settings +) -> List[AllDiscussionsDiscussionNode]: data = get_graphql_response( settings=settings, query=all_discussions_query, category_id=questions_translations_category_id, ) - graphql_response = AllDiscussionsResponse.parse_obj(data) + graphql_response = AllDiscussionsResponse.model_validate(data) return graphql_response.data.repository.discussions.nodes def get_graphql_translation_discussion_comments_edges( *, settings: Settings, discussion_number: int, after: Union[str, None] = None -): +) -> List[CommentsEdge]: data = get_graphql_response( settings=settings, query=translation_discussion_query, discussion_number=discussion_number, after=after, ) - graphql_response = CommentsResponse.parse_obj(data) + graphql_response = CommentsResponse.model_validate(data) return graphql_response.data.repository.discussion.comments.edges def get_graphql_translation_discussion_comments( *, settings: Settings, discussion_number: int -): +) -> list[Comment]: comment_nodes: List[Comment] = [] discussion_edges = get_graphql_translation_discussion_comments_edges( settings=settings, discussion_number=discussion_number @@ -276,43 +281,49 @@ def get_graphql_translation_discussion_comments( return comment_nodes -def create_comment(*, settings: Settings, discussion_id: str, body: str): +def create_comment(*, settings: Settings, discussion_id: str, body: str) -> Comment: data = get_graphql_response( settings=settings, query=add_comment_mutation, discussion_id=discussion_id, body=body, ) - response = AddCommentResponse.parse_obj(data) + response = AddCommentResponse.model_validate(data) return response.data.addDiscussionComment.comment -def update_comment(*, settings: Settings, comment_id: str, body: str): +def update_comment(*, settings: Settings, comment_id: str, body: str) -> Comment: data = get_graphql_response( settings=settings, query=update_comment_mutation, comment_id=comment_id, body=body, ) - response = UpdateCommentResponse.parse_obj(data) + response = UpdateCommentResponse.model_validate(data) return response.data.updateDiscussionComment.comment -if __name__ == "__main__": +def main() -> None: settings = Settings() - if settings.input_debug: + if settings.debug: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) - logging.debug(f"Using config: {settings.json()}") - g = Github(settings.input_token.get_secret_value()) + logging.debug(f"Using config: {settings.model_dump_json()}") + g = Github(settings.github_token.get_secret_value()) repo = g.get_repo(settings.github_repository) if not settings.github_event_path.is_file(): raise RuntimeError( f"No github event file available at: {settings.github_event_path}" ) contents = settings.github_event_path.read_text() - github_event = PartialGitHubEvent.parse_raw(contents) + github_event = PartialGitHubEvent.model_validate_json(contents) + logging.info(f"Using GitHub event: {github_event}") + number = ( + github_event.pull_request and github_event.pull_request.number + ) or settings.number + if number is None: + raise RuntimeError("No PR number available") # Avoid race conditions with multiple labels sleep_time = random.random() * 10 # random number between 0 and 10 seconds @@ -323,8 +334,8 @@ if __name__ == "__main__": time.sleep(sleep_time) # Get PR - logging.debug(f"Processing PR: #{github_event.pull_request.number}") - pr = repo.get_pull(github_event.pull_request.number) + logging.debug(f"Processing PR: #{number}") + pr = repo.get_pull(number) label_strs = {label.name for label in pr.get_labels()} langs = [] for label in label_strs: @@ -415,3 +426,7 @@ if __name__ == "__main__": f"There doesn't seem to be anything to be done about PR #{pr.number}" ) logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/scripts/people.py b/scripts/people.py new file mode 100644 index 0000000000..7418b45956 --- /dev/null +++ b/scripts/people.py @@ -0,0 +1,402 @@ +import logging +import secrets +import subprocess +import time +from collections import Counter +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any, Container, Union + +import httpx +import yaml +from github import Github +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings + +github_graphql_url = "https://api.github.com/graphql" +questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0" + +discussions_query = """ +query Q($after: String, $category_id: ID) { + repository(name: "fastapi", owner: "fastapi") { + discussions(first: 100, after: $after, categoryId: $category_id) { + edges { + cursor + node { + number + author { + login + avatarUrl + url + } + createdAt + comments(first: 50) { + totalCount + nodes { + createdAt + author { + login + avatarUrl + url + } + isAnswer + replies(first: 10) { + totalCount + nodes { + createdAt + author { + login + avatarUrl + url + } + } + } + } + } + } + } + } + } +} +""" + + +class Author(BaseModel): + login: str + avatarUrl: str | None = None + url: str | None = None + + +class CommentsNode(BaseModel): + createdAt: datetime + author: Union[Author, None] = None + + +class Replies(BaseModel): + totalCount: int + nodes: list[CommentsNode] + + +class DiscussionsCommentsNode(CommentsNode): + replies: Replies + + +class DiscussionsComments(BaseModel): + totalCount: int + nodes: list[DiscussionsCommentsNode] + + +class DiscussionsNode(BaseModel): + number: int + author: Union[Author, None] = None + title: str | None = None + createdAt: datetime + comments: DiscussionsComments + + +class DiscussionsEdge(BaseModel): + cursor: str + node: DiscussionsNode + + +class Discussions(BaseModel): + edges: list[DiscussionsEdge] + + +class DiscussionsRepository(BaseModel): + discussions: Discussions + + +class DiscussionsResponseData(BaseModel): + repository: DiscussionsRepository + + +class DiscussionsResponse(BaseModel): + data: DiscussionsResponseData + + +class Settings(BaseSettings): + github_token: SecretStr + github_repository: str + httpx_timeout: int = 30 + sleep_interval: int = 5 + + +def get_graphql_response( + *, + settings: Settings, + query: str, + after: Union[str, None] = None, + category_id: Union[str, None] = None, +) -> dict[str, Any]: + headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} + variables = {"after": after, "category_id": category_id} + response = httpx.post( + github_graphql_url, + headers=headers, + timeout=settings.httpx_timeout, + json={"query": query, "variables": variables, "operationName": "Q"}, + ) + if response.status_code != 200: + logging.error( + f"Response was not 200, after: {after}, category_id: {category_id}" + ) + logging.error(response.text) + raise RuntimeError(response.text) + data = response.json() + if "errors" in data: + logging.error(f"Errors in response, after: {after}, category_id: {category_id}") + logging.error(data["errors"]) + logging.error(response.text) + raise RuntimeError(response.text) + return data + + +def get_graphql_question_discussion_edges( + *, + settings: Settings, + after: Union[str, None] = None, +) -> list[DiscussionsEdge]: + data = get_graphql_response( + settings=settings, + query=discussions_query, + after=after, + category_id=questions_category_id, + ) + graphql_response = DiscussionsResponse.model_validate(data) + return graphql_response.data.repository.discussions.edges + + +class DiscussionExpertsResults(BaseModel): + commenters: Counter[str] + last_month_commenters: Counter[str] + three_months_commenters: Counter[str] + six_months_commenters: Counter[str] + one_year_commenters: Counter[str] + authors: dict[str, Author] + + +def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]: + discussion_nodes: list[DiscussionsNode] = [] + discussion_edges = get_graphql_question_discussion_edges(settings=settings) + + while discussion_edges: + for discussion_edge in discussion_edges: + discussion_nodes.append(discussion_edge.node) + last_edge = discussion_edges[-1] + # Handle GitHub secondary rate limits, requests per minute + time.sleep(settings.sleep_interval) + discussion_edges = get_graphql_question_discussion_edges( + settings=settings, after=last_edge.cursor + ) + return discussion_nodes + + +def get_discussions_experts( + discussion_nodes: list[DiscussionsNode], +) -> DiscussionExpertsResults: + commenters = Counter[str]() + last_month_commenters = Counter[str]() + three_months_commenters = Counter[str]() + six_months_commenters = Counter[str]() + one_year_commenters = Counter[str]() + authors: dict[str, Author] = {} + + now = datetime.now(tz=timezone.utc) + one_month_ago = now - timedelta(days=30) + three_months_ago = now - timedelta(days=90) + six_months_ago = now - timedelta(days=180) + one_year_ago = now - timedelta(days=365) + + for discussion in discussion_nodes: + discussion_author_name = None + if discussion.author: + authors[discussion.author.login] = discussion.author + discussion_author_name = discussion.author.login + discussion_commentors: dict[str, datetime] = {} + for comment in discussion.comments.nodes: + if comment.author: + authors[comment.author.login] = comment.author + if comment.author.login != discussion_author_name: + author_time = discussion_commentors.get( + comment.author.login, comment.createdAt + ) + discussion_commentors[comment.author.login] = max( + author_time, comment.createdAt + ) + for reply in comment.replies.nodes: + if reply.author: + authors[reply.author.login] = reply.author + if reply.author.login != discussion_author_name: + author_time = discussion_commentors.get( + reply.author.login, reply.createdAt + ) + discussion_commentors[reply.author.login] = max( + author_time, reply.createdAt + ) + for author_name, author_time in discussion_commentors.items(): + commenters[author_name] += 1 + if author_time > one_month_ago: + last_month_commenters[author_name] += 1 + if author_time > three_months_ago: + three_months_commenters[author_name] += 1 + if author_time > six_months_ago: + six_months_commenters[author_name] += 1 + if author_time > one_year_ago: + one_year_commenters[author_name] += 1 + discussion_experts_results = DiscussionExpertsResults( + authors=authors, + commenters=commenters, + last_month_commenters=last_month_commenters, + three_months_commenters=three_months_commenters, + six_months_commenters=six_months_commenters, + one_year_commenters=one_year_commenters, + ) + return discussion_experts_results + + +def get_top_users( + *, + counter: Counter[str], + authors: dict[str, Author], + skip_users: Container[str], + min_count: int = 2, +) -> list[dict[str, Any]]: + users: list[dict[str, Any]] = [] + for commenter, count in counter.most_common(50): + if commenter in skip_users: + continue + if count >= min_count: + author = authors[commenter] + users.append( + { + "login": commenter, + "count": count, + "avatarUrl": author.avatarUrl, + "url": author.url, + } + ) + return users + + +def get_users_to_write( + *, + counter: Counter[str], + authors: dict[str, Author], + min_count: int = 2, +) -> list[dict[str, Any]]: + users: dict[str, Any] = {} + users_list: list[dict[str, Any]] = [] + for user, count in counter.most_common(60): + if count >= min_count: + author = authors[user] + user_data = { + "login": user, + "count": count, + "avatarUrl": author.avatarUrl, + "url": author.url, + } + users[user] = user_data + users_list.append(user_data) + return users_list + + +def update_content(*, content_path: Path, new_content: Any) -> bool: + old_content = content_path.read_text(encoding="utf-8") + + new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True) + if old_content == new_content: + logging.info(f"The content hasn't changed for {content_path}") + return False + content_path.write_text(new_content, encoding="utf-8") + logging.info(f"Updated {content_path}") + return True + + +def main() -> None: + logging.basicConfig(level=logging.INFO) + settings = Settings() + logging.info(f"Using config: {settings.model_dump_json()}") + g = Github(settings.github_token.get_secret_value()) + repo = g.get_repo(settings.github_repository) + + discussion_nodes = get_discussion_nodes(settings=settings) + experts_results = get_discussions_experts(discussion_nodes=discussion_nodes) + + authors = experts_results.authors + maintainers_logins = {"tiangolo"} + maintainers = [] + for login in maintainers_logins: + user = authors[login] + maintainers.append( + { + "login": login, + "answers": experts_results.commenters[login], + "avatarUrl": user.avatarUrl, + "url": user.url, + } + ) + + experts = get_users_to_write( + counter=experts_results.commenters, + authors=authors, + ) + last_month_experts = get_users_to_write( + counter=experts_results.last_month_commenters, + authors=authors, + ) + three_months_experts = get_users_to_write( + counter=experts_results.three_months_commenters, + authors=authors, + ) + six_months_experts = get_users_to_write( + counter=experts_results.six_months_commenters, + authors=authors, + ) + one_year_experts = get_users_to_write( + counter=experts_results.one_year_commenters, + authors=authors, + ) + + people = { + "maintainers": maintainers, + "experts": experts, + "last_month_experts": last_month_experts, + "three_months_experts": three_months_experts, + "six_months_experts": six_months_experts, + "one_year_experts": one_year_experts, + } + + # For local development + # people_path = Path("../docs/en/data/people.yml") + people_path = Path("./docs/en/data/people.yml") + + updated = update_content(content_path=people_path, new_content=people) + + if not updated: + logging.info("The data hasn't changed, finishing.") + return + + logging.info("Setting up GitHub Actions git user") + subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run( + ["git", "config", "user.email", "github-actions@github.com"], check=True + ) + branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}" + logging.info(f"Creating a new branch {branch_name}") + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + logging.info("Adding updated file") + subprocess.run(["git", "add", str(people_path)], check=True) + logging.info("Committing updated file") + message = "👥 Update FastAPI People - Experts" + subprocess.run(["git", "commit", "-m", message], check=True) + logging.info("Pushing branch") + subprocess.run(["git", "push", "origin", branch_name], check=True) + logging.info("Creating PR") + pr = repo.create_pull(title=message, body=message, base="master", head=branch_name) + logging.info(f"Created PR: {pr.number}") + logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/scripts/playwright/cookie_param_models/image01.py b/scripts/playwright/cookie_param_models/image01.py new file mode 100644 index 0000000000..77c91bfe22 --- /dev/null +++ b/scripts/playwright/cookie_param_models/image01.py @@ -0,0 +1,39 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + browser = playwright.chromium.launch(headless=False) + context = browser.new_context() + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("link", name="/items/").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/cookie-param-models/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/cookie_param_models/tutorial001.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/header_param_models/image01.py b/scripts/playwright/header_param_models/image01.py new file mode 100644 index 0000000000..53914251ed --- /dev/null +++ b/scripts/playwright/header_param_models/image01.py @@ -0,0 +1,38 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="GET /items/ Read Items").click() + page.get_by_role("button", name="Try it out").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/header-param-models/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/header_param_models/tutorial001.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/query_param_models/image01.py b/scripts/playwright/query_param_models/image01.py new file mode 100644 index 0000000000..0ea1d0df4e --- /dev/null +++ b/scripts/playwright/query_param_models/image01.py @@ -0,0 +1,41 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + browser = playwright.chromium.launch(headless=False) + context = browser.new_context() + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="GET /items/ Read Items").click() + page.get_by_role("button", name="Try it out").click() + page.get_by_role("heading", name="Servers").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/query-param-models/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/query_param_models/tutorial001.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/request_form_models/image01.py b/scripts/playwright/request_form_models/image01.py new file mode 100644 index 0000000000..fe4da32fcb --- /dev/null +++ b/scripts/playwright/request_form_models/image01.py @@ -0,0 +1,38 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="POST /login/ Login").click() + page.get_by_role("button", name="Try it out").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/request-form-models/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/request_form_models/tutorial001.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/separate_openapi_schemas/image01.py b/scripts/playwright/separate_openapi_schemas/image01.py index 0b40f3bbcf..0eb55fb73a 100644 --- a/scripts/playwright/separate_openapi_schemas/image01.py +++ b/scripts/playwright/separate_openapi_schemas/image01.py @@ -3,13 +3,16 @@ import subprocess from playwright.sync_api import Playwright, sync_playwright +# Run playwright codegen to generate the code below, copy paste the sections in run() def run(playwright: Playwright) -> None: browser = playwright.chromium.launch(headless=False) + # Update the viewport manually context = browser.new_context(viewport={"width": 960, "height": 1080}) page = context.new_page() page.goto("http://localhost:8000/docs") page.get_by_text("POST/items/Create Item").click() page.get_by_role("tab", name="Schema").first.click() + # Manually add the screenshot page.screenshot( path="docs/en/docs/img/tutorial/separate-openapi-schemas/image01.png" ) diff --git a/scripts/playwright/separate_openapi_schemas/image02.py b/scripts/playwright/separate_openapi_schemas/image02.py index f76af7ee22..0eb6c3c796 100644 --- a/scripts/playwright/separate_openapi_schemas/image02.py +++ b/scripts/playwright/separate_openapi_schemas/image02.py @@ -3,14 +3,17 @@ import subprocess from playwright.sync_api import Playwright, sync_playwright +# Run playwright codegen to generate the code below, copy paste the sections in run() def run(playwright: Playwright) -> None: browser = playwright.chromium.launch(headless=False) + # Update the viewport manually context = browser.new_context(viewport={"width": 960, "height": 1080}) page = context.new_page() page.goto("http://localhost:8000/docs") page.get_by_text("GET/items/Read Items").click() page.get_by_role("button", name="Try it out").click() page.get_by_role("button", name="Execute").click() + # Manually add the screenshot page.screenshot( path="docs/en/docs/img/tutorial/separate-openapi-schemas/image02.png" ) diff --git a/scripts/playwright/separate_openapi_schemas/image03.py b/scripts/playwright/separate_openapi_schemas/image03.py index 127f5c428e..b68e9d7db8 100644 --- a/scripts/playwright/separate_openapi_schemas/image03.py +++ b/scripts/playwright/separate_openapi_schemas/image03.py @@ -3,14 +3,17 @@ import subprocess from playwright.sync_api import Playwright, sync_playwright +# Run playwright codegen to generate the code below, copy paste the sections in run() def run(playwright: Playwright) -> None: browser = playwright.chromium.launch(headless=False) + # Update the viewport manually context = browser.new_context(viewport={"width": 960, "height": 1080}) page = context.new_page() page.goto("http://localhost:8000/docs") page.get_by_text("GET/items/Read Items").click() page.get_by_role("tab", name="Schema").click() page.get_by_label("Schema").get_by_role("button", name="Expand all").click() + # Manually add the screenshot page.screenshot( path="docs/en/docs/img/tutorial/separate-openapi-schemas/image03.png" ) diff --git a/scripts/playwright/separate_openapi_schemas/image04.py b/scripts/playwright/separate_openapi_schemas/image04.py index 208eaf8a0c..a36c2f6b2b 100644 --- a/scripts/playwright/separate_openapi_schemas/image04.py +++ b/scripts/playwright/separate_openapi_schemas/image04.py @@ -3,14 +3,17 @@ import subprocess from playwright.sync_api import Playwright, sync_playwright +# Run playwright codegen to generate the code below, copy paste the sections in run() def run(playwright: Playwright) -> None: browser = playwright.chromium.launch(headless=False) + # Update the viewport manually context = browser.new_context(viewport={"width": 960, "height": 1080}) page = context.new_page() page.goto("http://localhost:8000/docs") page.get_by_role("button", name="Item-Input").click() page.get_by_role("button", name="Item-Output").click() page.set_viewport_size({"width": 960, "height": 820}) + # Manually add the screenshot page.screenshot( path="docs/en/docs/img/tutorial/separate-openapi-schemas/image04.png" ) diff --git a/scripts/playwright/separate_openapi_schemas/image05.py b/scripts/playwright/separate_openapi_schemas/image05.py index 83966b4498..0da5db0cfa 100644 --- a/scripts/playwright/separate_openapi_schemas/image05.py +++ b/scripts/playwright/separate_openapi_schemas/image05.py @@ -3,13 +3,16 @@ import subprocess from playwright.sync_api import Playwright, sync_playwright +# Run playwright codegen to generate the code below, copy paste the sections in run() def run(playwright: Playwright) -> None: browser = playwright.chromium.launch(headless=False) + # Update the viewport manually context = browser.new_context(viewport={"width": 960, "height": 1080}) page = context.new_page() page.goto("http://localhost:8000/docs") page.get_by_role("button", name="Item", exact=True).click() page.set_viewport_size({"width": 960, "height": 700}) + # Manually add the screenshot page.screenshot( path="docs/en/docs/img/tutorial/separate-openapi-schemas/image05.png" ) diff --git a/scripts/playwright/sql_databases/image01.py b/scripts/playwright/sql_databases/image01.py new file mode 100644 index 0000000000..0dd6f25145 --- /dev/null +++ b/scripts/playwright/sql_databases/image01.py @@ -0,0 +1,37 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_label("post /heroes/").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/sql-databases/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/sql_databases/tutorial001.py"], +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/sql_databases/image02.py b/scripts/playwright/sql_databases/image02.py new file mode 100644 index 0000000000..6c4f685e86 --- /dev/null +++ b/scripts/playwright/sql_databases/image02.py @@ -0,0 +1,37 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_label("post /heroes/").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/sql-databases/image02.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/sql_databases/tutorial002.py"], +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/sponsors.py b/scripts/sponsors.py new file mode 100644 index 0000000000..45e02bd621 --- /dev/null +++ b/scripts/sponsors.py @@ -0,0 +1,221 @@ +import logging +import secrets +import subprocess +from collections import defaultdict +from pathlib import Path +from typing import Any + +import httpx +import yaml +from github import Github +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings + +github_graphql_url = "https://api.github.com/graphql" + + +sponsors_query = """ +query Q($after: String) { + user(login: "tiangolo") { + sponsorshipsAsMaintainer(first: 100, after: $after) { + edges { + cursor + node { + sponsorEntity { + ... on Organization { + login + avatarUrl + url + } + ... on User { + login + avatarUrl + url + } + } + tier { + name + monthlyPriceInDollars + } + } + } + } + } +} +""" + + +class SponsorEntity(BaseModel): + login: str + avatarUrl: str + url: str + + +class Tier(BaseModel): + name: str + monthlyPriceInDollars: float + + +class SponsorshipAsMaintainerNode(BaseModel): + sponsorEntity: SponsorEntity + tier: Tier + + +class SponsorshipAsMaintainerEdge(BaseModel): + cursor: str + node: SponsorshipAsMaintainerNode + + +class SponsorshipAsMaintainer(BaseModel): + edges: list[SponsorshipAsMaintainerEdge] + + +class SponsorsUser(BaseModel): + sponsorshipsAsMaintainer: SponsorshipAsMaintainer + + +class SponsorsResponseData(BaseModel): + user: SponsorsUser + + +class SponsorsResponse(BaseModel): + data: SponsorsResponseData + + +class Settings(BaseSettings): + sponsors_token: SecretStr + pr_token: SecretStr + github_repository: str + httpx_timeout: int = 30 + + +def get_graphql_response( + *, + settings: Settings, + query: str, + after: str | None = None, +) -> dict[str, Any]: + headers = {"Authorization": f"token {settings.sponsors_token.get_secret_value()}"} + variables = {"after": after} + response = httpx.post( + github_graphql_url, + headers=headers, + timeout=settings.httpx_timeout, + json={"query": query, "variables": variables, "operationName": "Q"}, + ) + if response.status_code != 200: + logging.error(f"Response was not 200, after: {after}") + logging.error(response.text) + raise RuntimeError(response.text) + data = response.json() + if "errors" in data: + logging.error(f"Errors in response, after: {after}") + logging.error(data["errors"]) + logging.error(response.text) + raise RuntimeError(response.text) + return data + + +def get_graphql_sponsor_edges( + *, settings: Settings, after: str | None = None +) -> list[SponsorshipAsMaintainerEdge]: + data = get_graphql_response(settings=settings, query=sponsors_query, after=after) + graphql_response = SponsorsResponse.model_validate(data) + return graphql_response.data.user.sponsorshipsAsMaintainer.edges + + +def get_individual_sponsors( + settings: Settings, +) -> defaultdict[float, dict[str, SponsorEntity]]: + nodes: list[SponsorshipAsMaintainerNode] = [] + edges = get_graphql_sponsor_edges(settings=settings) + + while edges: + for edge in edges: + nodes.append(edge.node) + last_edge = edges[-1] + edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor) + + tiers: defaultdict[float, dict[str, SponsorEntity]] = defaultdict(dict) + for node in nodes: + tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = ( + node.sponsorEntity + ) + return tiers + + +def update_content(*, content_path: Path, new_content: Any) -> bool: + old_content = content_path.read_text(encoding="utf-8") + + new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True) + if old_content == new_content: + logging.info(f"The content hasn't changed for {content_path}") + return False + content_path.write_text(new_content, encoding="utf-8") + logging.info(f"Updated {content_path}") + return True + + +def main() -> None: + logging.basicConfig(level=logging.INFO) + settings = Settings() + logging.info(f"Using config: {settings.model_dump_json()}") + g = Github(settings.pr_token.get_secret_value()) + repo = g.get_repo(settings.github_repository) + + tiers = get_individual_sponsors(settings=settings) + keys = list(tiers.keys()) + keys.sort(reverse=True) + sponsors = [] + for key in keys: + sponsor_group = [] + for login, sponsor in tiers[key].items(): + sponsor_group.append( + {"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url} + ) + sponsors.append(sponsor_group) + github_sponsors = { + "sponsors": sponsors, + } + + # For local development + # github_sponsors_path = Path("../docs/en/data/github_sponsors.yml") + github_sponsors_path = Path("./docs/en/data/github_sponsors.yml") + updated = update_content( + content_path=github_sponsors_path, new_content=github_sponsors + ) + + if not updated: + logging.info("The data hasn't changed, finishing.") + return + + logging.info("Setting up GitHub Actions git user") + subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run( + ["git", "config", "user.email", "github-actions@github.com"], check=True + ) + branch_name = f"fastapi-people-sponsors-{secrets.token_hex(4)}" + logging.info(f"Creating a new branch {branch_name}") + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + logging.info("Adding updated file") + subprocess.run( + [ + "git", + "add", + str(github_sponsors_path), + ], + check=True, + ) + logging.info("Committing updated file") + message = "👥 Update FastAPI People - Sponsors" + subprocess.run(["git", "commit", "-m", message], check=True) + logging.info("Pushing branch") + subprocess.run(["git", "push", "origin", branch_name], check=True) + logging.info("Creating PR") + pr = repo.create_pull(title=message, body=message, base="master", head=branch_name) + logging.info(f"Created PR: {pr.number}") + logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/scripts/test-cov-html.sh b/scripts/test-cov-html.sh index 517ac6422e..f87f906dc2 100755 --- a/scripts/test-cov-html.sh +++ b/scripts/test-cov-html.sh @@ -4,6 +4,4 @@ set -e set -x bash scripts/test.sh ${@} -coverage combine -coverage report -coverage html +bash scripts/coverage.sh diff --git a/scripts/topic_repos.py b/scripts/topic_repos.py new file mode 100644 index 0000000000..bc14977513 --- /dev/null +++ b/scripts/topic_repos.py @@ -0,0 +1,80 @@ +import logging +import secrets +import subprocess +from pathlib import Path + +import yaml +from github import Github +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + github_repository: str + github_token: SecretStr + + +class Repo(BaseModel): + name: str + html_url: str + stars: int + owner_login: str + owner_html_url: str + + +def main() -> None: + logging.basicConfig(level=logging.INFO) + settings = Settings() + + logging.info(f"Using config: {settings.model_dump_json()}") + g = Github(settings.github_token.get_secret_value(), per_page=100) + r = g.get_repo(settings.github_repository) + repos = g.search_repositories(query="topic:fastapi") + repos_list = list(repos) + final_repos: list[Repo] = [] + for repo in repos_list[:100]: + if repo.full_name == settings.github_repository: + continue + final_repos.append( + Repo( + name=repo.name, + html_url=repo.html_url, + stars=repo.stargazers_count, + owner_login=repo.owner.login, + owner_html_url=repo.owner.html_url, + ) + ) + data = [repo.model_dump() for repo in final_repos] + + # Local development + # repos_path = Path("../docs/en/data/topic_repos.yml") + repos_path = Path("./docs/en/data/topic_repos.yml") + repos_old_content = repos_path.read_text(encoding="utf-8") + new_repos_content = yaml.dump(data, sort_keys=False, width=200, allow_unicode=True) + if repos_old_content == new_repos_content: + logging.info("The data hasn't changed. Finishing.") + return + repos_path.write_text(new_repos_content, encoding="utf-8") + logging.info("Setting up GitHub Actions git user") + subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run( + ["git", "config", "user.email", "github-actions@github.com"], check=True + ) + branch_name = f"fastapi-topic-repos-{secrets.token_hex(4)}" + logging.info(f"Creating a new branch {branch_name}") + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + logging.info("Adding updated file") + subprocess.run(["git", "add", str(repos_path)], check=True) + logging.info("Committing updated file") + message = "👥 Update FastAPI GitHub topic repositories" + subprocess.run(["git", "commit", "-m", message], check=True) + logging.info("Pushing branch") + subprocess.run(["git", "push", "origin", branch_name], check=True) + logging.info("Creating PR") + pr = r.create_pull(title=message, body=message, base="master", head=branch_name) + logging.info(f"Created PR: {pr.number}") + logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/scripts/translate.py b/scripts/translate.py new file mode 100644 index 0000000000..ede101e8fc --- /dev/null +++ b/scripts/translate.py @@ -0,0 +1,981 @@ +import secrets +import subprocess +from collections.abc import Iterable +from functools import lru_cache +from os import sep as pathsep +from pathlib import Path +from typing import Annotated + +import git +import typer +import yaml +from github import Github +from pydantic_ai import Agent +from rich import print + +non_translated_sections = ( + f"reference{pathsep}", + "release-notes.md", + "fastapi-people.md", + "external-links.md", + "newsletter.md", + "management-tasks.md", + "management.md", + "contributing.md", +) + + +general_prompt = """ +### About literal text in this prompt + +1) In the following instructions (after I say: `The above rules are in effect now`) the two characters `«` and `»` will be used to surround LITERAL TEXT, which is text or characters you shall interpret literally. The `«` and the `»` are not part of the literal text, they are the meta characters denoting it. + +2) Furthermore, text surrounded by `«««` and `»»»` is a BLOCK OF LITERAL TEXT which spans multiple lines. To get its content, dedent all lines of the block until the `«««` and `»»»` are at column zero, then remove the newline (`\n`) after the `«««` and the newline before the `»»»`. The `«««` and the `»»»` are not part of the literal text block, they are the meta characters denoting it. + +3) If you see backticks or any other quotes inside literal text – inside `«` and `»` – or inside blocks of literal text – inside `«««` and `»»»` – then interpret them as literal characters, do NOT interpret them as meta characters. + +The above rules are in effect now. + + +### Definitions of terms used in this prompt + +"backtick" + + The character «`» + Unicode U+0060 (GRAVE ACCENT) + +"single backtick" + + A single backtick – «`» + +"triple backticks" + + Three backticks in a row – «```» + +"neutral double quote" + + The character «"» + Unicode U+0022 (QUOTATION MARK) + +"neutral single quote" + + The character «'» + Unicode U+0027 (APOSTROPHE) + +"English double typographic quotes" + + The characters «“» and «”» + Unicode U+201C (LEFT DOUBLE QUOTATION MARK) and Unicode U+201D (RIGHT DOUBLE QUOTATION MARK) + +"English single typographic quotes" + + The characters «‘» and «’» + Unicode U+2018 (LEFT SINGLE QUOTATION MARK) and Unicode U+2019 (RIGHT SINGLE QUOTATION MARK) + +"code snippet" + + Also called "inline code". Text in a Markdown document which is surrounded by single backticks. A paragraph in a Markdown document can have a more than one code snippet. + + Example: + + ««« + `i am a code snippet` + »»» + + Example: + + ««« + `first code snippet` `second code snippet` `third code snippet` + »»» + +"code block" + + Text in a Markdown document which is surrounded by triple backticks. Spreads multiple lines. + + Example: + + ««« + ``` + Hello + World + ``` + »»» + + Example: + + ««« + ```python + print("hello World") + ``` + »»» + +"HTML element" + + a HTML opening tag – e.g. «
» – and a HTML closing tag – e.g. «
» – surrounding text or other HTML elements. + + +### Your task + +Translate an English text – the original content – to a target language. + +The original content is written in Markdown, write the translation in Markdown as well. + +The original content will be surrounded by triple percentage signs («%%%»). Do not include the triple percentage signs in the translation. + + +### Technical terms in English + +For technical terms in English that don't have a common translation term, use the original term in English. + + +### Content of code snippets + +Do not translate the content of code snippets, keep the original in English. For example, «`list`», «`dict`», keep them as is. + + +### Content of code blocks + +Do not translate the content of code blocks, except for comments in the language which the code block uses. + +Examples: + + Source (English) – The code block is a bash code example with one comment: + + ««« + ```bash + # Print greeting + echo "Hello, World!" + ``` + »»» + + Result (German): + + ««« + ```bash + # Gruß ausgeben + echo "Hello, World!" + ``` + »»» + + Source (English) – The code block is a console example containing HTML tags. No comments, so nothing to change here: + + ««« + ```console + $ fastapi run main.py + FastAPI Starting server + Searching for package file structure + ``` + »»» + + Result (German): + + ««« + ```console + $ fastapi run main.py + FastAPI Starting server + Searching for package file structure + ``` + »»» + + Source (English) – The code block is a console example containing 5 comments: + + ««« + ```console + // Go to the home directory + $ cd + // Create a directory for all your code projects + $ mkdir code + // Enter into that code directory + $ cd code + // Create a directory for this project + $ mkdir awesome-project + // Enter into that project directory + $ cd awesome-project + ``` + »»» + + Result (German): + + ««« + ```console + // Gehe zum Home-Verzeichnis + $ cd + // Erstelle ein Verzeichnis für alle Ihre Code-Projekte + $ mkdir code + // Gehe in dieses Code-Verzeichnis + $ cd code + // Erstelle ein Verzeichnis für dieses Projekt + $ mkdir awesome-project + // Gehe in dieses Projektverzeichnis + $ cd awesome-project + ``` + »»» + +If there is an existing translation and its Mermaid diagram is in sync with the Mermaid diagram in the English source, except a few translated words, then use the Mermaid diagram of the existing translation. The human editor of the translation translated these words in the Mermaid diagram. Keep these translations, do not revert them back to the English source. + +Example: + + Source (English): + + ««« + ```mermaid + flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end + ``` + »»» + + Existing translation (German) – has three translations: + + ««« + ```mermaid + flowchart LR + subgraph global[globale Umgebung] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone-Projekt] + stone(philosophers-stone) -->|benötigt| harry-1 + end + ``` + »»» + + Result (German) – you change nothing: + + ««« + ```mermaid + flowchart LR + subgraph global[globale Umgebung] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone-Projekt] + stone(philosophers-stone) -->|benötigt| harry-1 + end + ``` + »»» + + +### Special blocks + +There are special blocks of notes, tips and others that look like: + + ««« + /// note + »»» + +To translate it, keep the same line and add the translation after a vertical bar. + +For example, if you were translating to Spanish, you would write: + + ««« + /// note | Nota + »»» + +Some examples in Spanish: + + Source: + + ««« + /// tip + »»» + + Result: + + ««« + /// tip | Consejo + »»» + + Source: + + ««« + /// details | Preview + »»» + + Result: + + ««« + /// details | Vista previa + »»» + + +### Tab blocks + +There are special blocks surrounded by four slashes («////»). They mark text, which will be rendered as part of a tab in the final document. The scheme is: + + //// tab | {tab title} + {tab content, may span many lines} + //// + +Keep everything before the vertical bar («|») as is, including the vertical bar. Translate the tab title. Translate the tab content, applying the rules you know. Keep the four block closing slashes as is. + +Examples: + + Source (English): + + ««« + //// tab | Python 3.8+ non-Annotated + Hello + //// + »»» + + Result (German): + + ««« + //// tab | Python 3.8+ nicht annotiert + Hallo + //// + »»» + + Source (English) – Here there is nothing to translate in the tab title: + + ««« + //// tab | Linux, macOS, Windows Bash + Hello again + //// + »»» + + Result (German): + + ««« + //// tab | Linux, macOS, Windows Bash + Hallo wieder + //// + »»» + + +### Headings + +Every Markdown heading in the English text (all levels) ends with a part inside curly brackets. This part denotes the hash of this heading, which is used in links to this heading. In translations, translate the heading, but do not translate this hash part, so that links do not break. + +Examples of how to translate a heading: + + Source (English): + + ««« + ## Alternative API docs { #alternative-api-docs } + »»» + + Result (Spanish): + + ««« + ## Documentación de la API alternativa { #alternative-api-docs } + »»» + + Source (English): + + ««« + ### Example { #example } + »»» + + Result (German): + + ««« + ### Beispiel { #example } + »»» + + +### Links + +Use the following rules for links (apply both to Markdown-style links ([text](url)) and to HTML-style tags): + +1) For relative URLs, only translate link text. Do not translate the URL or its parts + +Example: + + Source (English): + + ««« + [One of the fastest Python frameworks available](#performance) + »»» + + Result (German): + + ««« + [Eines der schnellsten verfügbaren Python-Frameworks](#performance) + »»» + +2) For absolute URLs which DO NOT start EXACTLY with «https://fastapi.tiangolo.com», only translate link text and leave the URL unchanged. + +Example: + + Source (English): + + ««« + SQLModel docs + »»» + + Result (German): + + ««« + SQLModel-Dokumentation + »»» + +3) For absolute URLs which DO start EXACTLY with «https://fastapi.tiangolo.com», only translate link text and change the URL by adding language code («https://fastapi.tiangolo.com/{language_code}[rest part of the url]»). + +Example: + + Source (English): + + ««« + Documentation + »»» + + Result (Spanish): + + ««« + Documentación + »»» + +3.1) Do not add language codes for URLs that point to static assets (e.g., images, CSS, JavaScript). + +Example: + + Source (English): + + ««« + Something + »»» + + Result (Spanish): + + ««« + Algo + »»» + +4) For internal links, only translate link text. + +Example: + + Source (English): + + ««« + [Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} + »»» + + Result (German): + + ««« + [Pull Requests erzeugen](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} + »»» + +5) Do not translate anchor fragments in links (the part after «#»), as they must remain the same to work correctly. + +5.1) If an existing translation has a link with an anchor fragment different to the anchor fragment in the English source, then this is an error. Fix this by using the anchor fragment of the English source. + +Example: + + Source (English): + + ««« + [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank} + »»» + + Existing wrong translation (German) – notice the wrongly translated anchor fragment: + + ««« + [Body – Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#einzelne-werte-im-body){.internal-link target=_blank}. + »»» + + Result (German) – you fix the anchor fragment: + + ««« + [Body – Mehrere Parameter: Einfache Werte im Body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. + »»» + +5.2) Do not add anchor fragments at will, even if this makes sense. If the English source has no anchor, don't add one. + +Example: + + Source (English): + + ««« + Create a [virtual environment](../virtual-environments.md){.internal-link target=_blank} + »»» + + Wrong translation (German) – Anchor added to the URL. + + ««« + Erstelle eine [virtuelle Umgebung](../virtual-environments.md#create-a-virtual-environment){.internal-link target=_blank} + »»» + + Good translation (German) – URL stays like in the English source. + + ««« + Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link target=_blank} + »»» + + +### HTML abbr elements + +Translate HTML abbr elements («text») as follows: + +1) If the text surrounded by the abbr element is an abbreviation (the text may be surrounded by further HTML or Markdown markup or quotes, for example «text» or «`text`» or «"text"», ignore that further markup when deciding if the text is an abbreviation), and if the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash («–») to the full phrase, followed by the translation of the full phrase. + +Conversion scheme: + + Source (English): + + {abbreviation} + + Result: + + {abbreviation} + +Examples: + + Source (English): + + ««« + IoT + CPU + TL;DR: + »»» + + Result (German): + + ««« + IoT + CPU + TL;DR: + »»» + +1.1) If the language to which you translate mostly uses the letters of the ASCII char set (for example Spanish, French, German, but not Russian, Chinese) and if the translation of the full phrase is identical to, or starts with the same letters as the original full phrase, then only give the translation of the full phrase. + +Conversion scheme: + + Source (English): + + {abbreviation} + + Result: + + {abbreviation} + +Examples: + + Source (English): + + ««« + JWT + Enum + ASGI + »»» + + Result (German): + + ««« + JWT + Enum + ASGI + »»» + +2) If the description is not a full phrase for an abbreviation which the abbr element surrounds, but some other information, then just translate the description. + +Conversion scheme: + + Source (English): + + {text} + + Result: + + {translation of text} + +Examples: + + Source (English): + + ««« + path + linter + parsing + 0.95.0 + at the time of writing this + »»» + + Result (German): + + ««« + Pfad + Linter + Parsen + 0.95.0 + zum Zeitpunkt als das hier geschrieben wurde + »»» + + +3) If the text surrounded by the abbr element is an abbreviation and the description contains both the full phrase for that abbreviation, and other information, separated by a colon («:»), then append a dash («–») and the translation of the full phrase to the original full phrase and translate the other information. + +Conversion scheme: + + Source (English): + + {abbreviation} + + Result: + + {abbreviation} + +Examples: + + Source (English): + + ««« + I/O + CDN + IDE + »»» + + Result (German): + + ««« + I/O + CDN + IDE + »»» + +3.1) Like in rule 2.1, you can leave the original full phrase away, if the translated full phrase is identical or starts with the same letters as the original full phrase. + +Conversion scheme: + + Source (English): + + {abbreviation} + + Result: + + {abbreviation} + +Example: + + Source (English): + + ««« + ORM + »»» + + Result (German): + + ««« + ORM + »»» + +4) If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements. + +5) Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's from rule 4 alone. Do not change their formatting or content. + +""" + +app = typer.Typer() + + +@lru_cache +def get_langs() -> dict[str, str]: + return yaml.safe_load(Path("docs/language_names.yml").read_text(encoding="utf-8")) + + +def generate_lang_path(*, lang: str, path: Path) -> Path: + en_docs_path = Path("docs/en/docs") + assert str(path).startswith(str(en_docs_path)), ( + f"Path must be inside {en_docs_path}" + ) + lang_docs_path = Path(f"docs/{lang}/docs") + out_path = Path(str(path).replace(str(en_docs_path), str(lang_docs_path))) + return out_path + + +def generate_en_path(*, lang: str, path: Path) -> Path: + en_docs_path = Path("docs/en/docs") + assert not str(path).startswith(str(en_docs_path)), ( + f"Path must not be inside {en_docs_path}" + ) + lang_docs_path = Path(f"docs/{lang}/docs") + out_path = Path(str(path).replace(str(lang_docs_path), str(en_docs_path))) + return out_path + + +@app.command() +def translate_page( + *, + language: Annotated[str, typer.Option(envvar="LANGUAGE")], + en_path: Annotated[Path, typer.Option(envvar="EN_PATH")], +) -> None: + assert language != "en", ( + "`en` is the source language, choose another language as translation target" + ) + langs = get_langs() + language_name = langs[language] + lang_path = Path(f"docs/{language}") + lang_path.mkdir(exist_ok=True) + lang_prompt_path = lang_path / "llm-prompt.md" + assert lang_prompt_path.exists(), f"Prompt file not found: {lang_prompt_path}" + lang_prompt_content = lang_prompt_path.read_text(encoding="utf-8") + + en_docs_path = Path("docs/en/docs") + assert str(en_path).startswith(str(en_docs_path)), ( + f"Path must be inside {en_docs_path}" + ) + out_path = generate_lang_path(lang=language, path=en_path) + out_path.parent.mkdir(parents=True, exist_ok=True) + original_content = en_path.read_text(encoding="utf-8") + old_translation: str | None = None + if out_path.exists(): + print(f"Found existing translation: {out_path}") + old_translation = out_path.read_text(encoding="utf-8") + print(f"Translating {en_path} to {language} ({language_name})") + agent = Agent("openai:gpt-5") + + prompt_segments = [ + general_prompt, + lang_prompt_content, + ] + if old_translation: + prompt_segments.extend( + [ + "There is an existing previous translation for the original English content, that may be outdated.", + "Update the translation only where necessary:", + "- If the original English content has added parts, also add these parts to the translation.", + "- If the original English content has removed parts, also remove them from the translation, unless you were instructed earlier to not do that in specific cases.", + "- If parts of the original English content have changed, also change those parts in the translation.", + "- If the previous translation violates current instructions, update it.", + "- Otherwise, preserve the original translation LINE-BY-LINE, AS-IS.", + "Do not:", + "- rephrase or rewrite correct lines just to improve the style.", + "- add or remove line breaks, unless the original English content changed.", + "- change formatting or whitespace unless absolutely required.", + "Only change what must be changed. The goal is to minimize diffs for easier human review.", + "UNLESS you were instructed earlier to behave different, there MUST NOT be whole sentences or partial sentences in the updated translation, which are not in the original English content, and there MUST NOT be whole sentences or partial sentences in the original English content, which are not in the updated translation. Remember: the updated translation shall be IN SYNC with the original English content.", + "Previous translation:", + f"%%%\n{old_translation}%%%", + ] + ) + prompt_segments.extend( + [ + f"Translate to {language} ({language_name}).", + "Original content:", + f"%%%\n{original_content}%%%", + ] + ) + prompt = "\n\n".join(prompt_segments) + print(f"Running agent for {out_path}") + result = agent.run_sync(prompt) + out_content = f"{result.output.strip()}\n" + print(f"Saving translation to {out_path}") + out_path.write_text(out_content, encoding="utf-8", newline="\n") + + +def iter_all_en_paths() -> Iterable[Path]: + """ + Iterate on the markdown files to translate in order of priority. + """ + first_dirs = [ + Path("docs/en/docs/learn"), + Path("docs/en/docs/tutorial"), + Path("docs/en/docs/advanced"), + Path("docs/en/docs/about"), + Path("docs/en/docs/how-to"), + ] + first_parent = Path("docs/en/docs") + yield from first_parent.glob("*.md") + for dir_path in first_dirs: + yield from dir_path.rglob("*.md") + first_dirs_str = tuple(str(d) for d in first_dirs) + for path in Path("docs/en/docs").rglob("*.md"): + if str(path).startswith(first_dirs_str): + continue + if path.parent == first_parent: + continue + yield path + + +def iter_en_paths_to_translate() -> Iterable[Path]: + en_docs_root = Path("docs/en/docs/") + for path in iter_all_en_paths(): + relpath = path.relative_to(en_docs_root) + if not str(relpath).startswith(non_translated_sections): + yield path + + +@app.command() +def translate_lang(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: + paths_to_process = list(iter_en_paths_to_translate()) + print("Original paths:") + for p in paths_to_process: + print(f" - {p}") + print(f"Total original paths: {len(paths_to_process)}") + missing_paths: list[Path] = [] + skipped_paths: list[Path] = [] + for p in paths_to_process: + lang_path = generate_lang_path(lang=language, path=p) + if lang_path.exists(): + skipped_paths.append(p) + continue + missing_paths.append(p) + print("Paths to skip:") + for p in skipped_paths: + print(f" - {p}") + print(f"Total paths to skip: {len(skipped_paths)}") + print("Paths to process:") + for p in missing_paths: + print(f" - {p}") + print(f"Total paths to process: {len(missing_paths)}") + for p in missing_paths: + print(f"Translating: {p}") + translate_page(language="es", en_path=p) + print(f"Done translating: {p}") + + +@app.command() +def list_removable(language: str) -> list[Path]: + removable_paths: list[Path] = [] + lang_paths = Path(f"docs/{language}").rglob("*.md") + for path in lang_paths: + en_path = generate_en_path(lang=language, path=path) + if not en_path.exists(): + removable_paths.append(path) + print(removable_paths) + return removable_paths + + +@app.command() +def list_all_removable() -> list[Path]: + all_removable_paths: list[Path] = [] + langs = get_langs() + for lang in langs: + if lang == "en": + continue + removable_paths = list_removable(lang) + all_removable_paths.extend(removable_paths) + print(all_removable_paths) + return all_removable_paths + + +@app.command() +def remove_removable(language: str) -> None: + removable_paths = list_removable(language) + for path in removable_paths: + path.unlink() + print(f"Removed: {path}") + print("Done removing all removable paths") + + +@app.command() +def remove_all_removable() -> None: + all_removable = list_all_removable() + for removable_path in all_removable: + removable_path.unlink() + print(f"Removed: {removable_path}") + print("Done removing all removable paths") + + +@app.command() +def list_missing(language: str) -> list[Path]: + missing_paths: list[Path] = [] + en_lang_paths = list(iter_en_paths_to_translate()) + for path in en_lang_paths: + lang_path = generate_lang_path(lang=language, path=path) + if not lang_path.exists(): + missing_paths.append(path) + print(missing_paths) + return missing_paths + + +@app.command() +def list_outdated(language: str) -> list[Path]: + dir_path = Path(__file__).absolute().parent.parent + repo = git.Repo(dir_path) + + outdated_paths: list[Path] = [] + en_lang_paths = list(iter_en_paths_to_translate()) + for path in en_lang_paths: + lang_path = generate_lang_path(lang=language, path=path) + if not lang_path.exists(): + continue + en_commit_datetime = list(repo.iter_commits(paths=path, max_count=1))[ + 0 + ].committed_datetime + lang_commit_datetime = list(repo.iter_commits(paths=lang_path, max_count=1))[ + 0 + ].committed_datetime + if lang_commit_datetime < en_commit_datetime: + outdated_paths.append(path) + print(outdated_paths) + return outdated_paths + + +@app.command() +def update_outdated(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: + outdated_paths = list_outdated(language) + for path in outdated_paths: + print(f"Updating lang: {language} path: {path}") + translate_page(language=language, en_path=path) + print(f"Done updating: {path}") + print("Done updating all outdated paths") + + +@app.command() +def add_missing(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: + missing_paths = list_missing(language) + for path in missing_paths: + print(f"Adding lang: {language} path: {path}") + translate_page(language=language, en_path=path) + print(f"Done adding: {path}") + print("Done adding all missing paths") + + +@app.command() +def update_and_add(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: + print(f"Updating outdated translations for {language}") + update_outdated(language=language) + print(f"Adding missing translations for {language}") + add_missing(language=language) + print(f"Done updating and adding for {language}") + + +@app.command() +def make_pr( + *, + language: Annotated[str | None, typer.Option(envvar="LANGUAGE")] = None, + github_token: Annotated[str, typer.Option(envvar="GITHUB_TOKEN")], + github_repository: Annotated[str, typer.Option(envvar="GITHUB_REPOSITORY")], +) -> None: + print("Setting up GitHub Actions git user") + repo = git.Repo(Path(__file__).absolute().parent.parent) + if not repo.is_dirty(untracked_files=True): + print("Repository is clean, no changes to commit") + return + subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run( + ["git", "config", "user.email", "github-actions@github.com"], check=True + ) + branch_name = "translate" + if language: + branch_name += f"-{language}" + branch_name += f"-{secrets.token_hex(4)}" + print(f"Creating a new branch {branch_name}") + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + print("Adding updated files") + git_path = Path("docs") + subprocess.run(["git", "add", str(git_path)], check=True) + print("Committing updated file") + message = "🌐 Update translations" + if language: + message += f" for {language}" + subprocess.run(["git", "commit", "-m", message], check=True) + print("Pushing branch") + subprocess.run(["git", "push", "origin", branch_name], check=True) + print("Creating PR") + g = Github(github_token) + gh_repo = g.get_repo(github_repository) + pr = gh_repo.create_pull( + title=message, body=message, base="master", head=branch_name + ) + print(f"Created PR: {pr.number}") + print("Finished") + + +if __name__ == "__main__": + app() diff --git a/tests/main.py b/tests/main.py index 6927eab61b..2f1d617115 100644 --- a/tests/main.py +++ b/tests/main.py @@ -3,7 +3,12 @@ from typing import FrozenSet, List, Optional from fastapi import FastAPI, Path, Query -app = FastAPI() +external_docs = { + "description": "External API documentation.", + "url": "https://docs.example.com/api-general", +} + +app = FastAPI(openapi_external_docs=external_docs) @app.api_route("/api_route") diff --git a/tests/test_application.py b/tests/test_application.py index 5c62f5f6e2..8f1b0a18d3 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -43,7 +43,7 @@ def test_redoc(): response = client.get("/redoc") assert response.status_code == 200, response.text assert response.headers["content-type"] == "text/html; charset=utf-8" - assert "redoc@next" in response.text + assert "redoc@2" in response.text def test_enum_status_code_response(): @@ -58,6 +58,10 @@ def test_openapi_schema(): assert response.json() == { "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, + "externalDocs": { + "description": "External API documentation.", + "url": "https://docs.example.com/api-general", + }, "paths": { "/api_route": { "get": { diff --git a/tests/test_compat.py b/tests/test_compat.py index bf268b860b..0184c9a2ee 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,51 +1,49 @@ -from typing import List, Union +from typing import Any, Dict, List, Union from fastapi import FastAPI, UploadFile from fastapi._compat import ( - ModelField, Undefined, _get_model_config, - is_bytes_sequence_annotation, + get_cached_model_fields, + is_scalar_field, is_uploadfile_sequence_annotation, + may_v1, ) +from fastapi._compat.shared import is_bytes_sequence_annotation from fastapi.testclient import TestClient -from pydantic import BaseConfig, BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict from pydantic.fields import FieldInfo -from .utils import needs_pydanticv1, needs_pydanticv2 +from .utils import needs_py_lt_314, needs_pydanticv2 @needs_pydanticv2 def test_model_field_default_required(): + from fastapi._compat import v2 + # For coverage field_info = FieldInfo(annotation=str) - field = ModelField(name="foo", field_info=field_info) + field = v2.ModelField(name="foo", field_info=field_info) assert field.default is Undefined -@needs_pydanticv1 -def test_upload_file_dummy_with_info_plain_validator_function(): +@needs_py_lt_314 +def test_v1_plain_validator_function(): + from fastapi._compat import v1 + # For coverage - assert UploadFile.__get_pydantic_core_schema__(str, lambda x: None) == {} + def func(v): # pragma: no cover + return v + + result = v1.with_info_plain_validator_function(func) + assert result == {} -@needs_pydanticv1 -def test_union_scalar_list(): +def test_is_model_field(): # For coverage - # TODO: there might not be a current valid code path that uses this, it would - # potentially enable query parameters defined as both a scalar and a list - # but that would require more refactors, also not sure it's really useful - from fastapi._compat import is_pv1_scalar_field + from fastapi._compat import _is_model_field - field_info = FieldInfo() - field = ModelField( - name="foo", - field_info=field_info, - type_=Union[str, List[int]], - class_validators={}, - model_config=BaseConfig, - ) - assert not is_pv1_scalar_field(field) + assert not _is_model_field(str) @needs_pydanticv2 @@ -77,6 +75,51 @@ def test_complex(): assert response2.json() == [1, 2] +@needs_pydanticv2 +def test_propagates_pydantic2_model_config(): + app = FastAPI() + + class Missing: + def __bool__(self): + return False + + class EmbeddedModel(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + value: Union[str, Missing] = Missing() + + class Model(BaseModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + ) + value: Union[str, Missing] = Missing() + embedded_model: EmbeddedModel = EmbeddedModel() + + @app.post("/") + def foo(req: Model) -> Dict[str, Union[str, None]]: + return { + "value": req.value or None, + "embedded_value": req.embedded_model.value or None, + } + + client = TestClient(app) + + response = client.post("/", json={}) + assert response.status_code == 200, response.text + assert response.json() == { + "value": None, + "embedded_value": None, + } + + response2 = client.post( + "/", json={"value": "foo", "embedded_model": {"value": "bar"}} + ) + assert response2.status_code == 200, response2.text + assert response2.json() == { + "value": "foo", + "embedded_value": "bar", + } + + def test_is_bytes_sequence_annotation_union(): # For coverage # TODO: in theory this would allow declaring types that could be lists of bytes @@ -91,3 +134,33 @@ def test_is_uploadfile_sequence_annotation(): # and other types, but I'm not even sure it's a good idea to support it as a first # class "feature" assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]]) + + +@needs_py_lt_314 +def test_is_pv1_scalar_field(): + from fastapi._compat import v1 + + # For coverage + class Model(v1.BaseModel): + foo: Union[str, Dict[str, Any]] + + fields = v1.get_model_fields(Model) + assert not is_scalar_field(fields[0]) + + +@needs_py_lt_314 +def test_get_model_fields_cached(): + from fastapi._compat import v1 + + class Model(may_v1.BaseModel): + foo: str + + non_cached_fields = v1.get_model_fields(Model) + non_cached_fields2 = v1.get_model_fields(Model) + cached_fields = get_cached_model_fields(Model) + cached_fields2 = get_cached_model_fields(Model) + for f1, f2 in zip(cached_fields, cached_fields2): + assert f1 is f2 + + assert non_cached_fields is not non_cached_fields2 + assert cached_fields is cached_fields2 diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py new file mode 100644 index 0000000000..7064761cb5 --- /dev/null +++ b/tests/test_compat_params_v1.py @@ -0,0 +1,1122 @@ +import sys +from typing import List, Optional + +import pytest + +from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi import FastAPI +from fastapi._compat.v1 import BaseModel +from fastapi.temp_pydantic_v1_params import ( + Body, + Cookie, + File, + Form, + Header, + Path, + Query, +) +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from typing_extensions import Annotated + + +class Item(BaseModel): + name: str + price: float + description: Optional[str] = None + + +app = FastAPI() + + +@app.get("/items/{item_id}") +def get_item_with_path( + item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)], +): + return {"item_id": item_id} + + +@app.get("/items/") +def get_items_with_query( + q: Annotated[ + Optional[str], Query(min_length=3, max_length=50, pattern="^[a-zA-Z0-9 ]+$") + ] = None, + skip: Annotated[int, Query(ge=0)] = 0, + limit: Annotated[int, Query(ge=1, le=100, examples=[5])] = 10, +): + return {"q": q, "skip": skip, "limit": limit} + + +@app.get("/users/") +def get_user_with_header( + x_custom: Annotated[Optional[str], Header()] = None, + x_token: Annotated[Optional[str], Header(convert_underscores=True)] = None, +): + return {"x_custom": x_custom, "x_token": x_token} + + +@app.get("/cookies/") +def get_cookies( + session_id: Annotated[Optional[str], Cookie()] = None, + tracking_id: Annotated[Optional[str], Cookie(min_length=10)] = None, +): + return {"session_id": session_id, "tracking_id": tracking_id} + + +@app.post("/items/") +def create_item( + item: Annotated[ + Item, + Body(examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}]), + ], +): + return {"item": item} + + +@app.post("/items-embed/") +def create_item_embed( + item: Annotated[Item, Body(embed=True)], +): + return {"item": item} + + +@app.put("/items/{item_id}") +def update_item( + item_id: Annotated[int, Path(ge=1)], + item: Annotated[Item, Body()], + importance: Annotated[int, Body(gt=0, le=10)], +): + return {"item": item, "importance": importance} + + +@app.post("/form-data/") +def submit_form( + username: Annotated[str, Form(min_length=3, max_length=50)], + password: Annotated[str, Form(min_length=8)], + email: Annotated[Optional[str], Form()] = None, +): + return {"username": username, "password": password, "email": email} + + +@app.post("/upload/") +def upload_file( + file: Annotated[bytes, File()], + description: Annotated[Optional[str], Form()] = None, +): + return {"file_size": len(file), "description": description} + + +@app.post("/upload-multiple/") +def upload_multiple_files( + files: Annotated[List[bytes], File()], + note: Annotated[str, Form()] = "", +): + return { + "file_count": len(files), + "total_size": sum(len(f) for f in files), + "note": note, + } + + +client = TestClient(app) + + +# Path parameter tests +def test_path_param_valid(): + response = client.get("/items/50") + assert response.status_code == 200 + assert response.json() == {"item_id": 50} + + +def test_path_param_too_large(): + response = client.get("/items/1001") + assert response.status_code == 422 + error = response.json()["detail"][0] + assert error["loc"] == ["path", "item_id"] + + +def test_path_param_too_small(): + response = client.get("/items/0") + assert response.status_code == 422 + error = response.json()["detail"][0] + assert error["loc"] == ["path", "item_id"] + + +# Query parameter tests +def test_query_params_valid(): + response = client.get("/items/?q=test search&skip=5&limit=20") + assert response.status_code == 200 + assert response.json() == {"q": "test search", "skip": 5, "limit": 20} + + +def test_query_params_defaults(): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == {"q": None, "skip": 0, "limit": 10} + + +def test_query_param_too_short(): + response = client.get("/items/?q=ab") + assert response.status_code == 422 + error = response.json()["detail"][0] + assert error["loc"] == ["query", "q"] + + +def test_query_param_invalid_pattern(): + response = client.get("/items/?q=test@#$") + assert response.status_code == 422 + error = response.json()["detail"][0] + assert error["loc"] == ["query", "q"] + + +def test_query_param_limit_too_large(): + response = client.get("/items/?limit=101") + assert response.status_code == 422 + error = response.json()["detail"][0] + assert error["loc"] == ["query", "limit"] + + +# Header parameter tests +def test_header_params(): + response = client.get( + "/users/", + headers={"X-Custom": "Plumbus", "X-Token": "secret-token"}, + ) + assert response.status_code == 200 + assert response.json() == { + "x_custom": "Plumbus", + "x_token": "secret-token", + } + + +def test_header_underscore_conversion(): + response = client.get( + "/users/", + headers={"x-token": "secret-token-with-dash"}, + ) + assert response.status_code == 200 + assert response.json()["x_token"] == "secret-token-with-dash" + + +def test_header_params_none(): + response = client.get("/users/") + assert response.status_code == 200 + assert response.json() == {"x_custom": None, "x_token": None} + + +# Cookie parameter tests +def test_cookie_params(): + with TestClient(app) as client: + client.cookies.set("session_id", "abc123") + client.cookies.set("tracking_id", "1234567890abcdef") + response = client.get("/cookies/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "abc123", + "tracking_id": "1234567890abcdef", + } + + +def test_cookie_tracking_id_too_short(): + with TestClient(app) as client: + client.cookies.set("tracking_id", "short") + response = client.get("/cookies/") + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["cookie", "tracking_id"], + "msg": "ensure this value has at least 10 characters", + "type": "value_error.any_str.min_length", + "ctx": {"limit_value": 10}, + } + ] + } + ) + + +def test_cookie_params_none(): + response = client.get("/cookies/") + assert response.status_code == 200 + assert response.json() == {"session_id": None, "tracking_id": None} + + +# Body parameter tests +def test_body_param(): + response = client.post( + "/items/", + json={"name": "Test Item", "price": 29.99, "description": "A test item"}, + ) + assert response.status_code == 200 + assert response.json() == { + "item": { + "name": "Test Item", + "price": 29.99, + "description": "A test item", + } + } + + +def test_body_param_minimal(): + response = client.post( + "/items/", + json={"name": "Minimal", "price": 9.99}, + ) + assert response.status_code == 200 + assert response.json() == { + "item": {"name": "Minimal", "price": 9.99, "description": None} + } + + +def test_body_param_missing_required(): + response = client.post( + "/items/", + json={"name": "Incomplete"}, + ) + assert response.status_code == 422 + error = response.json()["detail"][0] + assert error["loc"] == ["body", "price"] + + +def test_body_embed(): + response = client.post( + "/items-embed/", + json={"item": {"name": "Embedded", "price": 15.0}}, + ) + assert response.status_code == 200 + assert response.json() == { + "item": {"name": "Embedded", "price": 15.0, "description": None} + } + + +def test_body_embed_wrong_structure(): + response = client.post( + "/items-embed/", + json={"name": "Not Embedded", "price": 15.0}, + ) + assert response.status_code == 422 + + +# Multiple body parameters test +def test_multiple_body_params(): + response = client.put( + "/items/5", + json={ + "item": {"name": "Updated Item", "price": 49.99}, + "importance": 8, + }, + ) + assert response.status_code == 200 + assert response.json() == snapshot( + { + "item": {"name": "Updated Item", "price": 49.99, "description": None}, + "importance": 8, + } + ) + + +def test_multiple_body_params_importance_too_large(): + response = client.put( + "/items/5", + json={ + "item": {"name": "Item", "price": 10.0}, + "importance": 11, + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "importance"], + "msg": "ensure this value is less than or equal to 10", + "type": "value_error.number.not_le", + "ctx": {"limit_value": 10}, + } + ] + } + ) + + +def test_multiple_body_params_importance_too_small(): + response = client.put( + "/items/5", + json={ + "item": {"name": "Item", "price": 10.0}, + "importance": 0, + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "importance"], + "msg": "ensure this value is greater than 0", + "type": "value_error.number.not_gt", + "ctx": {"limit_value": 0}, + } + ] + } + ) + + +# Form parameter tests +def test_form_data_valid(): + response = client.post( + "/form-data/", + data={ + "username": "testuser", + "password": "password123", + "email": "test@example.com", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "testuser", + "password": "password123", + "email": "test@example.com", + } + + +def test_form_data_optional_field(): + response = client.post( + "/form-data/", + data={"username": "testuser", "password": "password123"}, + ) + assert response.status_code == 200 + assert response.json() == { + "username": "testuser", + "password": "password123", + "email": None, + } + + +def test_form_data_username_too_short(): + response = client.post( + "/form-data/", + data={"username": "ab", "password": "password123"}, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "username"], + "msg": "ensure this value has at least 3 characters", + "type": "value_error.any_str.min_length", + "ctx": {"limit_value": 3}, + } + ] + } + ) + + +def test_form_data_password_too_short(): + response = client.post( + "/form-data/", + data={"username": "testuser", "password": "short"}, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "password"], + "msg": "ensure this value has at least 8 characters", + "type": "value_error.any_str.min_length", + "ctx": {"limit_value": 8}, + } + ] + } + ) + + +# File upload tests +def test_upload_file(): + response = client.post( + "/upload/", + files={"file": ("test.txt", b"Hello, World!", "text/plain")}, + data={"description": "A test file"}, + ) + assert response.status_code == 200 + assert response.json() == { + "file_size": 13, + "description": "A test file", + } + + +def test_upload_file_without_description(): + response = client.post( + "/upload/", + files={"file": ("test.txt", b"Hello!", "text/plain")}, + ) + assert response.status_code == 200 + assert response.json() == { + "file_size": 6, + "description": None, + } + + +def test_upload_multiple_files(): + response = client.post( + "/upload-multiple/", + files=[ + ("files", ("file1.txt", b"Content 1", "text/plain")), + ("files", ("file2.txt", b"Content 2", "text/plain")), + ("files", ("file3.txt", b"Content 3", "text/plain")), + ], + data={"note": "Multiple files uploaded"}, + ) + assert response.status_code == 200 + assert response.json() == { + "file_count": 3, + "total_size": 27, + "note": "Multiple files uploaded", + } + + +def test_upload_multiple_files_empty_note(): + response = client.post( + "/upload-multiple/", + files=[ + ("files", ("file1.txt", b"Test", "text/plain")), + ], + ) + assert response.status_code == 200 + assert response.json()["file_count"] == 1 + assert response.json()["note"] == "" + + +# __repr__ tests +def test_query_repr(): + query_param = Query(default=None, min_length=3) + assert repr(query_param) == "Query(None)" + + +def test_body_repr(): + body_param = Body(default=None) + assert repr(body_param) == "Body(None)" + + +# Deprecation warning tests for regex parameter +def test_query_regex_deprecation_warning(): + with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"): + Query(regex="^test$") + + +def test_body_regex_deprecation_warning(): + with pytest.warns(DeprecationWarning, match="`regex` has been deprecated"): + Body(regex="^test$") + + +# Deprecation warning tests for example parameter +def test_query_example_deprecation_warning(): + with pytest.warns(DeprecationWarning, match="`example` has been deprecated"): + Query(example="test example") + + +def test_body_example_deprecation_warning(): + with pytest.warns(DeprecationWarning, match="`example` has been deprecated"): + Body(example={"test": "example"}) + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Get Item With Path", + "operationId": "get_item_with_path_items__item_id__get", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": { + "title": "The ID of the item", + "minimum": 1, + "maximum": 1000, + "type": "integer", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": { + "title": "Item Id", + "minimum": 1, + "type": "integer", + }, + } + ], + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": pydantic_snapshot( + v1=snapshot( + { + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" + } + ), + v2=snapshot( + { + "title": "Body", + "allOf": [ + { + "$ref": "#/components/schemas/Body_update_item_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" + } + } + }, + }, + }, + }, + }, + "/items/": { + "get": { + "summary": "Get Items With Query", + "operationId": "get_items_with_query_items__get", + "parameters": [ + { + "name": "q", + "in": "query", + "required": False, + "schema": { + "title": "Q", + "maxLength": 50, + "minLength": 3, + "pattern": "^[a-zA-Z0-9 ]+$", + "type": "string", + }, + }, + { + "name": "skip", + "in": "query", + "required": False, + "schema": { + "title": "Skip", + "default": 0, + "minimum": 0, + "type": "integer", + }, + }, + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "title": "Limit", + "default": 10, + "minimum": 1, + "maximum": 100, + "examples": [5], + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "title": "Item", + "examples": [ + { + "name": "Foo", + "price": 35.4, + "description": "The Foo item", + } + ], + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/users/": { + "get": { + "summary": "Get User With Header", + "operationId": "get_user_with_header_users__get", + "parameters": [ + { + "name": "x-custom", + "in": "header", + "required": False, + "schema": {"title": "X-Custom", "type": "string"}, + }, + { + "name": "x-token", + "in": "header", + "required": False, + "schema": {"title": "X-Token", "type": "string"}, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookies/": { + "get": { + "summary": "Get Cookies", + "operationId": "get_cookies_cookies__get", + "parameters": [ + { + "name": "session_id", + "in": "cookie", + "required": False, + "schema": {"title": "Session Id", "type": "string"}, + }, + { + "name": "tracking_id", + "in": "cookie", + "required": False, + "schema": { + "title": "Tracking Id", + "minLength": 10, + "type": "string", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items-embed/": { + "post": { + "summary": "Create Item Embed", + "operationId": "create_item_embed_items_embed__post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v1=snapshot( + { + "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post" + } + ), + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post" + } + ], + "title": "Body", + } + ), + ), + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/form-data/": { + "post": { + "summary": "Submit Form", + "operationId": "submit_form_form_data__post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": pydantic_snapshot( + v1=snapshot( + { + "$ref": "#/components/schemas/Body_submit_form_form_data__post" + } + ), + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Body_submit_form_form_data__post" + } + ], + "title": "Body", + } + ), + ), + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/upload/": { + "post": { + "summary": "Upload File", + "operationId": "upload_file_upload__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": pydantic_snapshot( + v1=snapshot( + { + "$ref": "#/components/schemas/Body_upload_file_upload__post" + } + ), + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Body_upload_file_upload__post" + } + ], + "title": "Body", + } + ), + ), + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/upload-multiple/": { + "post": { + "summary": "Upload Multiple Files", + "operationId": "upload_multiple_files_upload_multiple__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": pydantic_snapshot( + v1=snapshot( + { + "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post" + } + ), + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post" + } + ], + "title": "Body", + } + ), + ), + } + }, + "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_item_embed_items_embed__post": { + "properties": pydantic_snapshot( + v1=snapshot( + {"item": {"$ref": "#/components/schemas/Item"}} + ), + v2=snapshot( + { + "item": { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + } + ), + ), + "type": "object", + "required": ["item"], + "title": "Body_create_item_embed_items_embed__post", + }, + "Body_submit_form_form_data__post": { + "properties": { + "username": { + "type": "string", + "maxLength": 50, + "minLength": 3, + "title": "Username", + }, + "password": { + "type": "string", + "minLength": 8, + "title": "Password", + }, + "email": {"type": "string", "title": "Email"}, + }, + "type": "object", + "required": ["username", "password"], + "title": "Body_submit_form_form_data__post", + }, + "Body_update_item_items__item_id__put": { + "properties": { + "item": pydantic_snapshot( + v1=snapshot({"$ref": "#/components/schemas/Item"}), + v2=snapshot( + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + ), + ), + "importance": { + "type": "integer", + "maximum": 10.0, + "exclusiveMinimum": 0.0, + "title": "Importance", + }, + }, + "type": "object", + "required": ["item", "importance"], + "title": "Body_update_item_items__item_id__put", + }, + "Body_upload_file_upload__post": { + "properties": { + "file": { + "type": "string", + "format": "binary", + "title": "File", + }, + "description": {"type": "string", "title": "Description"}, + }, + "type": "object", + "required": ["file"], + "title": "Body_upload_file_upload__post", + }, + "Body_upload_multiple_files_upload_multiple__post": { + "properties": { + "files": { + "items": {"type": "string", "format": "binary"}, + "type": "array", + "title": "Files", + }, + "note": {"type": "string", "title": "Note", "default": ""}, + }, + "type": "object", + "required": ["files"], + "title": "Body_upload_multiple_files_upload_multiple__post", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "price": {"type": "number", "title": "Price"}, + "description": {"type": "string", "title": "Description"}, + }, + "type": "object", + "required": ["name", "price"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index 5286507b2d..a1b4121688 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -24,13 +24,18 @@ def get_client(): def read_root() -> Rectangle: return Rectangle(width=3, length=4) + @app.get("/responses", responses={200: {"model": Rectangle}}) + def read_responses() -> Rectangle: + return Rectangle(width=3, length=4) + client = TestClient(app) return client +@pytest.mark.parametrize("path", ["/", "/responses"]) @needs_pydanticv2 -def test_get(client: TestClient): - response = client.get("/") +def test_get(client: TestClient, path: str): + response = client.get(path) assert response.status_code == 200, response.text assert response.json() == {"width": 3, "length": 4, "area": 12} @@ -58,7 +63,23 @@ def test_openapi_schema(client: TestClient): } }, } - } + }, + "/responses": { + "get": { + "summary": "Read Responses", + "operationId": "read_responses_responses_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Rectangle"} + } + }, + } + }, + } + }, }, "components": { "schemas": { diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py index ee51fc7ff5..d890291b19 100644 --- a/tests/test_custom_schema_fields.py +++ b/tests/test_custom_schema_fields.py @@ -1,7 +1,13 @@ +from typing import Optional + from fastapi import FastAPI from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel +from typing_extensions import Annotated + +if PYDANTIC_V2: + from pydantic import WithJsonSchema app = FastAPI() @@ -10,12 +16,17 @@ class Item(BaseModel): name: str if PYDANTIC_V2: + description: Annotated[ + Optional[str], WithJsonSchema({"type": ["string", "null"]}) + ] = None + model_config = { "json_schema_extra": { "x-something-internal": {"level": 4}, } } else: + description: Optional[str] = None # type: ignore[no-redef] class Config: schema_extra = { @@ -42,7 +53,11 @@ item_schema = { "name": { "title": "Name", "type": "string", - } + }, + "description": { + "title": "Description", + "type": ["string", "null"] if PYDANTIC_V2 else "string", + }, }, } @@ -57,4 +72,4 @@ def test_response(): # For coverage response = client.get("/foo") assert response.status_code == 200, response.text - assert response.json() == {"name": "Foo item"} + assert response.json() == {"name": "Foo item", "description": None} diff --git a/tests/test_dependency_after_yield_raise.py b/tests/test_dependency_after_yield_raise.py new file mode 100644 index 0000000000..b560dc36f9 --- /dev/null +++ b/tests/test_dependency_after_yield_raise.py @@ -0,0 +1,69 @@ +from typing import Any + +import pytest +from fastapi import Depends, FastAPI, HTTPException +from fastapi.testclient import TestClient +from typing_extensions import Annotated + + +class CustomError(Exception): + pass + + +def catching_dep() -> Any: + try: + yield "s" + except CustomError as err: + raise HTTPException(status_code=418, detail="Session error") from err + + +def broken_dep() -> Any: + yield "s" + raise ValueError("Broken after yield") + + +app = FastAPI() + + +@app.get("/catching") +def catching(d: Annotated[str, Depends(catching_dep)]) -> Any: + raise CustomError("Simulated error during streaming") + + +@app.get("/broken") +def broken(d: Annotated[str, Depends(broken_dep)]) -> Any: + return {"message": "all good?"} + + +client = TestClient(app) + + +def test_catching(): + response = client.get("/catching") + assert response.status_code == 418 + assert response.json() == {"detail": "Session error"} + + +def test_broken_raise(): + with pytest.raises(ValueError, match="Broken after yield"): + client.get("/broken") + + +def test_broken_no_raise(): + """ + When a dependency with yield raises after the yield (not in an except), the + response is already "successfully" sent back to the client, but there's still + an error in the server afterwards, an exception is raised and captured or shown + in the server logs. + """ + with TestClient(app, raise_server_exceptions=False) as client: + response = client.get("/broken") + assert response.status_code == 200 + assert response.json() == {"message": "all good?"} + + +def test_broken_return_finishes(): + client = TestClient(app, raise_server_exceptions=False) + response = client.get("/broken") + assert response.status_code == 200 + assert response.json() == {"message": "all good?"} diff --git a/tests/test_dependency_after_yield_streaming.py b/tests/test_dependency_after_yield_streaming.py new file mode 100644 index 0000000000..7e1c8822b8 --- /dev/null +++ b/tests/test_dependency_after_yield_streaming.py @@ -0,0 +1,130 @@ +from contextlib import contextmanager +from typing import Any, Generator + +import pytest +from fastapi import Depends, FastAPI +from fastapi.responses import StreamingResponse +from fastapi.testclient import TestClient +from typing_extensions import Annotated + + +class Session: + def __init__(self) -> None: + self.data = ["foo", "bar", "baz"] + self.open = True + + def __iter__(self) -> Generator[str, None, None]: + for item in self.data: + if self.open: + yield item + else: + raise ValueError("Session closed") + + +@contextmanager +def acquire_session() -> Generator[Session, None, None]: + session = Session() + try: + yield session + finally: + session.open = False + + +def dep_session() -> Any: + with acquire_session() as s: + yield s + + +def broken_dep_session() -> Any: + with acquire_session() as s: + s.open = False + yield s + + +SessionDep = Annotated[Session, Depends(dep_session)] +BrokenSessionDep = Annotated[Session, Depends(broken_dep_session)] + +app = FastAPI() + + +@app.get("/data") +def get_data(session: SessionDep) -> Any: + data = list(session) + return data + + +@app.get("/stream-simple") +def get_stream_simple(session: SessionDep) -> Any: + def iter_data(): + yield from ["x", "y", "z"] + + return StreamingResponse(iter_data()) + + +@app.get("/stream-session") +def get_stream_session(session: SessionDep) -> Any: + def iter_data(): + yield from session + + return StreamingResponse(iter_data()) + + +@app.get("/broken-session-data") +def get_broken_session_data(session: BrokenSessionDep) -> Any: + return list(session) + + +@app.get("/broken-session-stream") +def get_broken_session_stream(session: BrokenSessionDep) -> Any: + def iter_data(): + yield from session + + return StreamingResponse(iter_data()) + + +client = TestClient(app) + + +def test_regular_no_stream(): + response = client.get("/data") + assert response.json() == ["foo", "bar", "baz"] + + +def test_stream_simple(): + response = client.get("/stream-simple") + assert response.text == "xyz" + + +def test_stream_session(): + response = client.get("/stream-session") + assert response.text == "foobarbaz" + + +def test_broken_session_data(): + with pytest.raises(ValueError, match="Session closed"): + client.get("/broken-session-data") + + +def test_broken_session_data_no_raise(): + client = TestClient(app, raise_server_exceptions=False) + response = client.get("/broken-session-data") + assert response.status_code == 500 + assert response.text == "Internal Server Error" + + +def test_broken_session_stream_raise(): + # Can raise ValueError on Pydantic v2 and ExceptionGroup on Pydantic v1 + with pytest.raises((ValueError, Exception)): + client.get("/broken-session-stream") + + +def test_broken_session_stream_no_raise(): + """ + When a dependency with yield raises after the streaming response already started + the 200 status code is already sent, but there's still an error in the server + afterwards, an exception is raised and captured or shown in the server logs. + """ + with TestClient(app, raise_server_exceptions=False) as client: + response = client.get("/broken-session-stream") + assert response.status_code == 200 + assert response.text == "" diff --git a/tests/test_dependency_after_yield_websockets.py b/tests/test_dependency_after_yield_websockets.py new file mode 100644 index 0000000000..7c323c338b --- /dev/null +++ b/tests/test_dependency_after_yield_websockets.py @@ -0,0 +1,79 @@ +from contextlib import contextmanager +from typing import Any, Generator + +import pytest +from fastapi import Depends, FastAPI, WebSocket +from fastapi.testclient import TestClient +from typing_extensions import Annotated + + +class Session: + def __init__(self) -> None: + self.data = ["foo", "bar", "baz"] + self.open = True + + def __iter__(self) -> Generator[str, None, None]: + for item in self.data: + if self.open: + yield item + else: + raise ValueError("Session closed") + + +@contextmanager +def acquire_session() -> Generator[Session, None, None]: + session = Session() + try: + yield session + finally: + session.open = False + + +def dep_session() -> Any: + with acquire_session() as s: + yield s + + +def broken_dep_session() -> Any: + with acquire_session() as s: + s.open = False + yield s + + +SessionDep = Annotated[Session, Depends(dep_session)] +BrokenSessionDep = Annotated[Session, Depends(broken_dep_session)] + +app = FastAPI() + + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket, session: SessionDep): + await websocket.accept() + for item in session: + await websocket.send_text(f"{item}") + + +@app.websocket("/ws-broken") +async def websocket_endpoint_broken(websocket: WebSocket, session: BrokenSessionDep): + await websocket.accept() + for item in session: + await websocket.send_text(f"{item}") # pragma no cover + + +client = TestClient(app) + + +def test_websocket_dependency_after_yield(): + with client.websocket_connect("/ws") as websocket: + data = websocket.receive_text() + assert data == "foo" + data = websocket.receive_text() + assert data == "bar" + data = websocket.receive_text() + assert data == "baz" + + +def test_websocket_dependency_after_yield_broken(): + with pytest.raises(ValueError, match="Session closed"): + with client.websocket_connect("/ws-broken"): + pass # pragma no cover diff --git a/tests/test_dependency_contextmanager.py b/tests/test_dependency_contextmanager.py index 039c423b98..02c10458cb 100644 --- a/tests/test_dependency_contextmanager.py +++ b/tests/test_dependency_contextmanager.py @@ -286,12 +286,12 @@ def test_background_tasks(): assert data["context_a"] == "started a" assert data["bg"] == "not set" middleware_state = json.loads(response.headers["x-state"]) - assert middleware_state["context_b"] == "finished b with a: started a" - assert middleware_state["context_a"] == "finished a" + assert middleware_state["context_b"] == "started b" + assert middleware_state["context_a"] == "started a" assert middleware_state["bg"] == "not set" assert state["context_b"] == "finished b with a: started a" assert state["context_a"] == "finished a" - assert state["bg"] == "bg set - b: finished b with a: started a - a: finished a" + assert state["bg"] == "bg set - b: started b - a: started a" def test_sync_raise_raises(): @@ -397,7 +397,4 @@ def test_sync_background_tasks(): assert data["sync_bg"] == "not set" assert state["context_b"] == "finished b with a: started a" assert state["context_a"] == "finished a" - assert ( - state["sync_bg"] - == "sync_bg set - b: finished b with a: started a - a: finished a" - ) + assert state["sync_bg"] == "sync_bg set - b: started b - a: started a" diff --git a/tests/test_dependency_normal_exceptions.py b/tests/test_dependency_yield_except_httpexception.py similarity index 100% rename from tests/test_dependency_normal_exceptions.py rename to tests/test_dependency_yield_except_httpexception.py diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py index b64f8341b8..2e5ac6c062 100644 --- a/tests/test_enforce_once_required_parameter.py +++ b/tests/test_enforce_once_required_parameter.py @@ -48,7 +48,7 @@ expected_schema = { "type": "array", }, "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error " "Type", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, "required": ["loc", "msg", "type"], "title": "ValidationError", @@ -73,7 +73,7 @@ expected_schema = { "responses": { "200": { "content": {"application/json": {"schema": {}}}, - "description": "Successful " "Response", + "description": "Successful Response", }, "422": { "content": { @@ -83,7 +83,7 @@ expected_schema = { } } }, - "description": "Validation " "Error", + "description": "Validation Error", }, }, "summary": "Foo Handler", @@ -102,7 +102,7 @@ def test_schema(): def test_get_invalid(): response = client.get("/foo") - assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.status_code == 422 def test_get_valid(): diff --git a/tests/test_exception_handlers.py b/tests/test_exception_handlers.py index 67a4becec7..6a3cbd830d 100644 --- a/tests/test_exception_handlers.py +++ b/tests/test_exception_handlers.py @@ -1,5 +1,5 @@ import pytest -from fastapi import FastAPI, HTTPException +from fastapi import Depends, FastAPI, HTTPException from fastapi.exceptions import RequestValidationError from fastapi.testclient import TestClient from starlette.responses import JSONResponse @@ -28,6 +28,18 @@ app = FastAPI( client = TestClient(app) +def raise_value_error(): + raise ValueError() + + +def dependency_with_yield(): + yield raise_value_error() + + +@app.get("/dependency-with-yield", dependencies=[Depends(dependency_with_yield)]) +def with_yield(): ... + + @app.get("/http-exception") def route_with_http_exception(): raise HTTPException(status_code=400) @@ -65,3 +77,12 @@ def test_override_server_error_exception_response(): response = client.get("/server-error") assert response.status_code == 500 assert response.json() == {"exception": "server-error"} + + +def test_traceback_for_dependency_with_yield(): + client = TestClient(app, raise_server_exceptions=True) + with pytest.raises(ValueError) as exc_info: + client.get("/dependency-with-yield") + last_frame = exc_info.traceback[-1] + assert str(last_frame.path) == __file__ + assert last_frame.lineno == raise_value_error.__code__.co_firstlineno diff --git a/tests/test_fastapi_cli.py b/tests/test_fastapi_cli.py index 20c9281570..a5c10778ad 100644 --- a/tests/test_fastapi_cli.py +++ b/tests/test_fastapi_cli.py @@ -22,7 +22,7 @@ def test_fastapi_cli(): encoding="utf-8", ) assert result.returncode == 1, result.stdout - assert "Using path non_existent_file.py" in result.stdout + assert "Path does not exist non_existent_file.py" in result.stdout def test_fastapi_cli_not_installed(): diff --git a/tests/test_file_and_form_order_issue_9116.py b/tests/test_file_and_form_order_issue_9116.py new file mode 100644 index 0000000000..cb9a31d314 --- /dev/null +++ b/tests/test_file_and_form_order_issue_9116.py @@ -0,0 +1,90 @@ +""" +Regression test, Error 422 if Form is declared before File +See https://github.com/tiangolo/fastapi/discussions/9116 +""" + +from pathlib import Path +from typing import List + +import pytest +from fastapi import FastAPI, File, Form +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +app = FastAPI() + + +@app.post("/file_before_form") +def file_before_form( + file: bytes = File(), + city: str = Form(), +): + return {"file_content": file, "city": city} + + +@app.post("/file_after_form") +def file_after_form( + city: str = Form(), + file: bytes = File(), +): + return {"file_content": file, "city": city} + + +@app.post("/file_list_before_form") +def file_list_before_form( + files: Annotated[List[bytes], File()], + city: Annotated[str, Form()], +): + return {"file_contents": files, "city": city} + + +@app.post("/file_list_after_form") +def file_list_after_form( + city: Annotated[str, Form()], + files: Annotated[List[bytes], File()], +): + return {"file_contents": files, "city": city} + + +client = TestClient(app) + + +@pytest.fixture +def tmp_file_1(tmp_path: Path) -> Path: + f = tmp_path / "example1.txt" + f.write_text("foo") + return f + + +@pytest.fixture +def tmp_file_2(tmp_path: Path) -> Path: + f = tmp_path / "example2.txt" + f.write_text("bar") + return f + + +@pytest.mark.parametrize("endpoint_path", ("/file_before_form", "/file_after_form")) +def test_file_form_order(endpoint_path: str, tmp_file_1: Path): + response = client.post( + url=endpoint_path, + data={"city": "Thimphou"}, + files={"file": (tmp_file_1.name, tmp_file_1.read_bytes())}, + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_content": "foo", "city": "Thimphou"} + + +@pytest.mark.parametrize( + "endpoint_path", ("/file_list_before_form", "/file_list_after_form") +) +def test_file_list_form_order(endpoint_path: str, tmp_file_1: Path, tmp_file_2: Path): + response = client.post( + url=endpoint_path, + data={"city": "Thimphou"}, + files=( + ("files", (tmp_file_1.name, tmp_file_1.read_bytes())), + ("files", (tmp_file_2.name, tmp_file_2.read_bytes())), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_contents": ["foo", "bar"], "city": "Thimphou"} diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py new file mode 100644 index 0000000000..880ab38200 --- /dev/null +++ b/tests/test_forms_single_model.py @@ -0,0 +1,133 @@ +from typing import List, Optional + +from dirty_equals import IsDict +from fastapi import FastAPI, Form +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +app = FastAPI() + + +class FormModel(BaseModel): + username: str + lastname: str + age: Optional[int] = None + tags: List[str] = ["foo", "bar"] + alias_with: str = Field(alias="with", default="nothing") + + +@app.post("/form/") +def post_form(user: Annotated[FormModel, Form()]): + return user + + +client = TestClient(app) + + +def test_send_all_data(): + response = client.post( + "/form/", + data={ + "username": "Rick", + "lastname": "Sanchez", + "age": "70", + "tags": ["plumbus", "citadel"], + "with": "something", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "Rick", + "lastname": "Sanchez", + "age": 70, + "tags": ["plumbus", "citadel"], + "with": "something", + } + + +def test_defaults(): + response = client.post("/form/", data={"username": "Rick", "lastname": "Sanchez"}) + assert response.status_code == 200, response.text + assert response.json() == { + "username": "Rick", + "lastname": "Sanchez", + "age": None, + "tags": ["foo", "bar"], + "with": "nothing", + } + + +def test_invalid_data(): + response = client.post( + "/form/", + data={ + "username": "Rick", + "lastname": "Sanchez", + "age": "seventy", + "tags": ["plumbus", "citadel"], + }, + ) + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "type": "int_parsing", + "loc": ["body", "age"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "seventy", + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "age"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ] + } + ) + + +def test_no_data(): + response = client.post("/form/") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "username"], + "msg": "Field required", + "input": {"tags": ["foo", "bar"], "with": "nothing"}, + }, + { + "type": "missing", + "loc": ["body", "lastname"], + "msg": "Field required", + "input": {"tags": ["foo", "bar"], "with": "nothing"}, + }, + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "username"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "lastname"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py b/tests/test_forms_single_param.py similarity index 62% rename from tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py rename to tests/test_forms_single_param.py index 2182e87b75..3bb951441f 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py +++ b/tests/test_forms_single_param.py @@ -1,20 +1,22 @@ +from fastapi import FastAPI, Form from fastapi.testclient import TestClient +from typing_extensions import Annotated + +app = FastAPI() + + +@app.post("/form/") +def post_form(username: Annotated[str, Form()]): + return username -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") +def test_single_form_field(): + response = client.post("/form/", data={"username": "Rick"}) 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"} + assert response.json() == "Rick" def test_openapi_schema(): @@ -24,10 +26,20 @@ def test_openapi_schema(): "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", + "/form/": { + "post": { + "summary": "Post Form", + "operationId": "post_form_form__post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_post_form_form__post" + } + } + }, + "required": True, + }, "responses": { "200": { "description": "Successful Response", @@ -49,32 +61,38 @@ def test_openapi_schema(): }, "components": { "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", + "Body_post_form_form__post": { + "properties": {"username": {"type": "string", "title": "Username"}}, "type": "object", + "required": ["username"], + "title": "Body_post_form_form__post", + }, + "HTTPValidationError": { "properties": { "detail": { - "title": "Detail", - "type": "array", "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", } }, + "type": "object", + "title": "HTTPValidationError", }, "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", "properties": { "loc": { - "title": "Location", - "type": "array", "items": { "anyOf": [{"type": "string"}, {"type": "integer"}] }, + "type": "array", + "title": "Location", }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } }, diff --git a/tests/test_generic_parameterless_depends.py b/tests/test_generic_parameterless_depends.py index fe13ff89b8..5aa35320c9 100644 --- a/tests/test_generic_parameterless_depends.py +++ b/tests/test_generic_parameterless_depends.py @@ -55,7 +55,7 @@ def test_openapi_schema(): "responses": { "200": { "content": {"application/json": {"schema": {}}}, - "description": "Successful " "Response", + "description": "Successful Response", } }, "summary": "A", @@ -67,7 +67,7 @@ def test_openapi_schema(): "responses": { "200": { "content": {"application/json": {"schema": {}}}, - "description": "Successful " "Response", + "description": "Successful Response", } }, "summary": "B", diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py new file mode 100644 index 0000000000..6601585ef0 --- /dev/null +++ b/tests/test_get_model_definitions_formfeed_escape.py @@ -0,0 +1,180 @@ +from typing import Any, Iterator, Set, Type + +import fastapi._compat +import fastapi.openapi.utils +import pydantic.schema +import pytest +from fastapi import FastAPI +from pydantic import BaseModel +from starlette.testclient import TestClient + +from .utils import needs_pydanticv1 + + +class Address(BaseModel): + """ + This is a public description of an Address + \f + You can't see this part of the docstring, it's private! + """ + + line_1: str + city: str + state_province: str + + +class Facility(BaseModel): + id: str + address: Address + + +app = FastAPI() + +client = TestClient(app) + + +@app.get("/facilities/{facility_id}") +def get_facility(facility_id: str) -> Facility: ... + + +openapi_schema = { + "components": { + "schemas": { + "Address": { + # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring + "description": "This is a public description of an Address\n", + "properties": { + "city": {"title": "City", "type": "string"}, + "line_1": {"title": "Line 1", "type": "string"}, + "state_province": {"title": "State Province", "type": "string"}, + }, + "required": ["line_1", "city", "state_province"], + "title": "Address", + "type": "object", + }, + "Facility": { + "properties": { + "address": {"$ref": "#/components/schemas/Address"}, + "id": {"title": "Id", "type": "string"}, + }, + "required": ["id", "address"], + "title": "Facility", + "type": "object", + }, + "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.1.0", + "paths": { + "/facilities/{facility_id}": { + "get": { + "operationId": "get_facility_facilities__facility_id__get", + "parameters": [ + { + "in": "path", + "name": "facility_id", + "required": True, + "schema": {"title": "Facility Id", "type": "string"}, + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Facility"} + } + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error", + }, + }, + "summary": "Get Facility", + } + } + }, +} + + +def test_openapi_schema(): + """ + Sanity check to ensure our app's openapi schema renders as we expect + """ + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == openapi_schema + + +class SortedTypeSet(set): + """ + Set of Types whose `__iter__()` method yields results sorted by the type names + """ + + def __init__(self, seq: Set[Type[Any]], *, sort_reversed: bool): + super().__init__(seq) + self.sort_reversed = sort_reversed + + def __iter__(self) -> Iterator[Type[Any]]: + members_sorted = sorted( + super().__iter__(), + key=lambda type_: type_.__name__, + reverse=self.sort_reversed, + ) + yield from members_sorted + + +@needs_pydanticv1 +@pytest.mark.parametrize("sort_reversed", [True, False]) +def test_model_description_escaped_with_formfeed(sort_reversed: bool): + """ + Regression test for bug fixed by https://github.com/fastapi/fastapi/pull/6039. + + Test `get_model_definitions` with models passed in different order. + """ + from fastapi._compat import v1 + + all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes) + + flat_models = v1.get_flat_models_from_fields(all_fields, known_models=set()) + model_name_map = pydantic.schema.get_model_name_map(flat_models) + + expected_address_description = "This is a public description of an Address\n" + + models = v1.get_model_definitions( + flat_models=SortedTypeSet(flat_models, sort_reversed=sort_reversed), + model_name_map=model_name_map, + ) + assert models["Address"]["description"] == expected_address_description diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 1906d6bf17..447c5b4d6a 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -216,9 +216,12 @@ def test_custom_encoders(): instance = MyModel(dt_field=safe_datetime.now()) encoded_instance = jsonable_encoder( - instance, custom_encoder={safe_datetime: lambda o: o.isoformat()} + instance, custom_encoder={safe_datetime: lambda o: o.strftime("%H:%M:%S")} ) - assert encoded_instance["dt_field"] == instance.dt_field.isoformat() + assert encoded_instance["dt_field"] == instance.dt_field.strftime("%H:%M:%S") + + encoded_instance2 = jsonable_encoder(instance) + assert encoded_instance2["dt_field"] == instance.dt_field.isoformat() def test_custom_enum_encoders(): diff --git a/tests/test_modules_same_name_body/test_main.py b/tests/test_modules_same_name_body/test_main.py index cc165bdcaa..263d87df26 100644 --- a/tests/test_modules_same_name_body/test_main.py +++ b/tests/test_modules_same_name_body/test_main.py @@ -1,3 +1,4 @@ +import pytest from fastapi.testclient import TestClient from .app.main import app @@ -5,29 +6,22 @@ from .app.main import app client = TestClient(app) -def test_post_a(): +@pytest.mark.parametrize( + "path", ["/a/compute", "/a/compute/", "/b/compute", "/b/compute/"] +) +def test_post(path): data = {"a": 2, "b": "foo"} - response = client.post("/a/compute", json=data) + response = client.post(path, json=data) assert response.status_code == 200, response.text - data = response.json() + assert data == response.json() -def test_post_a_invalid(): +@pytest.mark.parametrize( + "path", ["/a/compute", "/a/compute/", "/b/compute", "/b/compute/"] +) +def test_post_invalid(path): data = {"a": "bar", "b": "foo"} - response = client.post("/a/compute", json=data) - assert response.status_code == 422, response.text - - -def test_post_b(): - data = {"a": 2, "b": "foo"} - response = client.post("/b/compute/", json=data) - assert response.status_code == 200, response.text - data = response.json() - - -def test_post_b_invalid(): - data = {"a": "bar", "b": "foo"} - response = client.post("/b/compute/", json=data) + response = client.post(path, json=data) assert response.status_code == 422, response.text diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py index 0102f0f1a0..33304827a9 100644 --- a/tests/test_multi_body_errors.py +++ b/tests/test_multi_body_errors.py @@ -185,7 +185,15 @@ def test_openapi_schema(): "title": "Age", "anyOf": [ {"exclusiveMinimum": 0.0, "type": "number"}, - {"type": "string"}, + IsOneOf( + # pydantic < 2.12.0 + {"type": "string"}, + # pydantic >= 2.12.0 + { + "type": "string", + "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$", + }, + ), ], } ) diff --git a/tests/test_multipart_installation.py b/tests/test_multipart_installation.py index 788d9ef5af..9c3e47c495 100644 --- a/tests/test_multipart_installation.py +++ b/tests/test_multipart_installation.py @@ -1,3 +1,5 @@ +import warnings + import pytest from fastapi import FastAPI, File, Form, UploadFile from fastapi.dependencies.utils import ( @@ -7,7 +9,10 @@ from fastapi.dependencies.utils import ( def test_incorrect_multipart_installed_form(monkeypatch): - monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) with pytest.raises(RuntimeError, match=multipart_incorrect_install_error): app = FastAPI() @@ -17,7 +22,10 @@ def test_incorrect_multipart_installed_form(monkeypatch): def test_incorrect_multipart_installed_file_upload(monkeypatch): - monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) with pytest.raises(RuntimeError, match=multipart_incorrect_install_error): app = FastAPI() @@ -27,7 +35,10 @@ def test_incorrect_multipart_installed_file_upload(monkeypatch): def test_incorrect_multipart_installed_file_bytes(monkeypatch): - monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) with pytest.raises(RuntimeError, match=multipart_incorrect_install_error): app = FastAPI() @@ -37,7 +48,10 @@ def test_incorrect_multipart_installed_file_bytes(monkeypatch): def test_incorrect_multipart_installed_multi_form(monkeypatch): - monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) with pytest.raises(RuntimeError, match=multipart_incorrect_install_error): app = FastAPI() @@ -47,7 +61,10 @@ def test_incorrect_multipart_installed_multi_form(monkeypatch): def test_incorrect_multipart_installed_form_file(monkeypatch): - monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.multipart.parse_options_header", raising=False) with pytest.raises(RuntimeError, match=multipart_incorrect_install_error): app = FastAPI() @@ -57,50 +74,76 @@ def test_incorrect_multipart_installed_form_file(monkeypatch): def test_no_multipart_installed(monkeypatch): - monkeypatch.delattr("multipart.__version__", raising=False) - with pytest.raises(RuntimeError, match=multipart_not_installed_error): + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.__version__", raising=False) + with pytest.raises(RuntimeError, match=multipart_not_installed_error): + app = FastAPI() + + @app.post("/") + async def root(username: str = Form()): + return username # pragma: nocover + + +def test_no_multipart_installed_file(monkeypatch): + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.__version__", raising=False) + with pytest.raises(RuntimeError, match=multipart_not_installed_error): + app = FastAPI() + + @app.post("/") + async def root(f: UploadFile = File()): + return f # pragma: nocover + + +def test_no_multipart_installed_file_bytes(monkeypatch): + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.__version__", raising=False) + with pytest.raises(RuntimeError, match=multipart_not_installed_error): + app = FastAPI() + + @app.post("/") + async def root(f: bytes = File()): + return f # pragma: nocover + + +def test_no_multipart_installed_multi_form(monkeypatch): + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.__version__", raising=False) + with pytest.raises(RuntimeError, match=multipart_not_installed_error): + app = FastAPI() + + @app.post("/") + async def root(username: str = Form(), password: str = Form()): + return username # pragma: nocover + + +def test_no_multipart_installed_form_file(monkeypatch): + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + monkeypatch.delattr("multipart.__version__", raising=False) + with pytest.raises(RuntimeError, match=multipart_not_installed_error): + app = FastAPI() + + @app.post("/") + async def root(username: str = Form(), f: UploadFile = File()): + return username # pragma: nocover + + +def test_old_multipart_installed(monkeypatch): + monkeypatch.setattr("python_multipart.__version__", "0.0.12") + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") app = FastAPI() @app.post("/") async def root(username: str = Form()): return username # pragma: nocover - - -def test_no_multipart_installed_file(monkeypatch): - monkeypatch.delattr("multipart.__version__", raising=False) - with pytest.raises(RuntimeError, match=multipart_not_installed_error): - app = FastAPI() - - @app.post("/") - async def root(f: UploadFile = File()): - return f # pragma: nocover - - -def test_no_multipart_installed_file_bytes(monkeypatch): - monkeypatch.delattr("multipart.__version__", raising=False) - with pytest.raises(RuntimeError, match=multipart_not_installed_error): - app = FastAPI() - - @app.post("/") - async def root(f: bytes = File()): - return f # pragma: nocover - - -def test_no_multipart_installed_multi_form(monkeypatch): - monkeypatch.delattr("multipart.__version__", raising=False) - with pytest.raises(RuntimeError, match=multipart_not_installed_error): - app = FastAPI() - - @app.post("/") - async def root(username: str = Form(), password: str = Form()): - return username # pragma: nocover - - -def test_no_multipart_installed_form_file(monkeypatch): - monkeypatch.delattr("multipart.__version__", raising=False) - with pytest.raises(RuntimeError, match=multipart_not_installed_error): - app = FastAPI() - - @app.post("/") - async def root(username: str = Form(), f: UploadFile = File()): - return username # pragma: nocover diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py index 6597e5058b..b3f83ae237 100644 --- a/tests/test_openapi_examples.py +++ b/tests/test_openapi_examples.py @@ -155,13 +155,26 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": { - "allOf": [{"$ref": "#/components/schemas/Item"}], - "title": "Item", - "examples": [ - {"data": "Data in Body examples, example1"} - ], - }, + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "Data in Body examples, example1"} + ], + } + ) + | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + {"data": "Data in Body examples, example1"} + ], + } + ), "examples": { "Example One": { "summary": "Example One Summary", diff --git a/tests/test_openapi_model_description_trim_on_formfeed.py b/tests/test_openapi_model_description_trim_on_formfeed.py new file mode 100644 index 0000000000..e18d4f6b29 --- /dev/null +++ b/tests/test_openapi_model_description_trim_on_formfeed.py @@ -0,0 +1,31 @@ +from fastapi import FastAPI +from fastapi.testclient import TestClient +from pydantic import BaseModel + +app = FastAPI() + + +class MyModel(BaseModel): + """ + A model with a form feed character in the title. + \f + Text after form feed character. + """ + + +@app.get("/foo") +def foo(v: MyModel): # pragma: no cover + pass + + +client = TestClient(app) + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + openapi_schema = response.json() + + assert openapi_schema["components"]["schemas"]["MyModel"]["description"] == ( + "A model with a form feed character in the title.\n" + ) diff --git a/tests/test_openapi_schema_type.py b/tests/test_openapi_schema_type.py new file mode 100644 index 0000000000..a45ea20c8c --- /dev/null +++ b/tests/test_openapi_schema_type.py @@ -0,0 +1,26 @@ +from typing import List, Optional, Union + +import pytest +from fastapi.openapi.models import Schema, SchemaType + + +@pytest.mark.parametrize( + "type_value", + [ + "array", + ["string", "null"], + None, + ], +) +def test_allowed_schema_type( + type_value: Optional[Union[SchemaType, List[SchemaType]]], +) -> None: + """Test that Schema accepts SchemaType, List[SchemaType] and None for type field.""" + schema = Schema(type=type_value) + assert schema.type == type_value + + +def test_invalid_type_value() -> None: + """Test that Schema raises ValueError for invalid type values.""" + with pytest.raises(ValueError, match="2 validation errors for Schema"): + Schema(type=True) # type: ignore[arg-type] diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py index aeb85f735b..fa73620eae 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -2,6 +2,7 @@ from typing import List, Optional from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel from .utils import PYDANTIC_V2, needs_pydanticv2 @@ -26,8 +27,8 @@ class Item(BaseModel): def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) - @app.post("/items/") - def create_item(item: Item): + @app.post("/items/", responses={402: {"model": Item}}) + def create_item(item: Item) -> Item: return item @app.post("/items-list/") @@ -135,201 +136,223 @@ def test_openapi_schema(): client = get_app_client() response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item-Output" + }, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item-Input" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item-Output" + } + } + }, + }, + "402": { + "description": "Payment Required", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item-Output" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/items-list/": { + "post": { + "summary": "Create Item List", + "operationId": "create_item_list_items_list__post", + "requestBody": { "content": { "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/Item-Output" + "$ref": "#/components/schemas/Item-Input" }, "type": "array", - "title": "Response Read Items Items Get", + "title": "Item", } } }, - } - }, - }, - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item-Input"} - } + "required": True, }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, + } }, }, - "/items-list/": { - "post": { - "summary": "Create Item List", - "operationId": "create_item_list_items_list__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item-Input" - }, - "type": "array", - "title": "Item", - } + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", } }, - "required": True, + "type": "object", + "title": "HTTPValidationError", }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "Item-Input": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "sub": { + "anyOf": [ + {"$ref": "#/components/schemas/SubItem-Input"}, + {"type": "null"}, + ] }, }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "Item-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "sub": { + "anyOf": [ + {"$ref": "#/components/schemas/SubItem-Output"}, + {"type": "null"}, + ] + }, + }, + "type": "object", + "required": ["name", "description", "sub"], + "title": "Item", + }, + "SubItem-Input": { + "properties": { + "subname": {"type": "string", "title": "Subname"}, + "sub_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Sub Description", + }, + "tags": { + "items": {"type": "string"}, + "type": "array", + "title": "Tags", + "default": [], + }, + }, + "type": "object", + "required": ["subname"], + "title": "SubItem", + }, + "SubItem-Output": { + "properties": { + "subname": {"type": "string", "title": "Subname"}, + "sub_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Sub Description", + }, + "tags": { + "items": {"type": "string"}, + "type": "array", + "title": "Tags", + "default": [], + }, + }, + "type": "object", + "required": ["subname", "sub_description", "tags"], + "title": "SubItem", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item-Input": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "sub": { - "anyOf": [ - {"$ref": "#/components/schemas/SubItem-Input"}, - {"type": "null"}, - ] - }, - }, - "type": "object", - "required": ["name"], - "title": "Item", - }, - "Item-Output": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "sub": { - "anyOf": [ - {"$ref": "#/components/schemas/SubItem-Output"}, - {"type": "null"}, - ] - }, - }, - "type": "object", - "required": ["name", "description", "sub"], - "title": "Item", - }, - "SubItem-Input": { - "properties": { - "subname": {"type": "string", "title": "Subname"}, - "sub_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Sub Description", - }, - "tags": { - "items": {"type": "string"}, - "type": "array", - "title": "Tags", - "default": [], - }, - }, - "type": "object", - "required": ["subname"], - "title": "SubItem", - }, - "SubItem-Output": { - "properties": { - "subname": {"type": "string", "title": "Subname"}, - "sub_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Sub Description", - }, - "tags": { - "items": {"type": "string"}, - "type": "array", - "title": "Tags", - "default": [], - }, - }, - "type": "object", - "required": ["subname", "sub_description", "tags"], - "title": "SubItem", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + } + ) @needs_pydanticv2 @@ -374,7 +397,19 @@ def test_openapi_schema_no_separate(): "responses": { "200": { "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "402": { + "description": "Payment Required", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, }, "422": { "description": "Validation Error", diff --git a/tests/test_pydantic_v1_v2_01.py b/tests/test_pydantic_v1_v2_01.py new file mode 100644 index 0000000000..769e5fab62 --- /dev/null +++ b/tests/test_pydantic_v1_v2_01.py @@ -0,0 +1,475 @@ +import sys +from typing import Any, List, Union + +from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi import FastAPI +from fastapi._compat.v1 import BaseModel +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + + +class SubItem(BaseModel): + name: str + + +class Item(BaseModel): + title: str + size: int + description: Union[str, None] = None + sub: SubItem + multi: List[SubItem] = [] + + +app = FastAPI() + + +@app.post("/simple-model") +def handle_simple_model(data: SubItem) -> SubItem: + return data + + +@app.post("/simple-model-filter", response_model=SubItem) +def handle_simple_model_filter(data: SubItem) -> Any: + extended_data = data.dict() + extended_data.update({"secret_price": 42}) + return extended_data + + +@app.post("/item") +def handle_item(data: Item) -> Item: + return data + + +@app.post("/item-filter", response_model=Item) +def handle_item_filter(data: Item) -> Any: + extended_data = data.dict() + extended_data.update({"secret_data": "classified", "internal_id": 12345}) + extended_data["sub"].update({"internal_id": 67890}) + return extended_data + + +client = TestClient(app) + + +def test_old_simple_model(): + response = client.post( + "/simple-model", + json={"name": "Foo"}, + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo"} + + +def test_old_simple_model_validation_error(): + response = client.post( + "/simple-model", + json={"wrong_name": "Foo"}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "name"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +def test_old_simple_model_filter(): + response = client.post( + "/simple-model-filter", + json={"name": "Foo"}, + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo"} + + +def test_item_model(): + response = client.post( + "/item", + json={ + "title": "Test Item", + "size": 100, + "description": "This is a test item", + "sub": {"name": "SubItem1"}, + "multi": [{"name": "Multi1"}, {"name": "Multi2"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "Test Item", + "size": 100, + "description": "This is a test item", + "sub": {"name": "SubItem1"}, + "multi": [{"name": "Multi1"}, {"name": "Multi2"}], + } + + +def test_item_model_minimal(): + response = client.post( + "/item", + json={"title": "Minimal Item", "size": 50, "sub": {"name": "SubMin"}}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "Minimal Item", + "size": 50, + "description": None, + "sub": {"name": "SubMin"}, + "multi": [], + } + + +def test_item_model_validation_errors(): + response = client.post( + "/item", + json={"title": "Missing fields"}, + ) + assert response.status_code == 422, response.text + error_detail = response.json()["detail"] + assert len(error_detail) == 2 + assert { + "loc": ["body", "size"], + "msg": "field required", + "type": "value_error.missing", + } in error_detail + assert { + "loc": ["body", "sub"], + "msg": "field required", + "type": "value_error.missing", + } in error_detail + + +def test_item_model_nested_validation_error(): + response = client.post( + "/item", + json={"title": "Test Item", "size": 100, "sub": {"wrong_field": "test"}}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "sub", "name"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +def test_item_model_invalid_type(): + response = client.post( + "/item", + json={"title": "Test Item", "size": "not_a_number", "sub": {"name": "SubItem"}}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "size"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ] + } + ) + + +def test_item_filter(): + response = client.post( + "/item-filter", + json={ + "title": "Filtered Item", + "size": 200, + "description": "Test filtering", + "sub": {"name": "SubFiltered"}, + "multi": [], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert result == { + "title": "Filtered Item", + "size": 200, + "description": "Test filtering", + "sub": {"name": "SubFiltered"}, + "multi": [], + } + assert "secret_data" not in result + assert "internal_id" not in result + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/simple-model": { + "post": { + "summary": "Handle Simple Model", + "operationId": "handle_simple_model_simple_model_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/SubItem" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/SubItem"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubItem" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/simple-model-filter": { + "post": { + "summary": "Handle Simple Model Filter", + "operationId": "handle_simple_model_filter_simple_model_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/SubItem" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/SubItem"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubItem" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/item": { + "post": { + "summary": "Handle Item", + "operationId": "handle_item_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "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" + } + } + }, + }, + }, + } + }, + "/item-filter": { + "post": { + "summary": "Handle Item Filter", + "operationId": "handle_item_filter_item_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "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" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "title": {"type": "string", "title": "Title"}, + "size": {"type": "integer", "title": "Size"}, + "description": {"type": "string", "title": "Description"}, + "sub": {"$ref": "#/components/schemas/SubItem"}, + "multi": { + "items": {"$ref": "#/components/schemas/SubItem"}, + "type": "array", + "title": "Multi", + "default": [], + }, + }, + "type": "object", + "required": ["title", "size", "sub"], + "title": "Item", + }, + "SubItem": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "SubItem", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_pydantic_v1_v2_list.py b/tests/test_pydantic_v1_v2_list.py new file mode 100644 index 0000000000..64f3dd3446 --- /dev/null +++ b/tests/test_pydantic_v1_v2_list.py @@ -0,0 +1,701 @@ +import sys +from typing import Any, List, Union + +from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi import FastAPI +from fastapi._compat.v1 import BaseModel +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + + +class SubItem(BaseModel): + name: str + + +class Item(BaseModel): + title: str + size: int + description: Union[str, None] = None + sub: SubItem + multi: List[SubItem] = [] + + +app = FastAPI() + + +@app.post("/item") +def handle_item(data: Item) -> List[Item]: + return [data, data] + + +@app.post("/item-filter", response_model=List[Item]) +def handle_item_filter(data: Item) -> Any: + extended_data = data.dict() + extended_data.update({"secret_data": "classified", "internal_id": 12345}) + extended_data["sub"].update({"internal_id": 67890}) + return [extended_data, extended_data] + + +@app.post("/item-list") +def handle_item_list(data: List[Item]) -> Item: + if data: + return data[0] + return Item(title="", size=0, sub=SubItem(name="")) + + +@app.post("/item-list-filter", response_model=Item) +def handle_item_list_filter(data: List[Item]) -> Any: + if data: + extended_data = data[0].dict() + extended_data.update({"secret_data": "classified", "internal_id": 12345}) + extended_data["sub"].update({"internal_id": 67890}) + return extended_data + return Item(title="", size=0, sub=SubItem(name="")) + + +@app.post("/item-list-to-list") +def handle_item_list_to_list(data: List[Item]) -> List[Item]: + return data + + +@app.post("/item-list-to-list-filter", response_model=List[Item]) +def handle_item_list_to_list_filter(data: List[Item]) -> Any: + if data: + extended_data = data[0].dict() + extended_data.update({"secret_data": "classified", "internal_id": 12345}) + extended_data["sub"].update({"internal_id": 67890}) + return [extended_data, extended_data] + return [] + + +client = TestClient(app) + + +def test_item_to_list(): + response = client.post( + "/item", + json={ + "title": "Test Item", + "size": 100, + "description": "This is a test item", + "sub": {"name": "SubItem1"}, + "multi": [{"name": "Multi1"}, {"name": "Multi2"}], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert isinstance(result, list) + assert len(result) == 2 + for item in result: + assert item == { + "title": "Test Item", + "size": 100, + "description": "This is a test item", + "sub": {"name": "SubItem1"}, + "multi": [{"name": "Multi1"}, {"name": "Multi2"}], + } + + +def test_item_to_list_filter(): + response = client.post( + "/item-filter", + json={ + "title": "Filtered Item", + "size": 200, + "description": "Test filtering", + "sub": {"name": "SubFiltered"}, + "multi": [], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert isinstance(result, list) + assert len(result) == 2 + for item in result: + assert item == { + "title": "Filtered Item", + "size": 200, + "description": "Test filtering", + "sub": {"name": "SubFiltered"}, + "multi": [], + } + # Verify secret fields are filtered out + assert "secret_data" not in item + assert "internal_id" not in item + assert "internal_id" not in item["sub"] + + +def test_list_to_item(): + response = client.post( + "/item-list", + json=[ + {"title": "First Item", "size": 50, "sub": {"name": "First Sub"}}, + {"title": "Second Item", "size": 75, "sub": {"name": "Second Sub"}}, + ], + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "First Item", + "size": 50, + "description": None, + "sub": {"name": "First Sub"}, + "multi": [], + } + + +def test_list_to_item_empty(): + response = client.post( + "/item-list", + json=[], + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "", + "size": 0, + "description": None, + "sub": {"name": ""}, + "multi": [], + } + + +def test_list_to_item_filter(): + response = client.post( + "/item-list-filter", + json=[ + { + "title": "First Item", + "size": 100, + "sub": {"name": "First Sub"}, + "multi": [{"name": "Multi1"}], + }, + {"title": "Second Item", "size": 200, "sub": {"name": "Second Sub"}}, + ], + ) + assert response.status_code == 200, response.text + result = response.json() + assert result == { + "title": "First Item", + "size": 100, + "description": None, + "sub": {"name": "First Sub"}, + "multi": [{"name": "Multi1"}], + } + # Verify secret fields are filtered out + assert "secret_data" not in result + assert "internal_id" not in result + + +def test_list_to_item_filter_no_data(): + response = client.post("/item-list-filter", json=[]) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "", + "size": 0, + "description": None, + "sub": {"name": ""}, + "multi": [], + } + + +def test_list_to_list(): + input_items = [ + {"title": "Item 1", "size": 10, "sub": {"name": "Sub1"}}, + { + "title": "Item 2", + "size": 20, + "description": "Second item", + "sub": {"name": "Sub2"}, + "multi": [{"name": "M1"}, {"name": "M2"}], + }, + {"title": "Item 3", "size": 30, "sub": {"name": "Sub3"}}, + ] + response = client.post( + "/item-list-to-list", + json=input_items, + ) + assert response.status_code == 200, response.text + result = response.json() + assert isinstance(result, list) + assert len(result) == 3 + assert result[0] == { + "title": "Item 1", + "size": 10, + "description": None, + "sub": {"name": "Sub1"}, + "multi": [], + } + assert result[1] == { + "title": "Item 2", + "size": 20, + "description": "Second item", + "sub": {"name": "Sub2"}, + "multi": [{"name": "M1"}, {"name": "M2"}], + } + assert result[2] == { + "title": "Item 3", + "size": 30, + "description": None, + "sub": {"name": "Sub3"}, + "multi": [], + } + + +def test_list_to_list_filter(): + response = client.post( + "/item-list-to-list-filter", + json=[{"title": "Item 1", "size": 100, "sub": {"name": "Sub1"}}], + ) + assert response.status_code == 200, response.text + result = response.json() + assert isinstance(result, list) + assert len(result) == 2 + for item in result: + assert item == { + "title": "Item 1", + "size": 100, + "description": None, + "sub": {"name": "Sub1"}, + "multi": [], + } + # Verify secret fields are filtered out + assert "secret_data" not in item + assert "internal_id" not in item + + +def test_list_to_list_filter_no_data(): + response = client.post( + "/item-list-to-list-filter", + json=[], + ) + assert response.status_code == 200, response.text + assert response.json() == [] + + +def test_list_validation_error(): + response = client.post( + "/item-list", + json=[ + {"title": "Valid Item", "size": 100, "sub": {"name": "Sub1"}}, + { + "title": "Invalid Item" + # Missing required fields: size and sub + }, + ], + ) + assert response.status_code == 422, response.text + error_detail = response.json()["detail"] + assert len(error_detail) == 2 + assert { + "loc": ["body", 1, "size"], + "msg": "field required", + "type": "value_error.missing", + } in error_detail + assert { + "loc": ["body", 1, "sub"], + "msg": "field required", + "type": "value_error.missing", + } in error_detail + + +def test_list_nested_validation_error(): + response = client.post( + "/item-list", + json=[ + {"title": "Item with bad sub", "size": 100, "sub": {"wrong_field": "value"}} + ], + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", 0, "sub", "name"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +def test_list_type_validation_error(): + response = client.post( + "/item-list", + json=[{"title": "Item", "size": "not_a_number", "sub": {"name": "Sub"}}], + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", 0, "size"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ] + } + ) + + +def test_invalid_list_structure(): + response = client.post( + "/item-list", + json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body"], + "msg": "value is not a valid list", + "type": "type_error.list", + } + ] + } + ) + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/item": { + "post": { + "summary": "Handle Item", + "operationId": "handle_item_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Handle Item Item Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/item-filter": { + "post": { + "summary": "Handle Item Filter", + "operationId": "handle_item_filter_item_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Handle Item Filter Item Filter Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/item-list": { + "post": { + "summary": "Handle Item List", + "operationId": "handle_item_list_item_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Data", + } + } + }, + "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" + } + } + }, + }, + }, + } + }, + "/item-list-filter": { + "post": { + "summary": "Handle Item List Filter", + "operationId": "handle_item_list_filter_item_list_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Data", + } + } + }, + "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" + } + } + }, + }, + }, + } + }, + "/item-list-to-list": { + "post": { + "summary": "Handle Item List To List", + "operationId": "handle_item_list_to_list_item_list_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Handle Item List To List Item List To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/item-list-to-list-filter": { + "post": { + "summary": "Handle Item List To List Filter", + "operationId": "handle_item_list_to_list_filter_item_list_to_list_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Handle Item List To List Filter Item List To List Filter Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "title": {"type": "string", "title": "Title"}, + "size": {"type": "integer", "title": "Size"}, + "description": {"type": "string", "title": "Description"}, + "sub": {"$ref": "#/components/schemas/SubItem"}, + "multi": { + "items": {"$ref": "#/components/schemas/SubItem"}, + "type": "array", + "title": "Multi", + "default": [], + }, + }, + "type": "object", + "required": ["title", "size", "sub"], + "title": "Item", + }, + "SubItem": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "SubItem", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_pydantic_v1_v2_mixed.py b/tests/test_pydantic_v1_v2_mixed.py new file mode 100644 index 0000000000..54d408827f --- /dev/null +++ b/tests/test_pydantic_v1_v2_mixed.py @@ -0,0 +1,1499 @@ +import sys +from typing import Any, List, Union + +from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi import FastAPI +from fastapi._compat.v1 import BaseModel +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from pydantic import BaseModel as NewBaseModel + + +class SubItem(BaseModel): + name: str + + +class Item(BaseModel): + title: str + size: int + description: Union[str, None] = None + sub: SubItem + multi: List[SubItem] = [] + + +class NewSubItem(NewBaseModel): + new_sub_name: str + + +class NewItem(NewBaseModel): + new_title: str + new_size: int + new_description: Union[str, None] = None + new_sub: NewSubItem + new_multi: List[NewSubItem] = [] + + +app = FastAPI() + + +@app.post("/v1-to-v2/item") +def handle_v1_item_to_v2(data: Item) -> NewItem: + return NewItem( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=NewSubItem(new_sub_name=data.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], + ) + + +@app.post("/v1-to-v2/item-filter", response_model=NewItem) +def handle_v1_item_to_v2_filter(data: Item) -> Any: + result = { + "new_title": data.title, + "new_size": data.size, + "new_description": data.description, + "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"}, + "new_multi": [ + {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi + ], + "secret": "hidden_v1_to_v2", + } + return result + + +@app.post("/v2-to-v1/item") +def handle_v2_item_to_v1(data: NewItem) -> Item: + return Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=SubItem(name=data.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], + ) + + +@app.post("/v2-to-v1/item-filter", response_model=Item) +def handle_v2_item_to_v1_filter(data: NewItem) -> Any: + result = { + "title": data.new_title, + "size": data.new_size, + "description": data.new_description, + "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, + "multi": [ + {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi + ], + "secret": "hidden_v2_to_v1", + } + return result + + +@app.post("/v1-to-v2/item-to-list") +def handle_v1_item_to_v2_list(data: Item) -> List[NewItem]: + converted = NewItem( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=NewSubItem(new_sub_name=data.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], + ) + return [converted, converted] + + +@app.post("/v1-to-v2/list-to-list") +def handle_v1_list_to_v2_list(data: List[Item]) -> List[NewItem]: + result = [] + for item in data: + result.append( + NewItem( + new_title=item.title, + new_size=item.size, + new_description=item.description, + new_sub=NewSubItem(new_sub_name=item.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi], + ) + ) + return result + + +@app.post("/v1-to-v2/list-to-list-filter", response_model=List[NewItem]) +def handle_v1_list_to_v2_list_filter(data: List[Item]) -> Any: + result = [] + for item in data: + converted = { + "new_title": item.title, + "new_size": item.size, + "new_description": item.description, + "new_sub": {"new_sub_name": item.sub.name, "new_sub_secret": "sub_hidden"}, + "new_multi": [ + {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} + for s in item.multi + ], + "secret": "hidden_v2_to_v1", + } + result.append(converted) + return result + + +@app.post("/v1-to-v2/list-to-item") +def handle_v1_list_to_v2_item(data: List[Item]) -> NewItem: + if data: + item = data[0] + return NewItem( + new_title=item.title, + new_size=item.size, + new_description=item.description, + new_sub=NewSubItem(new_sub_name=item.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi], + ) + return NewItem(new_title="", new_size=0, new_sub=NewSubItem(new_sub_name="")) + + +@app.post("/v2-to-v1/item-to-list") +def handle_v2_item_to_v1_list(data: NewItem) -> List[Item]: + converted = Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=SubItem(name=data.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], + ) + return [converted, converted] + + +@app.post("/v2-to-v1/list-to-list") +def handle_v2_list_to_v1_list(data: List[NewItem]) -> List[Item]: + result = [] + for item in data: + result.append( + Item( + title=item.new_title, + size=item.new_size, + description=item.new_description, + sub=SubItem(name=item.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in item.new_multi], + ) + ) + return result + + +@app.post("/v2-to-v1/list-to-list-filter", response_model=List[Item]) +def handle_v2_list_to_v1_list_filter(data: List[NewItem]) -> Any: + result = [] + for item in data: + converted = { + "title": item.new_title, + "size": item.new_size, + "description": item.new_description, + "sub": {"name": item.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, + "multi": [ + {"name": s.new_sub_name, "sub_secret": "sub_hidden"} + for s in item.new_multi + ], + "secret": "hidden_v2_to_v1", + } + result.append(converted) + return result + + +@app.post("/v2-to-v1/list-to-item") +def handle_v2_list_to_v1_item(data: List[NewItem]) -> Item: + if data: + item = data[0] + return Item( + title=item.new_title, + size=item.new_size, + description=item.new_description, + sub=SubItem(name=item.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in item.new_multi], + ) + return Item(title="", size=0, sub=SubItem(name="")) + + +client = TestClient(app) + + +def test_v1_to_v2_item(): + response = client.post( + "/v1-to-v2/item", + json={ + "title": "Old Item", + "size": 100, + "description": "V1 description", + "sub": {"name": "V1 Sub"}, + "multi": [{"name": "M1"}, {"name": "M2"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "new_title": "Old Item", + "new_size": 100, + "new_description": "V1 description", + "new_sub": {"new_sub_name": "V1 Sub"}, + "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], + } + + +def test_v1_to_v2_item_minimal(): + response = client.post( + "/v1-to-v2/item", + json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}}, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "new_title": "Minimal", + "new_size": 50, + "new_description": None, + "new_sub": {"new_sub_name": "MinSub"}, + "new_multi": [], + } + + +def test_v1_to_v2_item_filter(): + response = client.post( + "/v1-to-v2/item-filter", + json={ + "title": "Filtered Item", + "size": 50, + "sub": {"name": "Sub"}, + "multi": [{"name": "Multi1"}], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert result == snapshot( + { + "new_title": "Filtered Item", + "new_size": 50, + "new_description": None, + "new_sub": {"new_sub_name": "Sub"}, + "new_multi": [{"new_sub_name": "Multi1"}], + } + ) + # Verify secret fields are filtered out + assert "secret" not in result + assert "new_sub_secret" not in result["new_sub"] + assert "new_sub_secret" not in result["new_multi"][0] + + +def test_v2_to_v1_item(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "New Item", + "new_size": 200, + "new_description": "V2 description", + "new_sub": {"new_sub_name": "V2 Sub"}, + "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "New Item", + "size": 200, + "description": "V2 description", + "sub": {"name": "V2 Sub"}, + "multi": [{"name": "N1"}, {"name": "N2"}], + } + + +def test_v2_to_v1_item_minimal(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "MinimalNew", + "new_size": 75, + "new_sub": {"new_sub_name": "MinNewSub"}, + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "MinimalNew", + "size": 75, + "description": None, + "sub": {"name": "MinNewSub"}, + "multi": [], + } + + +def test_v2_to_v1_item_filter(): + response = client.post( + "/v2-to-v1/item-filter", + json={ + "new_title": "Filtered New", + "new_size": 75, + "new_sub": {"new_sub_name": "NewSub"}, + "new_multi": [], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert result == snapshot( + { + "title": "Filtered New", + "size": 75, + "description": None, + "sub": {"name": "NewSub"}, + "multi": [], + } + ) + # Verify secret fields are filtered out + assert "secret" not in result + assert "sub_secret" not in result["sub"] + + +def test_v1_item_to_v2_list(): + response = client.post( + "/v1-to-v2/item-to-list", + json={ + "title": "Single to List", + "size": 150, + "description": "Convert to list", + "sub": {"name": "Sub1"}, + "multi": [], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert result == [ + { + "new_title": "Single to List", + "new_size": 150, + "new_description": "Convert to list", + "new_sub": {"new_sub_name": "Sub1"}, + "new_multi": [], + }, + { + "new_title": "Single to List", + "new_size": 150, + "new_description": "Convert to list", + "new_sub": {"new_sub_name": "Sub1"}, + "new_multi": [], + }, + ] + + +def test_v1_list_to_v2_list(): + response = client.post( + "/v1-to-v2/list-to-list", + json=[ + {"title": "Item1", "size": 10, "sub": {"name": "Sub1"}}, + { + "title": "Item2", + "size": 20, + "description": "Second item", + "sub": {"name": "Sub2"}, + "multi": [{"name": "M1"}, {"name": "M2"}], + }, + {"title": "Item3", "size": 30, "sub": {"name": "Sub3"}}, + ], + ) + assert response.status_code == 200, response.text + assert response.json() == [ + { + "new_title": "Item1", + "new_size": 10, + "new_description": None, + "new_sub": {"new_sub_name": "Sub1"}, + "new_multi": [], + }, + { + "new_title": "Item2", + "new_size": 20, + "new_description": "Second item", + "new_sub": {"new_sub_name": "Sub2"}, + "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], + }, + { + "new_title": "Item3", + "new_size": 30, + "new_description": None, + "new_sub": {"new_sub_name": "Sub3"}, + "new_multi": [], + }, + ] + + +def test_v1_list_to_v2_list_filter(): + response = client.post( + "/v1-to-v2/list-to-list-filter", + json=[{"title": "FilterMe", "size": 30, "sub": {"name": "SubF"}}], + ) + assert response.status_code == 200, response.text + result = response.json() + assert result == snapshot( + [ + { + "new_title": "FilterMe", + "new_size": 30, + "new_description": None, + "new_sub": {"new_sub_name": "SubF"}, + "new_multi": [], + } + ] + ) + # Verify secret fields are filtered out + assert "secret" not in result[0] + assert "new_sub_secret" not in result[0]["new_sub"] + + +def test_v1_list_to_v2_item(): + response = client.post( + "/v1-to-v2/list-to-item", + json=[ + {"title": "First", "size": 100, "sub": {"name": "FirstSub"}}, + {"title": "Second", "size": 200, "sub": {"name": "SecondSub"}}, + ], + ) + assert response.status_code == 200, response.text + assert response.json() == { + "new_title": "First", + "new_size": 100, + "new_description": None, + "new_sub": {"new_sub_name": "FirstSub"}, + "new_multi": [], + } + + +def test_v1_list_to_v2_item_empty(): + response = client.post("/v1-to-v2/list-to-item", json=[]) + assert response.status_code == 200, response.text + assert response.json() == { + "new_title": "", + "new_size": 0, + "new_description": None, + "new_sub": {"new_sub_name": ""}, + "new_multi": [], + } + + +def test_v2_item_to_v1_list(): + response = client.post( + "/v2-to-v1/item-to-list", + json={ + "new_title": "Single New", + "new_size": 250, + "new_description": "New to list", + "new_sub": {"new_sub_name": "NewSub"}, + "new_multi": [], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [ + { + "title": "Single New", + "size": 250, + "description": "New to list", + "sub": {"name": "NewSub"}, + "multi": [], + }, + { + "title": "Single New", + "size": 250, + "description": "New to list", + "sub": {"name": "NewSub"}, + "multi": [], + }, + ] + + +def test_v2_list_to_v1_list(): + response = client.post( + "/v2-to-v1/list-to-list", + json=[ + {"new_title": "New1", "new_size": 15, "new_sub": {"new_sub_name": "NS1"}}, + { + "new_title": "New2", + "new_size": 25, + "new_description": "Second new", + "new_sub": {"new_sub_name": "NS2"}, + "new_multi": [{"new_sub_name": "NM1"}], + }, + ], + ) + assert response.status_code == 200, response.text + assert response.json() == [ + { + "title": "New1", + "size": 15, + "description": None, + "sub": {"name": "NS1"}, + "multi": [], + }, + { + "title": "New2", + "size": 25, + "description": "Second new", + "sub": {"name": "NS2"}, + "multi": [{"name": "NM1"}], + }, + ] + + +def test_v2_list_to_v1_list_filter(): + response = client.post( + "/v2-to-v1/list-to-list-filter", + json=[ + { + "new_title": "FilterNew", + "new_size": 35, + "new_sub": {"new_sub_name": "NSF"}, + } + ], + ) + assert response.status_code == 200, response.text + result = response.json() + assert result == snapshot( + [ + { + "title": "FilterNew", + "size": 35, + "description": None, + "sub": {"name": "NSF"}, + "multi": [], + } + ] + ) + # Verify secret fields are filtered out + assert "secret" not in result[0] + assert "sub_secret" not in result[0]["sub"] + + +def test_v2_list_to_v1_item(): + response = client.post( + "/v2-to-v1/list-to-item", + json=[ + { + "new_title": "FirstNew", + "new_size": 300, + "new_sub": {"new_sub_name": "FNS"}, + }, + { + "new_title": "SecondNew", + "new_size": 400, + "new_sub": {"new_sub_name": "SNS"}, + }, + ], + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "FirstNew", + "size": 300, + "description": None, + "sub": {"name": "FNS"}, + "multi": [], + } + + +def test_v2_list_to_v1_item_empty(): + response = client.post("/v2-to-v1/list-to-item", json=[]) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "", + "size": 0, + "description": None, + "sub": {"name": ""}, + "multi": [], + } + + +def test_v1_to_v2_validation_error(): + response = client.post("/v1-to-v2/item", json={"title": "Missing fields"}) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "size"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "sub"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + ) + + +def test_v1_to_v2_nested_validation_error(): + response = client.post( + "/v1-to-v2/item", + json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "sub", "name"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +def test_v1_to_v2_type_validation_error(): + response = client.post( + "/v1-to-v2/item", + json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "size"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ] + } + ) + + +def test_v2_to_v1_validation_error(): + response = client.post( + "/v2-to-v1/item", + json={"new_title": "Missing fields"}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": pydantic_snapshot( + v2=snapshot( + [ + { + "type": "missing", + "loc": ["body", "new_size"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + { + "type": "missing", + "loc": ["body", "new_sub"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + ] + ), + v1=snapshot( + [ + { + "loc": ["body", "new_size"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "new_sub"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + ), + ) + } + ) + + +def test_v2_to_v1_nested_validation_error(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "Bad sub", + "new_size": 200, + "new_sub": {"wrong_field": "value"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + pydantic_snapshot( + v2=snapshot( + { + "type": "missing", + "loc": ["body", "new_sub", "new_sub_name"], + "msg": "Field required", + "input": {"wrong_field": "value"}, + } + ), + v1=snapshot( + { + "loc": ["body", "new_sub", "new_sub_name"], + "msg": "field required", + "type": "value_error.missing", + } + ), + ) + ] + } + ) + + +def test_v1_list_validation_error(): + response = client.post( + "/v1-to-v2/list-to-list", + json=[ + {"title": "Valid", "size": 10, "sub": {"name": "S"}}, + {"title": "Invalid"}, + ], + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", 1, "size"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", 1, "sub"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + ) + + +def test_v2_list_validation_error(): + response = client.post( + "/v2-to-v1/list-to-list", + json=[ + {"new_title": "Valid", "new_size": 10, "new_sub": {"new_sub_name": "NS"}}, + {"new_title": "Invalid"}, + ], + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": pydantic_snapshot( + v2=snapshot( + [ + { + "type": "missing", + "loc": ["body", 1, "new_size"], + "msg": "Field required", + "input": {"new_title": "Invalid"}, + }, + { + "type": "missing", + "loc": ["body", 1, "new_sub"], + "msg": "Field required", + "input": {"new_title": "Invalid"}, + }, + ] + ), + v1=snapshot( + [ + { + "loc": ["body", 1, "new_size"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", 1, "new_sub"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + ), + ) + } + ) + + +def test_invalid_list_structure_v1(): + response = client.post( + "/v1-to-v2/list-to-list", + json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}}, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body"], + "msg": "value is not a valid list", + "type": "type_error.list", + } + ] + } + ) + + +def test_invalid_list_structure_v2(): + response = client.post( + "/v2-to-v1/list-to-list", + json={ + "new_title": "Not a list", + "new_size": 100, + "new_sub": {"new_sub_name": "Sub"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": pydantic_snapshot( + v2=snapshot( + [ + { + "type": "list_type", + "loc": ["body"], + "msg": "Input should be a valid list", + "input": { + "new_title": "Not a list", + "new_size": 100, + "new_sub": {"new_sub_name": "Sub"}, + }, + } + ] + ), + v1=snapshot( + [ + { + "loc": ["body"], + "msg": "value is not a valid list", + "type": "type_error.list", + } + ] + ), + ) + } + ) + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/v1-to-v2/item": { + "post": { + "summary": "Handle V1 Item To V2", + "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewItem" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/item-filter": { + "post": { + "summary": "Handle V1 Item To V2 Filter", + "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewItem" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/item": { + "post": { + "summary": "Handle V2 Item To V1", + "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/NewItem"} + } + }, + "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" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/item-filter": { + "post": { + "summary": "Handle V2 Item To V1 Filter", + "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/NewItem"} + } + }, + "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" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/item-to-list": { + "post": { + "summary": "Handle V1 Item To V2 List", + "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/NewItem" + }, + "type": "array", + "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/list-to-list": { + "post": { + "summary": "Handle V1 List To V2 List", + "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/NewItem" + }, + "type": "array", + "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/list-to-list-filter": { + "post": { + "summary": "Handle V1 List To V2 List Filter", + "operationId": "handle_v1_list_to_v2_list_filter_v1_to_v2_list_to_list_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/NewItem" + }, + "type": "array", + "title": "Response Handle V1 List To V2 List Filter V1 To V2 List To List Filter Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/list-to-item": { + "post": { + "summary": "Handle V1 List To V2 Item", + "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": {"$ref": "#/components/schemas/Item"}, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewItem" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/item-to-list": { + "post": { + "summary": "Handle V2 Item To V1 List", + "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/NewItem"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/list-to-list": { + "post": { + "summary": "Handle V2 List To V1 List", + "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/NewItem" + }, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/list-to-list-filter": { + "post": { + "summary": "Handle V2 List To V1 List Filter", + "operationId": "handle_v2_list_to_v1_list_filter_v2_to_v1_list_to_list_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/NewItem" + }, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Handle V2 List To V1 List Filter V2 To V1 List To List Filter Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/list-to-item": { + "post": { + "summary": "Handle V2 List To V1 Item", + "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/NewItem" + }, + "type": "array", + "title": "Data", + } + } + }, + "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" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "title": {"type": "string", "title": "Title"}, + "size": {"type": "integer", "title": "Size"}, + "description": {"type": "string", "title": "Description"}, + "sub": {"$ref": "#/components/schemas/SubItem"}, + "multi": { + "items": {"$ref": "#/components/schemas/SubItem"}, + "type": "array", + "title": "Multi", + "default": [], + }, + }, + "type": "object", + "required": ["title", "size", "sub"], + "title": "Item", + }, + "NewItem": { + "properties": { + "new_title": {"type": "string", "title": "New Title"}, + "new_size": {"type": "integer", "title": "New Size"}, + "new_description": pydantic_snapshot( + v2=snapshot( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + } + ), + v1=snapshot( + {"type": "string", "title": "New Description"} + ), + ), + "new_sub": {"$ref": "#/components/schemas/NewSubItem"}, + "new_multi": { + "items": {"$ref": "#/components/schemas/NewSubItem"}, + "type": "array", + "title": "New Multi", + "default": [], + }, + }, + "type": "object", + "required": ["new_title", "new_size", "new_sub"], + "title": "NewItem", + }, + "NewSubItem": { + "properties": { + "new_sub_name": {"type": "string", "title": "New Sub Name"} + }, + "type": "object", + "required": ["new_sub_name"], + "title": "NewSubItem", + }, + "SubItem": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "SubItem", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/docs_src/sql_databases/sql_app/__init__.py b/tests/test_pydantic_v1_v2_multifile/__init__.py similarity index 100% rename from docs_src/sql_databases/sql_app/__init__.py rename to tests/test_pydantic_v1_v2_multifile/__init__.py diff --git a/tests/test_pydantic_v1_v2_multifile/main.py b/tests/test_pydantic_v1_v2_multifile/main.py new file mode 100644 index 0000000000..8985cb7b4c --- /dev/null +++ b/tests/test_pydantic_v1_v2_multifile/main.py @@ -0,0 +1,142 @@ +from typing import List + +from fastapi import FastAPI + +from . import modelsv1, modelsv2, modelsv2b + +app = FastAPI() + + +@app.post("/v1-to-v2/item") +def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item: + return modelsv2.Item( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), + new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], + ) + + +@app.post("/v2-to-v1/item") +def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item: + return modelsv1.Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], + ) + + +@app.post("/v1-to-v2/item-to-list") +def handle_v1_item_to_v2_list(data: modelsv1.Item) -> List[modelsv2.Item]: + converted = modelsv2.Item( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), + new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], + ) + return [converted, converted] + + +@app.post("/v1-to-v2/list-to-list") +def handle_v1_list_to_v2_list(data: List[modelsv1.Item]) -> List[modelsv2.Item]: + result = [] + for item in data: + result.append( + modelsv2.Item( + new_title=item.title, + new_size=item.size, + new_description=item.description, + new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), + new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi], + ) + ) + return result + + +@app.post("/v1-to-v2/list-to-item") +def handle_v1_list_to_v2_item(data: List[modelsv1.Item]) -> modelsv2.Item: + if data: + item = data[0] + return modelsv2.Item( + new_title=item.title, + new_size=item.size, + new_description=item.description, + new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), + new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi], + ) + return modelsv2.Item( + new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="") + ) + + +@app.post("/v2-to-v1/item-to-list") +def handle_v2_item_to_v1_list(data: modelsv2.Item) -> List[modelsv1.Item]: + converted = modelsv1.Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], + ) + return [converted, converted] + + +@app.post("/v2-to-v1/list-to-list") +def handle_v2_list_to_v1_list(data: List[modelsv2.Item]) -> List[modelsv1.Item]: + result = [] + for item in data: + result.append( + modelsv1.Item( + title=item.new_title, + size=item.new_size, + description=item.new_description, + sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi], + ) + ) + return result + + +@app.post("/v2-to-v1/list-to-item") +def handle_v2_list_to_v1_item(data: List[modelsv2.Item]) -> modelsv1.Item: + if data: + item = data[0] + return modelsv1.Item( + title=item.new_title, + size=item.new_size, + description=item.new_description, + sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi], + ) + return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name="")) + + +@app.post("/v2-to-v1/same-name") +def handle_v2_same_name_to_v1( + item1: modelsv2.Item, item2: modelsv2b.Item +) -> modelsv1.Item: + return modelsv1.Item( + title=item1.new_title, + size=item2.dup_size, + description=item1.new_description, + sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi], + ) + + +@app.post("/v2-to-v1/list-of-items-to-list-of-items") +def handle_v2_items_in_list_to_v1_item_in_list( + data1: List[modelsv2.ItemInList], data2: List[modelsv2b.ItemInList] +) -> List[modelsv1.ItemInList]: + result = [] + item1 = data1[0] + item2 = data2[0] + result = [ + modelsv1.ItemInList(name1=item1.name2), + modelsv1.ItemInList(name1=item2.dup_name2), + ] + return result diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv1.py b/tests/test_pydantic_v1_v2_multifile/modelsv1.py new file mode 100644 index 0000000000..889291a1a7 --- /dev/null +++ b/tests/test_pydantic_v1_v2_multifile/modelsv1.py @@ -0,0 +1,19 @@ +from typing import List, Union + +from fastapi._compat.v1 import BaseModel + + +class SubItem(BaseModel): + name: str + + +class Item(BaseModel): + title: str + size: int + description: Union[str, None] = None + sub: SubItem + multi: List[SubItem] = [] + + +class ItemInList(BaseModel): + name1: str diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2.py b/tests/test_pydantic_v1_v2_multifile/modelsv2.py new file mode 100644 index 0000000000..2c8c6ea356 --- /dev/null +++ b/tests/test_pydantic_v1_v2_multifile/modelsv2.py @@ -0,0 +1,19 @@ +from typing import List, Union + +from pydantic import BaseModel + + +class SubItem(BaseModel): + new_sub_name: str + + +class Item(BaseModel): + new_title: str + new_size: int + new_description: Union[str, None] = None + new_sub: SubItem + new_multi: List[SubItem] = [] + + +class ItemInList(BaseModel): + name2: str diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py b/tests/test_pydantic_v1_v2_multifile/modelsv2b.py new file mode 100644 index 0000000000..dc0c06c669 --- /dev/null +++ b/tests/test_pydantic_v1_v2_multifile/modelsv2b.py @@ -0,0 +1,19 @@ +from typing import List, Union + +from pydantic import BaseModel + + +class SubItem(BaseModel): + dup_sub_name: str + + +class Item(BaseModel): + dup_title: str + dup_size: int + dup_description: Union[str, None] = None + dup_sub: SubItem + dup_multi: List[SubItem] = [] + + +class ItemInList(BaseModel): + dup_name2: str diff --git a/tests/test_pydantic_v1_v2_multifile/test_multifile.py b/tests/test_pydantic_v1_v2_multifile/test_multifile.py new file mode 100644 index 0000000000..4472bd73e3 --- /dev/null +++ b/tests/test_pydantic_v1_v2_multifile/test_multifile.py @@ -0,0 +1,1237 @@ +import sys + +from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from .main import app + +client = TestClient(app) + + +def test_v1_to_v2_item(): + response = client.post( + "/v1-to-v2/item", + json={"title": "Test", "size": 10, "sub": {"name": "SubTest"}}, + ) + assert response.status_code == 200 + assert response.json() == { + "new_title": "Test", + "new_size": 10, + "new_description": None, + "new_sub": {"new_sub_name": "SubTest"}, + "new_multi": [], + } + + +def test_v2_to_v1_item(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "NewTest", + "new_size": 20, + "new_sub": {"new_sub_name": "NewSubTest"}, + }, + ) + assert response.status_code == 200 + assert response.json() == { + "title": "NewTest", + "size": 20, + "description": None, + "sub": {"name": "NewSubTest"}, + "multi": [], + } + + +def test_v1_to_v2_item_to_list(): + response = client.post( + "/v1-to-v2/item-to-list", + json={"title": "ListTest", "size": 30, "sub": {"name": "SubListTest"}}, + ) + assert response.status_code == 200 + assert response.json() == [ + { + "new_title": "ListTest", + "new_size": 30, + "new_description": None, + "new_sub": {"new_sub_name": "SubListTest"}, + "new_multi": [], + }, + { + "new_title": "ListTest", + "new_size": 30, + "new_description": None, + "new_sub": {"new_sub_name": "SubListTest"}, + "new_multi": [], + }, + ] + + +def test_v1_to_v2_list_to_list(): + response = client.post( + "/v1-to-v2/list-to-list", + json=[ + {"title": "Item1", "size": 40, "sub": {"name": "Sub1"}}, + {"title": "Item2", "size": 50, "sub": {"name": "Sub2"}}, + ], + ) + assert response.status_code == 200 + assert response.json() == [ + { + "new_title": "Item1", + "new_size": 40, + "new_description": None, + "new_sub": {"new_sub_name": "Sub1"}, + "new_multi": [], + }, + { + "new_title": "Item2", + "new_size": 50, + "new_description": None, + "new_sub": {"new_sub_name": "Sub2"}, + "new_multi": [], + }, + ] + + +def test_v1_to_v2_list_to_item(): + response = client.post( + "/v1-to-v2/list-to-item", + json=[ + {"title": "FirstItem", "size": 60, "sub": {"name": "FirstSub"}}, + {"title": "SecondItem", "size": 70, "sub": {"name": "SecondSub"}}, + ], + ) + assert response.status_code == 200 + assert response.json() == { + "new_title": "FirstItem", + "new_size": 60, + "new_description": None, + "new_sub": {"new_sub_name": "FirstSub"}, + "new_multi": [], + } + + +def test_v2_to_v1_item_to_list(): + response = client.post( + "/v2-to-v1/item-to-list", + json={ + "new_title": "ListNew", + "new_size": 80, + "new_sub": {"new_sub_name": "SubListNew"}, + }, + ) + assert response.status_code == 200 + assert response.json() == [ + { + "title": "ListNew", + "size": 80, + "description": None, + "sub": {"name": "SubListNew"}, + "multi": [], + }, + { + "title": "ListNew", + "size": 80, + "description": None, + "sub": {"name": "SubListNew"}, + "multi": [], + }, + ] + + +def test_v2_to_v1_list_to_list(): + response = client.post( + "/v2-to-v1/list-to-list", + json=[ + { + "new_title": "New1", + "new_size": 90, + "new_sub": {"new_sub_name": "NewSub1"}, + }, + { + "new_title": "New2", + "new_size": 100, + "new_sub": {"new_sub_name": "NewSub2"}, + }, + ], + ) + assert response.status_code == 200 + assert response.json() == [ + { + "title": "New1", + "size": 90, + "description": None, + "sub": {"name": "NewSub1"}, + "multi": [], + }, + { + "title": "New2", + "size": 100, + "description": None, + "sub": {"name": "NewSub2"}, + "multi": [], + }, + ] + + +def test_v2_to_v1_list_to_item(): + response = client.post( + "/v2-to-v1/list-to-item", + json=[ + { + "new_title": "FirstNew", + "new_size": 110, + "new_sub": {"new_sub_name": "FirstNewSub"}, + }, + { + "new_title": "SecondNew", + "new_size": 120, + "new_sub": {"new_sub_name": "SecondNewSub"}, + }, + ], + ) + assert response.status_code == 200 + assert response.json() == { + "title": "FirstNew", + "size": 110, + "description": None, + "sub": {"name": "FirstNewSub"}, + "multi": [], + } + + +def test_v1_to_v2_list_to_item_empty(): + response = client.post("/v1-to-v2/list-to-item", json=[]) + assert response.status_code == 200 + assert response.json() == { + "new_title": "", + "new_size": 0, + "new_description": None, + "new_sub": {"new_sub_name": ""}, + "new_multi": [], + } + + +def test_v2_to_v1_list_to_item_empty(): + response = client.post("/v2-to-v1/list-to-item", json=[]) + assert response.status_code == 200 + assert response.json() == { + "title": "", + "size": 0, + "description": None, + "sub": {"name": ""}, + "multi": [], + } + + +def test_v2_same_name_to_v1(): + response = client.post( + "/v2-to-v1/same-name", + json={ + "item1": { + "new_title": "Title1", + "new_size": 100, + "new_description": "Description1", + "new_sub": {"new_sub_name": "Sub1"}, + "new_multi": [{"new_sub_name": "Multi1"}], + }, + "item2": { + "dup_title": "Title2", + "dup_size": 200, + "dup_description": "Description2", + "dup_sub": {"dup_sub_name": "Sub2"}, + "dup_multi": [ + {"dup_sub_name": "Multi2a"}, + {"dup_sub_name": "Multi2b"}, + ], + }, + }, + ) + assert response.status_code == 200 + assert response.json() == { + "title": "Title1", + "size": 200, + "description": "Description1", + "sub": {"name": "Sub1"}, + "multi": [{"name": "Multi2a"}, {"name": "Multi2b"}], + } + + +def test_v2_items_in_list_to_v1_item_in_list(): + response = client.post( + "/v2-to-v1/list-of-items-to-list-of-items", + json={ + "data1": [{"name2": "Item1"}, {"name2": "Item2"}], + "data2": [{"dup_name2": "Item3"}, {"dup_name2": "Item4"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == [ + {"name1": "Item1"}, + {"name1": "Item3"}, + ] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/v1-to-v2/item": { + "post": { + "summary": "Handle V1 Item To V2", + "operationId": "handle_v1_item_to_v2_v1_to_v2_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + } + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/item": { + "post": { + "summary": "Handle V2 Item To V1", + "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + } + ), + v1=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + } + ), + ), + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/item-to-list": { + "post": { + "summary": "Handle V1 Item To V2 List", + "operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + } + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + }, + "type": "array", + "title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/list-to-list": { + "post": { + "summary": "Handle V1 List To V2 List", + "operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + }, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + }, + "type": "array", + "title": "Response Handle V1 List To V2 List V1 To V2 List To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/list-to-item": { + "post": { + "summary": "Handle V1 List To V2 Item", + "operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + }, + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/item-to-list": { + "post": { + "summary": "Handle V2 Item To V1 List", + "operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + } + ), + v1=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + } + ), + ), + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + }, + "type": "array", + "title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/list-to-list": { + "post": { + "summary": "Handle V2 List To V1 List", + "operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": pydantic_snapshot( + v2=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + } + ), + v1=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + } + ), + ), + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + }, + "type": "array", + "title": "Response Handle V2 List To V1 List V2 To V1 List To List Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/list-to-item": { + "post": { + "summary": "Handle V2 List To V1 Item", + "operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": pydantic_snapshot( + v2=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + } + ), + v1=snapshot( + { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + } + ), + ), + "type": "array", + "title": "Data", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/same-name": { + "post": { + "summary": "Handle V2 Same Name To V1", + "operationId": "handle_v2_same_name_to_v1_v2_to_v1_same_name_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/list-of-items-to-list-of-items": { + "post": { + "summary": "Handle V2 Items In List To V1 Item In List", + "operationId": "handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList" + }, + "type": "array", + "title": "Response Handle V2 Items In List To V1 Item In List V2 To V1 List Of Items To List Of Items Post", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": pydantic_snapshot( + v1=snapshot( + { + "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": { + "properties": { + "data1": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList" + }, + "type": "array", + "title": "Data1", + }, + "data2": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList" + }, + "type": "array", + "title": "Data2", + }, + }, + "type": "object", + "required": ["data1", "data2"], + "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", + }, + "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": { + "properties": { + "item1": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" + }, + "item2": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item" + }, + }, + "type": "object", + "required": ["item1", "item2"], + "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": { + "properties": { + "title": {"type": "string", "title": "Title"}, + "size": {"type": "integer", "title": "Size"}, + "description": { + "type": "string", + "title": "Description", + }, + "sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" + }, + "multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" + }, + "type": "array", + "title": "Multi", + "default": [], + }, + }, + "type": "object", + "required": ["title", "size", "sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": { + "properties": { + "name1": {"type": "string", "title": "Name1"} + }, + "type": "object", + "required": ["name1"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": { + "properties": { + "name": {"type": "string", "title": "Name"} + }, + "type": "object", + "required": ["name"], + "title": "SubItem", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": { + "properties": { + "new_title": { + "type": "string", + "title": "New Title", + }, + "new_size": { + "type": "integer", + "title": "New Size", + }, + "new_description": { + "type": "string", + "title": "New Description", + }, + "new_sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" + }, + "new_multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" + }, + "type": "array", + "title": "New Multi", + "default": [], + }, + }, + "type": "object", + "required": ["new_title", "new_size", "new_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": { + "properties": { + "name2": {"type": "string", "title": "Name2"} + }, + "type": "object", + "required": ["name2"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": { + "properties": { + "new_sub_name": { + "type": "string", + "title": "New Sub Name", + } + }, + "type": "object", + "required": ["new_sub_name"], + "title": "SubItem", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": { + "properties": { + "dup_title": { + "type": "string", + "title": "Dup Title", + }, + "dup_size": { + "type": "integer", + "title": "Dup Size", + }, + "dup_description": { + "type": "string", + "title": "Dup Description", + }, + "dup_sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" + }, + "dup_multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" + }, + "type": "array", + "title": "Dup Multi", + "default": [], + }, + }, + "type": "object", + "required": ["dup_title", "dup_size", "dup_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": { + "properties": { + "dup_name2": { + "type": "string", + "title": "Dup Name2", + } + }, + "type": "object", + "required": ["dup_name2"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": { + "properties": { + "dup_sub_name": { + "type": "string", + "title": "Dup Sub Name", + } + }, + "type": "object", + "required": ["dup_sub_name"], + "title": "SubItem", + }, + } + ), + v2=snapshot( + { + "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": { + "properties": { + "data1": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList" + }, + "type": "array", + "title": "Data1", + }, + "data2": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList" + }, + "type": "array", + "title": "Data2", + }, + }, + "type": "object", + "required": ["data1", "data2"], + "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", + }, + "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": { + "properties": { + "item1": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + }, + "item2": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item" + }, + }, + "type": "object", + "required": ["item1", "item2"], + "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "SubItem-Output": { + "properties": { + "new_sub_name": { + "type": "string", + "title": "New Sub Name", + } + }, + "type": "object", + "required": ["new_sub_name"], + "title": "SubItem", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": { + "properties": { + "title": {"type": "string", "title": "Title"}, + "size": {"type": "integer", "title": "Size"}, + "description": { + "type": "string", + "title": "Description", + }, + "sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" + }, + "multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" + }, + "type": "array", + "title": "Multi", + "default": [], + }, + }, + "type": "object", + "required": ["title", "size", "sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": { + "properties": { + "name1": {"type": "string", "title": "Name1"} + }, + "type": "object", + "required": ["name1"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": { + "properties": { + "name": {"type": "string", "title": "Name"} + }, + "type": "object", + "required": ["name"], + "title": "SubItem", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": { + "properties": { + "new_title": { + "type": "string", + "title": "New Title", + }, + "new_size": { + "type": "integer", + "title": "New Size", + }, + "new_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + }, + "new_sub": { + "$ref": "#/components/schemas/SubItem-Output" + }, + "new_multi": { + "items": { + "$ref": "#/components/schemas/SubItem-Output" + }, + "type": "array", + "title": "New Multi", + "default": [], + }, + }, + "type": "object", + "required": ["new_title", "new_size", "new_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": { + "properties": { + "new_title": { + "type": "string", + "title": "New Title", + }, + "new_size": { + "type": "integer", + "title": "New Size", + }, + "new_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + }, + "new_sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" + }, + "new_multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" + }, + "type": "array", + "title": "New Multi", + "default": [], + }, + }, + "type": "object", + "required": ["new_title", "new_size", "new_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": { + "properties": { + "name2": {"type": "string", "title": "Name2"} + }, + "type": "object", + "required": ["name2"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": { + "properties": { + "new_sub_name": { + "type": "string", + "title": "New Sub Name", + } + }, + "type": "object", + "required": ["new_sub_name"], + "title": "SubItem", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": { + "properties": { + "dup_title": { + "type": "string", + "title": "Dup Title", + }, + "dup_size": { + "type": "integer", + "title": "Dup Size", + }, + "dup_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Dup Description", + }, + "dup_sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" + }, + "dup_multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" + }, + "type": "array", + "title": "Dup Multi", + "default": [], + }, + }, + "type": "object", + "required": ["dup_title", "dup_size", "dup_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": { + "properties": { + "dup_name2": { + "type": "string", + "title": "Dup Name2", + } + }, + "type": "object", + "required": ["dup_name2"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": { + "properties": { + "dup_sub_name": { + "type": "string", + "title": "Dup Sub Name", + } + }, + "type": "object", + "required": ["dup_sub_name"], + "title": "SubItem", + }, + } + ), + ), + }, + } + ) diff --git a/tests/test_pydantic_v1_v2_noneable.py b/tests/test_pydantic_v1_v2_noneable.py new file mode 100644 index 0000000000..d2d6f6635b --- /dev/null +++ b/tests/test_pydantic_v1_v2_noneable.py @@ -0,0 +1,766 @@ +import sys +from typing import Any, List, Union + +from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi import FastAPI +from fastapi._compat.v1 import BaseModel +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from pydantic import BaseModel as NewBaseModel + + +class SubItem(BaseModel): + name: str + + +class Item(BaseModel): + title: str + size: int + description: Union[str, None] = None + sub: SubItem + multi: List[SubItem] = [] + + +class NewSubItem(NewBaseModel): + new_sub_name: str + + +class NewItem(NewBaseModel): + new_title: str + new_size: int + new_description: Union[str, None] = None + new_sub: NewSubItem + new_multi: List[NewSubItem] = [] + + +app = FastAPI() + + +@app.post("/v1-to-v2/") +def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]: + if data.size < 0: + return None + return NewItem( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=NewSubItem(new_sub_name=data.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], + ) + + +@app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None]) +def handle_v1_item_to_v2_filter(data: Item) -> Any: + if data.size < 0: + return None + result = { + "new_title": data.title, + "new_size": data.size, + "new_description": data.description, + "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"}, + "new_multi": [ + {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi + ], + "secret": "hidden_v1_to_v2", + } + return result + + +@app.post("/v2-to-v1/item") +def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]: + if data.new_size < 0: + return None + return Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=SubItem(name=data.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], + ) + + +@app.post("/v2-to-v1/item-filter", response_model=Union[Item, None]) +def handle_v2_item_to_v1_filter(data: NewItem) -> Any: + if data.new_size < 0: + return None + result = { + "title": data.new_title, + "size": data.new_size, + "description": data.new_description, + "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, + "multi": [ + {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi + ], + "secret": "hidden_v2_to_v1", + } + return result + + +client = TestClient(app) + + +def test_v1_to_v2_item_success(): + response = client.post( + "/v1-to-v2/", + json={ + "title": "Old Item", + "size": 100, + "description": "V1 description", + "sub": {"name": "V1 Sub"}, + "multi": [{"name": "M1"}, {"name": "M2"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "new_title": "Old Item", + "new_size": 100, + "new_description": "V1 description", + "new_sub": {"new_sub_name": "V1 Sub"}, + "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], + } + + +def test_v1_to_v2_item_returns_none(): + response = client.post( + "/v1-to-v2/", + json={"title": "Invalid Item", "size": -10, "sub": {"name": "Sub"}}, + ) + assert response.status_code == 200, response.text + assert response.json() is None + + +def test_v1_to_v2_item_minimal(): + response = client.post( + "/v1-to-v2/", json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}} + ) + assert response.status_code == 200, response.text + assert response.json() == { + "new_title": "Minimal", + "new_size": 50, + "new_description": None, + "new_sub": {"new_sub_name": "MinSub"}, + "new_multi": [], + } + + +def test_v1_to_v2_item_filter_success(): + response = client.post( + "/v1-to-v2/item-filter", + json={ + "title": "Filtered Item", + "size": 50, + "sub": {"name": "Sub"}, + "multi": [{"name": "Multi1"}], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert result["new_title"] == "Filtered Item" + assert result["new_size"] == 50 + assert result["new_sub"]["new_sub_name"] == "Sub" + assert result["new_multi"][0]["new_sub_name"] == "Multi1" + # Verify secret fields are filtered out + assert "secret" not in result + assert "new_sub_secret" not in result["new_sub"] + assert "new_sub_secret" not in result["new_multi"][0] + + +def test_v1_to_v2_item_filter_returns_none(): + response = client.post( + "/v1-to-v2/item-filter", + json={"title": "Invalid", "size": -1, "sub": {"name": "Sub"}}, + ) + assert response.status_code == 200, response.text + assert response.json() is None + + +def test_v2_to_v1_item_success(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "New Item", + "new_size": 200, + "new_description": "V2 description", + "new_sub": {"new_sub_name": "V2 Sub"}, + "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "New Item", + "size": 200, + "description": "V2 description", + "sub": {"name": "V2 Sub"}, + "multi": [{"name": "N1"}, {"name": "N2"}], + } + + +def test_v2_to_v1_item_returns_none(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "Invalid New", + "new_size": -5, + "new_sub": {"new_sub_name": "NewSub"}, + }, + ) + assert response.status_code == 200, response.text + assert response.json() is None + + +def test_v2_to_v1_item_minimal(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "MinimalNew", + "new_size": 75, + "new_sub": {"new_sub_name": "MinNewSub"}, + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "title": "MinimalNew", + "size": 75, + "description": None, + "sub": {"name": "MinNewSub"}, + "multi": [], + } + + +def test_v2_to_v1_item_filter_success(): + response = client.post( + "/v2-to-v1/item-filter", + json={ + "new_title": "Filtered New", + "new_size": 75, + "new_sub": {"new_sub_name": "NewSub"}, + "new_multi": [], + }, + ) + assert response.status_code == 200, response.text + result = response.json() + assert result["title"] == "Filtered New" + assert result["size"] == 75 + assert result["sub"]["name"] == "NewSub" + # Verify secret fields are filtered out + assert "secret" not in result + assert "sub_secret" not in result["sub"] + + +def test_v2_to_v1_item_filter_returns_none(): + response = client.post( + "/v2-to-v1/item-filter", + json={ + "new_title": "Invalid Filtered", + "new_size": -100, + "new_sub": {"new_sub_name": "Sub"}, + }, + ) + assert response.status_code == 200, response.text + assert response.json() is None + + +def test_v1_to_v2_validation_error(): + response = client.post("/v1-to-v2/", json={"title": "Missing fields"}) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "loc": ["body", "size"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "sub"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + } + ) + + +def test_v1_to_v2_nested_validation_error(): + response = client.post( + "/v1-to-v2/", + json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}}, + ) + assert response.status_code == 422, response.text + error_detail = response.json()["detail"] + assert len(error_detail) == 1 + assert error_detail[0]["loc"] == ["body", "sub", "name"] + + +def test_v1_to_v2_type_validation_error(): + response = client.post( + "/v1-to-v2/", + json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}}, + ) + assert response.status_code == 422, response.text + error_detail = response.json()["detail"] + assert len(error_detail) == 1 + assert error_detail[0]["loc"] == ["body", "size"] + + +def test_v2_to_v1_validation_error(): + response = client.post("/v2-to-v1/item", json={"new_title": "Missing fields"}) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": pydantic_snapshot( + v2=snapshot( + [ + { + "type": "missing", + "loc": ["body", "new_size"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + { + "type": "missing", + "loc": ["body", "new_sub"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + ] + ), + v1=snapshot( + [ + { + "loc": ["body", "new_size"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "new_sub"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + ), + ) + } + ) + + +def test_v2_to_v1_nested_validation_error(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "Bad sub", + "new_size": 200, + "new_sub": {"wrong_field": "value"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + pydantic_snapshot( + v2=snapshot( + { + "type": "missing", + "loc": ["body", "new_sub", "new_sub_name"], + "msg": "Field required", + "input": {"wrong_field": "value"}, + } + ), + v1=snapshot( + { + "loc": ["body", "new_sub", "new_sub_name"], + "msg": "field required", + "type": "value_error.missing", + } + ), + ) + ] + } + ) + + +def test_v2_to_v1_type_validation_error(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "Bad type", + "new_size": "not_a_number", + "new_sub": {"new_sub_name": "Sub"}, + }, + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + pydantic_snapshot( + v2=snapshot( + { + "type": "int_parsing", + "loc": ["body", "new_size"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "not_a_number", + } + ), + v1=snapshot( + { + "loc": ["body", "new_size"], + "msg": "value is not a valid integer", + "type": "type_error.integer", + } + ), + ) + ] + } + ) + + +def test_v1_to_v2_with_multi_items(): + response = client.post( + "/v1-to-v2/", + json={ + "title": "Complex Item", + "size": 300, + "description": "Item with multiple sub-items", + "sub": {"name": "Main Sub"}, + "multi": [{"name": "Sub1"}, {"name": "Sub2"}, {"name": "Sub3"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "new_title": "Complex Item", + "new_size": 300, + "new_description": "Item with multiple sub-items", + "new_sub": {"new_sub_name": "Main Sub"}, + "new_multi": [ + {"new_sub_name": "Sub1"}, + {"new_sub_name": "Sub2"}, + {"new_sub_name": "Sub3"}, + ], + } + ) + + +def test_v2_to_v1_with_multi_items(): + response = client.post( + "/v2-to-v1/item", + json={ + "new_title": "Complex New Item", + "new_size": 400, + "new_description": "New item with multiple sub-items", + "new_sub": {"new_sub_name": "Main New Sub"}, + "new_multi": [{"new_sub_name": "NewSub1"}, {"new_sub_name": "NewSub2"}], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "title": "Complex New Item", + "size": 400, + "description": "New item with multiple sub-items", + "sub": {"name": "Main New Sub"}, + "multi": [{"name": "NewSub1"}, {"name": "NewSub2"}], + } + ) + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/v1-to-v2/": { + "post": { + "summary": "Handle V1 Item To V2", + "operationId": "handle_v1_item_to_v2_v1_to_v2__post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "anyOf": [ + { + "$ref": "#/components/schemas/NewItem" + }, + {"type": "null"}, + ], + "title": "Response Handle V1 Item To V2 V1 To V2 Post", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/NewItem"} + ), + ) + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v1-to-v2/item-filter": { + "post": { + "summary": "Handle V1 Item To V2 Filter", + "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "allOf": [ + { + "$ref": "#/components/schemas/Item" + } + ], + "title": "Data", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/Item"} + ), + ) + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": pydantic_snapshot( + v2=snapshot( + { + "anyOf": [ + { + "$ref": "#/components/schemas/NewItem" + }, + {"type": "null"}, + ], + "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post", + } + ), + v1=snapshot( + {"$ref": "#/components/schemas/NewItem"} + ), + ) + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/item": { + "post": { + "summary": "Handle V2 Item To V1", + "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/NewItem"} + } + }, + "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" + } + } + }, + }, + }, + } + }, + "/v2-to-v1/item-filter": { + "post": { + "summary": "Handle V2 Item To V1 Filter", + "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/NewItem"} + } + }, + "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" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "title": {"type": "string", "title": "Title"}, + "size": {"type": "integer", "title": "Size"}, + "description": {"type": "string", "title": "Description"}, + "sub": {"$ref": "#/components/schemas/SubItem"}, + "multi": { + "items": {"$ref": "#/components/schemas/SubItem"}, + "type": "array", + "title": "Multi", + "default": [], + }, + }, + "type": "object", + "required": ["title", "size", "sub"], + "title": "Item", + }, + "NewItem": { + "properties": { + "new_title": {"type": "string", "title": "New Title"}, + "new_size": {"type": "integer", "title": "New Size"}, + "new_description": pydantic_snapshot( + v2=snapshot( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + } + ), + v1=snapshot( + {"type": "string", "title": "New Description"} + ), + ), + "new_sub": {"$ref": "#/components/schemas/NewSubItem"}, + "new_multi": { + "items": {"$ref": "#/components/schemas/NewSubItem"}, + "type": "array", + "title": "New Multi", + "default": [], + }, + }, + "type": "object", + "required": ["new_title", "new_size", "new_sub"], + "title": "NewItem", + }, + "NewSubItem": { + "properties": { + "new_sub_name": {"type": "string", "title": "New Sub Name"} + }, + "type": "object", + "required": ["new_sub_name"], + "title": "NewSubItem", + }, + "SubItem": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "SubItem", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_repeated_dependency_schema.py b/tests/test_repeated_dependency_schema.py index d7d0dfa052..c21829bd97 100644 --- a/tests/test_repeated_dependency_schema.py +++ b/tests/test_repeated_dependency_schema.py @@ -41,7 +41,7 @@ schema = { "type": "array", }, "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error " "Type", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, "required": ["loc", "msg", "type"], "title": "ValidationError", @@ -66,7 +66,7 @@ schema = { "responses": { "200": { "content": {"application/json": {"schema": {}}}, - "description": "Successful " "Response", + "description": "Successful Response", }, "422": { "content": { @@ -76,7 +76,7 @@ schema = { } } }, - "description": "Validation " "Error", + "description": "Validation Error", }, }, "summary": "Get Deps", diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index 6948430a13..1745c69b60 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -7,6 +7,8 @@ from fastapi.responses import JSONResponse, Response from fastapi.testclient import TestClient from pydantic import BaseModel +from tests.utils import needs_pydanticv1 + class BaseUser(BaseModel): name: str @@ -509,6 +511,26 @@ def test_invalid_response_model_field(): assert "parameter response_model=None" in e.value.args[0] +# TODO: remove when dropping Pydantic v1 support +@needs_pydanticv1 +def test_invalid_response_model_field_pv1(): + from fastapi._compat import v1 + + app = FastAPI() + + class Model(v1.BaseModel): + foo: str + + with pytest.raises(FastAPIError) as e: + + @app.get("/") + def read_root() -> Union[Response, Model, 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 diff --git a/tests/test_response_model_default_factory.py b/tests/test_response_model_default_factory.py new file mode 100644 index 0000000000..13c1f442ba --- /dev/null +++ b/tests/test_response_model_default_factory.py @@ -0,0 +1,47 @@ +from fastapi import FastAPI +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field + +app = FastAPI() + + +class ResponseModel(BaseModel): + code: int = 200 + message: str = Field(default_factory=lambda: "Successful operation.") + + +@app.get( + "/response_model_has_default_factory_return_dict", + response_model=ResponseModel, +) +async def response_model_has_default_factory_return_dict(): + return {"code": 200} + + +@app.get( + "/response_model_has_default_factory_return_model", + response_model=ResponseModel, +) +async def response_model_has_default_factory_return_model(): + return ResponseModel() + + +client = TestClient(app) + + +def test_response_model_has_default_factory_return_dict(): + response = client.get("/response_model_has_default_factory_return_dict") + + assert response.status_code == 200, response.text + + assert response.json()["code"] == 200 + assert response.json()["message"] == "Successful operation." + + +def test_response_model_has_default_factory_return_model(): + response = client.get("/response_model_has_default_factory_return_model") + + assert response.status_code == 200, response.text + + assert response.json()["code"] == 200 + assert response.json()["message"] == "Successful operation." diff --git a/tests/test_return_none_stringified_annotations.py b/tests/test_return_none_stringified_annotations.py new file mode 100644 index 0000000000..be052d532e --- /dev/null +++ b/tests/test_return_none_stringified_annotations.py @@ -0,0 +1,17 @@ +import http + +from fastapi import FastAPI +from fastapi.testclient import TestClient + + +def test_no_content(): + app = FastAPI() + + @app.get("/no-content", status_code=http.HTTPStatus.NO_CONTENT) + def return_no_content() -> "None": + return + + client = TestClient(app) + response = client.get("/no-content") + assert response.status_code == http.HTTPStatus.NO_CONTENT, response.text + assert not response.content diff --git a/tests/test_route_scope.py b/tests/test_route_scope.py index 2021c828f4..792ea66c3a 100644 --- a/tests/test_route_scope.py +++ b/tests/test_route_scope.py @@ -47,4 +47,4 @@ def test_websocket(): def test_websocket_invalid_path_doesnt_match(): with pytest.raises(WebSocketDisconnect): with client.websocket_connect("/itemsx/portal-gun"): - pass + pass # pragma: no cover diff --git a/tests/test_security_http_digest_optional.py b/tests/test_security_http_digest_optional.py index 1e6eb8bd7f..0d66f9c72e 100644 --- a/tests/test_security_http_digest_optional.py +++ b/tests/test_security_http_digest_optional.py @@ -37,8 +37,8 @@ def test_security_http_digest_incorrect_scheme_credentials(): response = client.get( "/users/me", headers={"Authorization": "Other invalidauthorization"} ) - assert response.status_code == 403, response.text - assert response.json() == {"detail": "Invalid authentication credentials"} + assert response.status_code == 200, response.text + assert response.json() == {"msg": "Create an account first"} def test_openapi_schema(): diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py index 7d914d0345..2b7e3457a9 100644 --- a/tests/test_security_oauth2.py +++ b/tests/test_security_oauth2.py @@ -1,3 +1,4 @@ +import pytest from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -137,10 +138,18 @@ def test_strict_login_no_grant_type(): ) -def test_strict_login_incorrect_grant_type(): +@pytest.mark.parametrize( + argnames=["grant_type"], + argvalues=[ + pytest.param("incorrect", id="incorrect value"), + pytest.param("passwordblah", id="password with suffix"), + pytest.param("blahpassword", id="password with prefix"), + ], +) +def test_strict_login_incorrect_grant_type(grant_type: str): response = client.post( "/login", - data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, + data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 assert response.json() == IsDict( @@ -149,9 +158,9 @@ def test_strict_login_incorrect_grant_type(): { "type": "string_pattern_mismatch", "loc": ["body", "grant_type"], - "msg": "String should match pattern 'password'", - "input": "incorrect", - "ctx": {"pattern": "password"}, + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, } ] } @@ -161,9 +170,9 @@ def test_strict_login_incorrect_grant_type(): "detail": [ { "loc": ["body", "grant_type"], - "msg": 'string does not match regex "password"', + "msg": 'string does not match regex "^password$"', "type": "value_error.str.regex", - "ctx": {"pattern": "password"}, + "ctx": {"pattern": "^password$"}, } ] } @@ -248,7 +257,7 @@ def test_openapi_schema(): "properties": { "grant_type": { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", }, "username": {"title": "Username", "type": "string"}, diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index 0da3b911e8..046ac57637 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -1,5 +1,6 @@ from typing import Optional +import pytest from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -141,10 +142,18 @@ def test_strict_login_no_grant_type(): ) -def test_strict_login_incorrect_grant_type(): +@pytest.mark.parametrize( + argnames=["grant_type"], + argvalues=[ + pytest.param("incorrect", id="incorrect value"), + pytest.param("passwordblah", id="password with suffix"), + pytest.param("blahpassword", id="password with prefix"), + ], +) +def test_strict_login_incorrect_grant_type(grant_type: str): response = client.post( "/login", - data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, + data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 assert response.json() == IsDict( @@ -153,9 +162,9 @@ def test_strict_login_incorrect_grant_type(): { "type": "string_pattern_mismatch", "loc": ["body", "grant_type"], - "msg": "String should match pattern 'password'", - "input": "incorrect", - "ctx": {"pattern": "password"}, + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, } ] } @@ -165,9 +174,9 @@ def test_strict_login_incorrect_grant_type(): "detail": [ { "loc": ["body", "grant_type"], - "msg": 'string does not match regex "password"', + "msg": 'string does not match regex "^password$"', "type": "value_error.str.regex", - "ctx": {"pattern": "password"}, + "ctx": {"pattern": "^password$"}, } ] } @@ -252,7 +261,7 @@ def test_openapi_schema(): "properties": { "grant_type": { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", }, "username": {"title": "Username", "type": "string"}, diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index 85a9f9b391..629cddca2f 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -1,5 +1,6 @@ from typing import Optional +import pytest from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -142,10 +143,18 @@ def test_strict_login_no_grant_type(): ) -def test_strict_login_incorrect_grant_type(): +@pytest.mark.parametrize( + argnames=["grant_type"], + argvalues=[ + pytest.param("incorrect", id="incorrect value"), + pytest.param("passwordblah", id="password with suffix"), + pytest.param("blahpassword", id="password with prefix"), + ], +) +def test_strict_login_incorrect_grant_type(grant_type: str): response = client.post( "/login", - data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, + data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 assert response.json() == IsDict( @@ -154,9 +163,9 @@ def test_strict_login_incorrect_grant_type(): { "type": "string_pattern_mismatch", "loc": ["body", "grant_type"], - "msg": "String should match pattern 'password'", - "input": "incorrect", - "ctx": {"pattern": "password"}, + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, } ] } @@ -166,9 +175,9 @@ def test_strict_login_incorrect_grant_type(): "detail": [ { "loc": ["body", "grant_type"], - "msg": 'string does not match regex "password"', + "msg": 'string does not match regex "^password$"', "type": "value_error.str.regex", - "ctx": {"pattern": "password"}, + "ctx": {"pattern": "^password$"}, } ] } @@ -253,7 +262,7 @@ def test_openapi_schema(): "properties": { "grant_type": { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", }, "username": {"title": "Username", "type": "string"}, diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py index c382cb5fe2..b304f70153 100644 --- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py @@ -1,17 +1,35 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.additional_status_codes.tutorial001 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -def test_update(): +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.additional_status_codes.{request.param}") + + client = TestClient(mod.app) + return client + + +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} -def test_create(): +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.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an.py deleted file mode 100644 index 2cb2bb9930..0000000000 --- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an.py +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index c7660a3925..0000000000 --- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py310.py +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 303c5dbae2..0000000000 --- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_an_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 02f2e188c7..0000000000 --- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001_py310.py +++ /dev/null @@ -1,26 +0,0 @@ -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_async_sql_databases/__init__.py b/tests/test_tutorial/test_async_sql_databases/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002.py b/tests/test_tutorial/test_background_tasks/test_tutorial002.py index 74de1314bc..d5ef51ee2b 100644 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002.py +++ b/tests/test_tutorial/test_background_tasks/test_tutorial002.py @@ -1,14 +1,31 @@ +import importlib import os from pathlib import Path +import pytest from fastapi.testclient import TestClient -from docs_src.background_tasks.tutorial002 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -def test(): +@pytest.fixture( + name="client", + params=[ + "tutorial002", + pytest.param("tutorial002_py310", marks=needs_py310), + "tutorial002_an", + pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.background_tasks.{request.param}") + + client = TestClient(mod.app) + return client + + +def test(client: TestClient): log = Path("log.txt") if log.is_file(): os.remove(log) # pragma: no cover diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002_an.py b/tests/test_tutorial/test_background_tasks/test_tutorial002_an.py deleted file mode 100644 index af682ecff1..0000000000 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002_an.py +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 067b2787e9..0000000000 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py310.py +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index 06b5a2f57a..0000000000 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -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_background_tasks/test_tutorial002_py310.py b/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py deleted file mode 100644 index 6937c8fab4..0000000000 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py +++ /dev/null @@ -1,21 +0,0 @@ -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_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_behind_a_proxy/test_tutorial001_01.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py new file mode 100644 index 0000000000..f13046e018 --- /dev/null +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py @@ -0,0 +1,21 @@ +from fastapi.testclient import TestClient + +from docs_src.behind_a_proxy.tutorial001_01 import app + +client = TestClient( + app, + base_url="https://example.com", + follow_redirects=False, +) + + +def test_redirect() -> None: + response = client.get("/items") + assert response.status_code == 307 + assert response.headers["location"] == "https://example.com/items/" + + +def test_no_redirect() -> None: + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == ["plumbus", "portal gun"] diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py index 35fdfa4a66..fe40fad7d0 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main.py +++ b/tests/test_tutorial/test_bigger_applications/test_main.py @@ -1,13 +1,24 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py39 -@pytest.fixture(name="client") -def get_client(): - from docs_src.bigger_applications.app.main import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "app_an.main", + pytest.param("app_an_py39.main", marks=needs_py39), + "app.main", + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.bigger_applications.{request.param}") + + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_bigger_applications/test_main_an.py b/tests/test_tutorial/test_bigger_applications/test_main_an.py deleted file mode 100644 index 4e2e3e74d7..0000000000 --- a/tests/test_tutorial/test_bigger_applications/test_main_an.py +++ /dev/null @@ -1,715 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.bigger_applications.app_an.main import app - - client = TestClient(app) - return client - - -def test_users_token_jessica(client: TestClient): - response = client.get("/users?token=jessica") - assert response.status_code == 200 - assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] - - -def test_users_with_no_token(client: TestClient): - response = client.get("/users") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_users_foo_token_jessica(client: TestClient): - response = client.get("/users/foo?token=jessica") - assert response.status_code == 200 - assert response.json() == {"username": "foo"} - - -def test_users_foo_with_no_token(client: TestClient): - response = client.get("/users/foo") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_users_me_token_jessica(client: TestClient): - response = client.get("/users/me?token=jessica") - assert response.status_code == 200 - assert response.json() == {"username": "fakecurrentuser"} - - -def test_users_me_with_no_token(client: TestClient): - response = client.get("/users/me") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_users_token_monica_with_no_jessica(client: TestClient): - response = client.get("/users?token=monica") - assert response.status_code == 400 - assert response.json() == {"detail": "No Jessica token provided"} - - -def test_items_token_jessica(client: TestClient): - response = client.get( - "/items?token=jessica", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 200 - assert response.json() == { - "plumbus": {"name": "Plumbus"}, - "gun": {"name": "Portal Gun"}, - } - - -def test_items_with_no_token_jessica(client: TestClient): - response = client.get("/items", headers={"X-Token": "fake-super-secret-token"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_items_plumbus_token_jessica(client: TestClient): - response = client.get( - "/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 200 - assert response.json() == {"name": "Plumbus", "item_id": "plumbus"} - - -def test_items_bar_token_jessica(client: TestClient): - response = client.get( - "/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 404 - assert response.json() == {"detail": "Item not found"} - - -def test_items_plumbus_with_no_token(client: TestClient): - response = client.get( - "/items/plumbus", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_items_with_invalid_token(client: TestClient): - response = client.get("/items?token=jessica", headers={"X-Token": "invalid"}) - assert response.status_code == 400 - assert response.json() == {"detail": "X-Token header invalid"} - - -def test_items_bar_with_invalid_token(client: TestClient): - response = client.get("/items/bar?token=jessica", headers={"X-Token": "invalid"}) - assert response.status_code == 400 - assert response.json() == {"detail": "X-Token header invalid"} - - -def test_items_with_missing_x_token_header(client: TestClient): - response = client.get("/items?token=jessica") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_items_plumbus_with_missing_x_token_header(client: TestClient): - response = client.get("/items/plumbus?token=jessica") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_root_token_jessica(client: TestClient): - response = client.get("/?token=jessica") - assert response.status_code == 200 - assert response.json() == {"message": "Hello Bigger Applications!"} - - -def test_root_with_no_token(client: TestClient): - response = client.get("/") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_put_no_header(client: TestClient): - response = client.put("/items/foo") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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(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"} - - -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"} - - -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"} - - -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"} - - -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"} - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/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 deleted file mode 100644 index 8c9e976df2..0000000000 --- a/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py +++ /dev/null @@ -1,742 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.bigger_applications.app_an_py39.main import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_users_token_jessica(client: TestClient): - response = client.get("/users?token=jessica") - assert response.status_code == 200 - assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] - - -@needs_py39 -def test_users_with_no_token(client: TestClient): - response = client.get("/users") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_users_foo_token_jessica(client: TestClient): - response = client.get("/users/foo?token=jessica") - assert response.status_code == 200 - assert response.json() == {"username": "foo"} - - -@needs_py39 -def test_users_foo_with_no_token(client: TestClient): - response = client.get("/users/foo") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_users_me_token_jessica(client: TestClient): - response = client.get("/users/me?token=jessica") - assert response.status_code == 200 - assert response.json() == {"username": "fakecurrentuser"} - - -@needs_py39 -def test_users_me_with_no_token(client: TestClient): - response = client.get("/users/me") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_users_token_monica_with_no_jessica(client: TestClient): - response = client.get("/users?token=monica") - assert response.status_code == 400 - assert response.json() == {"detail": "No Jessica token provided"} - - -@needs_py39 -def test_items_token_jessica(client: TestClient): - response = client.get( - "/items?token=jessica", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 200 - assert response.json() == { - "plumbus": {"name": "Plumbus"}, - "gun": {"name": "Portal Gun"}, - } - - -@needs_py39 -def test_items_with_no_token_jessica(client: TestClient): - response = client.get("/items", headers={"X-Token": "fake-super-secret-token"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_items_plumbus_token_jessica(client: TestClient): - response = client.get( - "/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 200 - assert response.json() == {"name": "Plumbus", "item_id": "plumbus"} - - -@needs_py39 -def test_items_bar_token_jessica(client: TestClient): - response = client.get( - "/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 404 - assert response.json() == {"detail": "Item not found"} - - -@needs_py39 -def test_items_plumbus_with_no_token(client: TestClient): - response = client.get( - "/items/plumbus", headers={"X-Token": "fake-super-secret-token"} - ) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_items_with_invalid_token(client: TestClient): - response = client.get("/items?token=jessica", headers={"X-Token": "invalid"}) - assert response.status_code == 400 - assert response.json() == {"detail": "X-Token header invalid"} - - -@needs_py39 -def test_items_bar_with_invalid_token(client: TestClient): - response = client.get("/items/bar?token=jessica", headers={"X-Token": "invalid"}) - assert response.status_code == 400 - assert response.json() == {"detail": "X-Token header invalid"} - - -@needs_py39 -def test_items_with_missing_x_token_header(client: TestClient): - response = client.get("/items?token=jessica") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_items_plumbus_with_missing_x_token_header(client: TestClient): - response = client.get("/items/plumbus?token=jessica") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_root_token_jessica(client: TestClient): - response = client.get("/?token=jessica") - assert response.status_code == 200 - assert response.json() == {"message": "Hello Bigger Applications!"} - - -@needs_py39 -def test_root_with_no_token(client: TestClient): - response = client.get("/") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_put_no_header(client: TestClient): - response = client.put("/items/foo") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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.1.0", - "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 0d55d73ebe..f8b5aee8d2 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -1,15 +1,24 @@ +import importlib from unittest.mock import patch import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py310 -@pytest.fixture -def client(): - from docs_src.body.tutorial001 import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body.{request.param}") + + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py deleted file mode 100644 index 4b9c128063..0000000000 --- a/tests/test_tutorial/test_body/test_tutorial001_py310.py +++ /dev/null @@ -1,498 +0,0 @@ -from unittest.mock import patch - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture -def client(): - from docs_src.body.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_body_float(client: TestClient): - response = client.post("/items/", json={"name": "Foo", "price": 50.5}) - assert response.status_code == 200 - assert response.json() == { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - } - - -@needs_py310 -def test_post_with_str_float(client: TestClient): - response = client.post("/items/", json={"name": "Foo", "price": "50.5"}) - assert response.status_code == 200 - assert response.json() == { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - } - - -@needs_py310 -def test_post_with_str_float_description(client: TestClient): - response = client.post( - "/items/", json={"name": "Foo", "price": "50.5", "description": "Some Foo"} - ) - assert response.status_code == 200 - assert response.json() == { - "name": "Foo", - "price": 50.5, - "description": "Some Foo", - "tax": None, - } - - -@needs_py310 -def test_post_with_str_float_description_tax(client: TestClient): - response = client.post( - "/items/", - json={"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3}, - ) - assert response.status_code == 200 - assert response.json() == { - "name": "Foo", - "price": 50.5, - "description": "Some Foo", - "tax": 0.3, - } - - -@needs_py310 -def test_post_with_only_name(client: TestClient): - response = client.post("/items/", json={"name": "Foo"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "price"], - "msg": "Field required", - "input": {"name": "Foo"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "price"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py310 -def test_post_with_only_name_price(client: TestClient): - response = client.post("/items/", json={"name": "Foo", "price": "twenty"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "float_parsing", - "loc": ["body", "price"], - "msg": "Input should be a valid number, unable to parse string as a number", - "input": "twenty", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "price"], - "msg": "value is not a valid float", - "type": "type_error.float", - } - ] - } - ) - - -@needs_py310 -def test_post_with_no_data(client: TestClient): - response = client.post("/items/", json={}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "name"], - "msg": "Field required", - "input": {}, - }, - { - "type": "missing", - "loc": ["body", "price"], - "msg": "Field required", - "input": {}, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "name"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "price"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_post_with_none(client: TestClient): - response = client.post("/items/", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py310 -def test_post_broken_body(client: TestClient): - response = client.post( - "/items/", - headers={"content-type": "application/json"}, - content="{some broken json}", - ) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "json_invalid", - "loc": ["body", 1], - "msg": "JSON decode error", - "input": {}, - "ctx": { - "error": "Expecting property name enclosed in double quotes" - }, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", 1], - "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)", - "type": "value_error.jsondecode", - "ctx": { - "msg": "Expecting property name enclosed in double quotes", - "doc": "{some broken json}", - "pos": 1, - "lineno": 1, - "colno": 2, - }, - } - ] - } - ) - - -@needs_py310 -def test_post_form_for_json(client: TestClient): - response = client.post("/items/", data={"name": "Foo", "price": 50.5}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": "name=Foo&price=50.5", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) - - -@needs_py310 -def test_explicit_content_type(client: TestClient): - response = client.post( - "/items/", - content='{"name": "Foo", "price": 50.5}', - headers={"Content-Type": "application/json"}, - ) - assert response.status_code == 200, response.text - - -@needs_py310 -def test_geo_json(client: TestClient): - response = client.post( - "/items/", - content='{"name": "Foo", "price": 50.5}', - headers={"Content-Type": "application/geo+json"}, - ) - assert response.status_code == 200, response.text - - -@needs_py310 -def test_no_content_type_is_json(client: TestClient): - response = client.post( - "/items/", - content='{"name": "Foo", "price": 50.5}', - ) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Foo", - "description": None, - "price": 50.5, - "tax": None, - } - - -@needs_py310 -def test_wrong_headers(client: TestClient): - data = '{"name": "Foo", "price": 50.5}' - response = client.post( - "/items/", content=data, headers={"Content-Type": "text/plain"} - ) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": '{"name": "Foo", "price": 50.5}', - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) - - response = client.post( - "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"} - ) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": '{"name": "Foo", "price": 50.5}', - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) - response = client.post( - "/items/", content=data, headers={"Content-Type": "application/not-really-json"} - ) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "model_attributes_type", - "loc": ["body"], - "msg": "Input should be a valid dictionary or object to extract fields from", - "input": '{"name": "Foo", "price": 50.5}', - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } - ) - - -@needs_py310 -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.1.0", - "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py index fd6139eb9b..fb68f28689 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py @@ -1,13 +1,26 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_fields.tutorial001 import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_fields.{request.param}") + + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_an.py b/tests/test_tutorial/test_body_fields/test_tutorial001_an.py deleted file mode 100644 index 72c18c1f73..0000000000 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_an.py +++ /dev/null @@ -1,203 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_fields.tutorial001_an import app - - client = TestClient(app) - return client - - -def test_items_5(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None}, - } - - -def test_items_6(client: TestClient): - response = client.put( - "/items/6", - json={ - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": "5.4", - } - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 6, - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": 5.4, - }, - } - - -def test_invalid_price(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["body", "item", "price"], - "msg": "Input should be greater than 0", - "input": -3.0, - "ctx": {"gt": 0.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"limit_value": 0}, - "loc": ["body", "item", "price"], - "msg": "ensure this value is greater than 0", - "type": "value_error.number.not_gt", - } - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "The description of the item", - "anyOf": [ - {"maxLength": 300, "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 1bc62868fd..0000000000 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py +++ /dev/null @@ -1,209 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_fields.tutorial001_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_items_5(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None}, - } - - -@needs_py310 -def test_items_6(client: TestClient): - response = client.put( - "/items/6", - json={ - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": "5.4", - } - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 6, - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": 5.4, - }, - } - - -@needs_py310 -def test_invalid_price(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["body", "item", "price"], - "msg": "Input should be greater than 0", - "input": -3.0, - "ctx": {"gt": 0.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "The description of the item", - "anyOf": [ - {"maxLength": 300, "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 3c5557a1b2..0000000000 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py +++ /dev/null @@ -1,209 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_fields.tutorial001_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_items_5(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None}, - } - - -@needs_py39 -def test_items_6(client: TestClient): - response = client.put( - "/items/6", - json={ - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": "5.4", - } - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 6, - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": 5.4, - }, - } - - -@needs_py39 -def test_invalid_price(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["body", "item", "price"], - "msg": "Input should be greater than 0", - "input": -3.0, - "ctx": {"gt": 0.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "The description of the item", - "anyOf": [ - {"maxLength": 300, "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 8c1386aa67..0000000000 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py +++ /dev/null @@ -1,209 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_fields.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_items_5(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None}, - } - - -@needs_py310 -def test_items_6(client: TestClient): - response = client.put( - "/items/6", - json={ - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": "5.4", - } - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 6, - "item": { - "name": "Bar", - "price": 0.2, - "description": "Some bar", - "tax": 5.4, - }, - } - - -@needs_py310 -def test_invalid_price(client: TestClient): - response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "greater_than", - "loc": ["body", "item", "price"], - "msg": "Input should be greater than 0", - "input": -3.0, - "ctx": {"gt": 0.0}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "The description of the item", - "anyOf": [ - {"maxLength": 300, "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 6275ebe95f..1424055952 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py @@ -1,13 +1,26 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial001 import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}") + + client = TestClient(mod.app) return client 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 deleted file mode 100644 index 5cd3e2c4aa..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py +++ /dev/null @@ -1,206 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial001_an import app - - client = TestClient(app) - return client - - -def test_post_body_q_bar_content(client: TestClient): - response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "q": "bar", - } - - -def test_post_no_body_q_bar(client: TestClient): - response = client.put("/items/5?q=bar", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5, "q": "bar"} - - -def test_post_no_body(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5} - - -def test_post_id_foo(client: TestClient): - response = client.put("/items/foo", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "maximum": 1000.0, - "minimum": 0.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": IsDict( - { - "anyOf": [ - {"$ref": "#/components/schemas/Item"}, - {"type": "null"}, - ], - "title": "Item", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/Item"} - ) - } - } - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 0173ab21b3..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py +++ /dev/null @@ -1,213 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial001_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_body_q_bar_content(client: TestClient): - response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "q": "bar", - } - - -@needs_py310 -def test_post_no_body_q_bar(client: TestClient): - response = client.put("/items/5?q=bar", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5, "q": "bar"} - - -@needs_py310 -def test_post_no_body(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5} - - -@needs_py310 -def test_post_id_foo(client: TestClient): - response = client.put("/items/foo", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "maximum": 1000.0, - "minimum": 0.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": IsDict( - { - "anyOf": [ - {"$ref": "#/components/schemas/Item"}, - {"type": "null"}, - ], - "title": "Item", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/Item"} - ) - } - } - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index cda19918ac..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py +++ /dev/null @@ -1,213 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial001_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_body_q_bar_content(client: TestClient): - response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "q": "bar", - } - - -@needs_py39 -def test_post_no_body_q_bar(client: TestClient): - response = client.put("/items/5?q=bar", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5, "q": "bar"} - - -@needs_py39 -def test_post_no_body(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5} - - -@needs_py39 -def test_post_id_foo(client: TestClient): - response = client.put("/items/foo", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "maximum": 1000.0, - "minimum": 0.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": IsDict( - { - "anyOf": [ - {"$ref": "#/components/schemas/Item"}, - {"type": "null"}, - ], - "title": "Item", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/Item"} - ) - } - } - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 6632919331..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py +++ /dev/null @@ -1,213 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_body_q_bar_content(client: TestClient): - response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5}) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "q": "bar", - } - - -@needs_py310 -def test_post_no_body_q_bar(client: TestClient): - response = client.put("/items/5?q=bar", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5, "q": "bar"} - - -@needs_py310 -def test_post_no_body(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 200 - assert response.json() == {"item_id": 5} - - -@needs_py310 -def test_post_id_foo(client: TestClient): - response = client.put("/items/foo", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["path", "item_id"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["path", "item_id"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "maximum": 1000.0, - "minimum": 0.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": IsDict( - { - "anyOf": [ - {"$ref": "#/components/schemas/Item"}, - {"type": "null"}, - ], - "title": "Item", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/Item"} - ) - } - } - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 c26f8b89bc..d18ceae486 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -1,13 +1,26 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003 import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial003", + pytest.param("tutorial003_py310", marks=needs_py310), + "tutorial003_an", + pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}") + + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py deleted file mode 100644 index 62c7e2fad8..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py +++ /dev/null @@ -1,273 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_an import app - - client = TestClient(app) - return client - - -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py deleted file mode 100644 index f46430fb5a..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py +++ /dev/null @@ -1,279 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -@needs_py310 -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py deleted file mode 100644 index 29071cddca..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py +++ /dev/null @@ -1,279 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -@needs_py39 -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py deleted file mode 100644 index 133afe9b5e..0000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py +++ /dev/null @@ -1,279 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -@needs_py310 -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py index 762073aea6..38ba3c8875 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py @@ -1,13 +1,23 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py39 -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_nested_models.tutorial009 import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial009", + pytest.param("tutorial009_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_nested_models.{request.param}") + + client = TestClient(mod.app) return client 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 deleted file mode 100644 index 24623ceccb..0000000000 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py +++ /dev/null @@ -1,128 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_nested_models.tutorial009_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_body(client: TestClient): - data = {"2": 2.2, "3": 3.3} - response = client.post("/index-weights/", json=data) - assert response.status_code == 200, response.text - assert response.json() == data - - -@needs_py39 -def test_post_invalid_body(client: TestClient): - data = {"foo": 2.2, "3": 3.3} - response = client.post("/index-weights/", json=data) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "int_parsing", - "loc": ["body", "foo", "[key]"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "foo", - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "__key__"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/index-weights/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Index Weights", - "operationId": "create_index_weights_index_weights__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Weights", - "type": "object", - "additionalProperties": {"type": "number"}, - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index e586534a07..f874dc9bd6 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -1,14 +1,23 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_updates.tutorial001 import app +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + pytest.param("tutorial001_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_updates.{request.param}") - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py deleted file mode 100644 index 6bc969d43a..0000000000 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py +++ /dev/null @@ -1,317 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_updates.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_get(client: TestClient): - response = client.get("/items/baz") - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Baz", - "description": None, - "price": 50.2, - "tax": 10.5, - "tags": [], - } - - -@needs_py310 -def test_put(client: TestClient): - response = client.put( - "/items/bar", json={"name": "Barz", "price": 3, "description": None} - ) - assert response.json() == { - "name": "Barz", - "description": None, - "price": 3, - "tax": 10.5, - "tags": [], - } - - -@needs_py310 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "type": "object", - "title": "Item", - "properties": { - "name": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Name", - }, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "price": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Price", - }, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_py310 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py deleted file mode 100644 index a1edb33707..0000000000 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py +++ /dev/null @@ -1,317 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_updates.tutorial001_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get(client: TestClient): - response = client.get("/items/baz") - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Baz", - "description": None, - "price": 50.2, - "tax": 10.5, - "tags": [], - } - - -@needs_py39 -def test_put(client: TestClient): - response = client.put( - "/items/bar", json={"name": "Barz", "price": 3, "description": None} - ) - assert response.json() == { - "name": "Barz", - "description": None, - "price": 3, - "tax": 10.5, - "tags": [], - } - - -@needs_py39 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "type": "object", - "title": "Item", - "properties": { - "name": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Name", - }, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "price": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Price", - }, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_py39 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py index 72db54bd20..a04dba2197 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py @@ -8,31 +8,31 @@ client = TestClient(app) def test_swagger_ui(): response = client.get("/docs") assert response.status_code == 200, response.text - assert ( - '"syntaxHighlight": false' in response.text - ), "syntaxHighlight should be included and converted to JSON" - assert ( - '"dom_id": "#swagger-ui"' in response.text - ), "default configs should be preserved" + assert '"syntaxHighlight": false' in response.text, ( + "syntaxHighlight should be included and converted to JSON" + ) + assert '"dom_id": "#swagger-ui"' in response.text, ( + "default configs should be preserved" + ) assert "presets: [" in response.text, "default configs should be preserved" - assert ( - "SwaggerUIBundle.presets.apis," in response.text - ), "default configs should be preserved" - assert ( - "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text - ), "default configs should be preserved" - assert ( - '"layout": "BaseLayout",' in response.text - ), "default configs should be preserved" - assert ( - '"deepLinking": true,' in response.text - ), "default configs should be preserved" - assert ( - '"showExtensions": true,' in response.text - ), "default configs should be preserved" - assert ( - '"showCommonExtensions": true,' in response.text - ), "default configs should be preserved" + assert "SwaggerUIBundle.presets.apis," in response.text, ( + "default configs should be preserved" + ) + assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, ( + "default configs should be preserved" + ) + assert '"layout": "BaseLayout",' in response.text, ( + "default configs should be preserved" + ) + assert '"deepLinking": true,' in response.text, ( + "default configs should be preserved" + ) + assert '"showExtensions": true,' in response.text, ( + "default configs should be preserved" + ) + assert '"showCommonExtensions": true,' in response.text, ( + "default configs should be preserved" + ) def test_get_users(): diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py index 1669011885..ea56b6f21c 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py @@ -8,34 +8,34 @@ client = TestClient(app) def test_swagger_ui(): response = client.get("/docs") assert response.status_code == 200, response.text - assert ( - '"syntaxHighlight": false' not in response.text - ), "not used parameters should not be included" - assert ( - '"syntaxHighlight.theme": "obsidian"' in response.text - ), "parameters with middle dots should be included in a JSON compatible way" - assert ( - '"dom_id": "#swagger-ui"' in response.text - ), "default configs should be preserved" + assert '"syntaxHighlight": false' not in response.text, ( + "not used parameters should not be included" + ) + assert '"syntaxHighlight": {"theme": "obsidian"}' in response.text, ( + "parameters with middle dots should be included in a JSON compatible way" + ) + assert '"dom_id": "#swagger-ui"' in response.text, ( + "default configs should be preserved" + ) assert "presets: [" in response.text, "default configs should be preserved" - assert ( - "SwaggerUIBundle.presets.apis," in response.text - ), "default configs should be preserved" - assert ( - "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text - ), "default configs should be preserved" - assert ( - '"layout": "BaseLayout",' in response.text - ), "default configs should be preserved" - assert ( - '"deepLinking": true,' in response.text - ), "default configs should be preserved" - assert ( - '"showExtensions": true,' in response.text - ), "default configs should be preserved" - assert ( - '"showCommonExtensions": true,' in response.text - ), "default configs should be preserved" + assert "SwaggerUIBundle.presets.apis," in response.text, ( + "default configs should be preserved" + ) + assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, ( + "default configs should be preserved" + ) + assert '"layout": "BaseLayout",' in response.text, ( + "default configs should be preserved" + ) + assert '"deepLinking": true,' in response.text, ( + "default configs should be preserved" + ) + assert '"showExtensions": true,' in response.text, ( + "default configs should be preserved" + ) + assert '"showCommonExtensions": true,' in response.text, ( + "default configs should be preserved" + ) def test_get_users(): diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py index 187e89ace0..926bbb14f0 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py @@ -8,34 +8,34 @@ client = TestClient(app) def test_swagger_ui(): response = client.get("/docs") assert response.status_code == 200, response.text - assert ( - '"deepLinking": false,' in response.text - ), "overridden configs should be preserved" - assert ( - '"deepLinking": true' not in response.text - ), "overridden configs should not include the old value" - assert ( - '"syntaxHighlight": false' not in response.text - ), "not used parameters should not be included" - assert ( - '"dom_id": "#swagger-ui"' in response.text - ), "default configs should be preserved" + assert '"deepLinking": false,' in response.text, ( + "overridden configs should be preserved" + ) + assert '"deepLinking": true' not in response.text, ( + "overridden configs should not include the old value" + ) + assert '"syntaxHighlight": false' not in response.text, ( + "not used parameters should not be included" + ) + assert '"dom_id": "#swagger-ui"' in response.text, ( + "default configs should be preserved" + ) assert "presets: [" in response.text, "default configs should be preserved" - assert ( - "SwaggerUIBundle.presets.apis," in response.text - ), "default configs should be preserved" - assert ( - "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text - ), "default configs should be preserved" - assert ( - '"layout": "BaseLayout",' in response.text - ), "default configs should be preserved" - assert ( - '"showExtensions": true,' in response.text - ), "default configs should be preserved" - assert ( - '"showCommonExtensions": true,' in response.text - ), "default configs should be preserved" + assert "SwaggerUIBundle.presets.apis," in response.text, ( + "default configs should be preserved" + ) + assert "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text, ( + "default configs should be preserved" + ) + assert '"layout": "BaseLayout",' in response.text, ( + "default configs should be preserved" + ) + assert '"showExtensions": true,' in response.text, ( + "default configs should be preserved" + ) + assert '"showCommonExtensions": true,' in response.text, ( + "default configs should be preserved" + ) def test_get_users(): diff --git a/docs_src/sql_databases/sql_app/tests/__init__.py b/tests/test_tutorial/test_cookie_param_models/__init__.py similarity index 100% rename from docs_src/sql_databases/sql_app/tests/__init__.py rename to tests/test_tutorial/test_cookie_param_models/__init__.py diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py new file mode 100644 index 0000000000..60643185a4 --- /dev/null +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py @@ -0,0 +1,205 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.cookie_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_cookie_param_model(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("fatebook_tracker", "456") + c.cookies.set("googall_tracker", "789") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": "456", + "googall_tracker": "789", + } + + +def test_cookie_param_model_defaults(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": None, + "googall_tracker": None, + } + + +def test_cookie_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "session_id"], + "msg": "Field required", + "input": {}, + } + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.missing", + "loc": ["cookie", "session_id"], + "msg": "field required", + } + ] + } + ) + ) + + +def test_cookie_param_model_extra(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("extra", "track-me-here-too") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == snapshot( + {"session_id": "123", "fatebook_tracker": None, "googall_tracker": None} + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "session_id", + "in": "cookie", + "required": True, + "schema": {"type": "string", "title": "Session Id"}, + }, + { + "name": "fatebook_tracker", + "in": "cookie", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Fatebook Tracker", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Fatebook Tracker", + } + ), + }, + { + "name": "googall_tracker", + "in": "cookie", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Googall Tracker", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Googall Tracker", + } + ), + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py new file mode 100644 index 0000000000..cef6f66309 --- /dev/null +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py @@ -0,0 +1,243 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import ( + needs_py39, + needs_py310, + needs_pydanticv1, + needs_pydanticv2, + pydantic_snapshot, +) + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_an", marks=needs_pydanticv2), + pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.cookie_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_cookie_param_model(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("fatebook_tracker", "456") + c.cookies.set("googall_tracker", "789") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": "456", + "googall_tracker": "789", + } + + +def test_cookie_param_model_defaults(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + response = c.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "session_id": "123", + "fatebook_tracker": None, + "googall_tracker": None, + } + + +def test_cookie_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == pydantic_snapshot( + v2=snapshot( + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "session_id"], + "msg": "Field required", + "input": {}, + } + ] + } + ), + v1=snapshot( + { + "detail": [ + { + "type": "value_error.missing", + "loc": ["cookie", "session_id"], + "msg": "field required", + } + ] + } + ), + ) + + +def test_cookie_param_model_extra(client: TestClient): + with client as c: + c.cookies.set("session_id", "123") + c.cookies.set("extra", "track-me-here-too") + response = c.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "extra"], + "msg": "Extra inputs are not permitted", + "input": "track-me-here-too", + } + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.extra", + "loc": ["cookie", "extra"], + "msg": "extra fields not permitted", + } + ] + } + ) + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "session_id", + "in": "cookie", + "required": True, + "schema": {"type": "string", "title": "Session Id"}, + }, + { + "name": "fatebook_tracker", + "in": "cookie", + "required": False, + "schema": pydantic_snapshot( + v2=snapshot( + { + "anyOf": [ + {"type": "string"}, + {"type": "null"}, + ], + "title": "Fatebook Tracker", + } + ), + v1=snapshot( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Fatebook Tracker", + } + ), + ), + }, + { + "name": "googall_tracker", + "in": "cookie", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Googall Tracker", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Googall Tracker", + } + ), + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py index 7d0e669aba..90e8dfd37c 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py @@ -1,8 +1,27 @@ +import importlib +from types import ModuleType + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.cookie_params.tutorial001 import app +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="mod", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.cookie_params.{request.param}") + + return mod @pytest.mark.parametrize( @@ -19,15 +38,15 @@ from docs_src.cookie_params.tutorial001 import app ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), ], ) -def test(path, cookies, expected_status, expected_response): - client = TestClient(app, cookies=cookies) +def test(path, cookies, expected_status, expected_response, mod: ModuleType): + client = TestClient(mod.app, cookies=cookies) response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response -def test_openapi_schema(): - client = TestClient(app) +def test_openapi_schema(mod: ModuleType): + client = TestClient(mod.app) response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py deleted file mode 100644 index 2505876c87..0000000000 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py +++ /dev/null @@ -1,108 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.cookie_params.tutorial001_an import app - - -@pytest.mark.parametrize( - "path,cookies,expected_status,expected_response", - [ - ("/items", None, 200, {"ads_id": None}), - ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), - ( - "/items", - {"ads_id": "ads_track", "session": "cookiesession"}, - 200, - {"ads_id": "ads_track"}, - ), - ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), - ], -) -def test(path, cookies, expected_status, expected_response): - client = TestClient(app, cookies=cookies) - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -def test_openapi_schema(): - client = TestClient(app) - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Ads Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Ads Id", "type": "string"} - ), - "name": "ads_id", - "in": "cookie", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 108f78b9c8..0000000000 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py +++ /dev/null @@ -1,114 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@needs_py310 -@pytest.mark.parametrize( - "path,cookies,expected_status,expected_response", - [ - ("/items", None, 200, {"ads_id": None}), - ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), - ( - "/items", - {"ads_id": "ads_track", "session": "cookiesession"}, - 200, - {"ads_id": "ads_track"}, - ), - ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), - ], -) -def test(path, cookies, expected_status, expected_response): - from docs_src.cookie_params.tutorial001_an_py310 import app - - client = TestClient(app, cookies=cookies) - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(): - from docs_src.cookie_params.tutorial001_an_py310 import app - - client = TestClient(app) - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Ads Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Ads Id", "type": "string"} - ), - "name": "ads_id", - "in": "cookie", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 8126a10523..0000000000 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py +++ /dev/null @@ -1,114 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@needs_py39 -@pytest.mark.parametrize( - "path,cookies,expected_status,expected_response", - [ - ("/items", None, 200, {"ads_id": None}), - ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), - ( - "/items", - {"ads_id": "ads_track", "session": "cookiesession"}, - 200, - {"ads_id": "ads_track"}, - ), - ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), - ], -) -def test(path, cookies, expected_status, expected_response): - from docs_src.cookie_params.tutorial001_an_py39 import app - - client = TestClient(app, cookies=cookies) - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py39 -def test_openapi_schema(): - from docs_src.cookie_params.tutorial001_an_py39 import app - - client = TestClient(app) - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Ads Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Ads Id", "type": "string"} - ), - "name": "ads_id", - "in": "cookie", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py deleted file mode 100644 index 6711fa5818..0000000000 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py +++ /dev/null @@ -1,114 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@needs_py310 -@pytest.mark.parametrize( - "path,cookies,expected_status,expected_response", - [ - ("/items", None, 200, {"ads_id": None}), - ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}), - ( - "/items", - {"ads_id": "ads_track", "session": "cookiesession"}, - 200, - {"ads_id": "ads_track"}, - ), - ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}), - ], -) -def test(path, cookies, expected_status, expected_response): - from docs_src.cookie_params.tutorial001_py310 import app - - client = TestClient(app, cookies=cookies) - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(): - from docs_src.cookie_params.tutorial001_py310 import app - - client = TestClient(app) - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Ads Id", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Ads Id", "type": "string"} - ), - "name": "ads_id", - "in": "cookie", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py index aff070d747..cb8e8c2248 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py @@ -33,7 +33,7 @@ def test_swagger_ui_oauth2_redirect_html(client: TestClient): def test_redoc_html(client: TestClient): response = client.get("/redoc") assert response.status_code == 200, response.text - assert "https://unpkg.com/redoc@next/bundles/redoc.standalone.js" in response.text + assert "https://unpkg.com/redoc@2/bundles/redoc.standalone.js" in response.text def test_api(client: TestClient): diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py index 6f7355aaa1..647f1c5ddf 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py @@ -1,4 +1,4 @@ -from dirty_equals import IsDict +from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient from docs_src.custom_request_and_route.tutorial002 import app @@ -24,14 +24,16 @@ def test_exception_handler_body_access(): "input": {"numbers": [1, 2, 3]}, } ], - "body": '{"numbers": [1, 2, 3]}', + # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363 + "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'), } } ) | IsDict( # TODO: remove when deprecating Pydantic v1 { "detail": { - "body": '{"numbers": [1, 2, 3]}', + # httpx 0.28.0 switches to compact JSON https://github.com/encode/httpx/issues/3363 + "body": IsOneOf('{"numbers": [1, 2, 3]}', '{"numbers":[1,2,3]}'), "errors": [ { "loc": ["body"], diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001.py index d1324a6411..ed9944912e 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001.py @@ -1,10 +1,27 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.dependencies.tutorial001 import app +from ...utils import needs_py39, needs_py310 -client = TestClient(app) + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + client = TestClient(mod.app) + return client @pytest.mark.parametrize( @@ -17,13 +34,13 @@ client = TestClient(app) ("/users", 200, {"q": None, "skip": 0, "limit": 100}), ], ) -def test_get(path, expected_status, expected_response): +def test_get(path, expected_status, expected_response, client: TestClient): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an.py deleted file mode 100644 index 79c2a1e10e..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_an.py +++ /dev/null @@ -1,183 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.dependencies.tutorial001_an import app - -client = TestClient(app) - - -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ("/items", 200, {"q": None, "skip": 0, "limit": 100}), - ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}), - ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), - ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), - ("/users", 200, {"q": None, "skip": 0, "limit": 100}), - ], -) -def test_get(path, expected_status, expected_response): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py deleted file mode 100644 index 7db55a1c55..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py +++ /dev/null @@ -1,191 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial001_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ("/items", 200, {"q": None, "skip": 0, "limit": 100}), - ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}), - ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), - ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), - ("/users", 200, {"q": None, "skip": 0, "limit": 100}), - ], -) -def test_get(path, expected_status, expected_response, client: TestClient): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py deleted file mode 100644 index 68c2dedb1b..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py +++ /dev/null @@ -1,191 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial001_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ("/items", 200, {"q": None, "skip": 0, "limit": 100}), - ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}), - ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), - ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), - ("/users", 200, {"q": None, "skip": 0, "limit": 100}), - ], -) -def test_get(path, expected_status, expected_response, client: TestClient): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py deleted file mode 100644 index 381eecb639..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py +++ /dev/null @@ -1,191 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ("/items", 200, {"q": None, "skip": 0, "limit": 100}), - ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}), - ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}), - ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}), - ("/users", 200, {"q": None, "skip": 0, "limit": 100}), - ], -) -def test_get(path, expected_status, expected_response, client: TestClient): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py index 5c5d34cfcc..8221c83d44 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004.py @@ -1,10 +1,27 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.dependencies.tutorial004 import app +from ...utils import needs_py39, needs_py310 -client = TestClient(app) + +@pytest.fixture( + name="client", + params=[ + "tutorial004", + pytest.param("tutorial004_py310", marks=needs_py310), + "tutorial004_an", + pytest.param("tutorial004_an_py39", marks=needs_py39), + pytest.param("tutorial004_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + client = TestClient(mod.app) + return client @pytest.mark.parametrize( @@ -55,13 +72,13 @@ client = TestClient(app) ), ], ) -def test_get(path, expected_status, expected_response): +def test_get(path, expected_status, expected_response, client: TestClient): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an.py deleted file mode 100644 index c5c1a1fb88..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_an.py +++ /dev/null @@ -1,162 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.dependencies.tutorial004_an import app - -client = TestClient(app) - - -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ( - "/items", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ] - }, - ), - ( - "/items?q=foo", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ], - "q": "foo", - }, - ), - ( - "/items?q=foo&skip=1", - 200, - {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"}, - ), - ( - "/items?q=bar&limit=2", - 200, - {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?q=bar&skip=1&limit=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?limit=1&q=bar&skip=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ], -) -def test_get(path, expected_status, expected_response): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py deleted file mode 100644 index 6fd093ddb1..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py +++ /dev/null @@ -1,170 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial004_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ( - "/items", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ] - }, - ), - ( - "/items?q=foo", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ], - "q": "foo", - }, - ), - ( - "/items?q=foo&skip=1", - 200, - {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"}, - ), - ( - "/items?q=bar&limit=2", - 200, - {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?q=bar&skip=1&limit=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?limit=1&q=bar&skip=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ], -) -def test_get(path, expected_status, expected_response, client: TestClient): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py deleted file mode 100644 index fbbe84cc94..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py +++ /dev/null @@ -1,170 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial004_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ( - "/items", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ] - }, - ), - ( - "/items?q=foo", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ], - "q": "foo", - }, - ), - ( - "/items?q=foo&skip=1", - 200, - {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"}, - ), - ( - "/items?q=bar&limit=2", - 200, - {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?q=bar&skip=1&limit=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?limit=1&q=bar&skip=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ], -) -def test_get(path, expected_status, expected_response, client: TestClient): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py deleted file mode 100644 index 845b098e79..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py +++ /dev/null @@ -1,170 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial004_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,expected_status,expected_response", - [ - ( - "/items", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ] - }, - ), - ( - "/items?q=foo", - 200, - { - "items": [ - {"item_name": "Foo"}, - {"item_name": "Bar"}, - {"item_name": "Baz"}, - ], - "q": "foo", - }, - ), - ( - "/items?q=foo&skip=1", - 200, - {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"}, - ), - ( - "/items?q=bar&limit=2", - 200, - {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?q=bar&skip=1&limit=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ( - "/items?limit=1&q=bar&skip=1", - 200, - {"items": [{"item_name": "Bar"}], "q": "bar"}, - ), - ], -) -def test_get(path, expected_status, expected_response, client: TestClient): - response = client.get(path) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Q", "type": "string"} - ), - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py index 5f14d9a3ba..4530762f76 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py @@ -1,12 +1,28 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.dependencies.tutorial006 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_get_no_headers(): +@pytest.fixture( + name="client", + params=[ + "tutorial006", + "tutorial006_an", + pytest.param("tutorial006_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get_no_headers(client: TestClient): response = client.get("/items/") assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -45,13 +61,13 @@ def test_get_no_headers(): ) -def test_get_invalid_one_header(): +def test_get_invalid_one_header(client: TestClient): response = client.get("/items/", headers={"X-Token": "invalid"}) assert response.status_code == 400, response.text assert response.json() == {"detail": "X-Token header invalid"} -def test_get_invalid_second_header(): +def test_get_invalid_second_header(client: TestClient): response = client.get( "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} ) @@ -59,7 +75,7 @@ def test_get_invalid_second_header(): assert response.json() == {"detail": "X-Key header invalid"} -def test_get_valid_headers(): +def test_get_valid_headers(client: TestClient): response = client.get( "/items/", headers={ @@ -71,7 +87,7 @@ def test_get_valid_headers(): assert response.json() == [{"item": "Foo"}, {"item": "Bar"}] -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006_an.py b/tests/test_tutorial/test_dependencies/test_tutorial006_an.py deleted file mode 100644 index a307ff8087..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial006_an.py +++ /dev/null @@ -1,149 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.dependencies.tutorial006_an import app - -client = TestClient(app) - - -def test_get_no_headers(): - response = client.get("/items/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_get_invalid_one_header(): - response = client.get("/items/", headers={"X-Token": "invalid"}) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Token header invalid"} - - -def test_get_invalid_second_header(): - response = client.get( - "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} - ) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Key header invalid"} - - -def test_get_valid_headers(): - response = client.get( - "/items/", - headers={ - "X-Token": "fake-super-secret-token", - "X-Key": "fake-super-secret-key", - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [{"item": "Foo"}, {"item": "Bar"}] - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py deleted file mode 100644 index b41b1537ec..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py +++ /dev/null @@ -1,161 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial006_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get_no_headers(client: TestClient): - response = client.get("/items/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_get_invalid_one_header(client: TestClient): - response = client.get("/items/", headers={"X-Token": "invalid"}) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Token header invalid"} - - -@needs_py39 -def test_get_invalid_second_header(client: TestClient): - response = client.get( - "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} - ) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Key header invalid"} - - -@needs_py39 -def test_get_valid_headers(client: TestClient): - response = client.get( - "/items/", - headers={ - "X-Token": "fake-super-secret-token", - "X-Key": "fake-super-secret-key", - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [{"item": "Foo"}, {"item": "Bar"}] - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008b.py b/tests/test_tutorial/test_dependencies/test_tutorial008b.py index 86acba9e4f..4d70922657 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008b.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008b.py @@ -1,23 +1,39 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.dependencies.tutorial008b import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_get_no_item(): +@pytest.fixture( + name="client", + params=[ + "tutorial008b", + "tutorial008b_an", + pytest.param("tutorial008b_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get_no_item(client: TestClient): response = client.get("/items/foo") assert response.status_code == 404, response.text assert response.json() == {"detail": "Item not found"} -def test_owner_error(): +def test_owner_error(client: TestClient): response = client.get("/items/plumbus") assert response.status_code == 400, response.text assert response.json() == {"detail": "Owner error: Rick"} -def test_get_item(): +def test_get_item(client: TestClient): response = client.get("/items/portal-gun") assert response.status_code == 200, response.text assert response.json() == {"description": "Gun to create portals", "owner": "Rick"} diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008b_an.py b/tests/test_tutorial/test_dependencies/test_tutorial008b_an.py deleted file mode 100644 index 7f51fc52a5..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial008b_an.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.dependencies.tutorial008b_an import app - -client = TestClient(app) - - -def test_get_no_item(): - response = client.get("/items/foo") - assert response.status_code == 404, response.text - assert response.json() == {"detail": "Item not found"} - - -def test_owner_error(): - response = client.get("/items/plumbus") - assert response.status_code == 400, response.text - assert response.json() == {"detail": "Owner error: Rick"} - - -def test_get_item(): - response = client.get("/items/portal-gun") - assert response.status_code == 200, response.text - assert response.json() == {"description": "Gun to create portals", "owner": "Rick"} diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008b_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial008b_an_py39.py deleted file mode 100644 index 7d24809a8a..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial008b_an_py39.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial008b_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get_no_item(client: TestClient): - response = client.get("/items/foo") - assert response.status_code == 404, response.text - assert response.json() == {"detail": "Item not found"} - - -@needs_py39 -def test_owner_error(client: TestClient): - response = client.get("/items/plumbus") - assert response.status_code == 400, response.text - assert response.json() == {"detail": "Owner error: Rick"} - - -@needs_py39 -def test_get_item(client: TestClient): - response = client.get("/items/portal-gun") - assert response.status_code == 200, response.text - assert response.json() == {"description": "Gun to create portals", "owner": "Rick"} diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008c.py b/tests/test_tutorial/test_dependencies/test_tutorial008c.py index 27be8895a9..369b0a221d 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008c.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008c.py @@ -1,38 +1,50 @@ +import importlib +from types import ModuleType + import pytest from fastapi.exceptions import FastAPIError from fastapi.testclient import TestClient - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial008c import app - - client = TestClient(app) - return client +from ...utils import needs_py39 -def test_get_no_item(client: TestClient): +@pytest.fixture( + name="mod", + params=[ + "tutorial008c", + "tutorial008c_an", + pytest.param("tutorial008c_an_py39", marks=needs_py39), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + return mod + + +def test_get_no_item(mod: ModuleType): + client = TestClient(mod.app) response = client.get("/items/foo") assert response.status_code == 404, response.text assert response.json() == {"detail": "Item not found, there's only a plumbus here"} -def test_get(client: TestClient): +def test_get(mod: ModuleType): + client = TestClient(mod.app) response = client.get("/items/plumbus") assert response.status_code == 200, response.text assert response.json() == "plumbus" -def test_fastapi_error(client: TestClient): +def test_fastapi_error(mod: ModuleType): + client = TestClient(mod.app) with pytest.raises(FastAPIError) as exc_info: client.get("/items/portal-gun") - assert "No response object was returned" in exc_info.value.args[0] + assert "raising an exception and a dependency with yield" in exc_info.value.args[0] -def test_internal_server_error(): - from docs_src.dependencies.tutorial008c import app - - client = TestClient(app, raise_server_exceptions=False) +def test_internal_server_error(mod: ModuleType): + client = TestClient(mod.app, raise_server_exceptions=False) response = client.get("/items/portal-gun") assert response.status_code == 500, response.text assert response.text == "Internal Server Error" diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008c_an.py b/tests/test_tutorial/test_dependencies/test_tutorial008c_an.py deleted file mode 100644 index 10fa1ab508..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial008c_an.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -from fastapi.exceptions import FastAPIError -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial008c_an import app - - client = TestClient(app) - return client - - -def test_get_no_item(client: TestClient): - response = client.get("/items/foo") - assert response.status_code == 404, response.text - assert response.json() == {"detail": "Item not found, there's only a plumbus here"} - - -def test_get(client: TestClient): - response = client.get("/items/plumbus") - assert response.status_code == 200, response.text - assert response.json() == "plumbus" - - -def test_fastapi_error(client: TestClient): - with pytest.raises(FastAPIError) as exc_info: - client.get("/items/portal-gun") - assert "No response object was returned" in exc_info.value.args[0] - - -def test_internal_server_error(): - from docs_src.dependencies.tutorial008c_an import app - - client = TestClient(app, raise_server_exceptions=False) - response = client.get("/items/portal-gun") - assert response.status_code == 500, response.text - assert response.text == "Internal Server Error" diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008c_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial008c_an_py39.py deleted file mode 100644 index 6c3acff509..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial008c_an_py39.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from fastapi.exceptions import FastAPIError -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial008c_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get_no_item(client: TestClient): - response = client.get("/items/foo") - assert response.status_code == 404, response.text - assert response.json() == {"detail": "Item not found, there's only a plumbus here"} - - -@needs_py39 -def test_get(client: TestClient): - response = client.get("/items/plumbus") - assert response.status_code == 200, response.text - assert response.json() == "plumbus" - - -@needs_py39 -def test_fastapi_error(client: TestClient): - with pytest.raises(FastAPIError) as exc_info: - client.get("/items/portal-gun") - assert "No response object was returned" in exc_info.value.args[0] - - -@needs_py39 -def test_internal_server_error(): - from docs_src.dependencies.tutorial008c_an_py39 import app - - client = TestClient(app, raise_server_exceptions=False) - response = client.get("/items/portal-gun") - assert response.status_code == 500, response.text - assert response.text == "Internal Server Error" diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008d.py b/tests/test_tutorial/test_dependencies/test_tutorial008d.py index 0434961123..bc99bb3835 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008d.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008d.py @@ -1,41 +1,51 @@ +import importlib +from types import ModuleType + import pytest from fastapi.testclient import TestClient - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial008d import app - - client = TestClient(app) - return client +from ...utils import needs_py39 -def test_get_no_item(client: TestClient): +@pytest.fixture( + name="mod", + params=[ + "tutorial008d", + "tutorial008d_an", + pytest.param("tutorial008d_an_py39", marks=needs_py39), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + return mod + + +def test_get_no_item(mod: ModuleType): + client = TestClient(mod.app) response = client.get("/items/foo") assert response.status_code == 404, response.text assert response.json() == {"detail": "Item not found, there's only a plumbus here"} -def test_get(client: TestClient): +def test_get(mod: ModuleType): + client = TestClient(mod.app) response = client.get("/items/plumbus") assert response.status_code == 200, response.text assert response.json() == "plumbus" -def test_internal_error(client: TestClient): - from docs_src.dependencies.tutorial008d import InternalError - - with pytest.raises(InternalError) as exc_info: +def test_internal_error(mod: ModuleType): + client = TestClient(mod.app) + with pytest.raises(mod.InternalError) as exc_info: client.get("/items/portal-gun") assert ( exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick" ) -def test_internal_server_error(): - from docs_src.dependencies.tutorial008d import app - - client = TestClient(app, raise_server_exceptions=False) +def test_internal_server_error(mod: ModuleType): + client = TestClient(mod.app, raise_server_exceptions=False) response = client.get("/items/portal-gun") assert response.status_code == 500, response.text assert response.text == "Internal Server Error" diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008d_an.py b/tests/test_tutorial/test_dependencies/test_tutorial008d_an.py deleted file mode 100644 index f29d8cdbe5..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial008d_an.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial008d_an import app - - client = TestClient(app) - return client - - -def test_get_no_item(client: TestClient): - response = client.get("/items/foo") - assert response.status_code == 404, response.text - assert response.json() == {"detail": "Item not found, there's only a plumbus here"} - - -def test_get(client: TestClient): - response = client.get("/items/plumbus") - assert response.status_code == 200, response.text - assert response.json() == "plumbus" - - -def test_internal_error(client: TestClient): - from docs_src.dependencies.tutorial008d_an import InternalError - - with pytest.raises(InternalError) as exc_info: - client.get("/items/portal-gun") - assert ( - exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick" - ) - - -def test_internal_server_error(): - from docs_src.dependencies.tutorial008d_an import app - - client = TestClient(app, raise_server_exceptions=False) - response = client.get("/items/portal-gun") - assert response.status_code == 500, response.text - assert response.text == "Internal Server Error" diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008d_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial008d_an_py39.py deleted file mode 100644 index 0a585f4adb..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial008d_an_py39.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial008d_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get_no_item(client: TestClient): - response = client.get("/items/foo") - assert response.status_code == 404, response.text - assert response.json() == {"detail": "Item not found, there's only a plumbus here"} - - -@needs_py39 -def test_get(client: TestClient): - response = client.get("/items/plumbus") - assert response.status_code == 200, response.text - assert response.json() == "plumbus" - - -@needs_py39 -def test_internal_error(client: TestClient): - from docs_src.dependencies.tutorial008d_an_py39 import InternalError - - with pytest.raises(InternalError) as exc_info: - client.get("/items/portal-gun") - assert ( - exc_info.value.args[0] == "The portal gun is too dangerous to be owned by Rick" - ) - - -@needs_py39 -def test_internal_server_error(): - from docs_src.dependencies.tutorial008d_an_py39 import app - - client = TestClient(app, raise_server_exceptions=False) - response = client.get("/items/portal-gun") - assert response.status_code == 500, response.text - assert response.text == "Internal Server Error" diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py index 6b53c83bb5..0af17e9bc5 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py @@ -1,12 +1,28 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.dependencies.tutorial012 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_get_no_headers_items(): +@pytest.fixture( + name="client", + params=[ + "tutorial012", + "tutorial012_an", + pytest.param("tutorial012_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dependencies.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get_no_headers_items(client: TestClient): response = client.get("/items/") assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -45,7 +61,7 @@ def test_get_no_headers_items(): ) -def test_get_no_headers_users(): +def test_get_no_headers_users(client: TestClient): response = client.get("/users/") assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -84,19 +100,19 @@ def test_get_no_headers_users(): ) -def test_get_invalid_one_header_items(): +def test_get_invalid_one_header_items(client: TestClient): response = client.get("/items/", headers={"X-Token": "invalid"}) assert response.status_code == 400, response.text assert response.json() == {"detail": "X-Token header invalid"} -def test_get_invalid_one_users(): +def test_get_invalid_one_users(client: TestClient): response = client.get("/users/", headers={"X-Token": "invalid"}) assert response.status_code == 400, response.text assert response.json() == {"detail": "X-Token header invalid"} -def test_get_invalid_second_header_items(): +def test_get_invalid_second_header_items(client: TestClient): response = client.get( "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} ) @@ -104,7 +120,7 @@ def test_get_invalid_second_header_items(): assert response.json() == {"detail": "X-Key header invalid"} -def test_get_invalid_second_header_users(): +def test_get_invalid_second_header_users(client: TestClient): response = client.get( "/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} ) @@ -112,7 +128,7 @@ def test_get_invalid_second_header_users(): assert response.json() == {"detail": "X-Key header invalid"} -def test_get_valid_headers_items(): +def test_get_valid_headers_items(client: TestClient): response = client.get( "/items/", headers={ @@ -124,7 +140,7 @@ def test_get_valid_headers_items(): assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}] -def test_get_valid_headers_users(): +def test_get_valid_headers_users(client: TestClient): response = client.get( "/users/", headers={ @@ -136,7 +152,7 @@ def test_get_valid_headers_users(): assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012_an.py b/tests/test_tutorial/test_dependencies/test_tutorial012_an.py deleted file mode 100644 index 75adb69fc7..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial012_an.py +++ /dev/null @@ -1,250 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.dependencies.tutorial012_an import app - -client = TestClient(app) - - -def test_get_no_headers_items(): - response = client.get("/items/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_get_no_headers_users(): - response = client.get("/users/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_get_invalid_one_header_items(): - response = client.get("/items/", headers={"X-Token": "invalid"}) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Token header invalid"} - - -def test_get_invalid_one_users(): - response = client.get("/users/", headers={"X-Token": "invalid"}) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Token header invalid"} - - -def test_get_invalid_second_header_items(): - response = client.get( - "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} - ) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Key header invalid"} - - -def test_get_invalid_second_header_users(): - response = client.get( - "/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} - ) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Key header invalid"} - - -def test_get_valid_headers_items(): - response = client.get( - "/items/", - headers={ - "X-Token": "fake-super-secret-token", - "X-Key": "fake-super-secret-key", - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}] - - -def test_get_valid_headers_users(): - response = client.get( - "/users/", - headers={ - "X-Token": "fake-super-secret-token", - "X-Key": "fake-super-secret-key", - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py deleted file mode 100644 index e0a3d1ec27..0000000000 --- a/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py +++ /dev/null @@ -1,266 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.dependencies.tutorial012_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get_no_headers_items(client: TestClient): - response = client.get("/items/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_get_no_headers_users(client: TestClient): - response = client.get("/users/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["header", "x-token"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["header", "x-key"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["header", "x-token"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["header", "x-key"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_get_invalid_one_header_items(client: TestClient): - response = client.get("/items/", headers={"X-Token": "invalid"}) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Token header invalid"} - - -@needs_py39 -def test_get_invalid_one_users(client: TestClient): - response = client.get("/users/", headers={"X-Token": "invalid"}) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Token header invalid"} - - -@needs_py39 -def test_get_invalid_second_header_items(client: TestClient): - response = client.get( - "/items/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} - ) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Key header invalid"} - - -@needs_py39 -def test_get_invalid_second_header_users(client: TestClient): - response = client.get( - "/users/", headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"} - ) - assert response.status_code == 400, response.text - assert response.json() == {"detail": "X-Key header invalid"} - - -@needs_py39 -def test_get_valid_headers_items(client: TestClient): - response = client.get( - "/items/", - headers={ - "X-Token": "fake-super-secret-token", - "X-Key": "fake-super-secret-key", - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}] - - -@needs_py39 -def test_get_valid_headers_users(client: TestClient): - response = client.get( - "/users/", - headers={ - "X-Token": "fake-super-secret-token", - "X-Key": "fake-super-secret-key", - }, - ) - assert response.status_code == 200, response.text - assert response.json() == [{"username": "Rick"}, {"username": "Morty"}] - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } 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 5558671b91..b816c9cab5 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py @@ -1,12 +1,30 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.extra_data_types.tutorial001 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -def test_extra_types(): +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.extra_data_types.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_extra_types(client: TestClient): item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" data = { "start_datetime": "2018-12-22T14:00:00+00:00", @@ -27,7 +45,7 @@ def test_extra_types(): assert response.json() == expected_response -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { 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 deleted file mode 100644 index e309f8bd6b..0000000000 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py +++ /dev/null @@ -1,175 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.extra_data_types.tutorial001_an import app - -client = TestClient(app) - - -def test_extra_types(): - item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" - data = { - "start_datetime": "2018-12-22T14:00:00+00:00", - "end_datetime": "2018-12-24T15:00:00+00:00", - "repeat_at": "15:30:00", - "process_after": 300, - } - expected_response = data.copy() - expected_response.update( - { - "start_process": "2018-12-22T14:05:00+00:00", - "duration": 176_100, - "item_id": item_id, - } - ) - response = client.put(f"/items/{item_id}", json=data) - assert response.status_code == 200, response.text - assert response.json() == expected_response - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "format": "uuid", - }, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ) - } - }, - }, - } - } - }, - "components": { - "schemas": { - "Body_read_items_items__item_id__put": { - "title": "Body_read_items_items__item_id__put", - "type": "object", - "properties": { - "start_datetime": { - "title": "Start Datetime", - "type": "string", - "format": "date-time", - }, - "end_datetime": { - "title": "End Datetime", - "type": "string", - "format": "date-time", - }, - "repeat_at": IsDict( - { - "title": "Repeat At", - "anyOf": [ - {"type": "string", "format": "time"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Repeat At", - "type": "string", - "format": "time", - } - ), - "process_after": IsDict( - { - "title": "Process After", - "type": "string", - "format": "duration", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Process After", - "type": "number", - "format": "time-delta", - } - ), - }, - "required": ["start_datetime", "end_datetime", "process_after"], - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index ca110dc00d..0000000000 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py +++ /dev/null @@ -1,184 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.extra_data_types.tutorial001_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_extra_types(client: TestClient): - item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" - data = { - "start_datetime": "2018-12-22T14:00:00+00:00", - "end_datetime": "2018-12-24T15:00:00+00:00", - "repeat_at": "15:30:00", - "process_after": 300, - } - expected_response = data.copy() - expected_response.update( - { - "start_process": "2018-12-22T14:05:00+00:00", - "duration": 176_100, - "item_id": item_id, - } - ) - response = client.put(f"/items/{item_id}", json=data) - assert response.status_code == 200, response.text - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "format": "uuid", - }, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ) - } - }, - }, - } - } - }, - "components": { - "schemas": { - "Body_read_items_items__item_id__put": { - "title": "Body_read_items_items__item_id__put", - "type": "object", - "properties": { - "start_datetime": { - "title": "Start Datetime", - "type": "string", - "format": "date-time", - }, - "end_datetime": { - "title": "End Datetime", - "type": "string", - "format": "date-time", - }, - "repeat_at": IsDict( - { - "title": "Repeat At", - "anyOf": [ - {"type": "string", "format": "time"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Repeat At", - "type": "string", - "format": "time", - } - ), - "process_after": IsDict( - { - "title": "Process After", - "type": "string", - "format": "duration", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Process After", - "type": "number", - "format": "time-delta", - } - ), - }, - "required": ["start_datetime", "end_datetime", "process_after"], - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 3386fb1fd8..0000000000 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py +++ /dev/null @@ -1,184 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.extra_data_types.tutorial001_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_extra_types(client: TestClient): - item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" - data = { - "start_datetime": "2018-12-22T14:00:00+00:00", - "end_datetime": "2018-12-24T15:00:00+00:00", - "repeat_at": "15:30:00", - "process_after": 300, - } - expected_response = data.copy() - expected_response.update( - { - "start_process": "2018-12-22T14:05:00+00:00", - "duration": 176_100, - "item_id": item_id, - } - ) - response = client.put(f"/items/{item_id}", json=data) - assert response.status_code == 200, response.text - assert response.json() == expected_response - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "format": "uuid", - }, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ) - } - }, - }, - } - } - }, - "components": { - "schemas": { - "Body_read_items_items__item_id__put": { - "title": "Body_read_items_items__item_id__put", - "type": "object", - "properties": { - "start_datetime": { - "title": "Start Datetime", - "type": "string", - "format": "date-time", - }, - "end_datetime": { - "title": "End Datetime", - "type": "string", - "format": "date-time", - }, - "repeat_at": IsDict( - { - "title": "Repeat At", - "anyOf": [ - {"type": "string", "format": "time"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Repeat At", - "type": "string", - "format": "time", - } - ), - "process_after": IsDict( - { - "title": "Process After", - "type": "string", - "format": "duration", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Process After", - "type": "number", - "format": "time-delta", - } - ), - }, - "required": ["start_datetime", "end_datetime", "process_after"], - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 50c9aefdf2..0000000000 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py +++ /dev/null @@ -1,184 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.extra_data_types.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_extra_types(client: TestClient): - item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e" - data = { - "start_datetime": "2018-12-22T14:00:00+00:00", - "end_datetime": "2018-12-24T15:00:00+00:00", - "repeat_at": "15:30:00", - "process_after": 300, - } - expected_response = data.copy() - expected_response.update( - { - "start_process": "2018-12-22T14:05:00+00:00", - "duration": 176_100, - "item_id": item_id, - } - ) - response = client.put(f"/items/{item_id}", json=data) - assert response.status_code == 200, response.text - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "format": "uuid", - }, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_read_items_items__item_id__put" - } - ) - } - }, - }, - } - } - }, - "components": { - "schemas": { - "Body_read_items_items__item_id__put": { - "title": "Body_read_items_items__item_id__put", - "type": "object", - "properties": { - "start_datetime": { - "title": "Start Datetime", - "type": "string", - "format": "date-time", - }, - "end_datetime": { - "title": "End Datetime", - "type": "string", - "format": "date-time", - }, - "repeat_at": IsDict( - { - "title": "Repeat At", - "anyOf": [ - {"type": "string", "format": "time"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Repeat At", - "type": "string", - "format": "time", - } - ), - "process_after": IsDict( - { - "title": "Process After", - "type": "string", - "format": "duration", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Process After", - "type": "number", - "format": "time-delta", - } - ), - }, - "required": ["start_datetime", "end_datetime", "process_after"], - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py index 0ccb99948f..73aa299039 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py @@ -1,12 +1,27 @@ +import importlib + +import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from docs_src.extra_models.tutorial003 import app - -client = TestClient(app) +from ...utils import needs_py310 -def test_get_car(): +@pytest.fixture( + name="client", + params=[ + "tutorial003", + pytest.param("tutorial003_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.extra_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get_car(client: TestClient): response = client.get("/items/item1") assert response.status_code == 200, response.text assert response.json() == { @@ -15,7 +30,7 @@ def test_get_car(): } -def test_get_plane(): +def test_get_plane(client: TestClient): response = client.get("/items/item2") assert response.status_code == 200, response.text assert response.json() == { @@ -25,7 +40,7 @@ def test_get_plane(): } -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py deleted file mode 100644 index b2fe65fd9f..0000000000 --- a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py +++ /dev/null @@ -1,144 +0,0 @@ -import pytest -from dirty_equals import IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.extra_models.tutorial003_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_get_car(client: TestClient): - response = client.get("/items/item1") - assert response.status_code == 200, response.text - assert response.json() == { - "description": "All my friends drive a low rider", - "type": "car", - } - - -@needs_py310 -def test_get_plane(client: TestClient): - response = client.get("/items/item2") - assert response.status_code == 200, response.text - assert response.json() == { - "description": "Music is my aeroplane, it's my aeroplane", - "type": "plane", - "size": 5, - } - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Item Items Item Id Get", - "anyOf": [ - {"$ref": "#/components/schemas/PlaneItem"}, - {"$ref": "#/components/schemas/CarItem"}, - ], - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "PlaneItem": { - "title": "PlaneItem", - "required": IsOneOf( - ["description", "type", "size"], - # TODO: remove when deprecating Pydantic v1 - ["description", "size"], - ), - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "plane"}, - "size": {"title": "Size", "type": "integer"}, - }, - }, - "CarItem": { - "title": "CarItem", - "required": IsOneOf( - ["description", "type"], - # TODO: remove when deprecating Pydantic v1 - ["description"], - ), - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "type": {"title": "Type", "type": "string", "default": "car"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py index 71f6a8c700..7628db30c7 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py @@ -1,11 +1,26 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.extra_models.tutorial004 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_get_items(): +@pytest.fixture( + name="client", + params=[ + "tutorial004", + pytest.param("tutorial004_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.extra_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get_items(client: TestClient): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [ @@ -14,7 +29,7 @@ def test_get_items(): ] -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py deleted file mode 100644 index 5475b92e10..0000000000 --- a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.extra_models.tutorial004_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get_items(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200, response.text - assert response.json() == [ - {"name": "Foo", "description": "There comes my hero"}, - {"name": "Red", "description": "It's my aeroplane"}, - ] - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "description"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - } - } - }, - } diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py index b0861c37f7..553e442385 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py @@ -1,17 +1,32 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.extra_models.tutorial005 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_get_items(): +@pytest.fixture( + name="client", + params=[ + "tutorial005", + pytest.param("tutorial005_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.extra_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get_items(client: TestClient): response = client.get("/keyword-weights/") assert response.status_code == 200, response.text assert response.json() == {"foo": 2.3, "bar": 3.4} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py deleted file mode 100644 index 7278e93c36..0000000000 --- a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.extra_models.tutorial005_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_get_items(client: TestClient): - response = client.get("/keyword-weights/") - assert response.status_code == 200, response.text - assert response.json() == {"foo": 2.3, "bar": 3.4} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/keyword-weights/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Keyword Weights Keyword Weights Get", - "type": "object", - "additionalProperties": {"type": "number"}, - } - } - }, - } - }, - "summary": "Read Keyword Weights", - "operationId": "read_keyword_weights_keyword_weights__get", - } - } - }, - } diff --git a/docs_src/sql_databases/sql_app_py310/__init__.py b/tests/test_tutorial/test_header_param_models/__init__.py similarity index 100% rename from docs_src/sql_databases/sql_app_py310/__init__.py rename to tests/test_tutorial/test_header_param_models/__init__.py diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial001.py b/tests/test_tutorial/test_header_param_models/test_tutorial001.py new file mode 100644 index 0000000000..bc876897b2 --- /dev/null +++ b/tests/test_tutorial/test_header_param_models/test_tutorial001.py @@ -0,0 +1,238 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.header_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_header_param_model(client: TestClient): + response = client.get( + "/items/", + headers=[ + ("save-data", "true"), + ("if-modified-since", "yesterday"), + ("traceparent", "123"), + ("x-tag", "one"), + ("x-tag", "two"), + ], + ) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": "yesterday", + "traceparent": "123", + "x_tag": ["one", "two"], + } + + +def test_header_param_model_defaults(client: TestClient): + response = client.get("/items/", headers=[("save-data", "true")]) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + + +def test_header_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": { + "x_tag": [], + "host": "testserver", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "user-agent": "testclient", + }, + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.missing", + "loc": ["header", "save_data"], + "msg": "field required", + } + ) + ] + } + ) + + +def test_header_param_model_extra(client: TestClient): + response = client.get( + "/items/", headers=[("save-data", "true"), ("tool", "plumbus")] + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "host", + "in": "header", + "required": True, + "schema": {"type": "string", "title": "Host"}, + }, + { + "name": "save-data", + "in": "header", + "required": True, + "schema": {"type": "boolean", "title": "Save Data"}, + }, + { + "name": "if-modified-since", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "If Modified Since", + } + ), + }, + { + "name": "traceparent", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Traceparent", + } + ), + }, + { + "name": "x-tag", + "in": "header", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "X Tag", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial002.py b/tests/test_tutorial/test_header_param_models/test_tutorial002.py new file mode 100644 index 0000000000..0615521c43 --- /dev/null +++ b/tests/test_tutorial/test_header_param_models/test_tutorial002.py @@ -0,0 +1,249 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_an", marks=needs_pydanticv2), + pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.header_param_models.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_header_param_model(client: TestClient): + response = client.get( + "/items/", + headers=[ + ("save-data", "true"), + ("if-modified-since", "yesterday"), + ("traceparent", "123"), + ("x-tag", "one"), + ("x-tag", "two"), + ], + ) + assert response.status_code == 200, response.text + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": "yesterday", + "traceparent": "123", + "x_tag": ["one", "two"], + } + + +def test_header_param_model_defaults(client: TestClient): + response = client.get("/items/", headers=[("save-data", "true")]) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + + +def test_header_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": {"x_tag": [], "host": "testserver"}, + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.missing", + "loc": ["header", "save_data"], + "msg": "field required", + } + ) + ] + } + ) + + +def test_header_param_model_extra(client: TestClient): + response = client.get( + "/items/", headers=[("save-data", "true"), ("tool", "plumbus")] + ) + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.extra", + "loc": ["header", "tool"], + "msg": "extra fields not permitted", + } + ) + ] + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "host", + "in": "header", + "required": True, + "schema": {"type": "string", "title": "Host"}, + }, + { + "name": "save-data", + "in": "header", + "required": True, + "schema": {"type": "boolean", "title": "Save Data"}, + }, + { + "name": "if-modified-since", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "If Modified Since", + } + ), + }, + { + "name": "traceparent", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Traceparent", + } + ), + }, + { + "name": "x-tag", + "in": "header", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "X Tag", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial003.py b/tests/test_tutorial/test_header_param_models/test_tutorial003.py new file mode 100644 index 0000000000..60940e1da2 --- /dev/null +++ b/tests/test_tutorial/test_header_param_models/test_tutorial003.py @@ -0,0 +1,285 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial003", + pytest.param("tutorial003_py39", marks=needs_py39), + pytest.param("tutorial003_py310", marks=needs_py310), + "tutorial003_an", + pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.header_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_header_param_model(client: TestClient): + response = client.get( + "/items/", + headers=[ + ("save_data", "true"), + ("if_modified_since", "yesterday"), + ("traceparent", "123"), + ("x_tag", "one"), + ("x_tag", "two"), + ], + ) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": "yesterday", + "traceparent": "123", + "x_tag": ["one", "two"], + } + + +def test_header_param_model_no_underscore(client: TestClient): + response = client.get( + "/items/", + headers=[ + ("save-data", "true"), + ("if-modified-since", "yesterday"), + ("traceparent", "123"), + ("x-tag", "one"), + ("x-tag", "two"), + ], + ) + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": { + "host": "testserver", + "traceparent": "123", + "x_tag": [], + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "user-agent": "testclient", + "save-data": "true", + "if-modified-since": "yesterday", + "x-tag": "two", + }, + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.missing", + "loc": ["header", "save_data"], + "msg": "field required", + } + ) + ] + } + ) + + +def test_header_param_model_defaults(client: TestClient): + response = client.get("/items/", headers=[("save_data", "true")]) + assert response.status_code == 200 + assert response.json() == { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + + +def test_header_param_model_invalid(client: TestClient): + response = client.get("/items/") + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "missing", + "loc": ["header", "save_data"], + "msg": "Field required", + "input": { + "x_tag": [], + "host": "testserver", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "user-agent": "testclient", + }, + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.missing", + "loc": ["header", "save_data"], + "msg": "field required", + } + ) + ] + } + ) + + +def test_header_param_model_extra(client: TestClient): + response = client.get( + "/items/", headers=[("save_data", "true"), ("tool", "plumbus")] + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "host": "testserver", + "save_data": True, + "if_modified_since": None, + "traceparent": None, + "x_tag": [], + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "host", + "in": "header", + "required": True, + "schema": {"type": "string", "title": "Host"}, + }, + { + "name": "save_data", + "in": "header", + "required": True, + "schema": {"type": "boolean", "title": "Save Data"}, + }, + { + "name": "if_modified_since", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "If Modified Since", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "If Modified Since", + } + ), + }, + { + "name": "traceparent", + "in": "header", + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Traceparent", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Traceparent", + } + ), + }, + { + "name": "x_tag", + "in": "header", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "X Tag", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py index 746fc05024..d6f7fe6189 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001.py @@ -1,10 +1,26 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.header_params.tutorial001 import app +from ...utils import needs_py310 -client = TestClient(app) + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.header_params.{request.param}") + + client = TestClient(mod.app) + return client @pytest.mark.parametrize( @@ -15,13 +31,13 @@ client = TestClient(app) ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), ], ) -def test(path, headers, expected_status, expected_response): +def test(path, headers, expected_status, expected_response, client: TestClient): response = client.get(path, headers=headers) assert response.status_code == expected_status assert response.json() == expected_response -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_an.py b/tests/test_tutorial/test_header_params/test_tutorial001_an.py deleted file mode 100644 index a715228aa2..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial001_an.py +++ /dev/null @@ -1,102 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.header_params.tutorial001_an import app - -client = TestClient(app) - - -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"User-Agent": "testclient"}), - ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}), - ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), - ], -) -def test(path, headers, expected_status, expected_response): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User-Agent", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User-Agent", "type": "string"} - ), - "name": "user-agent", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index caf85bc6ca..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py +++ /dev/null @@ -1,110 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial001_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"User-Agent": "testclient"}), - ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}), - ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User-Agent", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User-Agent", "type": "string"} - ), - "name": "user-agent", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py deleted file mode 100644 index 57e0a296af..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py +++ /dev/null @@ -1,110 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"User-Agent": "testclient"}), - ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}), - ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User-Agent", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "User-Agent", "type": "string"} - ), - "name": "user-agent", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py index 78bac838cf..7158f8651b 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002.py @@ -1,10 +1,27 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.header_params.tutorial002 import app +from ...utils import needs_py39, needs_py310 -client = TestClient(app) + +@pytest.fixture( + name="client", + params=[ + "tutorial002", + pytest.param("tutorial002_py310", marks=needs_py310), + "tutorial002_an", + pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.header_params.{request.param}") + + client = TestClient(mod.app) + return client @pytest.mark.parametrize( @@ -26,13 +43,13 @@ client = TestClient(app) ), ], ) -def test(path, headers, expected_status, expected_response): +def test(path, headers, expected_status, expected_response, client: TestClient): response = client.get(path, headers=headers) assert response.status_code == expected_status assert response.json() == expected_response -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_an.py b/tests/test_tutorial/test_header_params/test_tutorial002_an.py deleted file mode 100644 index ffda8158fc..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial002_an.py +++ /dev/null @@ -1,113 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.header_params.tutorial002_an import app - -client = TestClient(app) - - -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"strange_header": None}), - ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), - ( - "/items", - {"strange_header": "FastAPI test"}, - 200, - {"strange_header": "FastAPI test"}, - ), - ( - "/items", - {"strange-header": "Not really underscore"}, - 200, - {"strange_header": None}, - ), - ], -) -def test(path, headers, expected_status, expected_response): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Strange Header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Strange Header", "type": "string"} - ), - "name": "strange_header", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 6f332f3bac..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py +++ /dev/null @@ -1,121 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial002_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"strange_header": None}), - ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), - ( - "/items", - {"strange_header": "FastAPI test"}, - 200, - {"strange_header": "FastAPI test"}, - ), - ( - "/items", - {"strange-header": "Not really underscore"}, - 200, - {"strange_header": None}, - ), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Strange Header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Strange Header", "type": "string"} - ), - "name": "strange_header", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 8202bc671e..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py +++ /dev/null @@ -1,124 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial002_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"strange_header": None}), - ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), - ( - "/items", - {"strange_header": "FastAPI test"}, - 200, - {"strange_header": "FastAPI test"}, - ), - ( - "/items", - {"strange-header": "Not really underscore"}, - 200, - {"strange_header": None}, - ), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py39 -def test_openapi_schema(): - from docs_src.header_params.tutorial002_an_py39 import app - - client = TestClient(app) - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Strange Header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Strange Header", "type": "string"} - ), - "name": "strange_header", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_py310.py b/tests/test_tutorial/test_header_params/test_tutorial002_py310.py deleted file mode 100644 index c113ed23e1..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial002_py310.py +++ /dev/null @@ -1,124 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial002_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"strange_header": None}), - ("/items", {"X-Header": "notvalid"}, 200, {"strange_header": None}), - ( - "/items", - {"strange_header": "FastAPI test"}, - 200, - {"strange_header": "FastAPI test"}, - ), - ( - "/items", - {"strange-header": "Not really underscore"}, - 200, - {"strange_header": None}, - ), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(): - from docs_src.header_params.tutorial002_py310 import app - - client = TestClient(app) - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Strange Header", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Strange Header", "type": "string"} - ), - "name": "strange_header", - "in": "header", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py index 6f7de8ed41..473b961236 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003.py @@ -1,10 +1,27 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.header_params.tutorial003 import app +from ...utils import needs_py39, needs_py310 -client = TestClient(app) + +@pytest.fixture( + name="client", + params=[ + "tutorial003", + pytest.param("tutorial003_py310", marks=needs_py310), + "tutorial003_an", + pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.header_params.{request.param}") + + client = TestClient(mod.app) + return client @pytest.mark.parametrize( @@ -20,13 +37,13 @@ client = TestClient(app) ), ], ) -def test(path, headers, expected_status, expected_response): +def test(path, headers, expected_status, expected_response, client: TestClient): response = client.get(path, headers=headers) assert response.status_code == expected_status assert response.json() == expected_response -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_an.py b/tests/test_tutorial/test_header_params/test_tutorial003_an.py deleted file mode 100644 index 742ed41f48..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial003_an.py +++ /dev/null @@ -1,110 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.header_params.tutorial003_an import app - -client = TestClient(app) - - -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"X-Token values": None}), - ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), - # TODO: fix this, is it a bug? - # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), - ], -) -def test(path, headers, expected_status, expected_response): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "title": "X-Token", - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "X-Token", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "x-token", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } 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 deleted file mode 100644 index fdac4a416c..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial003_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"X-Token values": None}), - ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), - # TODO: fix this, is it a bug? - # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "title": "X-Token", - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "X-Token", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "x-token", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } 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 deleted file mode 100644 index c50543cc88..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial003_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"X-Token values": None}), - ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), - # TODO: fix this, is it a bug? - # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "title": "X-Token", - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "X-Token", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "x-token", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_py310.py b/tests/test_tutorial/test_header_params/test_tutorial003_py310.py deleted file mode 100644 index 3afb355e94..0000000000 --- a/tests/test_tutorial/test_header_params/test_tutorial003_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.header_params.tutorial003_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "path,headers,expected_status,expected_response", - [ - ("/items", None, 200, {"X-Token values": None}), - ("/items", {"x-token": "foo"}, 200, {"X-Token values": ["foo"]}), - # TODO: fix this, is it a bug? - # ("/items", [("x-token", "foo"), ("x-token", "bar")], 200, {"X-Token values": ["foo", "bar"]}), - ], -) -def test(path, headers, expected_status, expected_response, client: TestClient): - response = client.get(path, headers=headers) - assert response.status_code == expected_status - assert response.json() == expected_response - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "title": "X-Token", - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "X-Token", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "x-token", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } 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 d3792e701f..0742f5d0e8 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -1,13 +1,29 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.path_operation_configuration.tutorial005 import app - -from ...utils import needs_pydanticv1, needs_pydanticv2 - -client = TestClient(app) +from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 -def test_query_params_str_validations(): +@pytest.fixture( + name="client", + params=[ + "tutorial005", + pytest.param("tutorial005_py39", marks=needs_py39), + pytest.param("tutorial005_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.path_operation_configuration.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_query_params_str_validations(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": 42}) assert response.status_code == 200, response.text assert response.json() == { @@ -20,7 +36,7 @@ def test_query_params_str_validations(): @needs_pydanticv2 -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { @@ -123,7 +139,7 @@ def test_openapi_schema(): # TODO: remove when deprecating Pydantic v1 @needs_pydanticv1 -def test_openapi_schema_pv1(): +def test_openapi_schema_pv1(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { 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 deleted file mode 100644 index a68deb3df5..0000000000 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py +++ /dev/null @@ -1,226 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.path_operation_configuration.tutorial005_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_query_params_str_validations(client: TestClient): - response = client.post("/items/", json={"name": "Foo", "price": 42}) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Foo", - "price": 42, - "description": None, - "tax": None, - "tags": [], - } - - -@needs_py310 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_py310 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index e17f2592dc..0000000000 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py +++ /dev/null @@ -1,226 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.path_operation_configuration.tutorial005_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_query_params_str_validations(client: TestClient): - response = client.post("/items/", json={"name": "Foo", "price": 42}) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Foo", - "price": 42, - "description": None, - "tax": None, - "tags": [], - } - - -@needs_py39 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } - - -# TODO: remove when deprecating Pydantic v1 -@needs_py39 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/docs_src/sql_databases/sql_app_py310/tests/__init__.py b/tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py similarity index 100% rename from docs_src/sql_databases/sql_app_py310/tests/__init__.py rename to tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py new file mode 100644 index 0000000000..3075a05f51 --- /dev/null +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py @@ -0,0 +1,37 @@ +import sys +from typing import Any + +import pytest +from fastapi._compat import PYDANTIC_V2 + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + +import importlib + +import pytest + +from ...utils import needs_py310 + + +@pytest.fixture( + name="mod", + params=[ + "tutorial001_an", + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + return mod + + +def test_model(mod: Any): + item = mod.Item(name="Foo", size=3.4) + assert item.dict() == {"name": "Foo", "description": None, "size": 3.4} diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py new file mode 100644 index 0000000000..a402c663d1 --- /dev/null +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py @@ -0,0 +1,140 @@ +import sys + +import pytest +from fastapi._compat import PYDANTIC_V2 +from inline_snapshot import snapshot + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial002_an", + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + + c = TestClient(mod.app) + return c + + +def test_call(client: TestClient): + response = client.post("/items/", json={"name": "Foo", "size": 3.4}) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": None, + "size": 3.4, + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + } + }, + "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" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": {"type": "string", "title": "Description"}, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py new file mode 100644 index 0000000000..03155c9249 --- /dev/null +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py @@ -0,0 +1,154 @@ +import sys + +import pytest +from fastapi._compat import PYDANTIC_V2 +from inline_snapshot import snapshot + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + + +import importlib + +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial003_an", + pytest.param("tutorial003_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + + c = TestClient(mod.app) + return c + + +def test_call(client: TestClient): + response = client.post("/items/", json={"name": "Foo", "size": 3.4}) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": None, + "size": 3.4, + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ItemV2" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": {"type": "string", "title": "Description"}, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "Item", + }, + "ItemV2": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "ItemV2", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py similarity index 51% rename from tests/test_tutorial/test_async_sql_databases/test_tutorial001.py rename to tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py index 13568a5328..d2e204ddaf 100644 --- a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py @@ -1,69 +1,83 @@ +import sys + import pytest -from fastapi import FastAPI +from fastapi._compat import PYDANTIC_V2 +from inline_snapshot import snapshot + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + + +import importlib + from fastapi.testclient import TestClient -from ...utils import needs_pydanticv1 +from ...utils import needs_py39, needs_py310 -@pytest.fixture(name="app", scope="module") -def get_app(): - with pytest.warns(DeprecationWarning): - from docs_src.async_sql_databases.tutorial001 import app - yield app +@pytest.fixture( + name="client", + params=[ + "tutorial004_an", + pytest.param("tutorial004_an_py39", marks=needs_py39), + pytest.param("tutorial004_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + + c = TestClient(mod.app) + return c -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_read(app: FastAPI): - with TestClient(app) as client: - note = {"text": "Foo bar", "completed": False} - response = client.post("/notes/", json=note) - assert response.status_code == 200, response.text - data = response.json() - assert data["text"] == note["text"] - assert data["completed"] == note["completed"] - assert "id" in data - response = client.get("/notes/") - assert response.status_code == 200, response.text - assert data in response.json() +def test_call(client: TestClient): + response = client.post("/items/", json={"item": {"name": "Foo", "size": 3.4}}) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": None, + "size": 3.4, + } -def test_openapi_schema(app: FastAPI): - with TestClient(app) as client: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { "openapi": "3.1.0", "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", - }, + "/items/": { "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Body_create_item_items__post" + } + ], + "title": "Body", + } + } + }, + "required": True, + }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Note"} + "schema": {"$ref": "#/components/schemas/Item"} } }, }, @@ -78,69 +92,62 @@ def test_openapi_schema(app: FastAPI): }, }, }, - "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", + "Body_create_item_items__post": { "properties": { - "text": {"title": "Text", "type": "string"}, - "completed": {"title": "Completed", "type": "boolean"}, + "item": { + "allOf": [{"$ref": "#/components/schemas/Item"}], + "title": "Item", + } }, - }, - "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"}, - }, + "required": ["item"], + "title": "Body_create_item_items__post", }, "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", "properties": { "detail": { - "title": "Detail", - "type": "array", "items": { "$ref": "#/components/schemas/ValidationError" }, + "type": "array", + "title": "Detail", } }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": {"type": "string", "title": "Description"}, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } }, } + ) diff --git a/docs_src/sql_databases/sql_app_py39/__init__.py b/tests/test_tutorial/test_query_param_models/__init__.py similarity index 100% rename from docs_src/sql_databases/sql_app_py39/__init__.py rename to tests/test_tutorial/test_query_param_models/__init__.py diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial001.py b/tests/test_tutorial/test_query_param_models/test_tutorial001.py new file mode 100644 index 0000000000..5b7bc7b424 --- /dev/null +++ b/tests/test_tutorial/test_query_param_models/test_tutorial001.py @@ -0,0 +1,260 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_query_param_model(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + }, + ) + assert response.status_code == 200 + assert response.json() == { + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + } + + +def test_query_param_model_defaults(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "limit": 100, + "offset": 0, + "order_by": "created_at", + "tags": [], + } + + +def test_query_param_model_invalid(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 150, + "offset": -1, + "order_by": "invalid", + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "less_than_equal", + "loc": ["query", "limit"], + "msg": "Input should be less than or equal to 100", + "input": "150", + "ctx": {"le": 100}, + }, + { + "type": "greater_than_equal", + "loc": ["query", "offset"], + "msg": "Input should be greater than or equal to 0", + "input": "-1", + "ctx": {"ge": 0}, + }, + { + "type": "literal_error", + "loc": ["query", "order_by"], + "msg": "Input should be 'created_at' or 'updated_at'", + "input": "invalid", + "ctx": {"expected": "'created_at' or 'updated_at'"}, + }, + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.number.not_le", + "loc": ["query", "limit"], + "msg": "ensure this value is less than or equal to 100", + "ctx": {"limit_value": 100}, + }, + { + "type": "value_error.number.not_ge", + "loc": ["query", "offset"], + "msg": "ensure this value is greater than or equal to 0", + "ctx": {"limit_value": 0}, + }, + { + "type": "value_error.const", + "loc": ["query", "order_by"], + "msg": "unexpected value; permitted: 'created_at', 'updated_at'", + "ctx": { + "given": "invalid", + "permitted": ["created_at", "updated_at"], + }, + }, + ] + } + ) + ) + + +def test_query_param_model_extra(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + "tool": "plumbus", + }, + ) + assert response.status_code == 200 + assert response.json() == { + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "exclusiveMinimum": 0, + "default": 100, + "title": "Limit", + }, + }, + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset", + }, + }, + { + "name": "order_by", + "in": "query", + "required": False, + "schema": { + "enum": ["created_at", "updated_at"], + "type": "string", + "default": "created_at", + "title": "Order By", + }, + }, + { + "name": "tags", + "in": "query", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "Tags", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial002.py b/tests/test_tutorial/test_query_param_models/test_tutorial002.py new file mode 100644 index 0000000000..4432c9d8a3 --- /dev/null +++ b/tests/test_tutorial/test_query_param_models/test_tutorial002.py @@ -0,0 +1,282 @@ +import importlib + +import pytest +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_an", marks=needs_pydanticv2), + pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), + pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_param_models.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_query_param_model(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + }, + ) + assert response.status_code == 200 + assert response.json() == { + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + } + + +def test_query_param_model_defaults(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == { + "limit": 100, + "offset": 0, + "order_by": "created_at", + "tags": [], + } + + +def test_query_param_model_invalid(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 150, + "offset": -1, + "order_by": "invalid", + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + IsDict( + { + "detail": [ + { + "type": "less_than_equal", + "loc": ["query", "limit"], + "msg": "Input should be less than or equal to 100", + "input": "150", + "ctx": {"le": 100}, + }, + { + "type": "greater_than_equal", + "loc": ["query", "offset"], + "msg": "Input should be greater than or equal to 0", + "input": "-1", + "ctx": {"ge": 0}, + }, + { + "type": "literal_error", + "loc": ["query", "order_by"], + "msg": "Input should be 'created_at' or 'updated_at'", + "input": "invalid", + "ctx": {"expected": "'created_at' or 'updated_at'"}, + }, + ] + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "type": "value_error.number.not_le", + "loc": ["query", "limit"], + "msg": "ensure this value is less than or equal to 100", + "ctx": {"limit_value": 100}, + }, + { + "type": "value_error.number.not_ge", + "loc": ["query", "offset"], + "msg": "ensure this value is greater than or equal to 0", + "ctx": {"limit_value": 0}, + }, + { + "type": "value_error.const", + "loc": ["query", "order_by"], + "msg": "unexpected value; permitted: 'created_at', 'updated_at'", + "ctx": { + "given": "invalid", + "permitted": ["created_at", "updated_at"], + }, + }, + ] + } + ) + ) + + +def test_query_param_model_extra(client: TestClient): + response = client.get( + "/items/", + params={ + "limit": 10, + "offset": 5, + "order_by": "updated_at", + "tags": ["tag1", "tag2"], + "tool": "plumbus", + }, + ) + assert response.status_code == 422 + assert response.json() == snapshot( + { + "detail": [ + IsDict( + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "value_error.extra", + "loc": ["query", "tool"], + "msg": "extra fields not permitted", + } + ) + ] + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "exclusiveMinimum": 0, + "default": 100, + "title": "Limit", + }, + }, + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset", + }, + }, + { + "name": "order_by", + "in": "query", + "required": False, + "schema": { + "enum": ["created_at", "updated_at"], + "type": "string", + "default": "created_at", + "title": "Order By", + }, + }, + { + "name": "tags", + "in": "query", + "required": False, + "schema": { + "type": "array", + "items": {"type": "string"}, + "default": [], + "title": "Tags", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py index dbd63da160..a0b5ef4943 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006.py @@ -1,13 +1,23 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py310 -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params.tutorial006 import app - c = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial006", + pytest.param("tutorial006_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.query_params.{request.param}") + + c = TestClient(mod.app) return c diff --git a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py deleted file mode 100644 index 5055e38052..0000000000 --- a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py +++ /dev/null @@ -1,180 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params.tutorial006_py310 import app - - c = TestClient(app) - return c - - -@needs_py310 -def test_foo_needy_very(client: TestClient): - response = client.get("/items/foo?needy=very") - assert response.status_code == 200 - assert response.json() == { - "item_id": "foo", - "needy": "very", - "skip": 0, - "limit": None, - } - - -@needs_py310 -def test_foo_no_needy(client: TestClient): - response = client.get("/items/foo?skip=a&limit=b") - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["query", "needy"], - "msg": "Field required", - "input": None, - }, - { - "type": "int_parsing", - "loc": ["query", "skip"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "a", - }, - { - "type": "int_parsing", - "loc": ["query", "limit"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "b", - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["query", "needy"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["query", "skip"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - }, - { - "loc": ["query", "limit"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - }, - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User Item", - "operationId": "read_user_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Needy", "type": "string"}, - "name": "needy", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": IsDict( - { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Limit", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Limit", "type": "integer"} - ), - "name": "limit", - "in": "query", - }, - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index 945cee3d26..e08e169633 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -1,13 +1,29 @@ +import importlib + import pytest from dirty_equals import IsDict +from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010 import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial010", + pytest.param("tutorial010_py310", marks=needs_py310), + "tutorial010_an", + pytest.param("tutorial010_an_py39", marks=needs_py39), + pytest.param("tutorial010_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) return client @@ -107,6 +123,12 @@ def test_openapi_schema(client: TestClient): ], "title": "Query string", "description": "Query string for the items to search in the database that have a good match", + # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. + **( + {"deprecated": True} + if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) + else {} + ), } ) | IsDict( diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py deleted file mode 100644 index 23951a9aa3..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py +++ /dev/null @@ -1,161 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an import app - - client = TestClient(app) - return client - - -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py deleted file mode 100644 index 2968af563c..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py +++ /dev/null @@ -1,168 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py310 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py deleted file mode 100644 index 534ba87594..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py +++ /dev/null @@ -1,168 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py39 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py39 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py39 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py deleted file mode 100644 index 886bceca21..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py +++ /dev/null @@ -1,168 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py310 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index 5ba39b05d6..f4da25752b 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -1,26 +1,47 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial011 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -def test_multi_query_values(): +@pytest.fixture( + name="client", + params=[ + "tutorial011", + pytest.param("tutorial011_py39", marks=needs_py310), + pytest.param("tutorial011_py310", marks=needs_py310), + "tutorial011_an", + pytest.param("tutorial011_an_py39", marks=needs_py39), + pytest.param("tutorial011_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_query_no_values(): +def test_query_no_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": None} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py deleted file mode 100644 index 3942ea77a9..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py +++ /dev/null @@ -1,108 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial011_an import app - -client = TestClient(app) - - -def test_multi_query_values(): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_query_no_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py deleted file mode 100644 index f2ec38c950..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py310 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py deleted file mode 100644 index cd7b156798..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py deleted file mode 100644 index bdc7295162..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py310 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py deleted file mode 100644 index 26ac56b2f1..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index 1436db384c..549a90519e 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -1,25 +1,44 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial012 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_default_query_values(): +@pytest.fixture( + name="client", + params=[ + "tutorial012", + pytest.param("tutorial012_py39", marks=needs_py39), + "tutorial012_an", + pytest.param("tutorial012_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_default_query_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_multi_query_values(): +def test_multi_query_values(client: TestClient): url = "/items/?q=baz&q=foobar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["baz", "foobar"]} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py deleted file mode 100644 index 270763f1d1..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py +++ /dev/null @@ -1,96 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial012_an import app - -client = TestClient(app) - - -def test_default_query_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_multi_query_values(): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py deleted file mode 100644 index 5483916839..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial012_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_default_query_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py deleted file mode 100644 index e7d7451548..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial012_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_default_query_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index 1ba1fdf618..f2f5f7a858 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -1,25 +1,43 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial013 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_multi_query_values(): +@pytest.fixture( + name="client", + params=[ + "tutorial013", + "tutorial013_an", + pytest.param("tutorial013_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_query_no_values(): +def test_query_no_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": []} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py deleted file mode 100644 index 3432617481..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py +++ /dev/null @@ -1,96 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial013_an import app - -client = TestClient(app) - - -def test_multi_query_values(): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_query_no_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": []} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py deleted file mode 100644 index 537d6325b3..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial013_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": []} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index 7bce7590c2..edd40bb1ab 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -1,23 +1,43 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial014 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -def test_hidden_query(): +@pytest.fixture( + name="client", + params=[ + "tutorial014", + pytest.param("tutorial014_py310", marks=needs_py310), + "tutorial014_an", + pytest.param("tutorial014_an_py39", marks=needs_py39), + pytest.param("tutorial014_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_hidden_query(client: TestClient): response = client.get("/items?hidden_query=somevalue") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "somevalue"} -def test_no_hidden_query(): +def test_no_hidden_query(client: TestClient): response = client.get("/items") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "Not found"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py deleted file mode 100644 index 344004d01b..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py deleted file mode 100644 index 5d4f6df3d0..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py deleted file mode 100644 index dad49fb12d..0000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py new file mode 100644 index 0000000000..ae1c40286d --- /dev/null +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py @@ -0,0 +1,143 @@ +import importlib + +import pytest +from dirty_equals import IsStr +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from ...utils import needs_py39, needs_py310, needs_pydanticv2 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial015_an", marks=needs_pydanticv2), + pytest.param("tutorial015_an_py310", marks=(needs_py310, needs_pydanticv2)), + pytest.param("tutorial015_an_py39", marks=(needs_py39, needs_pydanticv2)), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) + + client = TestClient(mod.app) + return client + + +def test_get_random_item(client: TestClient): + response = client.get("/items") + assert response.status_code == 200, response.text + assert response.json() == {"id": IsStr(), "name": IsStr()} + + +def test_get_item(client: TestClient): + response = client.get("/items?id=isbn-9781529046137") + assert response.status_code == 200, response.text + assert response.json() == { + "id": "isbn-9781529046137", + "name": "The Hitchhiker's Guide to the Galaxy", + } + + +def test_get_item_does_not_exist(client: TestClient): + response = client.get("/items?id=isbn-nope") + assert response.status_code == 200, response.text + assert response.json() == {"id": "isbn-nope", "name": None} + + +def test_get_invalid_item(client: TestClient): + response = client.get("/items?id=wtf-yes") + assert response.status_code == 422, response.text + assert response.json() == snapshot( + { + "detail": [ + { + "type": "value_error", + "loc": ["query", "id"], + "msg": 'Value error, Invalid ID format, it must start with "isbn-" or "imdb-"', + "input": "wtf-yes", + "ctx": {"error": {}}, + } + ] + } + ) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "id", + "in": "query", + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Id", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index f5817593bb..b069199612 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -1,23 +1,28 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.request_files.tutorial001 import app - -client = TestClient(app) +from ...utils import needs_py39 -file_required = { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] -} +@pytest.fixture( + name="client", + params=[ + "tutorial001", + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") + + client = TestClient(mod.app) + return client -def test_post_form_no_body(): +def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -45,7 +50,7 @@ def test_post_form_no_body(): ) -def test_post_body_json(): +def test_post_body_json(client: TestClient): response = client.post("/files/", json={"file": "Foo"}) assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -73,41 +78,38 @@ def test_post_body_json(): ) -def test_post_file(tmp_path): +def test_post_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": 14} -def test_post_large_file(tmp_path): +def test_post_large_file(tmp_path, client: TestClient): default_pydantic_max_size = 2**16 path = tmp_path / "test.txt" path.write_bytes(b"x" * (default_pydantic_max_size + 1)) - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": default_pydantic_max_size + 1} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index 42f75442a5..9075a17561 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -1,46 +1,63 @@ +import importlib +from pathlib import Path + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.request_files.tutorial001_02 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -def test_post_form_no_body(): +@pytest.fixture( + name="client", + params=[ + "tutorial001_02", + pytest.param("tutorial001_02_py310", marks=needs_py310), + "tutorial001_02_an", + pytest.param("tutorial001_02_an_py39", marks=needs_py39), + pytest.param("tutorial001_02_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 200, response.text assert response.json() == {"message": "No file sent"} -def test_post_uploadfile_no_body(): +def test_post_uploadfile_no_body(client: TestClient): response = client.post("/uploadfile/") assert response.status_code == 200, response.text assert response.json() == {"message": "No upload file sent"} -def test_post_file(tmp_path): +def test_post_file(tmp_path: Path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": 14} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path: Path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py deleted file mode 100644 index f63eb339c4..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py +++ /dev/null @@ -1,208 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.request_files.tutorial001_02_an import app - -client = TestClient(app) - - -def test_post_form_no_body(): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -def test_post_uploadfile_no_body(): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -def test_post_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -def test_post_upload_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py deleted file mode 100644 index 94b6ac67ee..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py +++ /dev/null @@ -1,220 +0,0 @@ -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_02_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -@needs_py310 -def test_post_uploadfile_no_body(client: TestClient): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -@needs_py310 -def test_post_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py310 -def test_post_upload_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py deleted file mode 100644 index fcb39f8f18..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py +++ /dev/null @@ -1,220 +0,0 @@ -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_02_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -@needs_py39 -def test_post_uploadfile_no_body(client: TestClient): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -@needs_py39 -def test_post_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py39 -def test_post_upload_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py deleted file mode 100644 index a700752a30..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py +++ /dev/null @@ -1,220 +0,0 @@ -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_02_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -@needs_py310 -def test_post_uploadfile_no_body(client: TestClient): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -@needs_py310 -def test_post_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py310 -def test_post_upload_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index f02170814c..9fbe2166c1 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -1,33 +1,47 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.request_files.tutorial001_03 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_post_file(tmp_path): +@pytest.fixture( + name="client", + params=[ + "tutorial001_03", + "tutorial001_03_an", + pytest.param("tutorial001_03_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": 14} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py deleted file mode 100644 index acfb749ce2..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py +++ /dev/null @@ -1,159 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.request_files.tutorial001_03_an import app - -client = TestClient(app) - - -def test_post_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -def test_post_upload_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as bytes", - "format": "binary", - } - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as UploadFile", - "format": "binary", - } - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py deleted file mode 100644 index 36e5faac18..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py +++ /dev/null @@ -1,167 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_03_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py39 -def test_post_upload_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as bytes", - "format": "binary", - } - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as UploadFile", - "format": "binary", - } - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_an.py deleted file mode 100644 index 1c78e3679e..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_an.py +++ /dev/null @@ -1,218 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.request_files.tutorial001_an import app - -client = TestClient(app) - - -def test_post_form_no_body(): - response = client.post("/files/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_body_json(): - response = client.post("/files/", json={"file": "Foo"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -def test_post_large_file(tmp_path): - default_pydantic_max_size = 2**16 - path = tmp_path / "test.txt" - path.write_bytes(b"x" * (default_pydantic_max_size + 1)) - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": default_pydantic_max_size + 1} - - -def test_post_upload_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - } - }, - "/uploadfile/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py deleted file mode 100644 index 843fcec287..0000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py +++ /dev/null @@ -1,228 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_post_body_json(client: TestClient): - response = client.post("/files/", json={"file": "Foo"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_post_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py39 -def test_post_large_file(tmp_path, client: TestClient): - default_pydantic_max_size = 2**16 - path = tmp_path / "test.txt" - path.write_bytes(b"x" * (default_pydantic_max_size + 1)) - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": default_pydantic_max_size + 1} - - -@needs_py39 -def test_post_upload_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - } - }, - "/uploadfile/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index db1552e5c6..446a876578 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -1,12 +1,35 @@ +import importlib + +import pytest from dirty_equals import IsDict +from fastapi import FastAPI from fastapi.testclient import TestClient -from docs_src.request_files.tutorial002 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_post_form_no_body(): +@pytest.fixture( + name="app", + params=[ + "tutorial002", + "tutorial002_an", + pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_an_py39", marks=needs_py39), + ], +) +def get_app(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") + + return mod.app + + +@pytest.fixture(name="client") +def get_client(app: FastAPI): + client = TestClient(app) + return client + + +def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -34,7 +57,7 @@ def test_post_form_no_body(): ) -def test_post_body_json(): +def test_post_body_json(client: TestClient): response = client.post("/files/", json={"file": "Foo"}) assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -62,7 +85,7 @@ def test_post_body_json(): ) -def test_post_files(tmp_path): +def test_post_files(tmp_path, app: FastAPI): path = tmp_path / "test.txt" path.write_bytes(b"") path2 = tmp_path / "test2.txt" @@ -81,7 +104,7 @@ def test_post_files(tmp_path): assert response.json() == {"file_sizes": [14, 15]} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path, app: FastAPI): path = tmp_path / "test.txt" path.write_bytes(b"") path2 = tmp_path / "test2.txt" @@ -100,14 +123,14 @@ def test_post_upload_file(tmp_path): assert response.json() == {"filenames": ["test.txt", "test2.txt"]} -def test_get_root(): +def test_get_root(app: FastAPI): client = TestClient(app) response = client.get("/") assert response.status_code == 200, response.text assert b"") - 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]} - - -@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" @@ -24,7 +47,7 @@ def test_post_files(tmp_path): assert response.json() == {"file_sizes": [14, 15]} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path, app: FastAPI): path = tmp_path / "test.txt" path.write_bytes(b"") path2 = tmp_path / "test2.txt" @@ -43,14 +66,14 @@ def test_post_upload_file(tmp_path): assert response.json() == {"filenames": ["test.txt", "test2.txt"]} -def test_get_root(): +def test_get_root(app: FastAPI): client = TestClient(app) response = client.get("/") assert response.status_code == 200, response.text assert b"") - 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]} - - -@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() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "fileb"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "token"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -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", - } - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - } - } - }, - "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 deleted file mode 100644 index 3f1204efa7..0000000000 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py +++ /dev/null @@ -1,317 +0,0 @@ -import pytest -from dirty_equals import IsDict -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 - - -@needs_py39 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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_file(client: TestClient): - response = client.post("/files/", data={"token": "foo"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "fileb"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@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() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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_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() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "fileb"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "token"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "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_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.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - } - } - }, - "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 384c8e0f14..70cfd6e4cc 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003.py @@ -1,12 +1,27 @@ +import importlib + +import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.response_model.tutorial003 import app - -client = TestClient(app) +from ...utils import needs_py310 -def test_post_user(): +@pytest.fixture( + name="client", + params=[ + "tutorial003", + pytest.param("tutorial003_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_user(client: TestClient): response = client.post( "/user/", json={ @@ -24,7 +39,7 @@ def test_post_user(): } -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py index 3a6a0b20d9..3975856b6c 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_01.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py @@ -1,12 +1,27 @@ +import importlib + +import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.response_model.tutorial003_01 import app - -client = TestClient(app) +from ...utils import needs_py310 -def test_post_user(): +@pytest.fixture( + name="client", + params=[ + "tutorial003_01", + pytest.param("tutorial003_01_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_user(client: TestClient): response = client.post( "/user/", json={ @@ -24,7 +39,7 @@ def test_post_user(): } -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py deleted file mode 100644 index 6985b9de6a..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py +++ /dev/null @@ -1,160 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -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.1.0", - "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": IsOneOf( - ["username", "email", "full_name"], - # TODO: remove when deprecating Pydantic v1 - ["username", "email"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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_04.py b/tests/test_tutorial/test_response_model/test_tutorial003_04.py index 4aa80145a5..f32e93ddcb 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_04.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_04.py @@ -1,9 +1,18 @@ +import importlib + import pytest from fastapi.exceptions import FastAPIError +from ...utils import needs_py310 -def test_invalid_response_model(): + +@pytest.mark.parametrize( + "module_name", + [ + "tutorial003_04", + pytest.param("tutorial003_04_py310", marks=needs_py310), + ], +) +def test_invalid_response_model(module_name: str) -> None: with pytest.raises(FastAPIError): - from docs_src.response_model.tutorial003_04 import app - - assert app # pragma: no cover + importlib.import_module(f"docs_src.response_model.{module_name}") 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 deleted file mode 100644 index b876facc8b..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial003_04_py310.py +++ /dev/null @@ -1,12 +0,0 @@ -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 index c7a39cc748..9500852e15 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_05.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py @@ -1,23 +1,38 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.response_model.tutorial003_05 import app - -client = TestClient(app) +from ...utils import needs_py310 -def test_get_portal(): +@pytest.fixture( + name="client", + params=[ + "tutorial003_05", + pytest.param("tutorial003_05_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client + + +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."} -def test_get_redirect(): +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" -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py deleted file mode 100644 index f80d62572e..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py +++ /dev/null @@ -1,103 +0,0 @@ -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.1.0", - "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 deleted file mode 100644 index 3a3aee38aa..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py +++ /dev/null @@ -1,160 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.response_model.tutorial003_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.1.0", - "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": IsOneOf( - ["username", "email", "full_name"], - # TODO: remove when deprecating Pydantic v1 - ["username", "email"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 e9bde18dd5..449a52b813 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004.py @@ -1,10 +1,25 @@ +import importlib + import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.response_model.tutorial004 import app +from ...utils import needs_py39, needs_py310 -client = TestClient(app) + +@pytest.fixture( + name="client", + params=[ + "tutorial004", + pytest.param("tutorial004_py39", marks=needs_py39), + pytest.param("tutorial004_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client @pytest.mark.parametrize( @@ -27,13 +42,13 @@ client = TestClient(app) ), ], ) -def test_get(url, data): +def test_get(url, data, client: TestClient): response = client.get(url) assert response.status_code == 200, response.text assert response.json() == data -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py deleted file mode 100644 index 6f8a3cbea8..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py +++ /dev/null @@ -1,147 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.response_model.tutorial004_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@pytest.mark.parametrize( - "url,data", - [ - ("/items/foo", {"name": "Foo", "price": 50.2}), - ( - "/items/bar", - {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - ), - ( - "/items/baz", - { - "name": "Baz", - "description": None, - "price": 50.2, - "tax": 10.5, - "tags": [], - }, - ), - ], -) -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.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": IsOneOf( - ["name", "description", "price", "tax", "tags"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index cfaa1eba21..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py +++ /dev/null @@ -1,147 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.response_model.tutorial004_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -@pytest.mark.parametrize( - "url,data", - [ - ("/items/foo", {"name": "Foo", "price": 50.2}), - ( - "/items/bar", - {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - ), - ( - "/items/baz", - { - "name": "Baz", - "description": None, - "price": 50.2, - "tax": 10.5, - "tags": [], - }, - ), - ], -) -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.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": IsOneOf( - ["name", "description", "price", "tax", "tags"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 b20864c07a..a633a3fddd 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005.py @@ -1,18 +1,33 @@ +import importlib + +import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.response_model.tutorial005 import app - -client = TestClient(app) +from ...utils import needs_py310 -def test_read_item_name(): +@pytest.fixture( + name="client", + params=[ + "tutorial005", + pytest.param("tutorial005_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_read_item_name(client: TestClient): response = client.get("/items/bar/name") assert response.status_code == 200, response.text assert response.json() == {"name": "Bar", "description": "The Bar fighters"} -def test_read_item_public_data(): +def test_read_item_public_data(client: TestClient): response = client.get("/items/bar/public") assert response.status_code == 200, response.text assert response.json() == { @@ -22,7 +37,7 @@ def test_read_item_public_data(): } -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py deleted file mode 100644 index de552c8f22..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py +++ /dev/null @@ -1,166 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.response_model.tutorial005_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_read_item_name(client: TestClient): - response = client.get("/items/bar/name") - assert response.status_code == 200, response.text - assert response.json() == {"name": "Bar", "description": "The Bar fighters"} - - -@needs_py310 -def test_read_item_public_data(client: TestClient): - response = client.get("/items/bar/public") - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Bar", - "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.1.0", - "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": IsOneOf( - ["name", "description", "price", "tax"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 1e47e2eadb..863522d1b4 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006.py @@ -1,18 +1,33 @@ +import importlib + +import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.response_model.tutorial006 import app - -client = TestClient(app) +from ...utils import needs_py310 -def test_read_item_name(): +@pytest.fixture( + name="client", + params=[ + "tutorial006", + pytest.param("tutorial006_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_model.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_read_item_name(client: TestClient): response = client.get("/items/bar/name") assert response.status_code == 200, response.text assert response.json() == {"name": "Bar", "description": "The Bar fighters"} -def test_read_item_public_data(): +def test_read_item_public_data(client: TestClient): response = client.get("/items/bar/public") assert response.status_code == 200, response.text assert response.json() == { @@ -22,7 +37,7 @@ def test_read_item_public_data(): } -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py deleted file mode 100644 index 40058b12de..0000000000 --- a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py +++ /dev/null @@ -1,166 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.response_model.tutorial006_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_read_item_name(client: TestClient): - response = client.get("/items/bar/name") - assert response.status_code == 200, response.text - assert response.json() == {"name": "Bar", "description": "The Bar fighters"} - - -@needs_py310 -def test_read_item_public_data(client: TestClient): - response = client.get("/items/bar/public") - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Bar", - "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.1.0", - "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": IsOneOf( - ["name", "description", "price", "tax"], - # TODO: remove when deprecating Pydantic v1 - ["name", "price"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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_tutorial001.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py index 98b1873554..c21cbb4bc2 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py @@ -1,14 +1,22 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv2 -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial001 import app +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py index 3520ef61da..b79f42e642 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py @@ -1,14 +1,22 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv1 +from ...utils import needs_py310, needs_pydanticv1 -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial001_pv1 import app +@pytest.fixture( + name="client", + params=[ + "tutorial001_pv1", + pytest.param("tutorial001_pv1_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310.py deleted file mode 100644 index e63e33cda5..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310.py +++ /dev/null @@ -1,135 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial001_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -@needs_pydanticv2 -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 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": {"type": "integer", "title": "Item Id"}, - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "price": {"type": "number", "title": "Price"}, - "tax": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Tax", - }, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - "examples": [ - { - "description": "A very nice Item", - "name": "Foo", - "price": 35.4, - "tax": 3.2, - } - ], - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310_pv1.py deleted file mode 100644 index e036d6b68b..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310_pv1.py +++ /dev/null @@ -1,129 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv1 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial001_py310_pv1 import app - - client = TestClient(app) - return client - - -@needs_py310 -@needs_pydanticv1 -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 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "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": {"type": "integer", "title": "Item Id"}, - "name": "item_id", - "in": "path", - } - ], - "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": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": {"type": "string", "title": "Description"}, - "price": {"type": "number", "title": "Price"}, - "tax": {"type": "number", "title": "Tax"}, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ], - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "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 eac0d1e29b..61aefd12a8 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py @@ -1,13 +1,30 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.schema_extra_example.tutorial004 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -# Test required and embedded body parameters with no bodies sent -def test_post_body_example(): +@pytest.fixture( + name="client", + params=[ + "tutorial004", + pytest.param("tutorial004_py310", marks=needs_py310), + "tutorial004_an", + pytest.param("tutorial004_an_py39", marks=needs_py39), + pytest.param("tutorial004_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_body_example(client: TestClient): response = client.put( "/items/5", json={ @@ -20,7 +37,7 @@ def test_post_body_example(): assert response.status_code == 200 -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py deleted file mode 100644 index a9cecd0983..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py +++ /dev/null @@ -1,168 +0,0 @@ -from dirty_equals import IsDict -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.1.0", - "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": IsDict( - { - "$ref": "#/components/schemas/Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } 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 deleted file mode 100644 index b6a7355996..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py +++ /dev/null @@ -1,177 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.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.1.0", - "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": IsDict( - { - "$ref": "#/components/schemas/Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } 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 deleted file mode 100644 index 2493194a00..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py +++ /dev/null @@ -1,177 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.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.1.0", - "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": IsDict( - { - "$ref": "#/components/schemas/Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } 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 deleted file mode 100644 index 15f54bd5a6..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py +++ /dev/null @@ -1,177 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial004_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.1.0", - "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": IsDict( - { - "$ref": "#/components/schemas/Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py index 94a40ed5a3..12859227b1 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py @@ -1,13 +1,26 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial005 import app - client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial005", + pytest.param("tutorial005_py310", marks=needs_py310), + "tutorial005_an", + pytest.param("tutorial005_an_py39", marks=needs_py39), + pytest.param("tutorial005_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") + + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py deleted file mode 100644 index da92f98f6a..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py +++ /dev/null @@ -1,166 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial005_an import app - - client = TestClient(app) - return client - - -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 - - -def test_openapi_schema(client: TestClient) -> None: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "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": IsDict({"$ref": "#/components/schemas/Item"}) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py deleted file mode 100644 index 9109cb14ef..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py +++ /dev/null @@ -1,170 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial005_an_py310 import app - - client = TestClient(app) - return client - - -@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) -> None: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "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": IsDict({"$ref": "#/components/schemas/Item"}) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py deleted file mode 100644 index fd4ec0575e..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py +++ /dev/null @@ -1,170 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial005_an_py39 import app - - client = TestClient(app) - return client - - -@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) -> None: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "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": IsDict({"$ref": "#/components/schemas/Item"}) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py deleted file mode 100644 index 05df534223..0000000000 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py +++ /dev/null @@ -1,170 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.schema_extra_example.tutorial005_py310 import app - - client = TestClient(app) - return client - - -@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) -> None: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "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": IsDict({"$ref": "#/components/schemas/Item"}) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py index 417bed8f7a..f572d6e3e1 100644 --- a/tests/test_tutorial/test_security/test_tutorial001.py +++ b/tests/test_tutorial/test_security/test_tutorial001.py @@ -1,31 +1,47 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.security.tutorial001 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_no_token(): +@pytest.fixture( + name="client", + params=[ + "tutorial001", + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.security.{request.param}") + + client = TestClient(mod.app) + return client + + +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" -def test_token(): +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"} -def test_incorrect_token(): +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" -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_security/test_tutorial001_an.py b/tests/test_tutorial/test_security/test_tutorial001_an.py deleted file mode 100644 index 59460da7ff..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial001_an.py +++ /dev/null @@ -1,57 +0,0 @@ -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.1.0", - "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 deleted file mode 100644 index d8e7127736..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial001_an_py39.py +++ /dev/null @@ -1,68 +0,0 @@ -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.1.0", - "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 18d4680f62..2bbb2e8510 100644 --- a/tests/test_tutorial/test_security/test_tutorial003.py +++ b/tests/test_tutorial/test_security/test_tutorial003.py @@ -1,18 +1,36 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.security.tutorial003 import app - -client = TestClient(app) +from ...utils import needs_py39, needs_py310 -def test_login(): +@pytest.fixture( + name="client", + params=[ + "tutorial003", + pytest.param("tutorial003_py310", marks=needs_py310), + "tutorial003_an", + pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.security.{request.param}") + + client = TestClient(mod.app) + return client + + +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"} -def test_login_incorrect_password(): +def test_login_incorrect_password(client: TestClient): response = client.post( "/token", data={"username": "johndoe", "password": "incorrect"} ) @@ -20,20 +38,20 @@ def test_login_incorrect_password(): assert response.json() == {"detail": "Incorrect username or password"} -def test_login_incorrect_username(): +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"} -def test_no_token(): +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" -def test_token(): +def test_token(client: TestClient): response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"}) assert response.status_code == 200, response.text assert response.json() == { @@ -45,14 +63,14 @@ def test_token(): } -def test_incorrect_token(): +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" -def test_incorrect_token_type(): +def test_incorrect_token_type(client: TestClient): response = client.get( "/users/me", headers={"Authorization": "Notexistent testtoken"} ) @@ -61,13 +79,13 @@ def test_incorrect_token_type(): assert response.headers["WWW-Authenticate"] == "Bearer" -def test_inactive_user(): +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"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { @@ -131,7 +149,7 @@ def test_openapi_schema(): { "title": "Grant Type", "anyOf": [ - {"pattern": "password", "type": "string"}, + {"pattern": "^password$", "type": "string"}, {"type": "null"}, ], } @@ -140,12 +158,16 @@ def test_openapi_schema(): # TODO: remove when deprecating Pydantic v1 { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", } ), "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, + "password": { + "title": "Password", + "type": "string", + "format": "password", + }, "scope": {"title": "Scope", "type": "string", "default": ""}, "client_id": IsDict( { @@ -161,11 +183,16 @@ def test_openapi_schema(): { "title": "Client Secret", "anyOf": [{"type": "string"}, {"type": "null"}], + "format": "password", } ) | IsDict( # TODO: remove when deprecating Pydantic v1 - {"title": "Client Secret", "type": "string"} + { + "title": "Client Secret", + "type": "string", + "format": "password", + } ), }, }, diff --git a/tests/test_tutorial/test_security/test_tutorial003_an.py b/tests/test_tutorial/test_security/test_tutorial003_an.py deleted file mode 100644 index a8f64d0c60..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial003_an.py +++ /dev/null @@ -1,207 +0,0 @@ -from dirty_equals import IsDict -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.1.0", - "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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 7cbbcee2f5..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial003_an_py310.py +++ /dev/null @@ -1,223 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.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.1.0", - "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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 7b21fbcc92..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial003_an_py39.py +++ /dev/null @@ -1,223 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.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.1.0", - "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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 512504534f..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial003_py310.py +++ /dev/null @@ -1,223 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.security.tutorial003_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.1.0", - "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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 2e580dbb35..ad644d61bb 100644 --- a/tests/test_tutorial/test_security/test_tutorial005.py +++ b/tests/test_tutorial/test_security/test_tutorial005.py @@ -1,18 +1,33 @@ +import importlib +from types import ModuleType + +import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.security.tutorial005 import ( - app, - create_access_token, - fake_users_db, - get_password_hash, - verify_password, +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="mod", + params=[ + "tutorial005", + pytest.param("tutorial005_py310", marks=needs_py310), + "tutorial005_an", + pytest.param("tutorial005_py39", marks=needs_py39), + pytest.param("tutorial005_an_py39", marks=needs_py39), + pytest.param("tutorial005_an_py310", marks=needs_py310), + ], ) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.security.{request.param}") -client = TestClient(app) + return mod -def get_access_token(username="johndoe", password="secret", scope=None): +def get_access_token( + *, username="johndoe", password="secret", scope=None, client: TestClient +): data = {"username": username, "password": password} if scope: data["scope"] = scope @@ -22,7 +37,8 @@ def get_access_token(username="johndoe", password="secret", scope=None): return access_token -def test_login(): +def test_login(mod: ModuleType): + client = TestClient(mod.app) response = client.post("/token", data={"username": "johndoe", "password": "secret"}) assert response.status_code == 200, response.text content = response.json() @@ -30,7 +46,8 @@ def test_login(): assert content["token_type"] == "bearer" -def test_login_incorrect_password(): +def test_login_incorrect_password(mod: ModuleType): + client = TestClient(mod.app) response = client.post( "/token", data={"username": "johndoe", "password": "incorrect"} ) @@ -38,21 +55,24 @@ def test_login_incorrect_password(): assert response.json() == {"detail": "Incorrect username or password"} -def test_login_incorrect_username(): +def test_login_incorrect_username(mod: ModuleType): + client = TestClient(mod.app) 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(): +def test_no_token(mod: ModuleType): + client = TestClient(mod.app) 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") +def test_token(mod: ModuleType): + client = TestClient(mod.app) + access_token = get_access_token(scope="me", client=client) response = client.get( "/users/me", headers={"Authorization": f"Bearer {access_token}"} ) @@ -65,14 +85,16 @@ def test_token(): } -def test_incorrect_token(): +def test_incorrect_token(mod: ModuleType): + client = TestClient(mod.app) 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(): +def test_incorrect_token_type(mod: ModuleType): + client = TestClient(mod.app) response = client.get( "/users/me", headers={"Authorization": "Notexistent testtoken"} ) @@ -81,20 +103,24 @@ def test_incorrect_token_type(): assert response.headers["WWW-Authenticate"] == "Bearer" -def test_verify_password(): - assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"]) +def test_verify_password(mod: ModuleType): + assert mod.verify_password( + "secret", mod.fake_users_db["johndoe"]["hashed_password"] + ) -def test_get_password_hash(): - assert get_password_hash("secretalice") +def test_get_password_hash(mod: ModuleType): + assert mod.get_password_hash("secretalice") -def test_create_access_token(): - access_token = create_access_token(data={"data": "foo"}) +def test_create_access_token(mod: ModuleType): + access_token = mod.create_access_token(data={"data": "foo"}) assert access_token -def test_token_no_sub(): +def test_token_no_sub(mod: ModuleType): + client = TestClient(mod.app) + response = client.get( "/users/me", headers={ @@ -106,7 +132,9 @@ def test_token_no_sub(): assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' -def test_token_no_username(): +def test_token_no_username(mod: ModuleType): + client = TestClient(mod.app) + response = client.get( "/users/me", headers={ @@ -118,8 +146,10 @@ def test_token_no_username(): assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' -def test_token_no_scope(): - access_token = get_access_token() +def test_token_no_scope(mod: ModuleType): + client = TestClient(mod.app) + + access_token = get_access_token(client=client) response = client.get( "/users/me", headers={"Authorization": f"Bearer {access_token}"} ) @@ -128,7 +158,9 @@ def test_token_no_scope(): assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' -def test_token_nonexistent_user(): +def test_token_nonexistent_user(mod: ModuleType): + client = TestClient(mod.app) + response = client.get( "/users/me", headers={ @@ -140,9 +172,11 @@ def test_token_nonexistent_user(): assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' -def test_token_inactive_user(): +def test_token_inactive_user(mod: ModuleType): + client = TestClient(mod.app) + access_token = get_access_token( - username="alice", password="secretalice", scope="me" + username="alice", password="secretalice", scope="me", client=client ) response = client.get( "/users/me", headers={"Authorization": f"Bearer {access_token}"} @@ -151,8 +185,9 @@ def test_token_inactive_user(): assert response.json() == {"detail": "Inactive user"} -def test_read_items(): - access_token = get_access_token(scope="me items") +def test_read_items(mod: ModuleType): + client = TestClient(mod.app) + access_token = get_access_token(scope="me items", client=client) response = client.get( "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"} ) @@ -160,8 +195,9 @@ def test_read_items(): assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] -def test_read_system_status(): - access_token = get_access_token() +def test_read_system_status(mod: ModuleType): + client = TestClient(mod.app) + access_token = get_access_token(client=client) response = client.get( "/status/", headers={"Authorization": f"Bearer {access_token}"} ) @@ -169,14 +205,16 @@ def test_read_system_status(): assert response.json() == {"status": "ok"} -def test_read_system_status_no_token(): +def test_read_system_status_no_token(mod: ModuleType): + client = TestClient(mod.app) 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(): +def test_openapi_schema(mod: ModuleType): + client = TestClient(mod.app) response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { @@ -325,7 +363,7 @@ def test_openapi_schema(): { "title": "Grant Type", "anyOf": [ - {"pattern": "password", "type": "string"}, + {"pattern": "^password$", "type": "string"}, {"type": "null"}, ], } @@ -334,12 +372,16 @@ def test_openapi_schema(): # TODO: remove when deprecating Pydantic v1 { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", } ), "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, + "password": { + "title": "Password", + "type": "string", + "format": "password", + }, "scope": {"title": "Scope", "type": "string", "default": ""}, "client_id": IsDict( { @@ -355,11 +397,16 @@ def test_openapi_schema(): { "title": "Client Secret", "anyOf": [{"type": "string"}, {"type": "null"}], + "format": "password", } ) | IsDict( # TODO: remove when deprecating Pydantic v1 - {"title": "Client Secret", "type": "string"} + { + "title": "Client Secret", + "type": "string", + "format": "password", + } ), }, }, diff --git a/tests/test_tutorial/test_security/test_tutorial005_an.py b/tests/test_tutorial/test_security/test_tutorial005_an.py deleted file mode 100644 index 04c7d60bcf..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial005_an.py +++ /dev/null @@ -1,409 +0,0 @@ -from dirty_equals import IsDict, IsOneOf -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_nonexistent_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.1.0", - "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": IsOneOf( - ["username", "email", "full_name", "disabled"], - # TODO: remove when deprecating Pydantic v1 - ["username"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": IsDict( - { - "title": "Email", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Email", "type": "string"} - ), - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - "disabled": IsDict( - { - "title": "Disabled", - "anyOf": [{"type": "boolean"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 9c7f83ed29..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial005_an_py310.py +++ /dev/null @@ -1,437 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -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_nonexistent_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.1.0", - "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": IsOneOf( - ["username", "email", "full_name", "disabled"], - # TODO: remove when deprecating Pydantic v1 - ["username"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": IsDict( - { - "title": "Email", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Email", "type": "string"} - ), - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - "disabled": IsDict( - { - "title": "Disabled", - "anyOf": [{"type": "boolean"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 04cc1b014e..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial005_an_py39.py +++ /dev/null @@ -1,437 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -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_nonexistent_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.1.0", - "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": IsOneOf( - ["username", "email", "full_name", "disabled"], - # TODO: remove when deprecating Pydantic v1 - ["username"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": IsDict( - { - "title": "Email", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Email", "type": "string"} - ), - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - "disabled": IsDict( - { - "title": "Disabled", - "anyOf": [{"type": "boolean"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 98c60c1c20..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial005_py310.py +++ /dev/null @@ -1,437 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.security.tutorial005_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_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_py310 import get_password_hash - - assert get_password_hash("secretalice") - - -@needs_py310 -def test_create_access_token(): - from docs_src.security.tutorial005_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_nonexistent_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.1.0", - "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": IsOneOf( - ["username", "email", "full_name", "disabled"], - # TODO: remove when deprecating Pydantic v1 - ["username"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": IsDict( - { - "title": "Email", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Email", "type": "string"} - ), - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - "disabled": IsDict( - { - "title": "Disabled", - "anyOf": [{"type": "boolean"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index cd2157d54d..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial005_py39.py +++ /dev/null @@ -1,437 +0,0 @@ -import pytest -from dirty_equals import IsDict, IsOneOf -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.security.tutorial005_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_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_py39 import get_password_hash - - assert get_password_hash("secretalice") - - -@needs_py39 -def test_create_access_token(): - from docs_src.security.tutorial005_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_nonexistent_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.1.0", - "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": IsOneOf( - ["username", "email", "full_name", "disabled"], - # TODO: remove when deprecating Pydantic v1 - ["username"], - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": IsDict( - { - "title": "Email", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Email", "type": "string"} - ), - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - "disabled": IsDict( - { - "title": "Disabled", - "anyOf": [{"type": "boolean"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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": IsDict( - { - "title": "Grant Type", - "anyOf": [ - {"pattern": "password", "type": "string"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "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": IsDict( - { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Client Id", "type": "string"} - ), - "client_secret": IsDict( - { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 dc459b6fd0..40b4138062 100644 --- a/tests/test_tutorial/test_security/test_tutorial006.py +++ b/tests/test_tutorial/test_security/test_tutorial006.py @@ -1,26 +1,41 @@ +import importlib from base64 import b64encode +import pytest from fastapi.testclient import TestClient -from docs_src.security.tutorial006 import app - -client = TestClient(app) +from ...utils import needs_py39 -def test_security_http_basic(): +@pytest.fixture( + name="client", + params=[ + "tutorial006", + "tutorial006_an", + pytest.param("tutorial006_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.security.{request.param}") + + client = TestClient(mod.app) + return client + + +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"} -def test_security_http_basic_no_credentials(): +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" -def test_security_http_basic_invalid_credentials(): +def test_security_http_basic_invalid_credentials(client: TestClient): response = client.get( "/users/me", headers={"Authorization": "Basic notabase64token"} ) @@ -29,7 +44,7 @@ def test_security_http_basic_invalid_credentials(): assert response.json() == {"detail": "Invalid authentication credentials"} -def test_security_http_basic_non_basic_credentials(): +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}) @@ -38,7 +53,7 @@ def test_security_http_basic_non_basic_credentials(): assert response.json() == {"detail": "Invalid authentication credentials"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_security/test_tutorial006_an.py b/tests/test_tutorial/test_security/test_tutorial006_an.py deleted file mode 100644 index 52ddd938f9..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial006_an.py +++ /dev/null @@ -1,65 +0,0 @@ -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.1.0", - "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 deleted file mode 100644 index 52b22e5739..0000000000 --- a/tests/test_tutorial/test_security/test_tutorial006_an_py39.py +++ /dev/null @@ -1,77 +0,0 @@ -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.1.0", - "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_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py index cdfae9f8c1..059fb889b3 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py @@ -1,14 +1,23 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv2 +from ...utils import needs_py39, needs_py310, needs_pydanticv2 -@pytest.fixture(name="client") -def get_client() -> TestClient: - from docs_src.separate_openapi_schemas.tutorial001 import app +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + pytest.param("tutorial001_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module(f"docs_src.separate_openapi_schemas.{request.param}") - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py deleted file mode 100644 index 991abe8113..0000000000 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py +++ /dev/null @@ -1,136 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client() -> TestClient: - from docs_src.separate_openapi_schemas.tutorial001_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_create_item(client: TestClient) -> None: - response = client.post("/items/", json={"name": "Foo"}) - assert response.status_code == 200, response.text - assert response.json() == {"name": "Foo", "description": None} - - -@needs_py39 -def test_read_items(client: TestClient) -> None: - response = client.get("/items/") - assert response.status_code == 200, response.text - assert response.json() == [ - { - "name": "Portal Gun", - "description": "Device to travel through the multi-rick-verse", - }, - {"name": "Plumbus", "description": None}, - ] - - -@needs_py39 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient) -> None: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Response Read Items Items Get", - } - } - }, - } - }, - }, - "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": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - }, - "type": "object", - "required": ["name"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py index d2cf7945b7..cc9afeab75 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py @@ -1,14 +1,23 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv2 +from ...utils import needs_py39, needs_py310, needs_pydanticv2 -@pytest.fixture(name="client") -def get_client() -> TestClient: - from docs_src.separate_openapi_schemas.tutorial002 import app +@pytest.fixture( + name="client", + params=[ + "tutorial002", + pytest.param("tutorial002_py310", marks=needs_py310), + pytest.param("tutorial002_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest) -> TestClient: + mod = importlib.import_module(f"docs_src.separate_openapi_schemas.{request.param}") - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py deleted file mode 100644 index 89c9ce9770..0000000000 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py +++ /dev/null @@ -1,136 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client() -> TestClient: - from docs_src.separate_openapi_schemas.tutorial002_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_create_item(client: TestClient) -> None: - response = client.post("/items/", json={"name": "Foo"}) - assert response.status_code == 200, response.text - assert response.json() == {"name": "Foo", "description": None} - - -@needs_py310 -def test_read_items(client: TestClient) -> None: - response = client.get("/items/") - assert response.status_code == 200, response.text - assert response.json() == [ - { - "name": "Portal Gun", - "description": "Device to travel through the multi-rick-verse", - }, - {"name": "Plumbus", "description": None}, - ] - - -@needs_py310 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient) -> None: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Response Read Items Items Get", - } - } - }, - } - }, - }, - "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": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - }, - "type": "object", - "required": ["name"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py deleted file mode 100644 index 6ac3d8f791..0000000000 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py +++ /dev/null @@ -1,136 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv2 - - -@pytest.fixture(name="client") -def get_client() -> TestClient: - from docs_src.separate_openapi_schemas.tutorial002_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_create_item(client: TestClient) -> None: - response = client.post("/items/", json={"name": "Foo"}) - assert response.status_code == 200, response.text - assert response.json() == {"name": "Foo", "description": None} - - -@needs_py39 -def test_read_items(client: TestClient) -> None: - response = client.get("/items/") - assert response.status_code == 200, response.text - assert response.json() == [ - { - "name": "Portal Gun", - "description": "Device to travel through the multi-rick-verse", - }, - {"name": "Plumbus", "description": None}, - ] - - -@needs_py39 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient) -> None: - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Response Read Items Items Get", - } - } - }, - } - }, - }, - "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": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - }, - "type": "object", - "required": ["name"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } diff --git a/tests/test_tutorial/test_settings/test_tutorial001.py b/tests/test_tutorial/test_settings/test_tutorial001.py index eb30dbcee4..92a5782d4d 100644 --- a/tests/test_tutorial/test_settings/test_tutorial001.py +++ b/tests/test_tutorial/test_settings/test_tutorial001.py @@ -1,14 +1,26 @@ +import importlib + +import pytest from fastapi.testclient import TestClient from pytest import MonkeyPatch -from ...utils import needs_pydanticv2 +from ...utils import needs_pydanticv1, needs_pydanticv2 -@needs_pydanticv2 -def test_settings(monkeypatch: MonkeyPatch): +@pytest.fixture( + name="app", + params=[ + pytest.param("tutorial001", marks=needs_pydanticv2), + pytest.param("tutorial001_pv1", marks=needs_pydanticv1), + ], +) +def get_app(request: pytest.FixtureRequest, monkeypatch: MonkeyPatch): monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") - from docs_src.settings.tutorial001 import app + mod = importlib.import_module(f"docs_src.settings.{request.param}") + return mod.app + +def test_settings(app): client = TestClient(app) response = client.get("/info") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_settings/test_tutorial001_pv1.py b/tests/test_tutorial/test_settings/test_tutorial001_pv1.py deleted file mode 100644 index e4659de665..0000000000 --- a/tests/test_tutorial/test_settings/test_tutorial001_pv1.py +++ /dev/null @@ -1,19 +0,0 @@ -from fastapi.testclient import TestClient -from pytest import MonkeyPatch - -from ...utils import needs_pydanticv1 - - -@needs_pydanticv1 -def test_settings(monkeypatch: MonkeyPatch): - monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") - from docs_src.settings.tutorial001_pv1 import app - - client = TestClient(app) - response = client.get("/info") - assert response.status_code == 200, response.text - assert response.json() == { - "app_name": "Awesome API", - "admin_email": "admin@example.com", - "items_per_user": 50, - } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases.py b/tests/test_tutorial/test_sql_databases/test_sql_databases.py deleted file mode 100644 index e3e2b36a80..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases.py +++ /dev/null @@ -1,419 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app import main - - # Ensure import side effects are re-executed - importlib.reload(main) - with TestClient(main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - 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"] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 73b97e09d9..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py +++ /dev/null @@ -1,421 +0,0 @@ -import importlib -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(): - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app import alt_main - - # Ensure import side effects are re-executed - importlib.reload(alt_main) - - with TestClient(alt_main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - 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"] - 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"] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index a078f012a9..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py +++ /dev/null @@ -1,433 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py310 import alt_main - - # Ensure import side effects are re-executed - importlib.reload(alt_main) - - with TestClient(alt_main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - 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"] - 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"] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index a5da07ac6f..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py +++ /dev/null @@ -1,433 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py39 import alt_main - - # Ensure import side effects are re-executed - importlib.reload(alt_main) - - with TestClient(alt_main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - 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"] - 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"] - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index 5a91065988..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py +++ /dev/null @@ -1,432 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv1 - - -@pytest.fixture(scope="module", name="client") -def get_client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py310 import main - - # Ensure import side effects are re-executed - importlib.reload(main) - with TestClient(main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - 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"] - 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"] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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 deleted file mode 100644 index a354ba9053..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py +++ /dev/null @@ -1,432 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv1 - - -@pytest.fixture(scope="module", name="client") -def get_client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py39 import main - - # Ensure import side effects are re-executed - importlib.reload(main) - with TestClient(main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py39 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - 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"] - 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"] - - -@needs_py39 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/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": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"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_testing_databases.py b/tests/test_tutorial/test_sql_databases/test_testing_databases.py deleted file mode 100644 index ce6ce230c8..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_testing_databases.py +++ /dev/null @@ -1,27 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest - -from ...utils import needs_pydanticv1 - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_testing_dbs(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./test.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app.tests import test_sql_app - - # Ensure import side effects are re-executed - importlib.reload(test_sql_app) - test_sql_app.test_create_user() - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py b/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py deleted file mode 100644 index 545d63c2a8..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py +++ /dev/null @@ -1,28 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest - -from ...utils import needs_py310, needs_pydanticv1 - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_testing_dbs_py39(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./test.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py310.tests import test_sql_app - - # Ensure import side effects are re-executed - importlib.reload(test_sql_app) - test_sql_app.test_create_user() - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py b/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py deleted file mode 100644 index 99bfd3fa8a..0000000000 --- a/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest - -from ...utils import needs_py39, needs_pydanticv1 - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_testing_dbs_py39(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./test.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py39.tests import test_sql_app - - # Ensure import side effects are re-executed - importlib.reload(test_sql_app) - test_sql_app.test_create_user() - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_sql_databases/test_tutorial001.py new file mode 100644 index 0000000000..6604a2fd38 --- /dev/null +++ b/tests/test_tutorial/test_sql_databases/test_tutorial001.py @@ -0,0 +1,375 @@ +import importlib +import warnings + +import pytest +from dirty_equals import IsDict, IsInt +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from sqlalchemy import StaticPool +from sqlmodel import SQLModel, create_engine +from sqlmodel.main import default_registry + +from tests.utils import needs_py39, needs_py310 + + +def clear_sqlmodel(): + # Clear the tables in the metadata for the default base model + SQLModel.metadata.clear() + # Clear the Models associated with the registry, to avoid warnings + default_registry.dispose() + + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + clear_sqlmodel() + # TODO: remove when updating SQL tutorial to use new lifespan API + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + mod = importlib.import_module(f"docs_src.sql_databases.{request.param}") + clear_sqlmodel() + importlib.reload(mod) + mod.sqlite_url = "sqlite://" + mod.engine = create_engine( + mod.sqlite_url, connect_args={"check_same_thread": False}, poolclass=StaticPool + ) + + with TestClient(mod.app) as c: + yield c + # Clean up connection explicitely to avoid resource warning + mod.engine.dispose() + + +def test_crud_app(client: TestClient): + # TODO: this warns that SQLModel.from_orm is deprecated in Pydantic v1, refactor + # this if using obj.model_validate becomes independent of Pydantic v2 + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + # No heroes before creating + response = client.get("heroes/") + assert response.status_code == 200, response.text + assert response.json() == [] + + # Create a hero + response = client.post( + "/heroes/", + json={ + "id": 999, + "name": "Dead Pond", + "age": 30, + "secret_name": "Dive Wilson", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"age": 30, "secret_name": "Dive Wilson", "id": 999, "name": "Dead Pond"} + ) + + # Read a hero + hero_id = response.json()["id"] + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dead Pond", "age": 30, "id": 999, "secret_name": "Dive Wilson"} + ) + + # Read all heroes + # Create more heroes first + response = client.post( + "/heroes/", + json={"name": "Spider-Boy", "age": 18, "secret_name": "Pedro Parqueador"}, + ) + assert response.status_code == 200, response.text + response = client.post( + "/heroes/", json={"name": "Rusty-Man", "secret_name": "Tommy Sharp"} + ) + assert response.status_code == 200, response.text + + response = client.get("/heroes/") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [ + { + "name": "Dead Pond", + "age": 30, + "id": IsInt(), + "secret_name": "Dive Wilson", + }, + { + "name": "Spider-Boy", + "age": 18, + "id": IsInt(), + "secret_name": "Pedro Parqueador", + }, + { + "name": "Rusty-Man", + "age": None, + "id": IsInt(), + "secret_name": "Tommy Sharp", + }, + ] + ) + + response = client.get("/heroes/?offset=1&limit=1") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [ + { + "name": "Spider-Boy", + "age": 18, + "id": IsInt(), + "secret_name": "Pedro Parqueador", + } + ] + ) + + # Delete a hero + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot({"ok": True}) + + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + assert response.json() == snapshot({"detail": "Hero not found"}) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Hero"} + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Hero"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "default": 0, + "title": "Offset", + }, + }, + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "default": 100, + "title": "Limit", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Hero" + }, + "title": "Response Read Heroes Heroes Get", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Hero"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Hero": { + "properties": { + "id": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Id", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Id", + } + ), + "name": {"type": "string", "title": "Name"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "secret_name": {"type": "string", "title": "Secret Name"}, + }, + "type": "object", + "required": ["name", "secret_name"], + "title": "Hero", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial002.py b/tests/test_tutorial/test_sql_databases/test_tutorial002.py new file mode 100644 index 0000000000..2c4e0988ce --- /dev/null +++ b/tests/test_tutorial/test_sql_databases/test_tutorial002.py @@ -0,0 +1,483 @@ +import importlib +import warnings + +import pytest +from dirty_equals import IsDict, IsInt +from fastapi.testclient import TestClient +from inline_snapshot import Is, snapshot +from sqlalchemy import StaticPool +from sqlmodel import SQLModel, create_engine +from sqlmodel.main import default_registry + +from tests.utils import needs_py39, needs_py310 + + +def clear_sqlmodel(): + # Clear the tables in the metadata for the default base model + SQLModel.metadata.clear() + # Clear the Models associated with the registry, to avoid warnings + default_registry.dispose() + + +@pytest.fixture( + name="client", + params=[ + "tutorial002", + pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_py310", marks=needs_py310), + "tutorial002_an", + pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + clear_sqlmodel() + # TODO: remove when updating SQL tutorial to use new lifespan API + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + mod = importlib.import_module(f"docs_src.sql_databases.{request.param}") + clear_sqlmodel() + importlib.reload(mod) + mod.sqlite_url = "sqlite://" + mod.engine = create_engine( + mod.sqlite_url, connect_args={"check_same_thread": False}, poolclass=StaticPool + ) + + with TestClient(mod.app) as c: + yield c + # Clean up connection explicitely to avoid resource warning + mod.engine.dispose() + + +def test_crud_app(client: TestClient): + # TODO: this warns that SQLModel.from_orm is deprecated in Pydantic v1, refactor + # this if using obj.model_validate becomes independent of Pydantic v2 + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + # No heroes before creating + response = client.get("heroes/") + assert response.status_code == 200, response.text + assert response.json() == [] + + # Create a hero + response = client.post( + "/heroes/", + json={ + "id": 9000, + "name": "Dead Pond", + "age": 30, + "secret_name": "Dive Wilson", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"age": 30, "id": IsInt(), "name": "Dead Pond"} + ) + assert response.json()["id"] != 9000, ( + "The ID should be generated by the database" + ) + + # Read a hero + hero_id = response.json()["id"] + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dead Pond", "age": 30, "id": IsInt()} + ) + + # Read all heroes + # Create more heroes first + response = client.post( + "/heroes/", + json={"name": "Spider-Boy", "age": 18, "secret_name": "Pedro Parqueador"}, + ) + assert response.status_code == 200, response.text + response = client.post( + "/heroes/", json={"name": "Rusty-Man", "secret_name": "Tommy Sharp"} + ) + assert response.status_code == 200, response.text + + response = client.get("/heroes/") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [ + {"name": "Dead Pond", "age": 30, "id": IsInt()}, + {"name": "Spider-Boy", "age": 18, "id": IsInt()}, + {"name": "Rusty-Man", "age": None, "id": IsInt()}, + ] + ) + + response = client.get("/heroes/?offset=1&limit=1") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [{"name": "Spider-Boy", "age": 18, "id": IsInt()}] + ) + + # Update a hero + response = client.patch( + f"/heroes/{hero_id}", json={"name": "Dog Pond", "age": None} + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dog Pond", "age": None, "id": Is(hero_id)} + ) + + # Get updated hero + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dog Pond", "age": None, "id": Is(hero_id)} + ) + + # Delete a hero + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot({"ok": True}) + + # The hero is no longer found + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + + # Delete a hero that does not exist + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + assert response.json() == snapshot({"detail": "Hero not found"}) + + # Update a hero that does not exist + response = client.patch(f"/heroes/{hero_id}", json={"name": "Dog Pond"}) + assert response.status_code == 404, response.text + assert response.json() == snapshot({"detail": "Hero not found"}) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroPublic" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "default": 0, + "title": "Offset", + }, + }, + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "default": 100, + "title": "Limit", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroPublic" + }, + "title": "Response Read Heroes Heroes Get", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroPublic" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Hero", + "operationId": "update_hero_heroes__hero_id__patch", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroPublic" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "HeroCreate": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "secret_name": {"type": "string", "title": "Secret Name"}, + }, + "type": "object", + "required": ["name", "secret_name"], + "title": "HeroCreate", + }, + "HeroPublic": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "id": {"type": "integer", "title": "Id"}, + }, + "type": "object", + "required": ["name", "id"], + "title": "HeroPublic", + }, + "HeroUpdate": { + "properties": { + "name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Name", + } + ), + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "secret_name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Secret Name", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Secret Name", + } + ), + }, + "type": "object", + "title": "HeroUpdate", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_testing/test_main_b.py b/tests/test_tutorial/test_testing/test_main_b.py index 1e1836f5b3..aa7f969c6d 100644 --- a/tests/test_tutorial/test_testing/test_main_b.py +++ b/tests/test_tutorial/test_testing/test_main_b.py @@ -1,7 +1,28 @@ -from docs_src.app_testing.app_b import test_main +import importlib +from types import ModuleType + +import pytest + +from ...utils import needs_py39, needs_py310 -def test_app(): +@pytest.fixture( + name="test_module", + params=[ + "app_b.test_main", + pytest.param("app_b_py310.test_main", marks=needs_py310), + "app_b_an.test_main", + pytest.param("app_b_an_py39.test_main", marks=needs_py39), + pytest.param("app_b_an_py310.test_main", marks=needs_py310), + ], +) +def get_test_module(request: pytest.FixtureRequest) -> ModuleType: + mod: ModuleType = importlib.import_module(f"docs_src.app_testing.{request.param}") + return mod + + +def test_app(test_module: ModuleType): + test_main = test_module test_main.test_create_existing_item() test_main.test_create_item() test_main.test_create_item_bad_token() diff --git a/tests/test_tutorial/test_testing/test_main_b_an.py b/tests/test_tutorial/test_testing/test_main_b_an.py deleted file mode 100644 index e53fc32246..0000000000 --- a/tests/test_tutorial/test_testing/test_main_b_an.py +++ /dev/null @@ -1,10 +0,0 @@ -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_nonexistent_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 deleted file mode 100644 index c974e5dc1e..0000000000 --- a/tests/test_tutorial/test_testing/test_main_b_an_py310.py +++ /dev/null @@ -1,13 +0,0 @@ -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_nonexistent_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 deleted file mode 100644 index 71f99726c9..0000000000 --- a/tests/test_tutorial/test_testing/test_main_b_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -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_nonexistent_item() - test_main.test_read_item() - test_main.test_read_item_bad_token() diff --git a/tests/test_tutorial/test_testing/test_main_b_py310.py b/tests/test_tutorial/test_testing/test_main_b_py310.py deleted file mode 100644 index e30cdc073e..0000000000 --- a/tests/test_tutorial/test_testing/test_main_b_py310.py +++ /dev/null @@ -1,13 +0,0 @@ -from ...utils import needs_py310 - - -@needs_py310 -def test_app(): - from docs_src.app_testing.app_b_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_nonexistent_item() - test_main.test_read_item() - test_main.test_read_item_bad_token() diff --git a/tests/test_tutorial/test_testing/test_tutorial004.py b/tests/test_tutorial/test_testing/test_tutorial004.py new file mode 100644 index 0000000000..812ee44c1f --- /dev/null +++ b/tests/test_tutorial/test_testing/test_tutorial004.py @@ -0,0 +1,5 @@ +from docs_src.app_testing.tutorial004 import test_read_items + + +def test_main(): + test_read_items() diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py index af26307f54..00ee6ab1ea 100644 --- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py @@ -1,25 +1,48 @@ -from docs_src.dependency_testing.tutorial001 import ( - app, - client, - test_override_in_items, - test_override_in_items_with_params, - test_override_in_items_with_q, +import importlib +from types import ModuleType + +import pytest + +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="test_module", + params=[ + "tutorial001", + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], ) +def get_test_module(request: pytest.FixtureRequest) -> ModuleType: + mod: ModuleType = importlib.import_module( + f"docs_src.dependency_testing.{request.param}" + ) + return mod -def test_override_in_items_run(): +def test_override_in_items_run(test_module: ModuleType): + test_override_in_items = test_module.test_override_in_items + test_override_in_items() -def test_override_in_items_with_q_run(): +def test_override_in_items_with_q_run(test_module: ModuleType): + test_override_in_items_with_q = test_module.test_override_in_items_with_q + test_override_in_items_with_q() -def test_override_in_items_with_params_run(): +def test_override_in_items_with_params_run(test_module: ModuleType): + test_override_in_items_with_params = test_module.test_override_in_items_with_params + test_override_in_items_with_params() -def test_override_in_users(): +def test_override_in_users(test_module: ModuleType): + client = test_module.client response = client.get("/users/") assert response.status_code == 200, response.text assert response.json() == { @@ -28,7 +51,8 @@ def test_override_in_users(): } -def test_override_in_users_with_q(): +def test_override_in_users_with_q(test_module: ModuleType): + client = test_module.client response = client.get("/users/?q=foo") assert response.status_code == 200, response.text assert response.json() == { @@ -37,7 +61,8 @@ def test_override_in_users_with_q(): } -def test_override_in_users_with_params(): +def test_override_in_users_with_params(test_module: ModuleType): + client = test_module.client response = client.get("/users/?q=foo&skip=100&limit=200") assert response.status_code == 200, response.text assert response.json() == { @@ -46,7 +71,9 @@ def test_override_in_users_with_params(): } -def test_normal_app(): +def test_normal_app(test_module: ModuleType): + app = test_module.app + client = test_module.client app.dependency_overrides = None response = client.get("/items/?q=foo&skip=100&limit=200") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an.py deleted file mode 100644 index fc1f9149a6..0000000000 --- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an.py +++ /dev/null @@ -1,56 +0,0 @@ -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 deleted file mode 100644 index a3d27f47f4..0000000000 --- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py310.py +++ /dev/null @@ -1,75 +0,0 @@ -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 deleted file mode 100644 index f03ed5e07a..0000000000 --- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py39.py +++ /dev/null @@ -1,75 +0,0 @@ -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 deleted file mode 100644 index 776b916ff0..0000000000 --- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001_py310.py +++ /dev/null @@ -1,75 +0,0 @@ -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 bb5ccbf8ef..51aa5752a6 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial002.py +++ b/tests/test_tutorial/test_websockets/test_tutorial002.py @@ -1,18 +1,37 @@ +import importlib + import pytest +from fastapi import FastAPI from fastapi.testclient import TestClient from fastapi.websockets import WebSocketDisconnect -from docs_src.websockets.tutorial002 import app +from ...utils import needs_py39, needs_py310 -def test_main(): +@pytest.fixture( + name="app", + params=[ + "tutorial002", + pytest.param("tutorial002_py310", marks=needs_py310), + "tutorial002_an", + pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_app(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.websockets.{request.param}") + + return mod.app + + +def test_main(app: FastAPI): client = TestClient(app) response = client.get("/") assert response.status_code == 200, response.text assert b"" in response.content -def test_websocket_with_cookie(): +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: @@ -30,7 +49,7 @@ def test_websocket_with_cookie(): assert data == f"Message text was: {message}, for item ID: foo" -def test_websocket_with_header(): +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: @@ -48,7 +67,7 @@ def test_websocket_with_header(): assert data == f"Message text was: {message}, for item ID: bar" -def test_websocket_with_header_and_query(): +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: @@ -70,7 +89,7 @@ def test_websocket_with_header_and_query(): assert data == f"Message text was: {message}, for item ID: 2" -def test_websocket_no_credentials(): +def test_websocket_no_credentials(app: FastAPI): client = TestClient(app) with pytest.raises(WebSocketDisconnect): with client.websocket_connect("/items/foo/ws"): @@ -79,7 +98,7 @@ def test_websocket_no_credentials(): ) # pragma: no cover -def test_websocket_invalid_data(): +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"): diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_an.py b/tests/test_tutorial/test_websockets/test_tutorial002_an.py deleted file mode 100644 index ec78d70d30..0000000000 --- a/tests/test_tutorial/test_websockets/test_tutorial002_an.py +++ /dev/null @@ -1,88 +0,0 @@ -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 deleted file mode 100644 index 23b4bcb788..0000000000 --- a/tests/test_tutorial/test_websockets/test_tutorial002_an_py310.py +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index 2d77f05b38..0000000000 --- a/tests/test_tutorial/test_websockets/test_tutorial002_an_py39.py +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index 03bc27bdfd..0000000000 --- a/tests/test_tutorial/test_websockets/test_tutorial002_py310.py +++ /dev/null @@ -1,102 +0,0 @@ -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_union_body_discriminator.py b/tests/test_union_body_discriminator.py new file mode 100644 index 0000000000..6af9e1d226 --- /dev/null +++ b/tests/test_union_body_discriminator.py @@ -0,0 +1,188 @@ +from typing import Any, Dict, Union + +from dirty_equals import IsDict +from fastapi import FastAPI +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from pydantic import BaseModel, Field +from typing_extensions import Annotated, Literal + +from .utils import needs_pydanticv2 + + +@needs_pydanticv2 +def test_discriminator_pydantic_v2() -> None: + from pydantic import Tag + + app = FastAPI() + + class FirstItem(BaseModel): + value: Literal["first"] + price: int + + class OtherItem(BaseModel): + value: Literal["other"] + price: float + + Item = Annotated[ + Union[Annotated[FirstItem, Tag("first")], Annotated[OtherItem, Tag("other")]], + Field(discriminator="value"), + ] + + @app.post("/items/") + def save_union_body_discriminator( + item: Item, q: Annotated[str, Field(description="Query string")] + ) -> Dict[str, Any]: + return {"item": item} + + client = TestClient(app) + response = client.post("/items/?q=first", json={"value": "first", "price": 100}) + assert response.status_code == 200, response.text + assert response.json() == {"item": {"value": "first", "price": 100}} + + response = client.post("/items/?q=other", json={"value": "other", "price": 100.5}) + assert response.status_code == 200, response.text + assert response.json() == {"item": {"value": "other", "price": 100.5}} + + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Save Union Body Discriminator", + "operationId": "save_union_body_discriminator_items__post", + "parameters": [ + { + "name": "q", + "in": "query", + "required": True, + "schema": { + "type": "string", + "description": "Query string", + "title": "Q", + }, + } + ], + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "oneOf": [ + {"$ref": "#/components/schemas/FirstItem"}, + {"$ref": "#/components/schemas/OtherItem"}, + ], + "discriminator": { + "propertyName": "value", + "mapping": { + "first": "#/components/schemas/FirstItem", + "other": "#/components/schemas/OtherItem", + }, + }, + "title": "Item", + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": IsDict( + { + # Pydantic 2.10, in Python 3.8 + # TODO: remove when dropping support for Python 3.8 + "type": "object", + "title": "Response Save Union Body Discriminator Items Post", + } + ) + | IsDict( + { + "type": "object", + "additionalProperties": True, + "title": "Response Save Union Body Discriminator Items Post", + } + ) + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "FirstItem": { + "properties": { + "value": { + "type": "string", + "const": "first", + "title": "Value", + }, + "price": {"type": "integer", "title": "Price"}, + }, + "type": "object", + "required": ["value", "price"], + "title": "FirstItem", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "OtherItem": { + "properties": { + "value": { + "type": "string", + "const": "other", + "title": "Value", + }, + "price": {"type": "number", "title": "Price"}, + }, + "type": "object", + "required": ["value", "price"], + "title": "OtherItem", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py b/tests/test_union_forms.py similarity index 53% rename from tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py rename to tests/test_union_forms.py index 3b22146f63..cbe98ea825 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py +++ b/tests/test_union_forms.py @@ -1,72 +1,86 @@ -import pytest +from typing import Union + +from fastapi import FastAPI, Form from fastapi.testclient import TestClient +from pydantic import BaseModel +from typing_extensions import Annotated -from ...utils import needs_py310, needs_pydanticv2 +app = FastAPI() -@pytest.fixture(name="client") -def get_client() -> TestClient: - from docs_src.separate_openapi_schemas.tutorial001_py310 import app - - client = TestClient(app) - return client +class UserForm(BaseModel): + name: str + email: str -@needs_py310 -def test_create_item(client: TestClient) -> None: - response = client.post("/items/", json={"name": "Foo"}) +class CompanyForm(BaseModel): + company_name: str + industry: str + + +@app.post("/form-union/") +def post_union_form(data: Annotated[Union[UserForm, CompanyForm], Form()]): + return {"received": data} + + +client = TestClient(app) + + +def test_post_user_form(): + response = client.post( + "/form-union/", data={"name": "John Doe", "email": "john@example.com"} + ) assert response.status_code == 200, response.text - assert response.json() == {"name": "Foo", "description": None} + assert response.json() == { + "received": {"name": "John Doe", "email": "john@example.com"} + } -@needs_py310 -def test_read_items(client: TestClient) -> None: - response = client.get("/items/") +def test_post_company_form(): + response = client.post( + "/form-union/", data={"company_name": "Tech Corp", "industry": "Technology"} + ) assert response.status_code == 200, response.text - assert response.json() == [ - { - "name": "Portal Gun", - "description": "Device to travel through the multi-rick-verse", - }, - {"name": "Plumbus", "description": None}, - ] + assert response.json() == { + "received": {"company_name": "Tech Corp", "industry": "Technology"} + } -@needs_py310 -@needs_pydanticv2 -def test_openapi_schema(client: TestClient) -> None: +def test_invalid_form_data(): + response = client.post( + "/form-union/", + data={"name": "John", "company_name": "Tech Corp"}, + ) + assert response.status_code == 422, response.text + + +def test_empty_form(): + response = client.post("/form-union/") + 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.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Response Read Items Items Get", - } - } - }, - } - }, - }, + "/form-union/": { "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", + "summary": "Post Union Form", + "operationId": "post_union_form_form_union__post", "requestBody": { "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + "application/x-www-form-urlencoded": { + "schema": { + "anyOf": [ + {"$ref": "#/components/schemas/UserForm"}, + {"$ref": "#/components/schemas/CompanyForm"}, + ], + "title": "Data", + } } }, "required": True, @@ -87,11 +101,20 @@ def test_openapi_schema(client: TestClient) -> None: }, }, }, - }, + } } }, "components": { "schemas": { + "CompanyForm": { + "properties": { + "company_name": {"type": "string", "title": "Company Name"}, + "industry": {"type": "string", "title": "Industry"}, + }, + "type": "object", + "required": ["company_name", "industry"], + "title": "CompanyForm", + }, "HTTPValidationError": { "properties": { "detail": { @@ -103,17 +126,14 @@ def test_openapi_schema(client: TestClient) -> None: "type": "object", "title": "HTTPValidationError", }, - "Item": { + "UserForm": { "properties": { "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, + "email": {"type": "string", "title": "Email"}, }, "type": "object", - "required": ["name"], - "title": "Item", + "required": ["name", "email"], + "title": "UserForm", }, "ValidationError": { "properties": { diff --git a/tests/test_validate_response_recursive/app_pv1.py b/tests/test_validate_response_recursive/app.py similarity index 79% rename from tests/test_validate_response_recursive/app_pv1.py rename to tests/test_validate_response_recursive/app.py index 4cfc4b3eef..d23d279808 100644 --- a/tests/test_validate_response_recursive/app_pv1.py +++ b/tests/test_validate_response_recursive/app.py @@ -1,6 +1,7 @@ from typing import List from fastapi import FastAPI +from fastapi._compat import PYDANTIC_V2 from pydantic import BaseModel app = FastAPI() @@ -11,9 +12,6 @@ class RecursiveItem(BaseModel): name: str -RecursiveItem.update_forward_refs() - - class RecursiveSubitemInSubmodel(BaseModel): sub_items2: List["RecursiveItemViaSubmodel"] = [] name: str @@ -24,7 +22,13 @@ class RecursiveItemViaSubmodel(BaseModel): name: str -RecursiveSubitemInSubmodel.update_forward_refs() +if PYDANTIC_V2: + RecursiveItem.model_rebuild() + RecursiveSubitemInSubmodel.model_rebuild() + RecursiveItemViaSubmodel.model_rebuild() +else: + RecursiveItem.update_forward_refs() + RecursiveSubitemInSubmodel.update_forward_refs() @app.get("/items/recursive", response_model=RecursiveItem) diff --git a/tests/test_validate_response_recursive/app_pv2.py b/tests/test_validate_response_recursive/app_pv2.py deleted file mode 100644 index 8c93a83490..0000000000 --- a/tests/test_validate_response_recursive/app_pv2.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import List - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class RecursiveItem(BaseModel): - sub_items: List["RecursiveItem"] = [] - name: str - - -RecursiveItem.model_rebuild() - - -class RecursiveSubitemInSubmodel(BaseModel): - sub_items2: List["RecursiveItemViaSubmodel"] = [] - name: str - - -class RecursiveItemViaSubmodel(BaseModel): - sub_items1: List[RecursiveSubitemInSubmodel] = [] - name: str - - -RecursiveSubitemInSubmodel.model_rebuild() -RecursiveItemViaSubmodel.model_rebuild() - - -@app.get("/items/recursive", response_model=RecursiveItem) -def get_recursive(): - return {"name": "item", "sub_items": [{"name": "subitem", "sub_items": []}]} - - -@app.get("/items/recursive-submodel", response_model=RecursiveItemViaSubmodel) -def get_recursive_submodel(): - return { - "name": "item", - "sub_items1": [ - { - "name": "subitem", - "sub_items2": [ - { - "name": "subsubitem", - "sub_items1": [{"name": "subsubsubitem", "sub_items2": []}], - } - ], - } - ], - } diff --git a/tests/test_validate_response_recursive/test_validate_response_recursive_pv2.py b/tests/test_validate_response_recursive/test_validate_response_recursive.py similarity index 90% rename from tests/test_validate_response_recursive/test_validate_response_recursive_pv2.py rename to tests/test_validate_response_recursive/test_validate_response_recursive.py index 7d45e7fe40..21a299ab8b 100644 --- a/tests/test_validate_response_recursive/test_validate_response_recursive_pv2.py +++ b/tests/test_validate_response_recursive/test_validate_response_recursive.py @@ -1,12 +1,9 @@ from fastapi.testclient import TestClient -from ..utils import needs_pydanticv2 +from .app import app -@needs_pydanticv2 def test_recursive(): - from .app_pv2 import app - client = TestClient(app) response = client.get("/items/recursive") assert response.status_code == 200, response.text diff --git a/tests/test_validate_response_recursive/test_validate_response_recursive_pv1.py b/tests/test_validate_response_recursive/test_validate_response_recursive_pv1.py deleted file mode 100644 index de578ae031..0000000000 --- a/tests/test_validate_response_recursive/test_validate_response_recursive_pv1.py +++ /dev/null @@ -1,33 +0,0 @@ -from fastapi.testclient import TestClient - -from ..utils import needs_pydanticv1 - - -@needs_pydanticv1 -def test_recursive(): - from .app_pv1 import app - - client = TestClient(app) - response = client.get("/items/recursive") - assert response.status_code == 200, response.text - assert response.json() == { - "sub_items": [{"name": "subitem", "sub_items": []}], - "name": "item", - } - - response = client.get("/items/recursive-submodel") - assert response.status_code == 200, response.text - assert response.json() == { - "name": "item", - "sub_items1": [ - { - "name": "subitem", - "sub_items2": [ - { - "name": "subsubitem", - "sub_items1": [{"name": "subsubsubitem", "sub_items2": []}], - } - ], - } - ], - } diff --git a/tests/utils.py b/tests/utils.py index 460c028f7f..691e92bbfb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,10 +2,42 @@ import sys import pytest from fastapi._compat import PYDANTIC_V2 +from inline_snapshot import Snapshot 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+" ) +needs_py_lt_314 = pytest.mark.skipif( + sys.version_info > (3, 13), reason="requires python3.13-" +) needs_pydanticv2 = pytest.mark.skipif(not PYDANTIC_V2, reason="requires Pydantic v2") needs_pydanticv1 = pytest.mark.skipif(PYDANTIC_V2, reason="requires Pydantic v1") + + +def skip_module_if_py_gte_314(): + """Skip entire module on Python 3.14+ at import time.""" + if sys.version_info >= (3, 14): + pytest.skip("requires python3.13-", allow_module_level=True) + + +def pydantic_snapshot( + *, + v2: Snapshot, + v1: Snapshot, # TODO: remove v1 argument when deprecating Pydantic v1 +): + """ + This function should be used like this: + + >>> assert value == pydantic_snapshot(v2=snapshot(),v1=snapshot()) + + inline-snapshot will create the snapshots when pytest is executed for each versions of pydantic. + + It is also possible to use the function inside snapshots for version-specific values. + + >>> assert value == snapshot({ + "data": "some data", + "version_specific": pydantic_snapshot(v2=snapshot(),v1=snapshot()), + }) + """ + return v2 if PYDANTIC_V2 else v1