diff --git a/.github/actions/notify-translations/Dockerfile b/.github/actions/notify-translations/Dockerfile deleted file mode 100644 index b68b4bb1a..000000000 --- a/.github/actions/notify-translations/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.9 - -RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0" - -COPY ./app /app - -CMD ["python", "/app/main.py"] diff --git a/.github/actions/notify-translations/action.yml b/.github/actions/notify-translations/action.yml deleted file mode 100644 index c3579977c..000000000 --- a/.github/actions/notify-translations/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Notify Translations" -description: "Notify in the issue for a translation when there's a new PR available" -author: "Sebastiรกn Ramรญrez " -inputs: - token: - description: 'Token, to read the GitHub API. Can be passed in using {{ secrets.GITHUB_TOKEN }}' - required: true -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/people/Dockerfile b/.github/actions/people/Dockerfile deleted file mode 100644 index 1455106bd..000000000 --- a/.github/actions/people/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.9 - -RUN pip install httpx PyGithub "pydantic==2.0.2" pydantic-settings "pyyaml>=5.3.1,<6.0.0" - -COPY ./app /app - -CMD ["python", "/app/main.py"] diff --git a/.github/actions/people/action.yml b/.github/actions/people/action.yml deleted file mode 100644 index 71745b874..000000000 --- a/.github/actions/people/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Generate FastAPI People" -description: "Generate the data for the FastAPI People page" -author: "Sebastiรกn Ramรญrez " -inputs: - token: - description: 'User token, to read the GitHub API. Can be passed in using {{ secrets.FASTAPI_PEOPLE }}' - required: true -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/people/app/main.py b/.github/actions/people/app/main.py deleted file mode 100644 index b752d9d2b..000000000 --- a/.github/actions/people/app/main.py +++ /dev/null @@ -1,682 +0,0 @@ -import logging -import subprocess -import sys -from collections import Counter, defaultdict -from datetime import datetime, timedelta, timezone -from pathlib import Path -from typing import Any, Container, DefaultDict, Dict, List, Set, Union - -import httpx -import yaml -from github import Github -from pydantic import BaseModel, SecretStr -from pydantic_settings import BaseSettings - -github_graphql_url = "https://api.github.com/graphql" -questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0" - -discussions_query = """ -query Q($after: String, $category_id: ID) { - repository(name: "fastapi", owner: "fastapi") { - discussions(first: 100, after: $after, categoryId: $category_id) { - edges { - cursor - node { - number - author { - login - avatarUrl - url - } - title - createdAt - comments(first: 100) { - nodes { - createdAt - author { - login - avatarUrl - url - } - isAnswer - replies(first: 10) { - nodes { - createdAt - author { - login - avatarUrl - url - } - } - } - } - } - } - } - } - } -} -""" - - -prs_query = """ -query Q($after: String) { - repository(name: "fastapi", owner: "fastapi") { - pullRequests(first: 100, after: $after) { - edges { - cursor - node { - number - labels(first: 100) { - nodes { - name - } - } - author { - login - avatarUrl - url - } - title - createdAt - state - comments(first: 100) { - nodes { - createdAt - author { - login - avatarUrl - url - } - } - } - reviews(first:100) { - nodes { - author { - login - avatarUrl - url - } - state - } - } - } - } - } - } -} -""" - -sponsors_query = """ -query Q($after: String) { - user(login: "fastapi") { - sponsorshipsAsMaintainer(first: 100, after: $after) { - edges { - cursor - node { - sponsorEntity { - ... on Organization { - login - avatarUrl - url - } - ... on User { - login - avatarUrl - url - } - } - tier { - name - monthlyPriceInDollars - } - } - } - } - } -} -""" - - -class Author(BaseModel): - login: str - avatarUrl: str - url: str - - -# Discussions - - -class CommentsNode(BaseModel): - createdAt: datetime - author: Union[Author, None] = None - - -class Replies(BaseModel): - nodes: List[CommentsNode] - - -class DiscussionsCommentsNode(CommentsNode): - replies: Replies - - -class Comments(BaseModel): - nodes: List[CommentsNode] - - -class DiscussionsComments(BaseModel): - nodes: List[DiscussionsCommentsNode] - - -class DiscussionsNode(BaseModel): - number: int - author: Union[Author, None] = None - title: str - createdAt: datetime - comments: DiscussionsComments - - -class DiscussionsEdge(BaseModel): - cursor: str - node: DiscussionsNode - - -class Discussions(BaseModel): - edges: List[DiscussionsEdge] - - -class DiscussionsRepository(BaseModel): - discussions: Discussions - - -class DiscussionsResponseData(BaseModel): - repository: DiscussionsRepository - - -class DiscussionsResponse(BaseModel): - data: DiscussionsResponseData - - -# PRs - - -class LabelNode(BaseModel): - name: str - - -class Labels(BaseModel): - nodes: List[LabelNode] - - -class ReviewNode(BaseModel): - author: Union[Author, None] = None - state: str - - -class Reviews(BaseModel): - nodes: List[ReviewNode] - - -class PullRequestNode(BaseModel): - number: int - labels: Labels - author: Union[Author, None] = None - title: str - createdAt: datetime - state: str - comments: Comments - reviews: Reviews - - -class PullRequestEdge(BaseModel): - cursor: str - node: PullRequestNode - - -class PullRequests(BaseModel): - edges: List[PullRequestEdge] - - -class PRsRepository(BaseModel): - pullRequests: PullRequests - - -class PRsResponseData(BaseModel): - repository: PRsRepository - - -class PRsResponse(BaseModel): - data: PRsResponseData - - -# Sponsors - - -class SponsorEntity(BaseModel): - login: str - avatarUrl: str - url: str - - -class Tier(BaseModel): - name: str - monthlyPriceInDollars: float - - -class SponsorshipAsMaintainerNode(BaseModel): - sponsorEntity: SponsorEntity - tier: Tier - - -class SponsorshipAsMaintainerEdge(BaseModel): - cursor: str - node: SponsorshipAsMaintainerNode - - -class SponsorshipAsMaintainer(BaseModel): - edges: List[SponsorshipAsMaintainerEdge] - - -class SponsorsUser(BaseModel): - sponsorshipsAsMaintainer: SponsorshipAsMaintainer - - -class SponsorsResponseData(BaseModel): - user: SponsorsUser - - -class SponsorsResponse(BaseModel): - data: SponsorsResponseData - - -class Settings(BaseSettings): - input_token: SecretStr - github_repository: str - httpx_timeout: int = 30 - - -def get_graphql_response( - *, - settings: Settings, - query: str, - after: Union[str, None] = None, - category_id: Union[str, None] = None, -) -> Dict[str, Any]: - headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} - # category_id is only used by one query, but GraphQL allows unused variables, so - # keep it here for simplicity - variables = {"after": after, "category_id": category_id} - response = httpx.post( - github_graphql_url, - headers=headers, - timeout=settings.httpx_timeout, - json={"query": query, "variables": variables, "operationName": "Q"}, - ) - if response.status_code != 200: - logging.error( - f"Response was not 200, after: {after}, category_id: {category_id}" - ) - logging.error(response.text) - raise RuntimeError(response.text) - data = response.json() - if "errors" in data: - logging.error(f"Errors in response, after: {after}, category_id: {category_id}") - logging.error(data["errors"]) - logging.error(response.text) - raise RuntimeError(response.text) - return data - - -def get_graphql_question_discussion_edges( - *, - settings: Settings, - after: Union[str, None] = None, -): - data = get_graphql_response( - settings=settings, - query=discussions_query, - after=after, - category_id=questions_category_id, - ) - graphql_response = DiscussionsResponse.model_validate(data) - return graphql_response.data.repository.discussions.edges - - -def get_graphql_pr_edges(*, settings: Settings, after: Union[str, None] = None): - data = get_graphql_response(settings=settings, query=prs_query, after=after) - graphql_response = PRsResponse.model_validate(data) - return graphql_response.data.repository.pullRequests.edges - - -def get_graphql_sponsor_edges(*, settings: Settings, after: Union[str, None] = None): - data = get_graphql_response(settings=settings, query=sponsors_query, after=after) - graphql_response = SponsorsResponse.model_validate(data) - return graphql_response.data.user.sponsorshipsAsMaintainer.edges - - -class DiscussionExpertsResults(BaseModel): - commenters: Counter - last_month_commenters: Counter - three_months_commenters: Counter - six_months_commenters: Counter - one_year_commenters: Counter - authors: Dict[str, Author] - - -def get_discussion_nodes(settings: Settings) -> List[DiscussionsNode]: - discussion_nodes: List[DiscussionsNode] = [] - discussion_edges = get_graphql_question_discussion_edges(settings=settings) - - while discussion_edges: - for discussion_edge in discussion_edges: - discussion_nodes.append(discussion_edge.node) - last_edge = discussion_edges[-1] - discussion_edges = get_graphql_question_discussion_edges( - settings=settings, after=last_edge.cursor - ) - return discussion_nodes - - -def get_discussions_experts( - discussion_nodes: List[DiscussionsNode], -) -> DiscussionExpertsResults: - commenters = Counter() - last_month_commenters = Counter() - three_months_commenters = Counter() - six_months_commenters = Counter() - one_year_commenters = Counter() - authors: Dict[str, Author] = {} - - now = datetime.now(tz=timezone.utc) - one_month_ago = now - timedelta(days=30) - three_months_ago = now - timedelta(days=90) - six_months_ago = now - timedelta(days=180) - one_year_ago = now - timedelta(days=365) - - for discussion in discussion_nodes: - discussion_author_name = None - if discussion.author: - authors[discussion.author.login] = discussion.author - discussion_author_name = discussion.author.login - discussion_commentors: dict[str, datetime] = {} - for comment in discussion.comments.nodes: - if comment.author: - authors[comment.author.login] = comment.author - if comment.author.login != discussion_author_name: - author_time = discussion_commentors.get( - comment.author.login, comment.createdAt - ) - discussion_commentors[comment.author.login] = max( - author_time, comment.createdAt - ) - for reply in comment.replies.nodes: - if reply.author: - authors[reply.author.login] = reply.author - if reply.author.login != discussion_author_name: - author_time = discussion_commentors.get( - reply.author.login, reply.createdAt - ) - discussion_commentors[reply.author.login] = max( - author_time, reply.createdAt - ) - for author_name, author_time in discussion_commentors.items(): - commenters[author_name] += 1 - if author_time > one_month_ago: - last_month_commenters[author_name] += 1 - if author_time > three_months_ago: - three_months_commenters[author_name] += 1 - if author_time > six_months_ago: - six_months_commenters[author_name] += 1 - if author_time > one_year_ago: - one_year_commenters[author_name] += 1 - discussion_experts_results = DiscussionExpertsResults( - authors=authors, - commenters=commenters, - last_month_commenters=last_month_commenters, - three_months_commenters=three_months_commenters, - six_months_commenters=six_months_commenters, - one_year_commenters=one_year_commenters, - ) - return discussion_experts_results - - -def get_pr_nodes(settings: Settings) -> List[PullRequestNode]: - pr_nodes: List[PullRequestNode] = [] - pr_edges = get_graphql_pr_edges(settings=settings) - - while pr_edges: - for edge in pr_edges: - pr_nodes.append(edge.node) - last_edge = pr_edges[-1] - pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor) - return pr_nodes - - -class ContributorsResults(BaseModel): - contributors: Counter - commenters: Counter - reviewers: Counter - translation_reviewers: Counter - authors: Dict[str, Author] - - -def get_contributors(pr_nodes: List[PullRequestNode]) -> ContributorsResults: - contributors = Counter() - commenters = Counter() - reviewers = Counter() - translation_reviewers = Counter() - authors: Dict[str, Author] = {} - - for pr in pr_nodes: - author_name = None - if pr.author: - authors[pr.author.login] = pr.author - author_name = pr.author.login - pr_commentors: Set[str] = set() - pr_reviewers: Set[str] = set() - for comment in pr.comments.nodes: - if comment.author: - authors[comment.author.login] = comment.author - if comment.author.login == author_name: - continue - pr_commentors.add(comment.author.login) - for author_name in pr_commentors: - commenters[author_name] += 1 - for review in pr.reviews.nodes: - if review.author: - authors[review.author.login] = review.author - pr_reviewers.add(review.author.login) - for label in pr.labels.nodes: - if label.name == "lang-all": - translation_reviewers[review.author.login] += 1 - break - for reviewer in pr_reviewers: - reviewers[reviewer] += 1 - if pr.state == "MERGED" and pr.author: - contributors[pr.author.login] += 1 - return ContributorsResults( - contributors=contributors, - commenters=commenters, - reviewers=reviewers, - translation_reviewers=translation_reviewers, - authors=authors, - ) - - -def get_individual_sponsors(settings: Settings): - nodes: List[SponsorshipAsMaintainerNode] = [] - edges = get_graphql_sponsor_edges(settings=settings) - - while edges: - for edge in edges: - nodes.append(edge.node) - last_edge = edges[-1] - edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor) - - tiers: DefaultDict[float, Dict[str, SponsorEntity]] = defaultdict(dict) - for node in nodes: - tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = ( - node.sponsorEntity - ) - return tiers - - -def get_top_users( - *, - counter: Counter, - authors: Dict[str, Author], - skip_users: Container[str], - min_count: int = 2, -): - users = [] - for commenter, count in counter.most_common(50): - if commenter in skip_users: - continue - if count >= min_count: - author = authors[commenter] - users.append( - { - "login": commenter, - "count": count, - "avatarUrl": author.avatarUrl, - "url": author.url, - } - ) - return users - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - settings = Settings() - logging.info(f"Using config: {settings.model_dump_json()}") - g = Github(settings.input_token.get_secret_value()) - repo = g.get_repo(settings.github_repository) - discussion_nodes = get_discussion_nodes(settings=settings) - experts_results = get_discussions_experts(discussion_nodes=discussion_nodes) - pr_nodes = get_pr_nodes(settings=settings) - contributors_results = get_contributors(pr_nodes=pr_nodes) - authors = {**experts_results.authors, **contributors_results.authors} - maintainers_logins = {"tiangolo"} - bot_names = {"codecov", "github-actions", "pre-commit-ci", "dependabot"} - maintainers = [] - for login in maintainers_logins: - user = authors[login] - maintainers.append( - { - "login": login, - "answers": experts_results.commenters[login], - "prs": contributors_results.contributors[login], - "avatarUrl": user.avatarUrl, - "url": user.url, - } - ) - - skip_users = maintainers_logins | bot_names - experts = get_top_users( - counter=experts_results.commenters, - authors=authors, - skip_users=skip_users, - ) - last_month_experts = get_top_users( - counter=experts_results.last_month_commenters, - authors=authors, - skip_users=skip_users, - ) - three_months_experts = get_top_users( - counter=experts_results.three_months_commenters, - authors=authors, - skip_users=skip_users, - ) - six_months_experts = get_top_users( - counter=experts_results.six_months_commenters, - authors=authors, - skip_users=skip_users, - ) - one_year_experts = get_top_users( - counter=experts_results.one_year_commenters, - authors=authors, - skip_users=skip_users, - ) - top_contributors = get_top_users( - counter=contributors_results.contributors, - authors=authors, - skip_users=skip_users, - ) - top_reviewers = get_top_users( - counter=contributors_results.reviewers, - authors=authors, - skip_users=skip_users, - ) - top_translations_reviewers = get_top_users( - counter=contributors_results.translation_reviewers, - authors=authors, - skip_users=skip_users, - ) - - tiers = get_individual_sponsors(settings=settings) - keys = list(tiers.keys()) - keys.sort(reverse=True) - sponsors = [] - for key in keys: - sponsor_group = [] - for login, sponsor in tiers[key].items(): - sponsor_group.append( - {"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url} - ) - sponsors.append(sponsor_group) - - people = { - "maintainers": maintainers, - "experts": experts, - "last_month_experts": last_month_experts, - "three_months_experts": three_months_experts, - "six_months_experts": six_months_experts, - "one_year_experts": one_year_experts, - "top_contributors": top_contributors, - "top_reviewers": top_reviewers, - "top_translations_reviewers": top_translations_reviewers, - } - github_sponsors = { - "sponsors": sponsors, - } - # For local development - # people_path = Path("../../../../docs/en/data/people.yml") - people_path = Path("./docs/en/data/people.yml") - github_sponsors_path = Path("./docs/en/data/github_sponsors.yml") - people_old_content = people_path.read_text(encoding="utf-8") - github_sponsors_old_content = github_sponsors_path.read_text(encoding="utf-8") - new_people_content = yaml.dump( - people, sort_keys=False, width=200, allow_unicode=True - ) - new_github_sponsors_content = yaml.dump( - github_sponsors, sort_keys=False, width=200, allow_unicode=True - ) - if ( - people_old_content == new_people_content - and github_sponsors_old_content == new_github_sponsors_content - ): - logging.info("The FastAPI People data hasn't changed, finishing.") - sys.exit(0) - people_path.write_text(new_people_content, encoding="utf-8") - github_sponsors_path.write_text(new_github_sponsors_content, encoding="utf-8") - logging.info("Setting up GitHub Actions git user") - subprocess.run(["git", "config", "user.name", "github-actions"], check=True) - subprocess.run( - ["git", "config", "user.email", "github-actions@github.com"], check=True - ) - branch_name = "fastapi-people" - logging.info(f"Creating a new branch {branch_name}") - subprocess.run(["git", "checkout", "-b", branch_name], check=True) - logging.info("Adding updated file") - subprocess.run( - ["git", "add", str(people_path), str(github_sponsors_path)], check=True - ) - logging.info("Committing updated file") - message = "๐Ÿ‘ฅ Update FastAPI People" - result = subprocess.run(["git", "commit", "-m", message], check=True) - logging.info("Pushing branch") - subprocess.run(["git", "push", "origin", branch_name], check=True) - logging.info("Creating PR") - pr = repo.create_pull(title=message, body=message, base="master", head=branch_name) - logging.info(f"Created PR: {pr.number}") - logging.info("Finished") diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index d9ed61910..0b3096143 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -64,7 +64,7 @@ jobs: BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' && 'main' ) || ( github.event.workflow_run.head_sha ) }} # TODO: Use v3 when it's fixed, probably in v3.11 # https://github.com/cloudflare/wrangler-action/issues/307 - uses: cloudflare/wrangler-action@v3.13 + uses: cloudflare/wrangler-action@v3.14 # uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/.github/workflows/notify-translations.yml b/.github/workflows/notify-translations.yml index 187322bca..c96992689 100644 --- a/.github/workflows/notify-translations.yml +++ b/.github/workflows/notify-translations.yml @@ -15,15 +15,14 @@ on: required: false default: 'false' -permissions: - discussions: write - env: UV_SYSTEM_PYTHON: 1 jobs: - notify-translations: + job: runs-on: ubuntu-latest + permissions: + discussions: write steps: - name: Dump GitHub context env: @@ -42,12 +41,19 @@ jobs: cache-dependency-glob: | requirements**.txt pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: ./.github/actions/notify-translations - with: - token: ${{ secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Notify Translations + run: python ./scripts/notify_translations.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUMBER: ${{ github.event.inputs.number || null }} + DEBUG: ${{ github.event.inputs.debug_enabled || 'false' }} diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml index c60c63d1b..6ec3c1ad2 100644 --- a/.github/workflows/people.yml +++ b/.github/workflows/people.yml @@ -6,29 +6,48 @@ on: workflow_dispatch: inputs: debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + description: Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate) required: false - default: 'false' + default: "false" + +env: + UV_SYSTEM_PYTHON: 1 jobs: - fastapi-people: + job: if: github.repository_owner == 'fastapi' runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - # Ref: https://github.com/actions/runner/issues/2033 - - name: Fix git safe.directory in container - run: mkdir -p /home/runner/work/_temp/_github_home && printf "[safe]\n\tdirectory = /github/workspace" > /home/runner/work/_temp/_github_home/.gitconfig + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.4.15" + enable-cache: true + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: ./.github/actions/people - with: - token: ${{ secrets.FASTAPI_PEOPLE }} + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} + - name: FastAPI People Experts + run: python ./scripts/people.py + env: + GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 38df75928..bf88d59b1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,7 +35,7 @@ jobs: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} run: python -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.12.3 + uses: pypa/gh-action-pypi-publish@v1.12.4 - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0daf7472..5e8092641 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,6 +48,7 @@ jobs: strategy: matrix: python-version: + - "3.13" - "3.12" - "3.11" - "3.10" @@ -81,6 +82,10 @@ jobs: - name: Install Pydantic v2 if: matrix.pydantic-version == 'pydantic-v2' run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0" + # TODO: Remove this once Python 3.8 is no longer supported + - name: Install older AnyIO in Python 3.8 + if: matrix.python-version == '3.8' + run: uv pip install "anyio[trio]<4.0.0" - run: mkdir coverage - name: Test run: bash scripts/test.sh diff --git a/README.md b/README.md index 6492ad745..d5d5ced52 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

- Test + Test Coverage @@ -62,6 +62,7 @@ The key features are: + diff --git a/docs/de/docs/tutorial/query-params-str-validations.md b/docs/de/docs/tutorial/query-params-str-validations.md index f181d501c..de8879ce8 100644 --- a/docs/de/docs/tutorial/query-params-str-validations.md +++ b/docs/de/docs/tutorial/query-params-str-validations.md @@ -315,22 +315,6 @@ Wenn Sie einen Parameter erforderlich machen wollen, wรคhrend Sie `Query` verwen {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Erforderlich mit Ellipse (`...`) - -Es gibt eine Alternative, die explizit deklariert, dass ein Wert erforderlich ist. Sie kรถnnen als Default das Literal `...` setzen: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// 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. diff --git a/docs/em/docs/tutorial/query-params-str-validations.md b/docs/em/docs/tutorial/query-params-str-validations.md index dbaab5735..fd077bf8f 100644 --- a/docs/em/docs/tutorial/query-params-str-validations.md +++ b/docs/em/docs/tutorial/query-params-str-validations.md @@ -148,22 +148,6 @@ q: Union[str, None] = Query(default=None, min_length=3) {* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} -### โœ” โฎ๏ธ โ• (`...`) - -๐Ÿ“ค ๐ŸŽ› ๐ŸŒŒ ๐ŸŽฏ ๐Ÿ“ฃ ๐Ÿ‘ˆ ๐Ÿ’ฒ โœ”. ๐Ÿ‘† ๐Ÿ’ช โš’ `default` ๐Ÿ”ข ๐Ÿ”‘ ๐Ÿ’ฒ `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b.py hl[7] *} - -/// info - -๐Ÿšฅ ๐Ÿ‘† ๐Ÿšซ ๐Ÿ‘€ ๐Ÿ‘ˆ `...` โญ: โšซ๏ธ ๐ŸŽ ๐Ÿ‘ ๐Ÿ’ฒ, โšซ๏ธ ๐Ÿ• ๐Ÿ & ๐Ÿค™ "โ•". - -โšซ๏ธ โš™๏ธ Pydantic & FastAPI ๐ŸŽฏ ๐Ÿ“ฃ ๐Ÿ‘ˆ ๐Ÿ’ฒ โœ”. - -/// - -๐Ÿ‘‰ ๐Ÿ”œ โžก๏ธ **FastAPI** ๐Ÿ’ญ ๐Ÿ‘ˆ ๐Ÿ‘‰ ๐Ÿ”ข โœ”. - ### โœ” โฎ๏ธ `None` ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ“ฃ ๐Ÿ‘ˆ ๐Ÿ”ข ๐Ÿ’ช ๐Ÿšซ `None`, โœ‹๏ธ ๐Ÿ‘ˆ โšซ๏ธ โœ”. ๐Ÿ‘‰ ๐Ÿ”œ โšก ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ“จ ๐Ÿ’ฒ, ๐Ÿšฅ ๐Ÿ’ฒ `None`. @@ -178,18 +162,6 @@ Pydantic, โ” โšซ๏ธโ” ๐Ÿ‹๏ธ ๐ŸŒ ๐Ÿ’ฝ ๐Ÿ”ฌ & ๐Ÿ› ๏ธ FastAPI, โœ”๏ธ /// -### โš™๏ธ Pydantic `Required` โ†ฉ๏ธ โ• (`...`) - -๐Ÿšฅ ๐Ÿ‘† ๐Ÿ’ญ ๐Ÿ˜ฌ โš™๏ธ `...`, ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ—„ & โš™๏ธ `Required` โšช๏ธโžก๏ธ Pydantic: - -{* ../../docs_src/query_params_str_validations/tutorial006d.py hl[2,8] *} - -/// tip - -๐Ÿ’ญ ๐Ÿ‘ˆ ๐ŸŒ… ๐Ÿ’ผ, ๐Ÿ•โ” ๐Ÿ•ณ ๐Ÿšš, ๐Ÿ‘† ๐Ÿ’ช ๐ŸŽฏ ๐Ÿšซ `default` ๐Ÿ”ข, ๐Ÿ‘† ๐Ÿ›Ž ๐Ÿšซ โœ”๏ธ โš™๏ธ `...` ๐Ÿšซ `Required`. - -/// - ## ๐Ÿ”ข ๐Ÿ”ข ๐Ÿ“‡ / ๐Ÿ’— ๐Ÿ’ฒ ๐Ÿ•โ” ๐Ÿ‘† ๐Ÿ”ฌ ๐Ÿ”ข ๐Ÿ”ข ๐ŸŽฏ โฎ๏ธ `Query` ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ“ฃ โšซ๏ธ ๐Ÿ“จ ๐Ÿ“‡ ๐Ÿ’ฒ, โš–๏ธ ๐Ÿ™†โ€โ™€ ๐ŸŽ ๐ŸŒŒ, ๐Ÿ“จ ๐Ÿ’— ๐Ÿ’ฒ. diff --git a/docs/en/data/contributors.yml b/docs/en/data/contributors.yml index f679d7286..0e1a6505b 100644 --- a/docs/en/data/contributors.yml +++ b/docs/en/data/contributors.yml @@ -1,13 +1,18 @@ tiangolo: login: tiangolo - count: 697 + count: 713 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo dependabot: login: dependabot - count: 89 + count: 90 avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4 url: https://github.com/apps/dependabot +alejsdev: + login: alejsdev + count: 47 + avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=356f39ff3f0211c720b06d3dbb060e98884085e3&v=4 + url: https://github.com/alejsdev github-actions: login: github-actions count: 26 @@ -15,7 +20,7 @@ github-actions: url: https://github.com/apps/github-actions Kludex: login: Kludex - count: 22 + count: 23 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex pre-commit-ci: @@ -23,11 +28,6 @@ pre-commit-ci: count: 22 avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4 url: https://github.com/apps/pre-commit-ci -alejsdev: - login: alejsdev - count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=356f39ff3f0211c720b06d3dbb060e98884085e3&v=4 - url: https://github.com/alejsdev dmontagu: login: dmontagu count: 17 @@ -108,6 +108,11 @@ 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=c9a1691e5ebdc94cbf543086099a6ed705cdb873&v=4 + url: https://github.com/ShahriyarR adriangb: login: adriangb count: 4 @@ -208,11 +213,6 @@ graingert: count: 3 avatarUrl: https://avatars.githubusercontent.com/u/413772?u=64b77b6aa405c68a9c6bcf45f84257c66eea5f32&v=4 url: https://github.com/graingert -ShahriyarR: - login: ShahriyarR - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/3852029?u=c9a1691e5ebdc94cbf543086099a6ed705cdb873&v=4 - url: https://github.com/ShahriyarR jaystone776: login: jaystone776 count: 3 @@ -433,6 +433,11 @@ 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 @@ -443,6 +448,11 @@ 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 @@ -503,3 +513,8 @@ AyushSinghal1794: count: 2 avatarUrl: https://avatars.githubusercontent.com/u/89984761?v=4 url: https://github.com/AyushSinghal1794 +DanielKusyDev: + login: DanielKusyDev + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/36250676?u=2ea6114ff751fc48b55f231987a0e2582c6b1bd2&v=4 + url: https://github.com/DanielKusyDev diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml index 55fe3dda9..feb4e727f 100644 --- a/docs/en/data/github_sponsors.yml +++ b/docs/en/data/github_sponsors.yml @@ -2,6 +2,9 @@ sponsors: - - login: bump-sh avatarUrl: https://avatars.githubusercontent.com/u/33217836?v=4 url: https://github.com/bump-sh + - login: renderinc + avatarUrl: https://avatars.githubusercontent.com/u/36424661?v=4 + url: https://github.com/renderinc - login: Nixtla avatarUrl: https://avatars.githubusercontent.com/u/79945230?v=4 url: https://github.com/Nixtla @@ -20,9 +23,6 @@ sponsors: - login: zuplo avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4 url: https://github.com/zuplo - - login: render-sponsorships - avatarUrl: https://avatars.githubusercontent.com/u/189296666?v=4 - url: https://github.com/render-sponsorships - login: porter-dev avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4 url: https://github.com/porter-dev @@ -44,6 +44,9 @@ sponsors: - 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: mercedes-benz avatarUrl: https://avatars.githubusercontent.com/u/34240465?v=4 url: https://github.com/mercedes-benz @@ -95,9 +98,6 @@ sponsors: - - login: samuelcolvin avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4 url: https://github.com/samuelcolvin - - login: vincentkoc - avatarUrl: https://avatars.githubusercontent.com/u/25068?u=cbf098fc04c0473523d373b0dd2145b4ec99ef93&v=4 - url: https://github.com/vincentkoc - login: ProteinQure avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4 url: https://github.com/ProteinQure @@ -107,6 +107,9 @@ sponsors: - login: otosky avatarUrl: https://avatars.githubusercontent.com/u/42260747?u=69d089387c743d89427aa4ad8740cfb34045a9e0&v=4 url: https://github.com/otosky + - 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 @@ -215,6 +218,9 @@ sponsors: - login: anomaly avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4 url: https://github.com/anomaly + - 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 @@ -248,6 +254,9 @@ sponsors: - login: TrevorBenson avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=dccbea3327a57750923333d8ebf1a0b3f1948949&v=4 url: https://github.com/TrevorBenson + - login: kaangiray26 + avatarUrl: https://avatars.githubusercontent.com/u/11297495?u=e85327a77db45906d44f3ff06dd7f3303c644096&v=4 + url: https://github.com/kaangiray26 - login: wdwinslow avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4 url: https://github.com/wdwinslow @@ -263,9 +272,9 @@ sponsors: - 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: 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=48f68868db8886fce31a1d802c1003914c6cd7c6&v=4 url: https://github.com/Ryandaydev @@ -314,9 +323,9 @@ sponsors: - 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: PelicanQ + avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4 + url: https://github.com/PelicanQ - login: TheR1D avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4 url: https://github.com/TheR1D @@ -341,6 +350,9 @@ sponsors: - login: dvlpjrs avatarUrl: https://avatars.githubusercontent.com/u/32254642?u=fbd6ad0324d4f1eb6231cf775be1c7bd4404e961&v=4 url: https://github.com/dvlpjrs + - 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=4e14bd64282bad8f385eafbdb004b5a279366d6e&v=4 url: https://github.com/caviri @@ -356,9 +368,6 @@ sponsors: - login: PunRabbit avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4 url: https://github.com/PunRabbit - - login: PelicanQ - avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4 - url: https://github.com/PelicanQ - login: tochikuji avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4 url: https://github.com/tochikuji @@ -380,9 +389,9 @@ sponsors: - 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: hcristea + avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4 + url: https://github.com/hcristea - login: ddanier avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4 url: https://github.com/ddanier @@ -434,6 +443,9 @@ sponsors: - login: artempronevskiy avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4 url: https://github.com/artempronevskiy + - 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 @@ -458,9 +470,6 @@ 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: larsyngvelundin avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4 url: https://github.com/larsyngvelundin @@ -479,9 +488,15 @@ sponsors: - login: FabulousCodingFox avatarUrl: https://avatars.githubusercontent.com/u/78906517?u=924a27cbee3db7e0ece5cc1509921402e1445e74&v=4 url: https://github.com/FabulousCodingFox - - login: anqorithm - avatarUrl: https://avatars.githubusercontent.com/u/61029571?u=468256fa4e2d9ce2870b608299724bebb7a33f18&v=4 - url: https://github.com/anqorithm + - login: gateremark + avatarUrl: https://avatars.githubusercontent.com/u/91592218?u=969314eb2cfb035196f4d19499ec6f5050d7583a&v=4 + url: https://github.com/gateremark + - login: morzan1001 + avatarUrl: https://avatars.githubusercontent.com/u/47593005?u=c30ab7230f82a12a9b938dcb54f84a996931409a&v=4 + url: https://github.com/morzan1001 + - login: Toothwitch + avatarUrl: https://avatars.githubusercontent.com/u/1710406?u=5eebb23b46cd26e48643b9e5179536cad491c17a&v=4 + url: https://github.com/Toothwitch - login: ssbarnea avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c7bd9ddf127785286fc939dd18cb02db0a453bce&v=4 url: https://github.com/ssbarnea diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 02d1779e0..7f910ab34 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,24 +1,35 @@ maintainers: - login: tiangolo - answers: 1885 - prs: 577 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=740f11212a731f56798f558ceddb0bd07642afa7&v=4 + answers: 1894 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo experts: +- login: tiangolo + count: 1894 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: github-actions + count: 770 + 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: 645 + 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: 250 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: YuriiMotov + count: 223 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 + url: https://github.com/YuriiMotov - login: Mause - count: 220 + count: 219 avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 url: https://github.com/Mause - login: ycd @@ -26,7 +37,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=29682e4b6ac7d5293742ccf818188394b9a82972&v=4 url: https://github.com/ycd - login: JarroVGIT - count: 193 + count: 192 avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 url: https://github.com/JarroVGIT - login: euri10 @@ -41,72 +52,76 @@ experts: 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: 85 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro - login: raphaelauv count: 83 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 url: https://github.com/raphaelauv -- login: ArcLightSlavik - count: 71 - avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 - url: https://github.com/ArcLightSlavik - login: ghandic 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: ArcLightSlavik + count: 71 + avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4 + url: https://github.com/ArcLightSlavik +- login: n8sty + count: 66 + 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 - 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: yinziyan1206 + count: 49 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + url: https://github.com/yinziyan1206 +- 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 -- login: frankie567 - count: 43 - avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4 - url: https://github.com/frankie567 + avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 + url: https://github.com/insomnes - login: odiseo0 count: 43 avatarUrl: https://avatars.githubusercontent.com/u/87550035?u=241a71f6b7068738b81af3e57f45ffd723538401&v=4 url: https://github.com/odiseo0 +- login: frankie567 + count: 43 + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4 + url: https://github.com/frankie567 - login: includeamin count: 40 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 url: https://github.com/includeamin +- login: sinisaos + count: 39 + avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 + url: https://github.com/sinisaos +- login: luzzodev + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?v=4 + url: https://github.com/luzzodev - login: chbndrhnns - count: 38 + count: 37 avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 url: https://github.com/chbndrhnns - login: STeveShary @@ -123,7 +138,7 @@ 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 @@ -137,10 +152,6 @@ experts: 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: SirTelemak count: 23 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 @@ -149,6 +160,10 @@ experts: count: 22 avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 url: https://github.com/nymous +- login: acnebs + count: 22 + avatarUrl: https://avatars.githubusercontent.com/u/9054108?v=4 + url: https://github.com/acnebs - login: chrisK824 count: 22 avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 @@ -157,30 +172,38 @@ experts: count: 21 avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4 url: https://github.com/rafsaf +- login: ebottos94 + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/100039558?u=8b91053b3abe4a9209375e3651e1c1ef192d884b&v=4 + url: https://github.com/ebottos94 - login: nsidnev 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: retnikt +- login: estebanx64 + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=45f015f95e1c0f06df602be4ab688d4b854cc8a8&v=4 + url: https://github.com/estebanx64 +- login: sehraramiz count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 - url: https://github.com/retnikt + avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4 + url: https://github.com/sehraramiz - login: zoliknemet count: 18 avatarUrl: https://avatars.githubusercontent.com/u/22326718?u=31ba446ac290e23e56eea8e4f0c558aaf0b40779&v=4 url: https://github.com/zoliknemet -- login: nkhitrov +- login: retnikt + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 + url: https://github.com/retnikt +- 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 +212,659 @@ 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: jonatasoli + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4 + url: https://github.com/jonatasoli - login: dstlny count: 16 avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 url: https://github.com/dstlny +- login: ceb10n + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +- login: jorgerpo + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4 + url: https://github.com/jorgerpo +- login: simondale00 + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/33907262?u=2721fb37014d50daf473267c808aa678ecaefe09&v=4 + url: https://github.com/simondale00 +- login: ghost + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4 + url: https://github.com/ghost +- login: abhint + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 + url: https://github.com/abhint last_month_experts: +- login: Kludex + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex - login: YuriiMotov - count: 29 + count: 10 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 +- login: sehraramiz 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 + avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4 + url: https://github.com/sehraramiz +- login: luzzodev count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan -- login: PhysicallyActive + avatarUrl: https://avatars.githubusercontent.com/u/27291415?v=4 + url: https://github.com/luzzodev +- login: yokwejuste + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/71908316?u=592c1e42aa0ee5cb94890e0b863e2acc78cc3bbc&v=4 + url: https://github.com/yokwejuste +- login: alv2017 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 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: Trinkes count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32837064?u=5a0e6559bc391442629a28b6923790b54deb4464&v=4 - url: https://github.com/pedroconceicao + avatarUrl: https://avatars.githubusercontent.com/u/9466879?v=4 + url: https://github.com/Trinkes - login: PREPONDERANCE count: 2 avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 url: https://github.com/PREPONDERANCE -- login: aanchlia +- login: nbx3 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2835374?u=3c3ed29aa8b09ccaf8d66def0ce82bc2f7e5aab6&v=4 - url: https://github.com/aanchlia -- login: 0sahil + avatarUrl: https://avatars.githubusercontent.com/u/34649527?u=943812f69e0d40adbd3fa1c9b8ef50dd971a2a45&v=4 + url: https://github.com/nbx3 +- login: XiaoXinYo count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58521386?u=ac00b731c07c712d0baa57b8b70ac8422acf183c&v=4 - url: https://github.com/0sahil -- login: jgould22 + avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=b3b7cb758997f283c271a581833e407229dab82c&v=4 + url: https://github.com/XiaoXinYo +- login: iloveitaly count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 + avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 + url: https://github.com/iloveitaly three_months_experts: +- login: luzzodev + count: 33 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?v=4 + url: https://github.com/luzzodev - login: YuriiMotov - count: 101 + count: 31 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 + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&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 + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&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 + count: 7 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 +- login: yvallois + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/36999744?v=4 + url: https://github.com/yvallois +- login: yokwejuste + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/71908316?u=592c1e42aa0ee5cb94890e0b863e2acc78cc3bbc&v=4 + url: https://github.com/yokwejuste +- login: jgould22 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: alv2017 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: omarcruzpantoja + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: viniciusCalcantara 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 + avatarUrl: https://avatars.githubusercontent.com/u/108818737?u=3d7ffe5808843ee4372f9cc5a559ff1674cf1792&v=4 + url: https://github.com/viniciusCalcantara +- login: Trinkes count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/164261630?v=4 - url: https://github.com/moreno-p -- login: 0sahil + avatarUrl: https://avatars.githubusercontent.com/u/9466879?v=4 + url: https://github.com/Trinkes +- login: PREPONDERANCE count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58521386?u=ac00b731c07c712d0baa57b8b70ac8422acf183c&v=4 - url: https://github.com/0sahil -- login: patrick91 + avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 + url: https://github.com/PREPONDERANCE +- login: nbx3 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/667029?u=e35958a75ac1f99c81b4bc99e22db8cd665ae7f0&v=4 - url: https://github.com/patrick91 -- login: pprunty + avatarUrl: https://avatars.githubusercontent.com/u/34649527?u=943812f69e0d40adbd3fa1c9b8ef50dd971a2a45&v=4 + url: https://github.com/nbx3 +- login: XiaoXinYo count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58374462?u=5736576e586429abc97a803b8bcd4a6d828b8a2f&v=4 - url: https://github.com/pprunty -- login: angely-dev + avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=b3b7cb758997f283c271a581833e407229dab82c&v=4 + url: https://github.com/XiaoXinYo +- login: JavierSanchezCastro count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4362224?v=4 - url: https://github.com/angely-dev -- login: mastizada + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: iloveitaly count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1975818?u=0751a06d7271c8bf17cb73b1b845644ab4d2c6dc&v=4 - url: https://github.com/mastizada -- login: sm-Fifteen + avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 + url: https://github.com/iloveitaly +- login: LincolnPuzey count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 - url: https://github.com/sm-Fifteen -- login: methane + avatarUrl: https://avatars.githubusercontent.com/u/18750802?v=4 + url: https://github.com/LincolnPuzey +- login: Knighthawk-Leo count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/199592?v=4 - url: https://github.com/methane -- login: konstantinos1981 + avatarUrl: https://avatars.githubusercontent.com/u/72437494?u=27c68db94a3107b605e603cc136f4ba83f0106d5&v=4 + url: https://github.com/Knighthawk-Leo +- login: gelezo43 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/39465388?v=4 - url: https://github.com/konstantinos1981 -- login: druidance + avatarUrl: https://avatars.githubusercontent.com/u/40732698?u=611f39d3c1d2f4207a590937a78c1f10eed6232c&v=4 + url: https://github.com/gelezo43 +- login: AliYmn count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/160344534?v=4 - url: https://github.com/druidance -- login: fabianfalon + avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=98c1fca46c7e4dabe8c39d17b5e55d1511d41cf9&v=4 + url: https://github.com/AliYmn +- login: RichieB2B count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/3700760?u=95f69e31280b17ac22299cdcd345323b142fe0af&v=4 - url: https://github.com/fabianfalon -- login: VatsalJagani + avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4 + url: https://github.com/RichieB2B +- login: Synrom count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/20964366?u=43552644be05c9107c029e26d5ab3be5a1920f45&v=4 - url: https://github.com/VatsalJagani -- login: khaledadrani + avatarUrl: https://avatars.githubusercontent.com/u/30272537?v=4 + url: https://github.com/Synrom +- login: iiotsrc count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/45245894?u=49ed5056426a149a5af29d385d8bd3847101d3a4&v=4 - url: https://github.com/khaledadrani -- login: ThirVondukr + avatarUrl: https://avatars.githubusercontent.com/u/131771119?u=bcaf2559ef6266af70b151b7fda31a1ee3dbecb3&v=4 + url: https://github.com/iiotsrc +- login: Kfir-G count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/50728601?u=167c0bd655e52817082e50979a86d2f98f95b1a3&v=4 - url: https://github.com/ThirVondukr + avatarUrl: https://avatars.githubusercontent.com/u/57500876?u=0cd29db046a17f12f382d398141319fca7ff230a&v=4 + url: https://github.com/Kfir-G six_months_experts: - login: YuriiMotov - count: 104 + count: 72 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 + count: 40 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex +- login: luzzodev + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?v=4 + url: https://github.com/luzzodev +- login: sinisaos + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 + url: https://github.com/sinisaos - login: JavierSanchezCastro - count: 40 + count: 16 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: Kfir-G + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/57500876?u=0cd29db046a17f12f382d398141319fca7ff230a&v=4 + url: https://github.com/Kfir-G +- login: tiangolo + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: sehraramiz + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4 + url: https://github.com/sehraramiz +- login: ceb10n + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n - login: estebanx64 count: 7 avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=45f015f95e1c0f06df602be4ab688d4b854cc8a8&v=4 url: https://github.com/estebanx64 -- login: PhysicallyActive +- login: yvallois 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 + avatarUrl: https://avatars.githubusercontent.com/u/36999744?v=4 + url: https://github.com/yvallois +- login: n8sty count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/90090313?v=4 - url: https://github.com/shashstormer -- login: GodMoonGoodman + avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 + url: https://github.com/n8sty +- login: TomFaulkner count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/29688727?u=7b251da620d999644c37c1feeb292d033eed7ad6&v=4 - url: https://github.com/GodMoonGoodman -- login: flo-at + avatarUrl: https://avatars.githubusercontent.com/u/14956620?v=4 + url: https://github.com/TomFaulkner +- login: yokwejuste count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/564288?v=4 - url: https://github.com/flo-at -- login: PREPONDERANCE + avatarUrl: https://avatars.githubusercontent.com/u/71908316?u=592c1e42aa0ee5cb94890e0b863e2acc78cc3bbc&v=4 + url: https://github.com/yokwejuste +- login: jgould22 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: alv2017 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 - url: https://github.com/PREPONDERANCE -- login: chrisK824 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: viniciusCalcantara count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 - url: https://github.com/chrisK824 -- login: angely-dev + avatarUrl: https://avatars.githubusercontent.com/u/108818737?u=3d7ffe5808843ee4372f9cc5a559ff1674cf1792&v=4 + url: https://github.com/viniciusCalcantara +- login: pawelad count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/4362224?v=4 - url: https://github.com/angely-dev -- login: fmelihh + avatarUrl: https://avatars.githubusercontent.com/u/7062874?u=d27dc220545a8401ad21840590a97d474d7101e6&v=4 + url: https://github.com/pawelad +- login: dbfreem count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/99879453?u=671117dba9022db2237e3da7a39cbc2efc838db0&v=4 - url: https://github.com/fmelihh -- login: ryanisn + avatarUrl: https://avatars.githubusercontent.com/u/9778569?u=f2f1e9135b5e4f1b0c6821a548b17f97572720fc&v=4 + url: https://github.com/dbfreem +- login: Isuxiz count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/53449841?v=4 - url: https://github.com/ryanisn -- login: JoshYuJump + avatarUrl: https://avatars.githubusercontent.com/u/48672727?u=34d7b4ade252687d22a27cf53037b735b244bfc1&v=4 + url: https://github.com/Isuxiz +- login: bertomaniac count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/5901894?u=cdbca6296ac4cdcdf6945c112a1ce8d5342839ea&v=4 - url: https://github.com/JoshYuJump -- login: pythonweb2 + avatarUrl: https://avatars.githubusercontent.com/u/10235051?u=14484a96833228a7b29fee4a7916d411c242c4f6&v=4 + url: https://github.com/bertomaniac +- login: PhysicallyActive count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: omarcruzpantoja + avatarUrl: https://avatars.githubusercontent.com/u/160476156?u=7a8e44f4a43d3bba636f795bb7d9476c9233b4d8&v=4 + url: https://github.com/PhysicallyActive +- login: Minibrams count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/15116058?u=4b64c643fad49225d854e1aaecd1ffc6f9071a1b&v=4 - url: https://github.com/omarcruzpantoja -- login: bogdan-coman-uv + avatarUrl: https://avatars.githubusercontent.com/u/8108085?u=b028dbc308fa8485e0e2e9402b3d03d8deb22bf9&v=4 + url: https://github.com/Minibrams +- login: AIdjis count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/92912507?v=4 - url: https://github.com/bogdan-coman-uv -- login: ahmedabdou14 + avatarUrl: https://avatars.githubusercontent.com/u/88404339?u=2a80d80b054e9228391e32fb9bb39571509dab6a&v=4 + url: https://github.com/AIdjis +- login: svlandeg count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=05365b155a1ff911532e8be316acfad2e0736f98&v=4 - url: https://github.com/ahmedabdou14 -- login: mskrip + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg +- login: Trinkes count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17459600?u=10019d5c38ae3374dd4a6743b0223e56a78d4855&v=4 - url: https://github.com/mskrip -- login: leonidktoto + avatarUrl: https://avatars.githubusercontent.com/u/9466879?v=4 + url: https://github.com/Trinkes +- login: PREPONDERANCE count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/159561986?v=4 - url: https://github.com/leonidktoto -- login: pedroconceicao + avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 + url: https://github.com/PREPONDERANCE +- login: nbx3 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32837064?u=5a0e6559bc391442629a28b6923790b54deb4464&v=4 - url: https://github.com/pedroconceicao -- login: hwong557 + avatarUrl: https://avatars.githubusercontent.com/u/34649527?u=943812f69e0d40adbd3fa1c9b8ef50dd971a2a45&v=4 + url: https://github.com/nbx3 +- login: yanggeorge count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/460259?u=7d2f1b33ea5bda4d8e177ab3cb924a673d53087e&v=4 - url: https://github.com/hwong557 -- login: Jackiexiao + avatarUrl: https://avatars.githubusercontent.com/u/2434407?v=4 + url: https://github.com/yanggeorge +- login: XiaoXinYo count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/18050469?u=a2003e21a7780477ba00bf87a9abef8af58e91d1&v=4 - url: https://github.com/Jackiexiao -- login: admo1 + avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=b3b7cb758997f283c271a581833e407229dab82c&v=4 + url: https://github.com/XiaoXinYo +- login: pythonweb2 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/14835916?v=4 - url: https://github.com/admo1 -- login: binbjz + avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 + url: https://github.com/pythonweb2 +- login: slafs count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/8213913?u=22b68b7a0d5bf5e09c02084c0f5f53d7503114cd&v=4 - url: https://github.com/binbjz -- login: nameer + avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 + url: https://github.com/slafs +- login: AmirHmZz count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/3931725?u=6199fb065df098fc13ac0a5e649f89672b586732&v=4 - url: https://github.com/nameer -- login: moreno-p + avatarUrl: https://avatars.githubusercontent.com/u/38752106?u=07f80e451bda00a9492bbc764e49d24ad3ada8cc&v=4 + url: https://github.com/AmirHmZz +- login: iloveitaly count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/164261630?v=4 - url: https://github.com/moreno-p -- login: 0sahil + avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 + url: https://github.com/iloveitaly +- login: LincolnPuzey count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58521386?u=ac00b731c07c712d0baa57b8b70ac8422acf183c&v=4 - url: https://github.com/0sahil -- login: nymous + avatarUrl: https://avatars.githubusercontent.com/u/18750802?v=4 + url: https://github.com/LincolnPuzey +- login: alejsdev count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4216559?u=360a36fb602cded27273cbfc0afc296eece90662&v=4 - url: https://github.com/nymous -- login: patrick91 + avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=356f39ff3f0211c720b06d3dbb060e98884085e3&v=4 + url: https://github.com/alejsdev +- login: Knighthawk-Leo count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/667029?u=e35958a75ac1f99c81b4bc99e22db8cd665ae7f0&v=4 - url: https://github.com/patrick91 -- login: pprunty + avatarUrl: https://avatars.githubusercontent.com/u/72437494?u=27c68db94a3107b605e603cc136f4ba83f0106d5&v=4 + url: https://github.com/Knighthawk-Leo +- login: gelezo43 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/58374462?u=5736576e586429abc97a803b8bcd4a6d828b8a2f&v=4 - url: https://github.com/pprunty -- login: JonnyBootsNpants + avatarUrl: https://avatars.githubusercontent.com/u/40732698?u=611f39d3c1d2f4207a590937a78c1f10eed6232c&v=4 + url: https://github.com/gelezo43 +- login: christiansicari count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/155071540?u=2d3a72b74a2c4c8eaacdb625c7ac850369579352&v=4 - url: https://github.com/JonnyBootsNpants -- login: richin13 + avatarUrl: https://avatars.githubusercontent.com/u/29756552?v=4 + url: https://github.com/christiansicari +- login: 1001pepi count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/8370058?u=8e37a4cdbc78983a5f4b4847f6d1879fb39c851c&v=4 - url: https://github.com/richin13 -- login: mastizada + avatarUrl: https://avatars.githubusercontent.com/u/82064861?u=8c6ffdf2275d6970a07294752c545cd2702c57d3&v=4 + url: https://github.com/1001pepi +- login: AliYmn count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1975818?u=0751a06d7271c8bf17cb73b1b845644ab4d2c6dc&v=4 - url: https://github.com/mastizada -- login: sm-Fifteen + avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=98c1fca46c7e4dabe8c39d17b5e55d1511d41cf9&v=4 + url: https://github.com/AliYmn +- login: RichieB2B count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 - url: https://github.com/sm-Fifteen -- login: amacfie + avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4 + url: https://github.com/RichieB2B +- login: Synrom + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/30272537?v=4 + url: https://github.com/Synrom +- login: ecly count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/889657?u=d70187989940b085bcbfa3bedad8dbc5f3ab1fe7&v=4 - url: https://github.com/amacfie -- login: garg10may + avatarUrl: https://avatars.githubusercontent.com/u/8410422?v=4 + url: https://github.com/ecly +- login: iiotsrc count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/8787120?u=7028d2b3a2a26534c1806eb76c7425a3fac9732f&v=4 - url: https://github.com/garg10may -- login: methane + avatarUrl: https://avatars.githubusercontent.com/u/131771119?u=bcaf2559ef6266af70b151b7fda31a1ee3dbecb3&v=4 + url: https://github.com/iiotsrc +- login: simondale00 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/199592?v=4 - url: https://github.com/methane -- login: konstantinos1981 + avatarUrl: https://avatars.githubusercontent.com/u/33907262?u=2721fb37014d50daf473267c808aa678ecaefe09&v=4 + url: https://github.com/simondale00 +- login: jd-solanki count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/39465388?v=4 - url: https://github.com/konstantinos1981 -- login: druidance + avatarUrl: https://avatars.githubusercontent.com/u/47495003?u=6e225cb42c688d0cd70e65c6baedb9f5922b1178&v=4 + url: https://github.com/jd-solanki +- login: AumGupta count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/160344534?v=4 - url: https://github.com/druidance + avatarUrl: https://avatars.githubusercontent.com/u/86357151?u=7d05aa606c0611a18f4db16cf26361ce10a6e195&v=4 + url: https://github.com/AumGupta +- login: DeoLeung + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/3764720?u=4c222ef513814de4c7fb3736d0a7adf11d953d43&v=4 + url: https://github.com/DeoLeung +- login: Reemyos + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/44867003?v=4 + url: https://github.com/Reemyos +- login: deight93 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/37678115?u=a608798b5bd0034183a9c430ebb42fb266db86ce&v=4 + url: https://github.com/deight93 +- login: Jkrox + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/83181939?u=d6a922d97129f7f3916d6a1c166bc011b3a72b7f&v=4 + url: https://github.com/Jkrox +- login: mmzeynalli + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/33568903?u=19efd0c0722730b83a70b7c86c36e5b7d83e07d2&v=4 + url: https://github.com/mmzeynalli +- login: ddahan + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1933516?u=1d200a620e8d6841df017e9f2bb7efb58b580f40&v=4 + url: https://github.com/ddahan +- login: jfeaver + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1091338?u=0bcba366447d8fadad63f6705a52d128da4c7ec2&v=4 + url: https://github.com/jfeaver +- login: Wurstnase + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/8709415?u=f479af475a97aee9a1dab302cfc35d07e9ea245f&v=4 + url: https://github.com/Wurstnase +- login: tristan + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1412?u=aab8aaa4cc0f1210ac45fc93873a5909d314c965&v=4 + url: https://github.com/tristan +- login: chandanch + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/8663552?u=afc484bc0a952c83f1fb6a1583cda443f807cd66&v=4 + url: https://github.com/chandanch +- login: rvishruth + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/79176273?v=4 + url: https://github.com/rvishruth +- login: mattmess1221 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=723662989f2027755e67d200137c13c53ae154ac&v=4 + url: https://github.com/mattmess1221 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 + count: 223 avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&v=4 url: https://github.com/YuriiMotov +- login: Kludex + count: 81 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex - login: JavierSanchezCastro - count: 59 + count: 47 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 url: https://github.com/JavierSanchezCastro +- login: jgould22 + count: 42 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: sinisaos + count: 39 + avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 + url: https://github.com/sinisaos +- login: luzzodev + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?v=4 + url: https://github.com/luzzodev +- login: tiangolo + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo - login: n8sty - count: 40 + count: 23 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 + count: 19 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: ceb10n + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +- login: sehraramiz + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4 + url: https://github.com/sehraramiz - login: PhysicallyActive - count: 6 + count: 14 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 +- login: Kfir-G + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/57500876?u=0cd29db046a17f12f382d398141319fca7ff230a&v=4 + url: https://github.com/Kfir-G +- login: mattmess1221 + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=723662989f2027755e67d200137c13c53ae154ac&v=4 + url: https://github.com/mattmess1221 +- login: hasansezertasan + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 + url: https://github.com/hasansezertasan +- login: AIdjis + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/88404339?u=2a80d80b054e9228391e32fb9bb39571509dab6a&v=4 + url: https://github.com/AIdjis +- login: yvallois count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/31826970?u=8625355dc25ddf9c85a8b2b0b9932826c4c8f44c&v=4 - url: https://github.com/White-Mask -- login: sehraramiz + avatarUrl: https://avatars.githubusercontent.com/u/36999744?v=4 + url: https://github.com/yvallois +- login: PREPONDERANCE count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/14166324?v=4 - url: https://github.com/sehraramiz + avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 + url: https://github.com/PREPONDERANCE +- login: pythonweb2 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 + url: https://github.com/pythonweb2 - 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 +- login: gustavosett count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/90090313?v=4 - url: https://github.com/shashstormer -- login: wu-clan + avatarUrl: https://avatars.githubusercontent.com/u/99373133?u=1739ca547c3d200f1b72450520bce46a97aab184&v=4 + url: https://github.com/gustavosett +- login: binbjz count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/52145145?u=f8c9e5c8c259d248e1683fedf5027b4ee08a0967&v=4 - url: https://github.com/wu-clan -- login: abhint + avatarUrl: https://avatars.githubusercontent.com/u/8213913?u=22b68b7a0d5bf5e09c02084c0f5f53d7503114cd&v=4 + url: https://github.com/binbjz +- login: chyok count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 - url: https://github.com/abhint -- login: anthonycepeda + avatarUrl: https://avatars.githubusercontent.com/u/32629225?u=3b7c30e8a09426a1b9284f6e8a0ae53a525596bf&v=4 + url: https://github.com/chyok +- login: TomFaulkner count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 - url: https://github.com/anthonycepeda -- login: GodMoonGoodman + avatarUrl: https://avatars.githubusercontent.com/u/14956620?v=4 + url: https://github.com/TomFaulkner +- login: yokwejuste count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/29688727?u=7b251da620d999644c37c1feeb292d033eed7ad6&v=4 - url: https://github.com/GodMoonGoodman + avatarUrl: https://avatars.githubusercontent.com/u/71908316?u=592c1e42aa0ee5cb94890e0b863e2acc78cc3bbc&v=4 + url: https://github.com/yokwejuste +- login: DeoLeung + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/3764720?u=4c222ef513814de4c7fb3736d0a7adf11d953d43&v=4 + url: https://github.com/DeoLeung - login: flo-at count: 4 avatarUrl: https://avatars.githubusercontent.com/u/564288?v=4 url: https://github.com/flo-at -- 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 +- login: GodMoonGoodman count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 - url: https://github.com/dmontagu -- login: sanzoghenzo + avatarUrl: https://avatars.githubusercontent.com/u/29688727?u=7b251da620d999644c37c1feeb292d033eed7ad6&v=4 + url: https://github.com/GodMoonGoodman +- login: bertomaniac count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/977953?v=4 - url: https://github.com/sanzoghenzo -- login: lucasgadams + avatarUrl: https://avatars.githubusercontent.com/u/10235051?u=14484a96833228a7b29fee4a7916d411c242c4f6&v=4 + url: https://github.com/bertomaniac +- login: alv2017 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/36425095?v=4 - url: https://github.com/lucasgadams -- login: NeilBotelho + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: msehnout count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/39030675?u=16fea2ff90a5c67b974744528a38832a6d1bb4f7&v=4 - url: https://github.com/NeilBotelho -- login: hhartzer + avatarUrl: https://avatars.githubusercontent.com/u/9369632?u=8c988f1b008a3f601385a3616f9327820f66e3a5&v=4 + url: https://github.com/msehnout +- login: viniciusCalcantara count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/100533792?v=4 - url: https://github.com/hhartzer -- login: binbjz + avatarUrl: https://avatars.githubusercontent.com/u/108818737?u=3d7ffe5808843ee4372f9cc5a559ff1674cf1792&v=4 + url: https://github.com/viniciusCalcantara +- login: pawelad count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/8213913?u=22b68b7a0d5bf5e09c02084c0f5f53d7503114cd&v=4 - url: https://github.com/binbjz -- login: PREPONDERANCE + avatarUrl: https://avatars.githubusercontent.com/u/7062874?u=d27dc220545a8401ad21840590a97d474d7101e6&v=4 + url: https://github.com/pawelad +- login: ThirVondukr count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/112809059?u=30ab12dc9ddba2f94ab90e6ad4ad8bc5cfa7fccd&v=4 - url: https://github.com/PREPONDERANCE -- login: nameer + avatarUrl: https://avatars.githubusercontent.com/u/50728601?u=167c0bd655e52817082e50979a86d2f98f95b1a3&v=4 + url: https://github.com/ThirVondukr +- login: dbfreem + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/9778569?u=f2f1e9135b5e4f1b0c6821a548b17f97572720fc&v=4 + url: https://github.com/dbfreem +- login: Isuxiz count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/3931725?u=6199fb065df098fc13ac0a5e649f89672b586732&v=4 - url: https://github.com/nameer + avatarUrl: https://avatars.githubusercontent.com/u/48672727?u=34d7b4ade252687d22a27cf53037b735b244bfc1&v=4 + url: https://github.com/Isuxiz - login: angely-dev count: 3 avatarUrl: https://avatars.githubusercontent.com/u/4362224?v=4 url: https://github.com/angely-dev -- login: fmelihh +- login: deight93 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/99879453?u=671117dba9022db2237e3da7a39cbc2efc838db0&v=4 - url: https://github.com/fmelihh + avatarUrl: https://avatars.githubusercontent.com/u/37678115?u=a608798b5bd0034183a9c430ebb42fb266db86ce&v=4 + url: https://github.com/deight93 +- login: mmzeynalli + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/33568903?u=19efd0c0722730b83a70b7c86c36e5b7d83e07d2&v=4 + url: https://github.com/mmzeynalli +- login: Minibrams + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/8108085?u=b028dbc308fa8485e0e2e9402b3d03d8deb22bf9&v=4 + url: https://github.com/Minibrams - login: ryanisn count: 3 avatarUrl: https://avatars.githubusercontent.com/u/53449841?v=4 url: https://github.com/ryanisn -- login: theobouwman +- login: svlandeg count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/16098190?u=dc70db88a7a99b764c9a89a6e471e0b7ca478a35&v=4 - url: https://github.com/theobouwman -- login: methane + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg +- login: alexandercronin 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 + avatarUrl: https://avatars.githubusercontent.com/u/8014288?u=69580504c51a0cdd756fc47b23bb7f404bd694e7&v=4 + url: https://github.com/alexandercronin +- login: aanchlia count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/1397556?v=4 - url: https://github.com/divums -- login: prostomarkeloff + avatarUrl: https://avatars.githubusercontent.com/u/2835374?u=3c3ed29aa8b09ccaf8d66def0ce82bc2f7e5aab6&v=4 + url: https://github.com/aanchlia +- login: chrisK824 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 - url: https://github.com/prostomarkeloff -- login: nsidnev + avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 + url: https://github.com/chrisK824 +- login: omarcruzpantoja count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4 - url: https://github.com/nsidnev -- login: pawamoy + avatarUrl: https://avatars.githubusercontent.com/u/15116058?u=4b64c643fad49225d854e1aaecd1ffc6f9071a1b&v=4 + url: https://github.com/omarcruzpantoja +- login: ahmedabdou14 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: YuriiMotov - count: 25 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=e83a39697a2d33ab2ec9bfbced794ee48bc29cec&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 - 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 -- login: JavierSanchezCastro - count: 19 - 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 - 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 -- login: JavierSanchezCastro - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: bezaca - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4 - url: https://github.com/bezaca + avatarUrl: https://avatars.githubusercontent.com/u/104530599?u=d87b866e7c1db970d6f8e8031643818349b046d5&v=4 + url: https://github.com/ahmedabdou14 +- login: Trinkes + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/9466879?v=4 + url: https://github.com/Trinkes +- login: Leon0824 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1922026?v=4 + url: https://github.com/Leon0824 +- login: CarlosOliveira-23 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/102637302?u=cf350a4db956f30cbb2c27d3be0d15c282e32b14&v=4 + url: https://github.com/CarlosOliveira-23 +- login: nbx3 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/34649527?u=943812f69e0d40adbd3fa1c9b8ef50dd971a2a45&v=4 + url: https://github.com/nbx3 +- login: yanggeorge + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/2434407?v=4 + url: https://github.com/yanggeorge +- login: XiaoXinYo + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=b3b7cb758997f283c271a581833e407229dab82c&v=4 + url: https://github.com/XiaoXinYo +- login: anantgupta129 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/66518357?u=6e25dcd84638f17d2c6df5dc26f07fd7c6dc118e&v=4 + url: https://github.com/anantgupta129 +- login: slafs + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 + url: https://github.com/slafs +- login: monchin + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/18521800?v=4 + url: https://github.com/monchin +- login: AmirHmZz + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/38752106?u=07f80e451bda00a9492bbc764e49d24ad3ada8cc&v=4 + url: https://github.com/AmirHmZz +- login: iloveitaly + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 + url: https://github.com/iloveitaly +- login: msukmanowsky + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/362755?u=782e6bf5b9f0356c3f74b4d894fda9f179252086&v=4 + url: https://github.com/msukmanowsky +- login: shurshilov + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/11828278?u=6bcadc5ce4f2f56a514331c9f68eb987d4afe29a&v=4 + url: https://github.com/shurshilov diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 4231452e4..91b23937c 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -48,6 +48,9 @@ silver: - 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 bronze: - url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source title: Biosecurity risk assessments made easy. @@ -55,3 +58,6 @@ bronze: - 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 diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 3e885a2f7..d507a500f 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -29,7 +29,12 @@ logins: - andrew-propelauth - svix - zuplo-oss + - zuplo - Kong - speakeasy-api - jess-render - blockbee-io + - liblaber + - render-sponsorships + - renderinc + - stainless-api diff --git a/docs/en/data/topic_repos.yml b/docs/en/data/topic_repos.yml index c1176e55c..302dc3bb5 100644 --- a/docs/en/data/topic_repos.yml +++ b/docs/en/data/topic_repos.yml @@ -1,495 +1,495 @@ - name: full-stack-fastapi-template html_url: https://github.com/fastapi/full-stack-fastapi-template - stars: 28796 + stars: 29409 owner_login: fastapi owner_html_url: https://github.com/fastapi - name: Hello-Python html_url: https://github.com/mouredev/Hello-Python - stars: 27554 + stars: 28113 owner_login: mouredev owner_html_url: https://github.com/mouredev - name: serve html_url: https://github.com/jina-ai/serve - stars: 21225 + stars: 21264 owner_login: jina-ai owner_html_url: https://github.com/jina-ai - name: sqlmodel html_url: https://github.com/fastapi/sqlmodel - stars: 14921 + stars: 15109 owner_login: fastapi owner_html_url: https://github.com/fastapi - name: HivisionIDPhotos html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos - stars: 14025 + stars: 14564 owner_login: Zeyi-Lin owner_html_url: https://github.com/Zeyi-Lin - name: Douyin_TikTok_Download_API html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API - stars: 10001 + stars: 10701 owner_login: Evil0ctal owner_html_url: https://github.com/Evil0ctal - name: fastapi-best-practices html_url: https://github.com/zhanymkanov/fastapi-best-practices - stars: 9820 + stars: 10180 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov - name: awesome-fastapi html_url: https://github.com/mjhea0/awesome-fastapi - stars: 8899 + stars: 9061 owner_login: mjhea0 owner_html_url: https://github.com/mjhea0 - name: FastUI html_url: https://github.com/pydantic/FastUI - stars: 8400 + stars: 8644 owner_login: pydantic owner_html_url: https://github.com/pydantic - name: nonebot2 html_url: https://github.com/nonebot/nonebot2 - stars: 6235 + stars: 6312 owner_login: nonebot owner_html_url: https://github.com/nonebot - name: serge html_url: https://github.com/serge-chat/serge - stars: 5685 + stars: 5686 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: 4787 - owner_login: fastapi-users - owner_html_url: https://github.com/fastapi-users - name: FileCodeBox html_url: https://github.com/vastsa/FileCodeBox - stars: 4479 + stars: 4933 owner_login: vastsa owner_html_url: https://github.com/vastsa +- name: fastapi-users + html_url: https://github.com/fastapi-users/fastapi-users + stars: 4849 + owner_login: fastapi-users + owner_html_url: https://github.com/fastapi-users - name: hatchet html_url: https://github.com/hatchet-dev/hatchet - stars: 4413 + stars: 4514 owner_login: hatchet-dev owner_html_url: https://github.com/hatchet-dev - name: chatgpt-web-share html_url: https://github.com/chatpire/chatgpt-web-share - stars: 4322 + stars: 4319 owner_login: chatpire owner_html_url: https://github.com/chatpire -- name: atrilabs-engine - html_url: https://github.com/Atri-Labs/atrilabs-engine - stars: 4115 - owner_login: Atri-Labs - owner_html_url: https://github.com/Atri-Labs +- name: polar + html_url: https://github.com/polarsource/polar + stars: 4216 + owner_login: polarsource + owner_html_url: https://github.com/polarsource - name: strawberry html_url: https://github.com/strawberry-graphql/strawberry - stars: 4084 + stars: 4126 owner_login: strawberry-graphql owner_html_url: https://github.com/strawberry-graphql +- name: atrilabs-engine + html_url: https://github.com/Atri-Labs/atrilabs-engine + stars: 4114 + owner_login: Atri-Labs + owner_html_url: https://github.com/Atri-Labs - name: dynaconf html_url: https://github.com/dynaconf/dynaconf - stars: 3844 + stars: 3874 owner_login: dynaconf owner_html_url: https://github.com/dynaconf - name: poem html_url: https://github.com/poem-web/poem - stars: 3698 + stars: 3746 owner_login: poem-web owner_html_url: https://github.com/poem-web -- name: polar - html_url: https://github.com/polarsource/polar - stars: 3355 - owner_login: polarsource - owner_html_url: https://github.com/polarsource - name: opyrator html_url: https://github.com/ml-tooling/opyrator - stars: 3114 + stars: 3117 owner_login: ml-tooling owner_html_url: https://github.com/ml-tooling - name: farfalle html_url: https://github.com/rashadphz/farfalle - stars: 3022 + stars: 3094 owner_login: rashadphz owner_html_url: https://github.com/rashadphz - name: fastapi-admin html_url: https://github.com/fastapi-admin/fastapi-admin - stars: 3002 + stars: 3040 owner_login: fastapi-admin owner_html_url: https://github.com/fastapi-admin - name: docarray html_url: https://github.com/docarray/docarray - stars: 2998 + stars: 3007 owner_login: docarray owner_html_url: https://github.com/docarray - name: datamodel-code-generator html_url: https://github.com/koxudaxi/datamodel-code-generator - stars: 2845 + stars: 2914 owner_login: koxudaxi owner_html_url: https://github.com/koxudaxi - name: fastapi-realworld-example-app html_url: https://github.com/nsidnev/fastapi-realworld-example-app - stars: 2832 + stars: 2840 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: 2727 - owner_login: tiangolo - owner_html_url: https://github.com/tiangolo -- name: WrenAI - html_url: https://github.com/Canner/WrenAI - stars: 2699 - owner_login: Canner - owner_html_url: https://github.com/Canner - name: LitServe html_url: https://github.com/Lightning-AI/LitServe - stars: 2664 + stars: 2804 owner_login: Lightning-AI owner_html_url: https://github.com/Lightning-AI +- name: uvicorn-gunicorn-fastapi-docker + html_url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker + stars: 2730 + owner_login: tiangolo + owner_html_url: https://github.com/tiangolo - name: logfire html_url: https://github.com/pydantic/logfire - stars: 2495 + stars: 2620 owner_login: pydantic owner_html_url: https://github.com/pydantic - name: huma html_url: https://github.com/danielgtaylor/huma - stars: 2479 + stars: 2567 owner_login: danielgtaylor owner_html_url: https://github.com/danielgtaylor - name: tracecat html_url: https://github.com/TracecatHQ/tracecat - stars: 2446 + stars: 2494 owner_login: TracecatHQ owner_html_url: https://github.com/TracecatHQ -- name: RasaGPT - html_url: https://github.com/paulpierre/RasaGPT - stars: 2378 - owner_login: paulpierre - owner_html_url: https://github.com/paulpierre - name: best-of-web-python html_url: https://github.com/ml-tooling/best-of-web-python - stars: 2374 + stars: 2433 owner_login: ml-tooling owner_html_url: https://github.com/ml-tooling +- name: RasaGPT + html_url: https://github.com/paulpierre/RasaGPT + stars: 2386 + owner_login: paulpierre + owner_html_url: https://github.com/paulpierre - name: fastapi-react html_url: https://github.com/Buuntu/fastapi-react - stars: 2274 + stars: 2293 owner_login: Buuntu owner_html_url: https://github.com/Buuntu - name: nextpy html_url: https://github.com/dot-agent/nextpy - stars: 2244 + stars: 2256 owner_login: dot-agent owner_html_url: https://github.com/dot-agent - name: 30-Days-of-Python html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python - stars: 2154 + stars: 2155 owner_login: codingforentrepreneurs owner_html_url: https://github.com/codingforentrepreneurs - name: FastAPI-template html_url: https://github.com/s3rius/FastAPI-template - stars: 2067 + stars: 2121 owner_login: s3rius owner_html_url: https://github.com/s3rius -- name: langserve - html_url: https://github.com/langchain-ai/langserve - stars: 1980 - owner_login: langchain-ai - owner_html_url: https://github.com/langchain-ai - name: sqladmin html_url: https://github.com/aminalaee/sqladmin - stars: 1980 + stars: 2021 owner_login: aminalaee owner_html_url: https://github.com/aminalaee +- name: langserve + html_url: https://github.com/langchain-ai/langserve + stars: 2006 + owner_login: langchain-ai + owner_html_url: https://github.com/langchain-ai - name: fastapi-utils html_url: https://github.com/fastapiutils/fastapi-utils - stars: 1970 + stars: 2002 owner_login: fastapiutils owner_html_url: https://github.com/fastapiutils - name: solara html_url: https://github.com/widgetti/solara - stars: 1950 + stars: 1967 owner_login: widgetti owner_html_url: https://github.com/widgetti -- name: python-week-2022 - html_url: https://github.com/rochacbruno/python-week-2022 - stars: 1836 - owner_login: rochacbruno - owner_html_url: https://github.com/rochacbruno - name: supabase-py html_url: https://github.com/supabase/supabase-py - stars: 1803 + stars: 1848 owner_login: supabase owner_html_url: https://github.com/supabase +- name: python-week-2022 + html_url: https://github.com/rochacbruno/python-week-2022 + stars: 1832 + owner_login: rochacbruno + owner_html_url: https://github.com/rochacbruno - name: mangum html_url: https://github.com/Kludex/mangum - stars: 1760 + stars: 1789 owner_login: Kludex owner_html_url: https://github.com/Kludex - name: manage-fastapi html_url: https://github.com/ycd/manage-fastapi - stars: 1704 + stars: 1711 owner_login: ycd owner_html_url: https://github.com/ycd - name: ormar html_url: https://github.com/collerek/ormar - stars: 1688 + stars: 1701 owner_login: collerek owner_html_url: https://github.com/collerek - name: agentkit html_url: https://github.com/BCG-X-Official/agentkit - stars: 1615 + stars: 1630 owner_login: BCG-X-Official owner_html_url: https://github.com/BCG-X-Official - name: langchain-serve html_url: https://github.com/jina-ai/langchain-serve - stars: 1615 + stars: 1617 owner_login: jina-ai owner_html_url: https://github.com/jina-ai - name: termpair html_url: https://github.com/cs01/termpair - stars: 1613 + stars: 1612 owner_login: cs01 owner_html_url: https://github.com/cs01 - name: coronavirus-tracker-api html_url: https://github.com/ExpDev07/coronavirus-tracker-api - stars: 1591 + stars: 1590 owner_login: ExpDev07 owner_html_url: https://github.com/ExpDev07 - name: piccolo html_url: https://github.com/piccolo-orm/piccolo - stars: 1477 + stars: 1519 owner_login: piccolo-orm owner_html_url: https://github.com/piccolo-orm - name: fastapi-crudrouter html_url: https://github.com/awtkns/fastapi-crudrouter - stars: 1435 + stars: 1449 owner_login: awtkns owner_html_url: https://github.com/awtkns - name: fastapi-cache html_url: https://github.com/long2ice/fastapi-cache - stars: 1412 + stars: 1447 owner_login: long2ice owner_html_url: https://github.com/long2ice - name: openapi-python-client html_url: https://github.com/openapi-generators/openapi-python-client - stars: 1398 + stars: 1434 owner_login: openapi-generators owner_html_url: https://github.com/openapi-generators - name: awesome-fastapi-projects html_url: https://github.com/Kludex/awesome-fastapi-projects - stars: 1386 + stars: 1398 owner_login: Kludex owner_html_url: https://github.com/Kludex - name: awesome-python-resources html_url: https://github.com/DjangoEx/awesome-python-resources - stars: 1371 + stars: 1380 owner_login: DjangoEx owner_html_url: https://github.com/DjangoEx - name: budgetml html_url: https://github.com/ebhy/budgetml - stars: 1342 + stars: 1344 owner_login: ebhy owner_html_url: https://github.com/ebhy - name: slowapi html_url: https://github.com/laurentS/slowapi - stars: 1289 + stars: 1339 owner_login: laurentS owner_html_url: https://github.com/laurentS - name: fastapi-pagination html_url: https://github.com/uriyyo/fastapi-pagination - stars: 1240 + stars: 1263 owner_login: uriyyo owner_html_url: https://github.com/uriyyo - name: fastapi-boilerplate html_url: https://github.com/teamhide/fastapi-boilerplate - stars: 1173 + stars: 1206 owner_login: teamhide owner_html_url: https://github.com/teamhide - name: fastapi-tutorial html_url: https://github.com/liaogx/fastapi-tutorial - stars: 1162 + stars: 1178 owner_login: liaogx owner_html_url: https://github.com/liaogx - name: fastapi-amis-admin html_url: https://github.com/amisadmin/fastapi-amis-admin - stars: 1118 + stars: 1142 owner_login: amisadmin owner_html_url: https://github.com/amisadmin - name: fastapi-code-generator html_url: https://github.com/koxudaxi/fastapi-code-generator - stars: 1095 + stars: 1119 owner_login: koxudaxi owner_html_url: https://github.com/koxudaxi - name: bolt-python html_url: https://github.com/slackapi/bolt-python - stars: 1086 + stars: 1116 owner_login: slackapi owner_html_url: https://github.com/slackapi - name: odmantic html_url: https://github.com/art049/odmantic - stars: 1085 + stars: 1096 owner_login: art049 owner_html_url: https://github.com/art049 - name: langchain-extract html_url: https://github.com/langchain-ai/langchain-extract - stars: 1068 + stars: 1093 owner_login: langchain-ai owner_html_url: https://github.com/langchain-ai - name: fastapi_production_template html_url: https://github.com/zhanymkanov/fastapi_production_template - stars: 1059 + stars: 1078 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov - name: fastapi-alembic-sqlmodel-async html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async - stars: 1031 + stars: 1055 owner_login: jonra1993 owner_html_url: https://github.com/jonra1993 +- name: Kokoro-FastAPI + html_url: https://github.com/remsky/Kokoro-FastAPI + stars: 1047 + owner_login: remsky + owner_html_url: https://github.com/remsky - name: prometheus-fastapi-instrumentator html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator - stars: 1013 + stars: 1036 owner_login: trallnag owner_html_url: https://github.com/trallnag +- name: SurfSense + html_url: https://github.com/MODSetter/SurfSense + stars: 1018 + owner_login: MODSetter + owner_html_url: https://github.com/MODSetter +- name: bedrock-claude-chat + html_url: https://github.com/aws-samples/bedrock-claude-chat + stars: 1010 + owner_login: aws-samples + owner_html_url: https://github.com/aws-samples - name: runhouse html_url: https://github.com/run-house/runhouse - stars: 988 + stars: 1000 owner_login: run-house owner_html_url: https://github.com/run-house - name: lanarky html_url: https://github.com/ajndkr/lanarky - stars: 982 + stars: 986 owner_login: ajndkr owner_html_url: https://github.com/ajndkr - name: autollm html_url: https://github.com/viddexa/autollm - stars: 981 + stars: 982 owner_login: viddexa owner_html_url: https://github.com/viddexa -- name: bedrock-claude-chat - html_url: https://github.com/aws-samples/bedrock-claude-chat - stars: 977 - owner_login: aws-samples - owner_html_url: https://github.com/aws-samples -- name: SurfSense - html_url: https://github.com/MODSetter/SurfSense - stars: 971 - owner_login: MODSetter - owner_html_url: https://github.com/MODSetter - name: restish html_url: https://github.com/danielgtaylor/restish - stars: 954 + stars: 970 owner_login: danielgtaylor owner_html_url: https://github.com/danielgtaylor +- name: fastcrud + html_url: https://github.com/igorbenav/fastcrud + stars: 929 + owner_login: igorbenav + owner_html_url: https://github.com/igorbenav - name: secure html_url: https://github.com/TypeError/secure - stars: 911 + stars: 921 owner_login: TypeError owner_html_url: https://github.com/TypeError - name: langcorn html_url: https://github.com/msoedov/langcorn - stars: 909 + stars: 915 owner_login: msoedov owner_html_url: https://github.com/msoedov -- name: energy-forecasting - html_url: https://github.com/iusztinpaul/energy-forecasting - stars: 884 - owner_login: iusztinpaul - owner_html_url: https://github.com/iusztinpaul - name: vue-fastapi-admin html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin - stars: 863 + stars: 915 owner_login: mizhexiaoxiao owner_html_url: https://github.com/mizhexiaoxiao +- name: energy-forecasting + html_url: https://github.com/iusztinpaul/energy-forecasting + stars: 891 + owner_login: iusztinpaul + owner_html_url: https://github.com/iusztinpaul - name: authx html_url: https://github.com/yezz123/authx - stars: 850 + stars: 862 owner_login: yezz123 owner_html_url: https://github.com/yezz123 - name: titiler html_url: https://github.com/developmentseed/titiler - stars: 809 + stars: 823 owner_login: developmentseed owner_html_url: https://github.com/developmentseed - name: marker-api html_url: https://github.com/adithya-s-k/marker-api - stars: 792 + stars: 798 owner_login: adithya-s-k owner_html_url: https://github.com/adithya-s-k +- name: FastAPI-boilerplate + html_url: https://github.com/igorbenav/FastAPI-boilerplate + stars: 774 + owner_login: igorbenav + owner_html_url: https://github.com/igorbenav - name: fastapi_best_architecture html_url: https://github.com/fastapi-practices/fastapi_best_architecture - stars: 742 + stars: 766 owner_login: fastapi-practices owner_html_url: https://github.com/fastapi-practices - name: fastapi-mail html_url: https://github.com/sabuhish/fastapi-mail - stars: 728 + stars: 735 owner_login: sabuhish owner_html_url: https://github.com/sabuhish -- name: fastcrud - html_url: https://github.com/igorbenav/fastcrud - stars: 727 - owner_login: igorbenav - owner_html_url: https://github.com/igorbenav - name: annotated-py-projects html_url: https://github.com/hhstore/annotated-py-projects - stars: 722 + stars: 725 owner_login: hhstore owner_html_url: https://github.com/hhstore -- name: FastAPI-boilerplate - html_url: https://github.com/igorbenav/FastAPI-boilerplate - stars: 716 - owner_login: igorbenav - owner_html_url: https://github.com/igorbenav +- name: fastapi-do-zero + html_url: https://github.com/dunossauro/fastapi-do-zero + stars: 723 + owner_login: dunossauro + owner_html_url: https://github.com/dunossauro - name: lccn_predictor html_url: https://github.com/baoliay2008/lccn_predictor - stars: 707 + stars: 718 owner_login: baoliay2008 owner_html_url: https://github.com/baoliay2008 +- name: fastapi-observability + html_url: https://github.com/blueswen/fastapi-observability + stars: 718 + owner_login: blueswen + owner_html_url: https://github.com/blueswen - name: chatGPT-web html_url: https://github.com/mic1on/chatGPT-web - stars: 706 + stars: 708 owner_login: mic1on owner_html_url: https://github.com/mic1on -- name: fastapi-do-zero - html_url: https://github.com/dunossauro/fastapi-do-zero - stars: 702 - owner_login: dunossauro - owner_html_url: https://github.com/dunossauro +- name: learn-generative-ai + html_url: https://github.com/panaverse/learn-generative-ai + stars: 701 + owner_login: panaverse + owner_html_url: https://github.com/panaverse - name: linbing html_url: https://github.com/taomujian/linbing - stars: 699 + stars: 700 owner_login: taomujian owner_html_url: https://github.com/taomujian -- name: fastapi-observability - html_url: https://github.com/blueswen/fastapi-observability - stars: 698 - owner_login: blueswen - owner_html_url: https://github.com/blueswen - name: FastAPI-Backend-Template html_url: https://github.com/Aeternalis-Ingenium/FastAPI-Backend-Template - stars: 682 + stars: 692 owner_login: Aeternalis-Ingenium owner_html_url: https://github.com/Aeternalis-Ingenium -- name: learn-generative-ai - html_url: https://github.com/panaverse/learn-generative-ai - stars: 673 - owner_login: panaverse - owner_html_url: https://github.com/panaverse +- name: starlette-admin + html_url: https://github.com/jowilf/starlette-admin + stars: 692 + owner_login: jowilf + owner_html_url: https://github.com/jowilf - name: fastapi-jwt-auth html_url: https://github.com/IndominusByte/fastapi-jwt-auth - stars: 668 + stars: 674 owner_login: IndominusByte owner_html_url: https://github.com/IndominusByte - name: pity html_url: https://github.com/wuranxu/pity - stars: 660 + stars: 663 owner_login: wuranxu owner_html_url: https://github.com/wuranxu -- name: starlette-admin - html_url: https://github.com/jowilf/starlette-admin - stars: 653 - owner_login: jowilf - owner_html_url: https://github.com/jowilf - name: fastapi_login html_url: https://github.com/MushroomMaula/fastapi_login - stars: 650 + stars: 656 owner_login: MushroomMaula owner_html_url: https://github.com/MushroomMaula diff --git a/docs/en/data/translation_reviewers.yml b/docs/en/data/translation_reviewers.yml index 6cc09a7c1..6f16893ba 100644 --- a/docs/en/data/translation_reviewers.yml +++ b/docs/en/data/translation_reviewers.yml @@ -5,12 +5,12 @@ s111d: url: https://github.com/s111d Xewus: login: Xewus - count: 139 + count: 140 avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4 url: https://github.com/Xewus ceb10n: login: ceb10n - count: 108 + count: 110 avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 url: https://github.com/ceb10n tokusumi: @@ -33,21 +33,26 @@ AlertRED: count: 81 avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 url: https://github.com/AlertRED +nazarepiedady: + login: nazarepiedady + count: 81 + avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=8dc25777dc9cb51fb0dbba2f137988953d330b78&v=4 + url: https://github.com/nazarepiedady sodaMelon: login: sodaMelon count: 81 avatarUrl: https://avatars.githubusercontent.com/u/66295123?u=be939db90f1119efee9e6110cc05066ff1f40f00&v=4 url: https://github.com/sodaMelon -nazarepiedady: - login: nazarepiedady - count: 78 - avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=8dc25777dc9cb51fb0dbba2f137988953d330b78&v=4 - url: https://github.com/nazarepiedady Alexandrhub: login: Alexandrhub count: 68 avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 url: https://github.com/Alexandrhub +alv2017: + login: alv2017 + count: 64 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 waynerv: login: waynerv count: 63 @@ -55,7 +60,7 @@ waynerv: url: https://github.com/waynerv cassiobotaro: login: cassiobotaro - count: 61 + count: 62 avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4 url: https://github.com/cassiobotaro mattwang44: @@ -138,26 +143,21 @@ romashevchenko: count: 32 avatarUrl: https://avatars.githubusercontent.com/u/132477732?v=4 url: https://github.com/romashevchenko +alejsdev: + login: alejsdev + count: 32 + avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=356f39ff3f0211c720b06d3dbb060e98884085e3&v=4 + url: https://github.com/alejsdev wdh99: login: wdh99 count: 31 avatarUrl: https://avatars.githubusercontent.com/u/108172295?u=8a8fb95d5afe3e0fa33257b2aecae88d436249eb&v=4 url: https://github.com/wdh99 -alv2017: - login: alv2017 - count: 31 - avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 - url: https://github.com/alv2017 LorhanSohaky: login: LorhanSohaky count: 30 avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4 url: https://github.com/LorhanSohaky -alejsdev: - login: alejsdev - count: 30 - avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=356f39ff3f0211c720b06d3dbb060e98884085e3&v=4 - url: https://github.com/alejsdev black-redoc: login: black-redoc count: 29 @@ -246,7 +246,7 @@ axel584: wisderfin: login: wisderfin count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/77553770?u=94478d3e1ef7d36d70479c5bd35d8de28b071c10&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/77553770?u=f3b00a26736ba664e9927a1116c6e8088295e073&v=4 url: https://github.com/wisderfin rostik1410: login: rostik1410 @@ -353,6 +353,11 @@ 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 @@ -363,11 +368,6 @@ Jedore: count: 15 avatarUrl: https://avatars.githubusercontent.com/u/17944025?u=81d503e1c800eb666b3861ca47a3a773bbc3f539&v=4 url: https://github.com/Jedore -Joao-Pedro-P-Holanda: - login: Joao-Pedro-P-Holanda - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 - url: https://github.com/Joao-Pedro-P-Holanda kim-sangah: login: kim-sangah count: 15 @@ -386,7 +386,7 @@ dukkee: mkdir700: login: mkdir700 count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/56359329?u=0ba13427420e7f6e4c83947736de247326f2c292&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/56359329?u=3d6ea8714f5000829b60dcf7b13a75b1e73aaf47&v=4 url: https://github.com/mkdir700 BORA040126: login: BORA040126 @@ -473,6 +473,11 @@ kwang1215: count: 12 avatarUrl: https://avatars.githubusercontent.com/u/74170199?u=2a63ff6692119dde3f5e5693365b9fcd6f977b08&v=4 url: https://github.com/kwang1215 +Rishat-F: + login: Rishat-F + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/66554797?v=4 + url: https://github.com/Rishat-F AdrianDeAnda: login: AdrianDeAnda count: 11 @@ -513,6 +518,11 @@ KNChiu: count: 11 avatarUrl: https://avatars.githubusercontent.com/u/36751646?v=4 url: https://github.com/KNChiu +gitgernit: + login: gitgernit + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/129539613?u=d04f10143ab32c93f563ea14bf242d1d2bc991b0&v=4 + url: https://github.com/gitgernit mariacamilagl: login: mariacamilagl count: 10 @@ -538,6 +548,11 @@ 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 @@ -626,7 +641,7 @@ marcelomarkus: JoaoGustavoRogel: login: JoaoGustavoRogel count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/29525510?u=1dd3096c6c2be2576fd5e818b1be15b2c9768aa5&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/29525510?u=a0a91251f5e43e132608d55d28ccb8645c5ea405&v=4 url: https://github.com/JoaoGustavoRogel Zhongheng-Cheng: login: Zhongheng-Cheng @@ -673,11 +688,6 @@ camigomezdev: count: 8 avatarUrl: https://avatars.githubusercontent.com/u/16061815?u=25b5ebc042fff53fa03dc107ded10e36b1b7a5b9&v=4 url: https://github.com/camigomezdev -gitgernit: - login: gitgernit - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/129539613?u=d04f10143ab32c93f563ea14bf242d1d2bc991b0&v=4 - url: https://github.com/gitgernit Serrones: login: Serrones count: 7 @@ -698,11 +708,6 @@ anthonycepeda: count: 7 avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4 url: https://github.com/anthonycepeda -vitumenezes: - login: vitumenezes - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/9680878?u=e7c6865aec49c3c94b8c8edc1198d1eac3e50b26&v=4 - url: https://github.com/vitumenezes fabioueno: login: fabioueno count: 7 @@ -956,7 +961,7 @@ devluisrodrigues: timothy-jeong: login: timothy-jeong count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=659311b6f6aeb0fbb8b527723fd4c83642f04327&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=db3d0cea2f5fab64d810113c5039a369699a2774&v=4 url: https://github.com/timothy-jeong lpdswing: login: lpdswing @@ -1053,6 +1058,11 @@ matiasbertani: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/65260383?u=d5edd86a6e2ab4fb1aab7751931fe045a963afd7&v=4 url: https://github.com/matiasbertani +k94-ishi: + login: k94-ishi + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/32672580?u=bc7c5c07af0656be9fe4f1784a444af8d81ded89&v=4 + url: https://github.com/k94-ishi javillegasna: login: javillegasna count: 4 @@ -1063,6 +1073,16 @@ javillegasna: 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 +Yarous: + login: Yarous + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/61277193?u=5b462347458a373b2d599c6f416d2b75eddbffad&v=4 + url: https://github.com/Yarous tyronedamasceno: login: tyronedamasceno count: 3 @@ -1151,7 +1171,7 @@ rafsaf: frnsimoes: login: frnsimoes count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/66239468?u=771c4b0c403a42ccf2676ac987ac4999e5ad09bc&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/66239468?u=a405e8f10654251e239a4a1d9dd5bda59216727d&v=4 url: https://github.com/frnsimoes lieryan: login: lieryan @@ -1283,11 +1303,16 @@ celestywang: count: 3 avatarUrl: https://avatars.githubusercontent.com/u/184830753?v=4 url: https://github.com/celestywang -ilhamfadillah: - login: ilhamfadillah +RyaWcksn: + login: RyaWcksn count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/20577838?u=c56192cf99b55affcaad408b240259c62e633450&v=4 - url: https://github.com/ilhamfadillah + avatarUrl: https://avatars.githubusercontent.com/u/42831964?u=0cb4265faf3e3425a89e59b6fddd3eb2de180af0&v=4 + url: https://github.com/RyaWcksn +gerry-sabar: + login: gerry-sabar + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1120123?v=4 + url: https://github.com/gerry-sabar blaisep: login: blaisep count: 2 @@ -1633,13 +1658,13 @@ logan2d5: count: 2 avatarUrl: https://avatars.githubusercontent.com/u/146642263?u=dbd6621f8b0330d6919f6a7131277b92e26fbe87&v=4 url: https://github.com/logan2d5 -RyaWcksn: - login: RyaWcksn +tiaggo16: + login: tiaggo16 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/42831964?u=0cb4265faf3e3425a89e59b6fddd3eb2de180af0&v=4 - url: https://github.com/RyaWcksn -gerry-sabar: - login: gerry-sabar + 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/1120123?v=4 - url: https://github.com/gerry-sabar + avatarUrl: https://avatars.githubusercontent.com/u/38311245?v=4 + url: https://github.com/kiharito diff --git a/docs/en/data/translators.yml b/docs/en/data/translators.yml index 7b199dc08..13859044d 100644 --- a/docs/en/data/translators.yml +++ b/docs/en/data/translators.yml @@ -8,6 +8,11 @@ jaystone776: count: 46 avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 url: https://github.com/jaystone776 +ceb10n: + login: ceb10n + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n tokusumi: login: tokusumi count: 23 @@ -23,11 +28,6 @@ hasansezertasan: count: 22 avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 url: https://github.com/hasansezertasan -ceb10n: - login: ceb10n - count: 22 - avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 - url: https://github.com/ceb10n waynerv: login: waynerv count: 20 @@ -55,7 +55,7 @@ Xewus: url: https://github.com/Xewus Joao-Pedro-P-Holanda: login: Joao-Pedro-P-Holanda - count: 12 + count: 13 avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 url: https://github.com/Joao-Pedro-P-Holanda Smlep: @@ -78,6 +78,11 @@ Vincy1230: count: 9 avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4 url: https://github.com/Vincy1230 +Zhongheng-Cheng: + login: Zhongheng-Cheng + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4 + url: https://github.com/Zhongheng-Cheng rjNemo: login: rjNemo count: 8 @@ -93,11 +98,6 @@ pablocm83: count: 8 avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4 url: https://github.com/pablocm83 -Zhongheng-Cheng: - login: Zhongheng-Cheng - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4 - url: https://github.com/Zhongheng-Cheng batlopes: login: batlopes count: 6 @@ -188,6 +188,11 @@ kwang1215: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/74170199?u=2a63ff6692119dde3f5e5693365b9fcd6f977b08&v=4 url: https://github.com/kwang1215 +alv2017: + login: alv2017 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 jfunez: login: jfunez count: 3 @@ -313,11 +318,11 @@ nahyunkeem: count: 3 avatarUrl: https://avatars.githubusercontent.com/u/174440096?u=e12401d492eee58570f8914d0872b52e421a776e&v=4 url: https://github.com/nahyunkeem -alv2017: - login: alv2017 +gerry-sabar: + login: gerry-sabar count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 - url: https://github.com/alv2017 + avatarUrl: https://avatars.githubusercontent.com/u/1120123?v=4 + url: https://github.com/gerry-sabar izaguerreiro: login: izaguerreiro count: 2 @@ -481,10 +486,10 @@ saeye: timothy-jeong: login: timothy-jeong count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=659311b6f6aeb0fbb8b527723fd4c83642f04327&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=db3d0cea2f5fab64d810113c5039a369699a2774&v=4 url: https://github.com/timothy-jeong -gerry-sabar: - login: gerry-sabar +Rishat-F: + login: Rishat-F count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1120123?v=4 - url: https://github.com/gerry-sabar + avatarUrl: https://avatars.githubusercontent.com/u/66554797?v=4 + url: https://github.com/Rishat-F diff --git a/docs/en/docs/fastapi-people.md b/docs/en/docs/fastapi-people.md index ffc579b10..f2ca26013 100644 --- a/docs/en/docs/fastapi-people.md +++ b/docs/en/docs/fastapi-people.md @@ -47,9 +47,11 @@ This is the current list of team members. ๐Ÿ˜Ž They have different levels of involvement and permissions, they can perform [repository management tasks](./management-tasks.md){.internal-link target=_blank} and together we [manage the FastAPI repository](./management.md){.internal-link target=_blank}.

+ {% for user in members["members"] %} + {% endfor %}
@@ -83,9 +85,15 @@ You can see the **FastAPI Experts** for: These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last month. ๐Ÿค“
+ {% for user in people.last_month_experts[:10] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
@@ -95,9 +103,15 @@ These are the users that have been [helping others the most with questions in Gi These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 3 months. ๐Ÿ˜Ž
+ {% for user in people.three_months_experts[:10] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
@@ -107,9 +121,15 @@ These are the users that have been [helping others the most with questions in Gi These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 6 months. ๐Ÿง
+ {% for user in people.six_months_experts[:10] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
@@ -119,9 +139,15 @@ These are the users that have been [helping others the most with questions in Gi These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last year. ๐Ÿง‘โ€๐Ÿ”ฌ
+ {% for user in people.one_year_experts[:20] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
@@ -133,9 +159,15 @@ Here are the all time **FastAPI Experts**. ๐Ÿค“๐Ÿคฏ These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} through *all time*. ๐Ÿง™
+ {% for user in people.experts[:50] %} +{% if user.login not in skip_users %} +
@{{ user.login }}
Questions replied: {{ user.count }}
+ +{% endif %} + {% endfor %}
@@ -149,6 +181,7 @@ These users have [created the most Pull Requests](help-fastapi.md#create-a-pull- They have contributed source code, documentation, etc. ๐Ÿ“ฆ
+ {% for user in (contributors.values() | list)[:50] %} {% if user.login not in skip_users %} diff --git a/docs/en/docs/img/sponsors/lambdatest.png b/docs/en/docs/img/sponsors/lambdatest.png new file mode 100644 index 000000000..674cbcb89 Binary files /dev/null and b/docs/en/docs/img/sponsors/lambdatest.png differ diff --git a/docs/en/docs/img/sponsors/permit.png b/docs/en/docs/img/sponsors/permit.png new file mode 100644 index 000000000..4f07f22e2 Binary files /dev/null and b/docs/en/docs/img/sponsors/permit.png differ diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index cbe71c87d..4a2777f25 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -12,7 +12,7 @@

- Test + Test Coverage diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a242407ea..5f3d98e5c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,95 @@ hide: ### Refactors +* โœ… Simplify tests for `query_params_str_validations`. PR [#13218](https://github.com/fastapi/fastapi/pull/13218) by [@alv2017](https://github.com/alv2017). +* โœ… Simplify tests for `app_testing`. PR [#13220](https://github.com/fastapi/fastapi/pull/13220) by [@alv2017](https://github.com/alv2017). +* โœ… Simplify tests for `dependency_testing`. PR [#13223](https://github.com/fastapi/fastapi/pull/13223) by [@alv2017](https://github.com/alv2017). + +### Docs + +* ๐Ÿ› Fix issue with Swagger theme change example in the official tutorial. PR [#13289](https://github.com/fastapi/fastapi/pull/13289) by [@Zerohertz](https://github.com/Zerohertz). +* ๐Ÿ“ Add more precise description of HTTP status code range in docs. PR [#13347](https://github.com/fastapi/fastapi/pull/13347) by [@DanielYang59](https://github.com/DanielYang59). +* ๐Ÿ”ฅ Remove manual type annotations in JWT tutorial to avoid typing expectations (JWT doesn't provide more types). PR [#13378](https://github.com/fastapi/fastapi/pull/13378) by [@tiangolo](https://github.com/tiangolo). +* ๐Ÿ“ Update docs for Query Params and String Validations, remove obsolete Ellipsis docs (`...`). PR [#13377](https://github.com/fastapi/fastapi/pull/13377) by [@tiangolo](https://github.com/tiangolo). +* โœ๏ธ Remove duplicate title in docs `body-multiple-params`. PR [#13345](https://github.com/fastapi/fastapi/pull/13345) by [@DanielYang59](https://github.com/DanielYang59). +* ๐Ÿ“ Fix test badge. PR [#13313](https://github.com/fastapi/fastapi/pull/13313) by [@esadek](https://github.com/esadek). + +### Translations + +* ๐ŸŒ Update Korean translation for `docs/ko/docs/help-fastapi.md`. PR [#13262](https://github.com/fastapi/fastapi/pull/13262) by [@Zerohertz](https://github.com/Zerohertz). +* ๐ŸŒ Add Korean translation for `docs/ko/docs/advanced/custom-response.md`. PR [#13265](https://github.com/fastapi/fastapi/pull/13265) by [@11kkw](https://github.com/11kkw). +* ๐ŸŒ Update Korean translation for `docs/ko/docs/tutorial/security/simple-oauth2.md`. PR [#13335](https://github.com/fastapi/fastapi/pull/13335) by [@yes0ng](https://github.com/yes0ng). +* ๐ŸŒ Add Russian translation for `docs/ru/docs/advanced/response-cookies.md`. PR [#13327](https://github.com/fastapi/fastapi/pull/13327) by [@Stepakinoyan](https://github.com/Stepakinoyan). +* ๐ŸŒ Add Vietnamese translation for `docs/vi/docs/tutorial/static-files.md`. PR [#11291](https://github.com/fastapi/fastapi/pull/11291) by [@ptt3199](https://github.com/ptt3199). +* ๐ŸŒ Add Korean translation for `docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md`. PR [#13257](https://github.com/fastapi/fastapi/pull/13257) by [@11kkw](https://github.com/11kkw). +* ๐ŸŒ Add Vietnamese translation for `docs/vi/docs/virtual-environments.md`. PR [#13282](https://github.com/fastapi/fastapi/pull/13282) by [@ptt3199](https://github.com/ptt3199). +* ๐ŸŒ Add Ukrainian translation for `docs/uk/docs/tutorial/static-files.md`. PR [#13285](https://github.com/fastapi/fastapi/pull/13285) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). +* ๐ŸŒ Add Vietnamese translation for `docs/vi/docs/environment-variables.md`. PR [#13287](https://github.com/fastapi/fastapi/pull/13287) by [@ptt3199](https://github.com/ptt3199). +* ๐ŸŒ Add Vietnamese translation for `docs/vi/docs/fastapi-cli.md`. PR [#13294](https://github.com/fastapi/fastapi/pull/13294) by [@ptt3199](https://github.com/ptt3199). +* ๐ŸŒ Add Ukrainian translation for `docs/uk/docs/features.md`. PR [#13308](https://github.com/fastapi/fastapi/pull/13308) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). +* ๐ŸŒ Add Ukrainian translation for `docs/uk/docs/learn/index.md`. PR [#13306](https://github.com/fastapi/fastapi/pull/13306) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). +* ๐ŸŒ Update Portuguese Translation for `docs/pt/docs/deployment/https.md`. PR [#13317](https://github.com/fastapi/fastapi/pull/13317) by [@Joao-Pedro-P-Holanda](https://github.com/Joao-Pedro-P-Holanda). +* ๐ŸŒ Update Portuguese Translation for `docs/pt/docs/index.md`. PR [#13328](https://github.com/fastapi/fastapi/pull/13328) by [@ceb10n](https://github.com/ceb10n). +* ๐ŸŒ Add Russian translation for `docs/ru/docs/advanced/websockets.md`. PR [#13279](https://github.com/fastapi/fastapi/pull/13279) by [@Rishat-F](https://github.com/Rishat-F). + +### Internal + +* ๐Ÿ”ง Update sponsors: Add LambdaTest. PR [#13389](https://github.com/fastapi/fastapi/pull/13389) by [@tiangolo](https://github.com/tiangolo). +* โฌ† Bump cloudflare/wrangler-action from 3.13 to 3.14. PR [#13350](https://github.com/fastapi/fastapi/pull/13350) by [@dependabot[bot]](https://github.com/apps/dependabot). +* โฌ† Bump mkdocs-material from 9.5.18 to 9.6.1. PR [#13301](https://github.com/fastapi/fastapi/pull/13301) by [@dependabot[bot]](https://github.com/apps/dependabot). +* โฌ† Bump pillow from 11.0.0 to 11.1.0. PR [#13300](https://github.com/fastapi/fastapi/pull/13300) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ๐Ÿ‘ฅ Update FastAPI People - Sponsors. PR [#13295](https://github.com/fastapi/fastapi/pull/13295) by [@tiangolo](https://github.com/tiangolo). +* ๐Ÿ‘ฅ Update FastAPI People - Experts. PR [#13303](https://github.com/fastapi/fastapi/pull/13303) by [@tiangolo](https://github.com/tiangolo). +* ๐Ÿ‘ฅ Update FastAPI GitHub topic repositories. PR [#13302](https://github.com/fastapi/fastapi/pull/13302) by [@tiangolo](https://github.com/tiangolo). +* ๐Ÿ‘ฅ Update FastAPI People - Contributors and Translators. PR [#13293](https://github.com/fastapi/fastapi/pull/13293) by [@tiangolo](https://github.com/tiangolo). +* โฌ† Bump inline-snapshot from 0.18.1 to 0.19.3. PR [#13298](https://github.com/fastapi/fastapi/pull/13298) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ๐Ÿ”ง Update sponsors, add Permit. PR [#13288](https://github.com/fastapi/fastapi/pull/13288) by [@tiangolo](https://github.com/tiangolo). + +## 0.115.8 + +### Fixes + +* ๐Ÿ› Fix `OAuth2PasswordRequestForm` and `OAuth2PasswordRequestFormStrict` fixed `grant_type` "password" RegEx. PR [#9783](https://github.com/fastapi/fastapi/pull/9783) by [@skarfie123](https://github.com/skarfie123). + +### Refactors + +* โœ… Simplify tests for body_multiple_params . PR [#13237](https://github.com/fastapi/fastapi/pull/13237) by [@alejsdev](https://github.com/alejsdev). +* โ™ป๏ธ Move duplicated code portion to a static method in the `APIKeyBase` super class. PR [#3142](https://github.com/fastapi/fastapi/pull/3142) by [@ShahriyarR](https://github.com/ShahriyarR). +* โœ… Simplify tests for request_files. PR [#13182](https://github.com/fastapi/fastapi/pull/13182) by [@alejsdev](https://github.com/alejsdev). + +### Docs + +* ๐Ÿ“ Change the word "unwrap" to "unpack" in `docs/en/docs/tutorial/extra-models.md`. PR [#13061](https://github.com/fastapi/fastapi/pull/13061) by [@timothy-jeong](https://github.com/timothy-jeong). +* ๐Ÿ“ Update Request Body's `tutorial002` to deal with `tax=0` case. PR [#13230](https://github.com/fastapi/fastapi/pull/13230) by [@togogh](https://github.com/togogh). +* ๐Ÿ‘ฅ Update FastAPI People - Experts. PR [#13269](https://github.com/fastapi/fastapi/pull/13269) by [@tiangolo](https://github.com/tiangolo). + +### Translations + +* ๐ŸŒ Add Japanese translation for `docs/ja/docs/environment-variables.md`. PR [#13226](https://github.com/fastapi/fastapi/pull/13226) by [@k94-ishi](https://github.com/k94-ishi). +* ๐ŸŒ Add Russian translation for `docs/ru/docs/advanced/async-tests.md`. PR [#13227](https://github.com/fastapi/fastapi/pull/13227) by [@Rishat-F](https://github.com/Rishat-F). +* ๐ŸŒ Update Russian translation for `docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#13252](https://github.com/fastapi/fastapi/pull/13252) by [@Rishat-F](https://github.com/Rishat-F). +* ๐ŸŒ Add Russian translation for `docs/ru/docs/tutorial/bigger-applications.md`. PR [#13154](https://github.com/fastapi/fastapi/pull/13154) by [@alv2017](https://github.com/alv2017). + +### Internal + +* โฌ†๏ธ Add support for Python 3.13. PR [#13274](https://github.com/fastapi/fastapi/pull/13274) by [@tiangolo](https://github.com/tiangolo). +* โฌ†๏ธ Upgrade AnyIO max version for tests, new range: `>=3.2.1,<5.0.0`. PR [#13273](https://github.com/fastapi/fastapi/pull/13273) by [@tiangolo](https://github.com/tiangolo). +* ๐Ÿ”ง Update Sponsors badges. PR [#13271](https://github.com/fastapi/fastapi/pull/13271) by [@tiangolo](https://github.com/tiangolo). +* โ™ป๏ธ Fix `notify_translations.py` empty env var handling for PR label events vs workflow_dispatch. PR [#13272](https://github.com/fastapi/fastapi/pull/13272) by [@tiangolo](https://github.com/tiangolo). +* โ™ป๏ธ Refactor and move `scripts/notify_translations.py`, no need for a custom GitHub Action. PR [#13270](https://github.com/fastapi/fastapi/pull/13270) by [@tiangolo](https://github.com/tiangolo). +* ๐Ÿ”จ Update FastAPI People Experts script, refactor and optimize data fetching to handle rate limits. PR [#13267](https://github.com/fastapi/fastapi/pull/13267) by [@tiangolo](https://github.com/tiangolo). +* โฌ† Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4. PR [#13251](https://github.com/fastapi/fastapi/pull/13251) by [@dependabot[bot]](https://github.com/apps/dependabot). + +## 0.115.7 + +### Upgrades + +* โฌ†๏ธ Upgrade `python-multipart` to >=0.0.18. PR [#13219](https://github.com/fastapi/fastapi/pull/13219) by [@DanielKusyDev](https://github.com/DanielKusyDev). +* โฌ†๏ธ Bump Starlette to allow up to 0.45.0: `>=0.40.0,<0.46.0`. PR [#13117](https://github.com/fastapi/fastapi/pull/13117) by [@Kludex](https://github.com/Kludex). +* โฌ†๏ธ Upgrade `jinja2` to >=3.1.5. PR [#13194](https://github.com/fastapi/fastapi/pull/13194) by [@DanielKusyDev](https://github.com/DanielKusyDev). + +### Refactors + * โœ… Simplify tests for websockets. PR [#13202](https://github.com/fastapi/fastapi/pull/13202) by [@alejsdev](https://github.com/alejsdev). * โœ… Simplify tests for request_form_models . PR [#13183](https://github.com/fastapi/fastapi/pull/13183) by [@alejsdev](https://github.com/alejsdev). * โœ… Simplify tests for separate_openapi_schemas. PR [#13201](https://github.com/fastapi/fastapi/pull/13201) by [@alejsdev](https://github.com/alejsdev). @@ -49,6 +138,7 @@ hide: ### Translations +* ๐ŸŒ Update Portuguese Translation for `docs/pt/docs/tutorial/request-forms.md`. PR [#13216](https://github.com/fastapi/fastapi/pull/13216) by [@Joao-Pedro-P-Holanda](https://github.com/Joao-Pedro-P-Holanda). * ๐ŸŒ Update Portuguese translation for `docs/pt/docs/advanced/settings.md`. PR [#13209](https://github.com/fastapi/fastapi/pull/13209) by [@ceb10n](https://github.com/ceb10n). * ๐ŸŒ Add Portuguese translation for `docs/pt/docs/tutorial/security/oauth2-jwt.md`. PR [#13205](https://github.com/fastapi/fastapi/pull/13205) by [@ceb10n](https://github.com/ceb10n). * ๐ŸŒ Add Indonesian translation for `docs/id/docs/index.md`. PR [#13191](https://github.com/fastapi/fastapi/pull/13191) by [@gerry-sabar](https://github.com/gerry-sabar). @@ -103,6 +193,7 @@ hide: ### Internal +* ๐Ÿ”ง Add Pydantic 2 trove classifier. PR [#13199](https://github.com/fastapi/fastapi/pull/13199) by [@johnthagen](https://github.com/johnthagen). * ๐Ÿ‘ฅ Update FastAPI People - Sponsors. PR [#13231](https://github.com/fastapi/fastapi/pull/13231) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Refactor FastAPI People Sponsors to use 2 tokens. PR [#13228](https://github.com/fastapi/fastapi/pull/13228) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update token for FastAPI People - Sponsors. PR [#13225](https://github.com/fastapi/fastapi/pull/13225) by [@tiangolo](https://github.com/tiangolo). diff --git a/docs/en/docs/tutorial/body-multiple-params.md b/docs/en/docs/tutorial/body-multiple-params.md index 9fced9652..71b308bb4 100644 --- a/docs/en/docs/tutorial/body-multiple-params.md +++ b/docs/en/docs/tutorial/body-multiple-params.md @@ -10,8 +10,6 @@ And you can also declare body parameters as optional, by setting the default to {* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} -## Multiple body parameters - /// note Notice that, in this case, the `item` that would be taken from the body is optional. As it has a `None` default value. diff --git a/docs/en/docs/tutorial/extra-models.md b/docs/en/docs/tutorial/extra-models.md index 5fac3f69e..ed1590ece 100644 --- a/docs/en/docs/tutorial/extra-models.md +++ b/docs/en/docs/tutorial/extra-models.md @@ -70,9 +70,9 @@ we would get a Python `dict` with: } ``` -#### Unwrapping a `dict` +#### Unpacking a `dict` -If we take a `dict` like `user_dict` and pass it to a function (or class) with `**user_dict`, Python will "unwrap" it. It will pass the keys and values of the `user_dict` directly as key-value arguments. +If we take a `dict` like `user_dict` and pass it to a function (or class) with `**user_dict`, Python will "unpack" it. It will pass the keys and values of the `user_dict` directly as key-value arguments. So, continuing with the `user_dict` from above, writing: @@ -117,11 +117,11 @@ would be equivalent to: UserInDB(**user_in.dict()) ``` -...because `user_in.dict()` is a `dict`, and then we make Python "unwrap" it by passing it to `UserInDB` prefixed with `**`. +...because `user_in.dict()` is a `dict`, and then we make Python "unpack" it by passing it to `UserInDB` prefixed with `**`. So, we get a Pydantic model from the data in another Pydantic model. -#### Unwrapping a `dict` and extra keywords +#### Unpacking a `dict` and extra keywords And then adding the extra keyword argument `hashed_password=hashed_password`, like in: diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index 1bf16334d..511099186 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -6,13 +6,13 @@ Let's take this application as example: {* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -The query parameter `q` is of type `Union[str, None]` (or `str | None` in Python 3.10), that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required. +The query parameter `q` is of type `str | None`, that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required. /// note FastAPI will know that the value of `q` is not required because of the default value `= None`. -The `Union` in `Union[str, None]` will allow your editor to give you better support and detect errors. +Having `str | None` will allow your editor to give you better support and detect errors. /// @@ -25,29 +25,9 @@ We are going to enforce that even though `q` is optional, whenever it is provide To achieve that, first import: * `Query` from `fastapi` -* `Annotated` from `typing` (or from `typing_extensions` in Python below 3.9) +* `Annotated` from `typing` -//// tab | Python 3.10+ - -In Python 3.9 or above, `Annotated` is part of the standard library, so you can import it from `typing`. - -```Python hl_lines="1 3" -{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -In versions of Python below Python 3.9 you import `Annotated` from `typing_extensions`. - -It will already be installed with FastAPI. - -```Python hl_lines="3-4" -{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} /// info @@ -145,54 +125,23 @@ As in this case (without using `Annotated`) we have to replace the default value So: -```Python -q: Union[str, None] = Query(default=None) -``` - -...makes the parameter optional, with a default value of `None`, the same as: - -```Python -q: Union[str, None] = None -``` - -And in Python 3.10 and above: - ```Python q: str | None = Query(default=None) ``` ...makes the parameter optional, with a default value of `None`, the same as: -```Python -q: str | None = None -``` - -But the `Query` versions declare it explicitly as being a query parameter. - -/// info - -Keep in mind that the most important part to make a parameter optional is the part: ```Python -= None -``` - -or the: - -```Python -= Query(default=None) +q: str | None = None ``` -as it will use that `None` as the default value, and that way make the parameter **not required**. - -The `Union[str, None]` part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required. - -/// +But the `Query` version declares it explicitly as being a query parameter. Then, we can pass more parameters to `Query`. In this case, the `max_length` parameter that applies to strings: ```Python -q: Union[str, None] = Query(default=None, max_length=50) +q: str | None = Query(default=None, max_length=50) ``` This will validate the data, show a clear error when the data is not valid, and document the parameter in the OpenAPI schema *path operation*. @@ -201,7 +150,7 @@ This will validate the data, show a clear error when the data is not valid, and Keep in mind that when using `Query` inside of `Annotated` you cannot use the `default` parameter for `Query`. -Instead use the actual default value of the function parameter. Otherwise, it would be inconsistent. +Instead, use the actual default value of the function parameter. Otherwise, it would be inconsistent. For example, this is not allowed: @@ -255,7 +204,7 @@ This specific regular expression pattern checks that the received parameter valu If you feel lost with all these **"regular expression"** ideas, don't worry. They are a hard topic for many people. You can still do a lot of stuff without needing regular expressions yet. -But whenever you need them and go and learn them, know that you can already use them directly in **FastAPI**. +Now you know that whenever you need them you can use them in **FastAPI**. ### Pydantic v1 `regex` instead of `pattern` @@ -296,7 +245,7 @@ q: str instead of: ```Python -q: Union[str, None] = None +q: str | None = None ``` But we are now declaring it with `Query`, for example like: @@ -304,15 +253,7 @@ But we are now declaring it with `Query`, for example like: //// tab | Annotated ```Python -q: Annotated[Union[str, None], Query(min_length=3)] = None -``` - -//// - -//// tab | non-Annotated - -```Python -q: Union[str, None] = Query(default=None, min_length=3) +q: Annotated[str | None, Query(min_length=3)] = None ``` //// @@ -321,42 +262,14 @@ So, when you need to declare a value as required while using `Query`, you can si {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Required with Ellipsis (`...`) - -There's an alternative way to explicitly declare that a value is required. You can set the default to the literal value `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// info - -If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis". - -It is used by Pydantic and FastAPI to explicitly declare that a value is required. - -/// - -This will let **FastAPI** know that this parameter is required. - ### Required, can be `None` You can declare that a parameter can accept `None`, but that it's still required. This would force clients to send a value, even if the value is `None`. -To do that, you can declare that `None` is a valid type but still use `...` as the default: +To do that, you can declare that `None` is a valid type but simply do not declare a default value: {* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} -/// tip - -Pydantic, which is what powers all the data validation and serialization in FastAPI, has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about Required fields. - -/// - -/// tip - -Remember that in most of the cases, when something is required, you can simply omit the default, so you normally don't have to use `...`. - -/// - ## Query parameter list / multiple values When you define a query parameter explicitly with `Query` you can also declare it to receive a list of values, or said in another way, to receive multiple values. @@ -396,7 +309,7 @@ The interactive API docs will update accordingly, to allow multiple values: ### Query parameter list / multiple values with defaults -And you can also define a default `list` of values if none are provided: +You can also define a default `list` of values if none are provided: {* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} @@ -419,7 +332,7 @@ the default of `q` will be: `["foo", "bar"]` and your response will be: #### Using just `list` -You can also use `list` directly instead of `List[str]` (or `list[str]` in Python 3.9+): +You can also use `list` directly instead of `list[str]`: {* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} @@ -427,7 +340,7 @@ You can also use `list` directly instead of `List[str]` (or `list[str]` in Pytho Keep in mind that in this case, FastAPI won't check the contents of the list. -For example, `List[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't. +For example, `list[int]` would check (and document) that the contents of the list are integers. But `list` alone wouldn't. /// diff --git a/docs/en/docs/tutorial/response-status-code.md b/docs/en/docs/tutorial/response-status-code.md index 711042a46..41bf02a8f 100644 --- a/docs/en/docs/tutorial/response-status-code.md +++ b/docs/en/docs/tutorial/response-status-code.md @@ -53,16 +53,16 @@ These status codes have a name associated to recognize them, but the important p In short: -* `100` and above are for "Information". You rarely use them directly. Responses with these status codes cannot have a body. -* **`200`** and above are for "Successful" responses. These are the ones you would use the most. +* `100 - 199` are for "Information". You rarely use them directly. Responses with these status codes cannot have a body. +* **`200 - 299`** are for "Successful" responses. These are the ones you would use the most. * `200` is the default status code, which means everything was "OK". * Another example would be `201`, "Created". It is commonly used after creating a new record in the database. * A special case is `204`, "No Content". This response is used when there is no content to return to the client, and so the response must not have a body. -* **`300`** and above are for "Redirection". Responses with these status codes may or may not have a body, except for `304`, "Not Modified", which must not have one. -* **`400`** and above are for "Client error" responses. These are the second type you would probably use the most. +* **`300 - 399`** are for "Redirection". Responses with these status codes may or may not have a body, except for `304`, "Not Modified", which must not have one. +* **`400 - 499`** are for "Client error" responses. These are the second type you would probably use the most. * An example is `404`, for a "Not Found" response. * For generic errors from the client, you can just use `400`. -* `500` and above are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes. +* `500 - 599` are for server errors. You almost never use them directly. When something goes wrong at some part in your application code, or server, it will automatically return one of these status codes. /// tip diff --git a/docs/es/docs/tutorial/query-params-str-validations.md b/docs/es/docs/tutorial/query-params-str-validations.md index f378b9dce..9cb76156f 100644 --- a/docs/es/docs/tutorial/query-params-str-validations.md +++ b/docs/es/docs/tutorial/query-params-str-validations.md @@ -321,22 +321,6 @@ Asรญ que, cuando necesites declarar un valor como requerido mientras usas `Query {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Requerido con Puntos suspensivos (`...`) - -Hay una manera alternativa de declarar explรญcitamente que un valor es requerido. Puedes establecer el valor por defecto al valor literal `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// info | Informaciรณn - -Si no habรญas visto eso `...` antes: es un valor especial รบnico, es parte de Python y se llama "Ellipsis". - -Se usa por Pydantic y FastAPI para declarar explรญcitamente que un valor es requerido. - -/// - -Esto le permitirรก a **FastAPI** saber que este parรกmetro es requerido. - ### Requerido, puede ser `None` Puedes declarar que un parรกmetro puede aceptar `None`, pero que aรบn asรญ es requerido. Esto obligarรญa a los clientes a enviar un valor, incluso si el valor es `None`. diff --git a/docs/ja/docs/environment-variables.md b/docs/ja/docs/environment-variables.md new file mode 100644 index 000000000..507af3a0c --- /dev/null +++ b/docs/ja/docs/environment-variables.md @@ -0,0 +1,301 @@ +# ็’ฐๅขƒๅค‰ๆ•ฐ + +/// tip + +ใ‚‚ใ—ใ€ใ€Œ็’ฐๅขƒๅค‰ๆ•ฐใ€ใจใฏไฝ•ใ‹ใ€ใใ‚Œใ‚’ใฉใ†ไฝฟใ†ใ‹ใ‚’ๆ—ขใซ็Ÿฅใฃใฆใ„ใ‚‹ๅ ดๅˆใฏใ€ใ“ใฎใ‚ปใ‚ฏใ‚ทใƒงใƒณใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใฆๆง‹ใ„ใพใ›ใ‚“ใ€‚ + +/// + +็’ฐๅขƒๅค‰ๆ•ฐ๏ผˆ**env var**ใจใ‚‚ๅ‘ผใฐใ‚Œใ‚‹๏ผ‰ใฏPythonใ‚ณใƒผใƒ‰ใฎ**ๅค–ๅด**ใ€ใคใพใ‚Š**OS**ใซๅญ˜ๅœจใ™ใ‚‹ๅค‰ๆ•ฐใงใ€Pythonใ‹ใ‚‰่ชญใฟๅ–ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚๏ผˆไป–ใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใงใ‚‚ๅŒๆง˜ใซ่ชญใฟๅ–ใ‚Œใพใ™ใ€‚๏ผ‰ + +็’ฐๅขƒๅค‰ๆ•ฐใฏใ€ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฎ**่จญๅฎš**ใฎ็ฎก็†ใ‚„ใ€Pythonใฎ**ใ‚คใƒณใ‚นใƒˆใƒผใƒซ**ใชใฉใซๅฝน็ซ‹ใกใพใ™ใ€‚ + +## ็’ฐๅขƒๅค‰ๆ•ฐใฎไฝœๆˆใจไฝฟ็”จ + +็’ฐๅขƒๅค‰ๆ•ฐใฏ**ใ‚ทใ‚งใƒซ๏ผˆใ‚ฟใƒผใƒŸใƒŠใƒซ๏ผ‰**ๅ†…ใง**ไฝœๆˆ**ใ—ใฆไฝฟ็”จใงใใ€ใใ‚Œใ‚‰ใซPythonใฏไธ่ฆใงใ™ใ€‚ + +//// tab | Linux, macOS, Windows Bash + +

+ +```console +// You could create an env var MY_NAME with +$ export MY_NAME="Wade Wilson" + +// Then you could use it with other programs, like +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ + +```console +// Create an env var MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Use it with other programs, like +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Pythonใง็’ฐๅขƒๅค‰ๆ•ฐใ‚’่ชญใฟๅ–ใ‚‹ + +็’ฐๅขƒๅค‰ๆ•ฐใ‚’Pythonใฎ**ๅค–ๅด**ใ€ใ‚ฟใƒผใƒŸใƒŠใƒซ๏ผˆใ‚„ไป–ใฎๆ–นๆณ•๏ผ‰ใงไฝœๆˆใ—ใ€**Pythonๅ†…ใง่ชญใฟๅ–ใ‚‹**ใ“ใจใ‚‚ใงใใพใ™ใ€‚ + +ไพ‹ใˆใฐใ€ไปฅไธ‹ใฎใ‚ˆใ†ใช`main.py`ใƒ•ใ‚กใ‚คใƒซใ‚’็”จๆ„ใ—ใพใ™: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip + +`os.getenv()` ใฎ็ฌฌ2ๅผ•ๆ•ฐใฏใ€ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใง่ฟ”ใ•ใ‚Œใ‚‹ๅ€คใ‚’ๆŒ‡ๅฎšใ—ใพใ™ใ€‚ + +ใ“ใฎๅผ•ๆ•ฐใ‚’็œ็•ฅใ™ใ‚‹ใจใƒ‡ใƒ•ใ‚ฉใƒซใƒˆๅ€คใจใ—ใฆ`None`ใŒ่ฟ”ใ•ใ‚Œใพใ™ใŒใ€ใ“ใ“ใงใฏใƒ‡ใƒ•ใ‚ฉใƒซใƒˆๅ€คใจใ—ใฆ`"World"`ใ‚’ๆŒ‡ๅฎšใ—ใฆใ„ใพใ™ใ€‚ + +/// + +ๆฌกใซใ€ใ“ใฎPythonใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’ๅ‘ผใณๅ‡บใ—ใพใ™ใ€‚ + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ export MY_NAME="Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ $Env:MY_NAME = "Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +็’ฐๅขƒๅค‰ๆ•ฐใฏใ‚ณใƒผใƒ‰ใฎๅค–ๅดใง่จญๅฎšใ—ใ€ๅ†…ๅดใ‹ใ‚‰่ชญใฟๅ–ใ‚‹ใ“ใจใŒใงใใ‚‹ใฎใงใ€ไป–ใฎใƒ•ใ‚กใ‚คใƒซใจไธ€็ท’ใซ๏ผˆ`git`ใซ๏ผ‰ไฟๅญ˜ใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ใใฎใŸใ‚ใ€็’ฐๅขƒๅค‰ๆ•ฐใ‚’ใ‚ณใƒณใƒ•ใ‚ฃใ‚ฐใƒฌใƒผใ‚ทใƒงใƒณใ‚„**่จญๅฎš**ใซไฝฟ็”จใ™ใ‚‹ใ“ใจใŒไธ€่ˆฌ็š„ใงใ™ใ€‚ + +ใพใŸใ€**็‰นๅฎšใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใฎๅ‘ผใณๅ‡บใ—**ใฎใŸใ‚ใฎ็’ฐๅขƒๅค‰ๆ•ฐใ‚’ใ€ใใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใฎใฟใ€ใใฎๅฎŸ่กŒไธญใซ้™ๅฎšใ—ใฆๅˆฉ็”จใงใใ‚‹ใ‚ˆใ†ไฝœๆˆใงใใพใ™ใ€‚ + +ใใฎใŸใ‚ใซใฏใ€ใƒ—ใƒญใ‚ฐใƒฉใƒ ่ตทๅ‹•ใ‚ณใƒžใƒณใƒ‰ใจๅŒใ˜ใ‚ณใƒžใƒณใƒ‰ใƒฉใ‚คใƒณไธŠใฎใ€่ตทๅ‹•ใ‚ณใƒžใƒณใƒ‰็›ดๅ‰ใง็’ฐๅขƒๅค‰ๆ•ฐใ‚’ไฝœๆˆใ—ใฆใใ ใ•ใ„ใ€‚ + +
+ +```console +// Create an env var MY_NAME in line for this program call +$ MY_NAME="Wade Wilson" python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python + +// The env var no longer exists afterwards +$ python main.py + +Hello World from Python +``` + +
+ +/// tip + +่ฉณใ—ใใฏ The Twelve-Factor App: Config ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ + +/// + +## ๅž‹ใจใƒใƒชใƒ‡ใƒผใ‚ทใƒงใƒณ + +็’ฐๅขƒๅค‰ๆ•ฐใฏ**ใƒ†ใ‚ญใ‚นใƒˆๆ–‡ๅญ—ๅˆ—**ใฎใฟใ‚’ๆ‰ฑใ†ใ“ใจใŒใงใใพใ™ใ€‚ใ“ใ‚Œใฏใ€็’ฐๅขƒๅค‰ๆ•ฐใŒPythonๅค–้ƒจใซๅญ˜ๅœจใ—ใ€ไป–ใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚„ใ‚ทใ‚นใƒ†ใƒ ๅ…จไฝ“๏ผˆLinuxใ€Windowsใ€macOS้–“ใฎไบ’ๆ›ๆ€งใ‚’ๅซใ‚€๏ผ‰ใจ้€ฃๆบใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚‹ใŸใ‚ใงใ™ใ€‚ + +ใคใพใ‚Šใ€PythonใŒ็’ฐๅขƒๅค‰ๆ•ฐใ‹ใ‚‰่ชญใฟๅ–ใ‚‹**ใ‚ใ‚‰ใ‚†ใ‚‹ๅ€ค**ใฏ **`str`ๅž‹ใจใชใ‚Š**ใ€ไป–ใฎๅž‹ใธใฎๅค‰ๆ›ใ‚„ใƒใƒชใƒ‡ใƒผใ‚ทใƒงใƒณใฏใ‚ณใƒผใƒ‰ๅ†…ใง่กŒใ†ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ + +็’ฐๅขƒๅค‰ๆ•ฐใ‚’ไฝฟ็”จใ—ใฆ**ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณ่จญๅฎš**ใ‚’็ฎก็†ใ™ใ‚‹ๆ–นๆณ•ใซใคใ„ใฆใฏใ€[้ซ˜ๅบฆใชใƒฆใƒผใ‚ถใƒผใ‚ฌใ‚คใƒ‰ - Settings and Environment Variables](./advanced/settings.md){.internal-link target=_blank}ใง่ฉณใ—ใๅญฆในใพใ™ใ€‚ + +## `PATH`็’ฐๅขƒๅค‰ๆ•ฐ + +**`PATH`**ใจใ„ใ†**็‰นๅˆฅใช**็’ฐๅขƒๅค‰ๆ•ฐใŒใ‚ใ‚Šใพใ™ใ€‚ใ“ใฎ็’ฐๅขƒๅค‰ๆ•ฐใฏใ€OS๏ผˆLinuxใ€macOSใ€Windows๏ผ‰ใŒๅฎŸ่กŒใ™ใ‚‹ใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’็™บ่ฆ‹ใ™ใ‚‹ใŸใ‚ใซไฝฟ็”จใ•ใ‚Œใพใ™ใ€‚ + +`PATH`ๅค‰ๆ•ฐใฏใ€่ค‡ๆ•ฐใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใฎใƒ‘ใ‚นใ‹ใ‚‰ๆˆใ‚‹้•ทใ„ๆ–‡ๅญ—ๅˆ—ใงใ™ใ€‚ใ“ใฎใƒ‘ใ‚นใฏLinuxใ‚„MacOSใฎๅ ดๅˆใฏ`:`ใงใ€Windowsใฎๅ ดๅˆใฏ`;`ใงๅŒบๅˆ‡ใ‚‰ใ‚Œใฆใ„ใพใ™ใ€‚ + +ไพ‹ใˆใฐใ€`PATH`็’ฐๅขƒๅค‰ๆ•ฐใฏๆฌกใฎใ‚ˆใ†ใชๆ–‡ๅญ—ๅˆ—ใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +ใ“ใ‚Œใฏใ€OSใฏใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’่ฆ‹ใคใ‘ใ‚‹ใŸใ‚ใซไปฅไธ‹ใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใ‚’ๆŽขใ™ใ€ใจใ„ใ†ใ“ใจใ‚’ๆ„ๅ‘ณใ—ใพใ™: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +ใ“ใ‚Œใฏใ€OSใฏใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’่ฆ‹ใคใ‘ใ‚‹ใŸใ‚ใซไปฅไธ‹ใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใ‚’ๆŽขใ™ใ€ใจใ„ใ†ใ“ใจใ‚’ๆ„ๅ‘ณใ—ใพใ™: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +ใ‚ฟใƒผใƒŸใƒŠใƒซไธŠใง**ใ‚ณใƒžใƒณใƒ‰**ใ‚’ๅ…ฅๅŠ›ใ™ใ‚‹ใจใ€ OSใฏใใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’่ฆ‹ใคใ‘ใ‚‹ใŸใ‚ใซใ€`PATH`็’ฐๅขƒๅค‰ๆ•ฐใฎใƒชใ‚นใƒˆใซ่จ˜่ผ‰ใ•ใ‚ŒใŸ**ใใ‚Œใžใ‚Œใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใ‚’ๆŽขใ—**ใพใ™ใ€‚ + +ไพ‹ใˆใฐใ€ใ‚ฟใƒผใƒŸใƒŠใƒซไธŠใง`python`ใ‚’ๅ…ฅๅŠ›ใ™ใ‚‹ใจใ€OSใฏ`python`ใซใ‚ˆใฃใฆๅ‘ผใฐใ‚Œใ‚‹ใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’่ฆ‹ใคใ‘ใ‚‹ใŸใ‚ใซใ€ใใฎใƒชใ‚นใƒˆใฎ**ๅ…ˆ้ ญใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช**ใ‚’ๆœ€ๅˆใซๆŽขใ—ใพใ™ใ€‚ + +OSใฏใ€ใ‚‚ใ—ใใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’ใใ“ใง็™บ่ฆ‹ใ™ใ‚Œใฐ**ๅฎŸ่กŒใ—**ใพใ™ใŒใ€ใใ†ใงใชใ‘ใ‚Œใฐใƒชใ‚นใƒˆใฎ**ไป–ใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช**ใ‚’ๆŽขใ—ใฆใ„ใใพใ™ใ€‚ + +### Pythonใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซใจPATH็’ฐๅขƒๅค‰ๆ•ฐใฎๆ›ดๆ–ฐ + +Pythonใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซๆ™‚ใซ`PATH`็’ฐๅขƒๅค‰ๆ•ฐใ‚’ๆ›ดๆ–ฐใ—ใŸใ„ใ‹่žใ‹ใ‚Œใ‚‹ใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“ใ€‚ + +/// tab | Linux, macOS + +Pythonใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใ€ใใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใŒ`/opt/custompython/bin`ใจใ„ใ†ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใซ้…็ฝฎใ•ใ‚ŒใŸใจใ—ใพใ™ใ€‚ + +ใ‚‚ใ—ใ€`PATH`็’ฐๅขƒๅค‰ๆ•ฐใ‚’ๆ›ดๆ–ฐใ™ใ‚‹ใ‚ˆใ†ใซ็ญ”ใˆใ‚‹ใจใ€`PATH`็’ฐๅขƒๅค‰ๆ•ฐใซ`/opt/custompython/bin`ใŒ่ฟฝๅŠ ใ•ใ‚Œใพใ™ใ€‚ + +`PATH`็’ฐๅขƒๅค‰ๆ•ฐใฏไปฅไธ‹ใฎใ‚ˆใ†ใซๆ›ดๆ–ฐใ•ใ‚Œใ‚‹ใงใ—ใ‚‡ใ†๏ผš + +``` plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +ใ“ใฎใ‚ˆใ†ใซใ—ใฆใ€ใ‚ฟใƒผใƒŸใƒŠใƒซใง`python`ใจๅ…ฅๅŠ›ใ—ใŸใจใใซใ€OSใฏ`/opt/custompython/bin`๏ผˆใƒชใ‚นใƒˆใฎๆœซๅฐพใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช๏ผ‰ใซใ‚ใ‚‹Pythonใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’่ฆ‹ใคใ‘ใ€ไฝฟ็”จใ—ใพใ™ใ€‚ + +/// + +/// tab | Windows + +Pythonใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใฆใ€ใใฎใƒ—ใƒญใ‚ฐใƒฉใƒ ใŒ`C:\opt\custompython\bin`ใจใ„ใ†ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใซ้…็ฝฎใ•ใ‚ŒใŸใจใ—ใพใ™ใ€‚ + +ใ‚‚ใ—ใ€`PATH`็’ฐๅขƒๅค‰ๆ•ฐใ‚’ๆ›ดๆ–ฐใ™ใ‚‹ใ‚ˆใ†ใซ็ญ”ใˆใ‚‹ใจใ€`PATH`็’ฐๅขƒๅค‰ๆ•ฐใซ`C:\opt\custompython\bin`ใŒ่ฟฝๅŠ ใ•ใ‚Œใพใ™ใ€‚ + +`PATH`็’ฐๅขƒๅค‰ๆ•ฐใฏไปฅไธ‹ใฎใ‚ˆใ†ใซๆ›ดๆ–ฐใ•ใ‚Œใ‚‹ใงใ—ใ‚‡ใ†๏ผš + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +ใ“ใฎใ‚ˆใ†ใซใ—ใฆใ€ใ‚ฟใƒผใƒŸใƒŠใƒซใง`python`ใจๅ…ฅๅŠ›ใ—ใŸใจใใซใ€OSใฏ`C:\opt\custompython\bin\python`๏ผˆใƒชใ‚นใƒˆใฎๆœซๅฐพใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช๏ผ‰ใซใ‚ใ‚‹Pythonใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’่ฆ‹ใคใ‘ใ€ไฝฟ็”จใ—ใพใ™ใ€‚ + +/// + +ใคใพใ‚Šใ€ใ‚ฟใƒผใƒŸใƒŠใƒซใงไปฅไธ‹ใฎใ‚ณใƒžใƒณใƒ‰ใ‚’ๅ…ฅๅŠ›ใ™ใ‚‹ใจ๏ผš + +
+ +``` console +$ python +``` + +
+ +/// tab | Linux, macOS + +OSใฏ`/opt/custompython/bin`ใซใ‚ใ‚‹`python`ใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’**่ฆ‹ใคใ‘**ใฆๅฎŸ่กŒใ—ใพใ™ใ€‚ + +ใ“ใ‚Œใฏใ€ๆฌกใฎใ‚ณใƒžใƒณใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใŸๅ ดๅˆใจใปใจใ‚“ใฉๅŒ็ญ‰ใงใ™๏ผš + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +/// + +/// tab | Windows + +OSใฏ`C:\opt\custompython\bin\python`ใซใ‚ใ‚‹`python`ใƒ—ใƒญใ‚ฐใƒฉใƒ ใ‚’**่ฆ‹ใคใ‘**ใฆๅฎŸ่กŒใ—ใพใ™ใ€‚ + +ใ“ใ‚Œใฏใ€ๆฌกใฎใ‚ณใƒžใƒณใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใŸๅ ดๅˆใจใปใจใ‚“ใฉๅŒ็ญ‰ใงใ™๏ผš + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +/// + +ใ“ใฎๆƒ…ๅ ฑใฏใ€[Virtual Environments](virtual-environments.md) ใซใคใ„ใฆๅญฆใถ้š›ใซใ‚‚ๅฝน็ซ‹ใกใพใ™ใ€‚ + +## ใพใจใ‚ + +ใ“ใ‚Œใงใ€**็’ฐๅขƒๅค‰ๆ•ฐ**ใจใฏไฝ•ใ‹ใ€Pythonใงใฉใฎใ‚ˆใ†ใซไฝฟ็”จใ™ใ‚‹ใ‹ใซใคใ„ใฆใ€ๅŸบๆœฌ็š„ใช็†่งฃใŒๅพ—ใ‚‰ใ‚ŒใŸใฏใšใงใ™ใ€‚ + +็’ฐๅขƒๅค‰ๆ•ฐใซใคใ„ใฆใฎ่ฉณ็ดฐใฏใ€Wikipedia: Environment Variable ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ + +็’ฐๅขƒๅค‰ๆ•ฐใฎ็”จ้€”ใ‚„้ฉ็”จๆ–นๆณ•ใŒๆœ€ๅˆใฏ็›ดๆ„Ÿ็š„ใงใฏใชใ„ใ‹ใ‚‚ใ—ใ‚Œใพใ›ใ‚“ใŒใ€้–‹็™บไธญใฎใ•ใพใ–ใพใชใ‚ทใƒŠใƒชใ‚ชใง็นฐใ‚Š่ฟ”ใ—็™ปๅ ดใ—ใพใ™ใ€‚ใใฎใŸใ‚ใ€ๅŸบๆœฌใ‚’็ŸฅใฃใฆใŠใใ“ใจใŒ้‡่ฆใงใ™ใ€‚ + +ใŸใจใˆใฐใ€ใ“ใฎๆƒ…ๅ ฑใฏๆฌกใฎใ‚ปใ‚ฏใ‚ทใƒงใƒณใงๆ‰ฑใ†[Virtual Environments](virtual-environments.md)ใซใ‚‚้–ข้€ฃใ—ใพใ™ใ€‚ diff --git a/docs/ko/docs/advanced/custom-response.md b/docs/ko/docs/advanced/custom-response.md new file mode 100644 index 000000000..2001956fa --- /dev/null +++ b/docs/ko/docs/advanced/custom-response.md @@ -0,0 +1,313 @@ +# ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต - HTML, Stream, ํŒŒ์ผ, ๊ธฐํƒ€ + +๊ธฐ๋ณธ์ ์œผ๋กœ, **FastAPI** ์‘๋‹ต์„ `JSONResponse`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ฅผ ์žฌ์ •์˜ ํ•˜๋ ค๋ฉด [์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ](response-directly.md){.internal-link target=_blank}์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ `Response`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋Ÿฌ๋‚˜ `Response` (๋˜๋Š” `JSONResponse`์™€ ๊ฐ™์€ ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ๋ฐ์ดํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜๋˜์ง€ ์•Š์œผ๋ฉฐ (์‹ฌ์ง€์–ด `response_model`์„ ์„ ์–ธํ–ˆ๋”๋ผ๋„), ๋ฌธ์„œํ™”๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด, ์ƒ์„ฑ๋œ OpenAPI์˜ ์ผ๋ถ€๋กœ HTTP ํ—ค๋” `Content-Type`์— ํŠน์ • "๋ฏธ๋””์–ด ํƒ€์ž…"์„ ํฌํ•จํ•˜๋Š” ๊ฒฝ์šฐ). + +ํ•˜์ง€๋งŒ *๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์›ํ•˜๋Š” `Response`(์˜ˆ: ๋ชจ๋“  `Response` ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +*๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜*์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‚ด์šฉ์€ ํ•ด๋‹น `Response`์•ˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ ๊ทธ `Response`๊ฐ€ `JSONResponse`์™€ `UJSONResponse`์˜ ๊ฒฝ์šฐ ์ฒ˜๋Ÿผ JSON ๋ฏธ๋””์–ด ํƒ€์ž…(`application/json`)์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, *๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ ์„ ์–ธํ•œ Pydantic์˜ `response_model`์„ ์‚ฌ์šฉํ•ด ์ž๋™์œผ๋กœ ๋ณ€ํ™˜(๋ฐ ํ•„ํ„ฐ๋ง) ๋ฉ๋‹ˆ๋‹ค. + +/// note | ์ฐธ๊ณ  + +๋ฏธ๋””์–ด ํƒ€์ž…์ด ์—†๋Š” ์‘๋‹ต ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, FastAPI๋Š” ์‘๋‹ต์— ๋‚ด์šฉ์ด ์—†์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•˜๋ฏ€๋กœ ์ƒ์„ฑ๋œ OpenAPI ๋ฌธ์„œ์—์„œ ์‘๋‹ต ํ˜•์‹์„ ๋ฌธ์„œํ™”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +/// + +## `ORJSONResponse` ์‚ฌ์šฉํ•˜๊ธฐ + +์˜ˆ๋ฅผ ๋“ค์–ด, ์„ฑ๋Šฅ์„ ๊ทน๋Œ€ํ™”ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, orjson์„ ์„ค์น˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์‘๋‹ต์„ `ORJSONResponse`๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” `Response` ํด๋ž˜์Šค(ํ•˜์œ„ ํด๋ž˜์Šค)๋ฅผ ์ž„ํฌํŠธํ•œ ํ›„, **๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์—์„œ ์„ ์–ธํ•˜์„ธ์š”. + +๋Œ€๊ทœ๋ชจ ์‘๋‹ต์˜ ๊ฒฝ์šฐ, ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค `Response`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋น ๋ฆ…๋‹ˆ๋‹ค. + +์ด์œ ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ, FastAPI๊ฐ€ ๋‚ด๋ถ€์˜ ๋ชจ๋“  ํ•ญ๋ชฉ์„ ๊ฒ€์‚ฌํ•˜๊ณ  JSON์œผ๋กœ ์ง๋ ฌํ™”ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์„œ์—์„œ ์„ค๋ช…๋œ [JSON ํ˜ธํ™˜ ๊ฐ€๋Šฅ ์ธ์ฝ”๋”](../tutorial/encoder.md){.internal-link target=_blank}๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ๊ณผ ๊ฐ™์€ **์ž„์˜์˜ ๊ฐ์ฒด**๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‚ด์šฉ์ด **JSON์œผ๋กœ ์ง๋ ฌํ™” ๊ฐ€๋Šฅ**ํ•˜๋‹ค๊ณ  ํ™•์‹ ํ•˜๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ๋‚ด์šฉ์„ ์‘๋‹ต ํด๋ž˜์Šค์— ์ง์ ‘ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, FastAPI๊ฐ€ ๋ฐ˜ํ™˜ ๋‚ด์šฉ์„ `jsonable_encoder`๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•œ ๋’ค ์‘๋‹ต ํด๋ž˜์Šค์— ์ „๋‹ฌํ•˜๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} + +/// info | ์ •๋ณด + +`response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์‘๋‹ต์˜ "๋ฏธ๋””์–ด ํƒ€์ž…"์„ ์ •์˜ํ•˜๋Š” ๋ฐ์—๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ, HTTP ํ—ค๋” `Content-Type`์€ `application/json`์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์ด๋Š” OpenAPI์— ๊ทธ๋Œ€๋กœ ๋ฌธ์„œํ™”๋ฉ๋‹ˆ๋‹ค. + +/// + +/// tip | ํŒ + +`ORJSONResponse`๋Š” FastAPI์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ  Starlette์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +/// + +## HTML ์‘๋‹ต + +**FastAPI**์—์„œ HTML ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด `HTMLResponse`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. + +* `HTMLResponse`๋ฅผ ์ž„ํฌํŠธ ํ•ฉ๋‹ˆ๋‹ค. +* *๊ฒฝ๋กœ ์ž‘์—… ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ*์˜ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ `HTMLResponse`๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} + +/// info | ์ •๋ณด + +`response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์‘๋‹ต์˜ "๋ฏธ๋””์–ด ํƒ€์ž…"์„ ์ •์˜ํ•˜๋Š” ๋ฐ์—๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ, HTTP ํ—ค๋” `Content-Type`์€ `text/html`๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  ์ด๋Š” OpenAPI์— ๊ทธ๋Œ€๋กœ ๋ฌธ์„œํ™” ๋ฉ๋‹ˆ๋‹ค. + +/// + +### `Response` ๋ฐ˜ํ™˜ํ•˜๊ธฐ + +[์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ](response-directly.md){.internal-link target=_blank}์—์„œ ๋ณธ ๊ฒƒ ์ฒ˜๋Ÿผ, *๊ฒฝ๋กœ ์ž‘์—…*์—์„œ ์‘๋‹ต์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์žฌ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์œ„์˜ ์˜ˆ์ œ์™€ ๋™์ผํ•˜๊ฒŒ `HTMLResponse`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} + +/// warning | ๊ฒฝ๊ณ  + +*๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜*์—์„œ ์ง์ ‘ ๋ฐ˜ํ™˜๋œ `Response`๋Š” OpenAPI์— ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ๋“ค์–ด, `Content-Type`์ด ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์Œ) ์ž๋™ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ๋„ ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +/// + +/// info | ์ •๋ณด + +๋ฌผ๋ก  ์‹ค์ œ `Content-Type` ํ—ค๋”, ์ƒํƒœ ์ฝ”๋“œ ๋“ฑ์€ ๋ฐ˜ํ™˜๋œ `Response` ๊ฐ์ฒด์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. + +/// + +### OpenAPI์— ๋ฌธ์„œํ™”ํ•˜๊ณ  `Response` ์žฌ์ •์˜ ํ•˜๊ธฐ + +ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์‘๋‹ต์„ ์žฌ์ •์˜ํ•˜๋ฉด์„œ ๋™์‹œ์— OpenAPI์—์„œ "๋ฏธ๋””์–ด ํƒ€์ž…"์„ ๋ฌธ์„œํ™”ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, `response_class` ๋งค๊ฒŒ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ `Response` ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ `response_class`๋Š” OpenAPI *๊ฒฝ๋กœ ์ž‘์—…*์„ ๋ฌธ์„œํ™”ํ•˜๋Š” ๋ฐ๋งŒ ์‚ฌ์šฉ๋˜๊ณ , ์‹ค์ œ๋กœ๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฐ˜ํ™˜ํ•œ `Response`๊ฐ€ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +### `HTMLResponse`์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๊ธฐ + +์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} + +์ด ์˜ˆ์ œ์—์„œ, `generate_html_response()` ํ•จ์ˆ˜๋Š” HTML์„ `str`๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ์ด๋ฏธ `Response`๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +`generate_html_response()`๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•จ์œผ๋กœ์จ, ๊ธฐ๋ณธ์ ์ธ **FastAPI** ๊ธฐ๋ณธ ๋™์ž‘์„ ์žฌ์ •์˜ ํ•˜๋Š” `Response`๋ฅผ ์ด๋ฏธ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ `response_class`์— `HTMLResponse`๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, FastAPI๋Š” ์ด๋ฅผ OpenAPI ๋ฐ ๋Œ€ํ™”ํ˜• ๋ฌธ์„œ์—์„œ `text/html`๋กœ HTML์„ ๋ฌธ์„œํ™” ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + + + +## ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต๋“ค + +๋‹ค์Œ์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡๊ฐ€์ง€ ์‘๋‹ต๋“ค ์ž…๋‹ˆ๋‹ค. + +`Response`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ์–ด๋–ค ๊ฒƒ๋„ ๋ฐ˜ํ™˜ ํ• ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ง์ ‘ ํ•˜์œ„ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +`from starlette.responses import HTMLResponse`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ๊ฐœ๋ฐœ์ž์ธ ์—ฌ๋Ÿฌ๋ถ„์˜ ํŽธ์˜๋ฅผ ์œ„ํ•ด `starlette.responses`๋ฅผ `fastapi.responses`๋กœ ์ œ๊ณต ํ•˜์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต์€ Starlette์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. + +/// + +### `Response` + +๊ธฐ๋ณธ `Response` ํด๋ž˜์Šค๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ์‘๋‹ต ํด๋ž˜์Šค์˜ ๋ถ€๋ชจ ํด๋ž˜์Šค ์ž…๋‹ˆ๋‹ค. + +์ด ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋‹ค์Œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +* `content` - `str` ๋˜๋Š” `bytes`. +* `status_code` - HTTP ์ƒํƒœ์ฝ”๋“œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” `int`. +* `headers` - ๋ฌธ์ž์—ด๋กœ ์ด๋ฃจ์–ด์ง„ `dict`. +* `media_type` - ๋ฏธ๋””์–ด ํƒ€์ž…์„ ๋‚˜ํƒ€๋‚ด๋Š” `str` ์˜ˆ: `"text/html"`. + +FastAPI (์‹ค์ œ๋กœ๋Š” Starlette)๊ฐ€ ์ž๋™์œผ๋กœ `Content-Length` ํ—ค๋”๋ฅผ ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค. ๋˜ํ•œ `media_type`์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ `Content-Type` ํ—ค๋”๋ฅผ ํฌํ•จํ•˜๋ฉฐ, ํ…์ŠคํŠธ ํƒ€์ž…์˜ ๊ฒฝ์šฐ ๋ฌธ์ž ์ง‘ํ•ฉ์„ ์ถ”๊ฐ€ ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} + +### `HTMLResponse` + +ํ…์ŠคํŠธ ๋˜๋Š” ๋ฐ”์ดํŠธ๋ฅผ ๋ฐ›์•„ HTML ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋‚ด์šฉ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. + +### `PlainTextResponse` + +ํ…์ŠคํŠธ ๋˜๋Š” ๋ฐ”์ดํŠธ๋ฅผ ๋ฐ›์•„ ์ผ๋ฐ˜ ํ…์ŠคํŠธ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} + +### `JSONResponse` + +๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ `application/json`์œผ๋กœ ์ธ์ฝ”๋”ฉ๋œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋Š” ์œ„์—์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด **FastAPI**์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์‘๋‹ต ํ˜•์‹์ž…๋‹ˆ๋‹ค. + +### `ORJSONResponse` + + `orjson`์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น ๋ฅธ JSON ์‘๋‹ต์„ ์ œ๊ณตํ•˜๋Š” ๋Œ€์•ˆ์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋‚ด์šฉ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด `orjson`์„ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: `pip install orjson`. + +/// + +### `UJSONResponse` + +`ujson`์„ ์‚ฌ์šฉํ•œ ๋˜ ๋‹ค๋ฅธ JSON ์‘๋‹ต ํ˜•์‹์ž…๋‹ˆ๋‹ค. + +/// info | ์ •๋ณด + +์ด ์‘๋‹ต์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด `ujson`์„ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: 'pip install ujson`. + +/// + +/// warning | ๊ฒฝ๊ณ  + +`ujson` ์€ ์ผ๋ถ€ ์˜ˆ์™ธ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์žˆ์–ด Python ๋‚ด์žฅ ๊ตฌํ˜„๋ณด๋‹ค ๋œ ์—„๊ฒฉํ•ฉ๋‹ˆ๋‹ค. + +/// + +{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} + +/// tip | ํŒ + +`ORJSONResponse`๊ฐ€ ๋” ๋น ๋ฅธ ๋Œ€์•ˆ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +### `RedirectResponse` + +HTTP ๋ฆฌ๋””๋ ‰์…˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋Š” 307(์ž„์‹œ ๋ฆฌ๋””๋ ‰์…˜)์œผ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. + +`RedirectResponse`๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} + +--- + +๋˜๋Š” `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: + + +{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} + +์ด ๊ฒฝ์šฐ, *๊ฒฝ๋กœ ์ž‘์—…* ํ•จ์ˆ˜์—์„œ URL์„ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ, ์‚ฌ์šฉ๋˜๋Š” `status_code`๋Š” `RedirectResponse`์˜ ๊ธฐ๋ณธ๊ฐ’์ธ `307` ์ž…๋‹ˆ๋‹ค. + +--- + +`status_code` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} + +### `StreamingResponse` + +๋น„๋™๊ธฐ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ๋˜๋Š” ์ผ๋ฐ˜ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ/์ดํ„ฐ๋ ˆ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์‘๋‹ต ๋ณธ๋ฌธ์„ ์ŠคํŠธ๋ฆฌ๋ฐ ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} + +#### ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•œ `StreamingResponse` + +ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด(์˜ˆ: `open()`์œผ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด)๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜๋ณต(iterate)ํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๋ฐฉ์‹์œผ๋กœ, ํŒŒ์ผ ์ „์ฒด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋จผ์ € ์ฝ์–ด๋“ค์ผ ํ•„์š” ์—†์ด, ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๋ฅผ `StreamingResponse`์— ์ „๋‹ฌํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๋ฐฉ์‹์€ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€, ๋น„๋””์˜ค ์ฒ˜๋ฆฌ ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} + +1. ์ด๊ฒƒ์ด ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. `yield` ๋ฌธ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ "์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜"์ž…๋‹ˆ๋‹ค. +2. `with` ๋ธ”๋ก์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ, ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋‹ซํžˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์‘๋‹ต ์ „์†ก์ด ๋๋‚œ ํ›„ ๋‹ซํž™๋‹ˆ๋‹ค. +3. ์ด `yield from`์€ ํ•จ์ˆ˜๊ฐ€ `file_like`๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜๋ณต(iterate)ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๋œ ๊ฐ ๋ถ€๋ถ„์€ ์ด ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜(`iterfile`)์—์„œ ์ƒ์„ฑ๋œ ๊ฒƒ์ฒ˜๋Ÿผ `yield` ๋ฉ๋‹ˆ๋‹ค. + + ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด "์ƒ์„ฑ(generating)" ์ž‘์—…์„ ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹ค๋ฅธ ๋ฌด์–ธ๊ฐ€์— ์œ„์ž„ํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. + + ์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด `with` ๋ธ”๋ก ์•ˆ์—์„œ ํŒŒ์ผ์„ ์—ด ์ˆ˜ ์žˆ์–ด, ์ž‘์—…์ด ์™„๋ฃŒ๋œ ํ›„ ํŒŒ์ผ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋‹ซํžˆ๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์—ฌ๊ธฐ์„œ ํ‘œ์ค€ `open()`์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— `async`์™€ `await`๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฒฝ๋กœ ์ž‘์—…์€ ์ผ๋ฐ˜ `def`๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. + +/// + +### `FileResponse` + +ํŒŒ์ผ์„ ๋น„๋™๊ธฐ๋กœ ์ŠคํŠธ๋ฆฌ๋ฐํ•˜์—ฌ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค. + +๋‹ค๋ฅธ ์‘๋‹ต ์œ ํ˜•๊ณผ๋Š” ๋‹ค๋ฅธ ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: + +* `path` - ์ŠคํŠธ๋ฆฌ๋ฐํ•  ํŒŒ์ผ์˜ ๊ฒฝ๋กœ. +* `headers` - ๋”•์…”๋„ˆ๋ฆฌ ํ˜•์‹์˜ ์‚ฌ์šฉ์ž ์ •์˜ ํ—ค๋”. +* `media_type` - ๋ฏธ๋””์–ด ํƒ€์ž…์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ฌธ์ž์—ด. ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ํŒŒ์ผ ์ด๋ฆ„์ด๋‚˜ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค. +* `filename` - ์„ค์ •๋œ ๊ฒฝ์šฐ ์‘๋‹ต์˜ `Content-Disposition`์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +ํŒŒ์ผ ์‘๋‹ต์—๋Š” ์ ์ ˆํ•œ `Content-Length`, `Last-Modified`, ๋ฐ `ETag` ํ—ค๋”๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} + +๋˜ํ•œ `response_class` ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} + +์ด ๊ฒฝ์šฐ, ๊ฒฝ๋กœ ์ž‘์—… ํ•จ์ˆ˜์—์„œ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต ํด๋ž˜์Šค + +`Response`๋ฅผ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ํฌํ•จ๋œ `ORJSONResponse` ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์„ค์ •์œผ๋กœ orjson์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. + +๋งŒ์•ฝ ๋“ค์—ฌ์“ฐ๊ธฐ ๋ฐ ํฌ๋งท๋œ JSON์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, `orjson.OPT_INDENT_2` ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +`CustomORJSONResponse`๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€ `Response.render(content)` ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋‚ด์šฉ์„ `bytes`๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค: + +{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} + +์ด์ œ ๋‹ค์Œ ๋Œ€์‹ : + +```json +{"message": "Hello World"} +``` + +์ด ์‘๋‹ต์€ ์ด๋ ‡๊ฒŒ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค: + +```json +{ + "message": "Hello World" +} +``` + +๋ฌผ๋ก  JSON ํฌ๋งทํŒ…๋ณด๋‹ค ๋” ์œ ์šฉํ•˜๊ฒŒ ํ™œ์šฉํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜‰ + +## ๊ธฐ๋ณธ ์‘๋‹ต ํด๋ž˜์Šค + +**FastAPI** ํด๋ž˜์Šค ๊ฐ์ฒด ๋˜๋Š” `APIRouter`๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์‘๋‹ต ํด๋ž˜์Šค๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋ฅผ ์ •์˜ํ•˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” `default_response_class`์ž…๋‹ˆ๋‹ค. + +์•„๋ž˜ ์˜ˆ์ œ์—์„œ **FastAPI**๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ ์ž‘์—…์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ `JSONResponse` ๋Œ€์‹  `ORJSONResponse`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} + +/// tip | ํŒ + +์—ฌ์ „ํžˆ ์ด์ „์ฒ˜๋Ÿผ *๊ฒฝ๋กœ ์ž‘์—…*์—์„œ `response_class`๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// + +## ์ถ”๊ฐ€ ๋ฌธ์„œํ™” + +OpenAPI์—์„œ `responses`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏธ๋””์–ด ํƒ€์ž… ๋ฐ ๊ธฐํƒ€ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค: [OpenAPI์—์„œ ์ถ”๊ฐ€ ์‘๋‹ต](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/ko/docs/help-fastapi.md b/docs/ko/docs/help-fastapi.md index 932952b4a..06435d4bb 100644 --- a/docs/ko/docs/help-fastapi.md +++ b/docs/ko/docs/help-fastapi.md @@ -1,162 +1,269 @@ -* # FastAPI ์ง€์› - ๋„์›€๋ง ๋ฐ›๊ธฐ +# FastAPI ์ง€์› - ๋„์›€ ๋ฐ›๊ธฐ - **FastAPI** ๊ฐ€ ๋งˆ์Œ์— ๋“œ์‹œ๋‚˜์š”? +**FastAPI** ๊ฐ€ ๋งˆ์Œ์— ๋“œ์‹œ๋‚˜์š”? - FastAPI, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž, ๊ฐœ๋ฐœ์ž๋ฅผ ์‘์›ํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? +FastAPI, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž, ๊ฐœ๋ฐœ์ž๋ฅผ ์‘์›ํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”? - ํ˜น์€ **FastAPI** ์— ๋Œ€ํ•ด ๋„์›€์ด ํ•„์š”ํ•˜์‹ ๊ฐ€์š”? +ํ˜น์€ **FastAPI** ์— ๋Œ€ํ•ด ๋„์›€์ด ํ•„์š”ํ•˜์‹ ๊ฐ€์š”? - ์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‘์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๋ช‡ ๋ฒˆ์˜ ํด๋ฆญ๋งŒ์œผ๋กœ). +์•„์ฃผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‘์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๋ช‡ ๋ฒˆ์˜ ํด๋ฆญ๋งŒ์œผ๋กœ). - ๋˜ํ•œ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ๋ช‡ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค. +๋˜ํ•œ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ๋ช‡ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค. - ## ๋‰ด์Šค๋ ˆํ„ฐ ๊ตฌ๋… +## ๋‰ด์Šค๋ ˆํ„ฐ ๊ตฌ๋… - [**FastAPI์™€ ์นœ๊ตฌ** ๋‰ด์Šค๋ ˆํ„ฐ](https://github.com/fastapi/fastapi/blob/master/newsletter)๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ์ตœ์‹  ์ •๋ณด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค{.internal-link target=_blank}: +[**FastAPI and friends** ๋‰ด์Šค๋ ˆํ„ฐ](newsletter.md){.internal-link target=\_blank}๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ์ตœ์‹  ์ •๋ณด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - - FastAPI ์™€ ๊ทธ ์นœ๊ตฌ๋“ค์— ๋Œ€ํ•œ ๋‰ด์Šค ๐Ÿš€ - - ๊ฐ€์ด๋“œ ๐Ÿ“ - - ํŠน์ง• โœจ - - ํš๊ธฐ์ ์ธ ๋ณ€ํ™” ๐Ÿšจ - - ํŒ๊ณผ ์š”๋ น โœ… +* FastAPI and friends์— ๋Œ€ํ•œ ๋‰ด์Šค ๐Ÿš€ +* ๊ฐ€์ด๋“œ ๐Ÿ“ +* ๊ธฐ๋Šฅ โœจ +* ํš๊ธฐ์ ์ธ ๋ณ€ํ™” ๐Ÿšจ +* ํŒ๊ณผ ์š”๋ น โœ… - ## ํŠธ์œ„ํ„ฐ์—์„œ FastAPI ํŒ”๋กœ์šฐํ•˜๊ธฐ +## ํŠธ์œ„ํ„ฐ์—์„œ FastAPI ํŒ”๋กœ์šฐํ•˜๊ธฐ - [Follow @fastapi on **Twitter**](https://twitter.com/fastapi) ๋ฅผ ํŒ”๋กœ์šฐํ•˜์—ฌ **FastAPI** ์— ๋Œ€ํ•œ ์ตœ์‹  ๋‰ด์Šค๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿฆ +**Twitter**์˜ @fastapi๋ฅผ ํŒ”๋กœ์šฐํ•˜์—ฌ **FastAPI** ์— ๋Œ€ํ•œ ์ตœ์‹  ๋‰ด์Šค๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿฆ - ## Star **FastAPI** in GitHub +## Star **FastAPI** in GitHub - GitHub์—์„œ FastAPI์— "star"๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ์˜ star ๋ฒ„ํŠผ์„ ํด๋ฆญ): https://github.com/fastapi/fastapi. โญ๏ธ +GitHub์—์„œ FastAPI์— "star"๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ์˜ star ๋ฒ„ํŠผ์„ ํด๋ฆญ): https://github.com/fastapi/fastapi. โญ๏ธ - ์Šคํƒ€๋ฅผ ๋Š˜๋ฆผ์œผ๋กœ์จ, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋“ค์ด ์ข€ ๋” ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ๊ณ , ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์œ ์šฉํ•œ ๊ฒƒ์ž„์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์Šคํƒ€๋ฅผ ๋Š˜๋ฆผ์œผ๋กœ์จ, ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋“ค์ด ์ข€ ๋” ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ๊ณ , ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์œ ์šฉํ•œ ๊ฒƒ์ž„์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ## GitHub ์ €์žฅ์†Œ์—์„œ ๋ฆด๋ฆฌ์ฆˆ ํ™•์ธ +## GitHub ์ €์žฅ์†Œ์—์„œ ๋ฆด๋ฆฌ์ฆˆ ํ™•์ธ - GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ watch ๋ฒ„ํŠผ์„ ํด๋ฆญ): https://github.com/fastapi/fastapi. ๐Ÿ‘€ +GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ watch ๋ฒ„ํŠผ์„ ํด๋ฆญ): https://github.com/fastapi/fastapi. ๐Ÿ‘€ - ์—ฌ๊ธฐ์„œ "Releases only"์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์—ฌ๊ธฐ์„œ "Releases only"์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ์ด๋ ‡๊ฒŒํ•˜๋ฉด, **FastAPI** ์˜ ๋ฒ„๊ทธ ์ˆ˜์ • ๋ฐ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„ ๋“ฑ์˜ ์ƒˆ๋กœ์šด ์ž๋ฃŒ (์ตœ์‹  ๋ฒ„์ „)์ด ์žˆ์„ ๋•Œ๋งˆ๋‹ค (์ด๋ฉ”์ผ) ํ†ต์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +์ด๋ ‡๊ฒŒํ•˜๋ฉด, **FastAPI** ์˜ ๋ฒ„๊ทธ ์ˆ˜์ • ๋ฐ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„ ๋“ฑ์˜ ์ƒˆ๋กœ์šด ์ž๋ฃŒ (์ตœ์‹  ๋ฒ„์ „)์ด ์žˆ์„ ๋•Œ๋งˆ๋‹ค (์ด๋ฉ”์ผ) ํ†ต์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ## ๊ฐœ๋ฐœ์ž์™€์˜ ์—ฐ๊ฒฐ +## ๊ฐœ๋ฐœ์ž์™€์˜ ์—ฐ๊ฒฐ - ๊ฐœ๋ฐœ์ž์ธ [me (Sebastiรกn Ramรญrez / `tiangolo`)](https://tiangolo.com/) ์™€ ์—ฐ๋ฝ์„ ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +๊ฐœ๋ฐœ์ž(Sebastiรกn Ramรญrez / `tiangolo`)์™€ ์—ฐ๋ฝ์„ ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ์—ฌ๋Ÿฌ๋ถ„์€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: +์—ฌ๋Ÿฌ๋ถ„์€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - - [**GitHub**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ](https://github.com/tiangolo). - - ๋‹น์‹ ์—๊ฒŒ ๋„์›€์ด ๋  ์ €์˜ ๋‹ค๋ฅธ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. - - ์ƒˆ๋กœ์šด ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ํ™•์ธํ•˜๋ ค๋ฉด ํŒ”๋กœ์šฐ ํ•˜์‹ญ์‹œ์˜ค. +* **GitHub**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ.. + * ๋‹น์‹ ์—๊ฒŒ ๋„์›€์ด ๋  ์ €์˜ ๋‹ค๋ฅธ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. + * ์ƒˆ๋กœ์šด ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ํ™•์ธํ•˜๋ ค๋ฉด ํŒ”๋กœ์šฐ ํ•˜์‹ญ์‹œ์˜ค. +* **Twitter** ๋˜๋Š” Mastodon์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ. + * FastAPI์˜ ์‚ฌ์šฉ ์šฉ๋„๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š” (๊ทธ๊ฒƒ์„ ๋“ฃ๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค). + * ๋ฐœํ‘œ๋‚˜ ์ƒˆ๋กœ์šด ํˆด ์ถœ์‹œ ์†Œ์‹์„ ๋ฐ›์•„๋ณด์‹ญ์‹œ์˜ค. + * **Twitter**์˜ @fastapi๋ฅผ ํŒ”๋กœ์šฐ (๋ณ„๋„ ๊ณ„์ •์—์„œ) ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* **LinkedIn**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ.. + * ์ƒˆ๋กœ์šด ํˆด์˜ ๋ฐœํ‘œ๋‚˜ ์ถœ์‹œ ์†Œ์‹์„ ๋ฐ›์•„๋ณด์‹ญ์‹œ์˜ค. (๋‹จ, Twitter๋ฅผ ๋” ์ž์ฃผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค ๐Ÿคทโ€โ™‚). +* **Dev.to** ๋˜๋Š” **Medium**์—์„œ ์ œ๊ฐ€ ์ž‘์„ฑํ•œ ๋‚ด์šฉ์„ ์ฝ์–ด ๋ณด์‹ญ์‹œ์˜ค (๋˜๋Š” ํŒ”๋กœ์šฐ). + * ๋‹ค๋ฅธ ๊ธฐ์‚ฌ๋‚˜ ์•„์ด๋””์–ด๋“ค์„ ์ฝ๊ณ , ์ œ๊ฐ€ ๋งŒ๋“ค์–ด์™”๋˜ ํˆด์— ๋Œ€ํ•ด์„œ๋„ ์ฝ์œผ์‹ญ์‹œ์˜ค. + * ์ƒˆ๋กœ์šด ๊ธฐ์‚ฌ๋ฅผ ์ฝ๊ธฐ ์œ„ํ•ด ํŒ”๋กœ์šฐ ํ•˜์‹ญ์‹œ์˜ค. - - [**Twitter**์—์„œ ํŒ”๋กœ์šฐํ•˜๊ธฐ](https://twitter.com/tiangolo). - - FastAPI์˜ ์‚ฌ์šฉ ์šฉ๋„๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š” (๊ทธ๊ฒƒ์„ ๋“ฃ๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค). - - ๋ฐœํ‘œ ๋˜๋Š” ์ƒˆ๋กœ์šด ํˆด ์ถœ์‹œํ•  ๋•Œ ๋“ค์œผ์‹ญ์‹œ์˜ค. - - [follow @fastapi on Twitter](https://twitter.com/fastapi) (๋ณ„๋„ ๊ณ„์ •์—์„œ) ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +## **FastAPI**์— ๋Œ€ํ•œ ํŠธ์œ— - - [**Linkedin**์—์„œ์˜ ์—ฐ๊ฒฐ](https://www.linkedin.com/in/tiangolo/). - - ์ƒˆ๋กœ์šด ํˆด์˜ ๋ฐœํ‘œ๋‚˜ ๋ฆด๋ฆฌ์Šค๋ฅผ ๋“ค์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๋‹จ, Twitter๋ฅผ ๋” ์ž์ฃผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค ๐Ÿคทโ€โ™‚). +**FastAPI**์— ๋Œ€ํ•ด ํŠธ์œ— ํ•˜๊ณ  FastAPI๊ฐ€ ๋งˆ์Œ์— ๋“œ๋Š” ์ด์œ ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”. ๐ŸŽ‰ - - [**Dev.to**](https://dev.to/tiangolo) ๋˜๋Š” [**Medium**](https://medium.com/@tiangolo)์—์„œ ์ œ๊ฐ€ ์ž‘์„ฑํ•œ ๋‚ด์šฉ์„ ์ฝ์–ด ๋ณด์‹ญ์‹œ์˜ค(๋˜๋Š” ํŒ”๋กœ์šฐ). - - ๋‹ค๋ฅธ ๊ธฐ์‚ฌ๋‚˜ ์•„์ด๋””์–ด๋“ค์„ ์ฝ๊ณ , ์ œ๊ฐ€ ๋งŒ๋“ค์–ด์™”๋˜ ํˆด์— ๋Œ€ํ•ด์„œ๋„ ์ฝ์œผ์‹ญ์‹œ์˜ค. - - ์ƒˆ๋กœ์šด ๊ธฐ์‚ฌ๋ฅผ ์ฝ๊ธฐ ์œ„ํ•ด ํŒ”๋กœ์šฐ ํ•˜์‹ญ์‹œ์˜ค. +**FastAPI**๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”์ง€, ์–ด๋–ค ์ ์ด ๋งˆ์Œ์— ๋“ค์—ˆ๋Š”์ง€, ์–ด๋–ค ํ”„๋กœ์ ํŠธ/ํšŒ์‚ฌ์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ๋“ฑ์— ๋Œ€ํ•ด ๋“ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. - ## **FastAPI**์— ๋Œ€ํ•œ ํŠธ์œ— +## FastAPI์— ํˆฌํ‘œํ•˜๊ธฐ - [**FastAPI**์— ๋Œ€ํ•ด ํŠธ์œ—](https://twitter.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi) ํ•˜๊ณ  FastAPI๊ฐ€ ๋งˆ์Œ์— ๋“œ๋Š” ์ด์œ ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”. ๐ŸŽ‰ +* Slant์—์„œ **FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค. +* AlternativeTo์—์„œ **FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค. +* StackShare์—์„œ **FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค. - **FastAPI**๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”์ง€, ์–ด๋–ค ์ ์ด ๋งˆ์Œ์— ๋“ค์—ˆ๋Š”์ง€, ์–ด๋–ค ํ”„๋กœ์ ํŠธ/ํšŒ์‚ฌ์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€ ๋“ฑ์— ๋Œ€ํ•ด ๋“ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. +## GitHub์˜ ์ด์Šˆ๋กœ ๋‹ค๋ฅธ์‚ฌ๋žŒ ๋•๊ธฐ - ## FastAPI์— ํˆฌํ‘œํ•˜๊ธฐ +๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: - - [Slant์—์„œ **FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค](https://www.slant.co/options/34241/~fastapi-review). - - [AlternativeTo**FastAPI** ์— ๋Œ€ํ•ด ํˆฌํ‘œํ•˜์‹ญ์‹œ์˜ค](https://alternativeto.net/software/fastapi/). +* GitHub ๋””์Šค์ปค์…˜ +* GitHub ์ด์Šˆ - ## GitHub์˜ ์ด์Šˆ๋กœ ๋‹ค๋ฅธ์‚ฌ๋žŒ ๋•๊ธฐ +๋งŽ์€ ๊ฒฝ์šฐ, ์—ฌ๋Ÿฌ๋ถ„์€ ์ด๋ฏธ ๊ทธ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต์„ ์•Œ๊ณ  ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ - [์กด์žฌํ•˜๋Š” ์ด์Šˆ](https://github.com/fastapi/fastapi/issues)๋ฅผ ํ™•์ธํ•˜๊ณ  ๊ทธ๊ฒƒ์„ ์‹œ๋„ํ•˜๊ณ  ๋„์™€์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์ด๋ฏธ ๋‹ต์„ ์•Œ๊ณ  ์žˆ๋Š” ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค. ๐Ÿค“ +๋งŒ์•ฝ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์˜ ๋ฌธ์ œ๋ฅผ ๋„์™€์ค€๋‹ค๋ฉด, ๊ณต์‹์ ์ธ [FastAPI ์ „๋ฌธ๊ฐ€](fastapi-people.md#fastapi-experts){.internal-link target=\_blank} ๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐ŸŽ‰ - ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์˜ ๋ฌธ์ œ๋ฅผ ๋„์™€์ค€๋‹ค๋ฉด, ๊ณต์‹์ ์ธ [FastAPI ์ „๋ฌธ๊ฐ€](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค{.internal-link target=_blank}. ๐ŸŽ‰ +๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ ์€: ์นœ์ ˆํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค์€ ์ขŒ์ ˆ๊ฐ์„ ์•ˆ๊ณ  ์˜ค๋ฉฐ, ๋งŽ์€ ๊ฒฝ์šฐ ์ตœ์„ ์˜ ๋ฐฉ์‹์œผ๋กœ ์งˆ๋ฌธํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ตœ๋Œ€ํ•œ ์นœ์ ˆํ•˜๊ฒŒ ๋Œ€ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•˜์„ธ์š”. ๐Ÿค— - ## GitHub ์ €์žฅ์†Œ ๋ณด๊ธฐ +**FastAPI** ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ๋ชฉํ‘œ๋Š” ์นœ์ ˆํ•˜๊ณ  ํ™˜์˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋™์‹œ์—, ๊ดด๋กญํž˜์ด๋‚˜ ๋ฌด๋ก€ํ•œ ํ–‰๋™์„ ๋ฐ›์•„๋“ค์ด์ง€ ๋งˆ์„ธ์š”. ์šฐ๋ฆฌ๋Š” ์„œ๋กœ๋ฅผ ๋Œ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. - GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ watch ๋ฒ„ํŠผ์„ ํด๋ฆญ): https://github.com/fastapi/fastapi. ๐Ÿ‘€ +--- - "Releases only" ๋Œ€์‹  "Watching"์„ ์„ ํƒํ•˜๋ฉด ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ์šด issue๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์•Œ๋ฆผ์ด ์ˆ˜์‹ ๋ฉ๋‹ˆ๋‹ค. +๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ (๋””์Šค์ปค์…˜ ๋˜๋Š” ์ด์Šˆ์—์„œ) ํ•ด๊ฒฐ์„ ๋„์šธ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. - ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด๋Ÿฐ issues๋ฅผ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +### ์งˆ๋ฌธ ์ดํ•ดํ•˜๊ธฐ - ## ์ด์Šˆ ์ƒ์„ฑํ•˜๊ธฐ +* ์งˆ๋ฌธํ•˜๋Š” ์‚ฌ๋žŒ์ด ๊ฐ€์ง„ **๋ชฉ์ **๊ณผ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. - GitHub ์ €์žฅ์†Œ์— [์ƒˆ๋กœ์šด ์ด์Šˆ ์ƒ์„ฑ](https://github.com/fastapi/fastapi/issues/new/choose) ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, ์˜ˆ๋ฅผ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: +* ์งˆ๋ฌธ (๋Œ€๋ถ€๋ถ„์€ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค)์ด **๋ช…ํ™•**ํ•œ์ง€ ํ™•์ธํ•˜์„ธ์š”. - - **์งˆ๋ฌธ**์„ ํ•˜๊ฑฐ๋‚˜ **๋ฌธ์ œ**์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•ฉ๋‹ˆ๋‹ค. - - ์ƒˆ๋กœ์šด **๊ธฐ๋Šฅ**์„ ์ œ์•ˆ ํ•ฉ๋‹ˆ๋‹ค. +* ๋งŽ์€ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์ •ํ•œ ํ•ด๊ฒฐ์ฑ…์— ๋Œ€ํ•œ ์งˆ๋ฌธ์„ ํ•˜์ง€๋งŒ, ๋” **์ข‹์€** ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ์™€ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋” ์ž˜ ์ดํ•ดํ•˜๋ฉด ๋” ๋‚˜์€ **๋Œ€์•ˆ์ ์ธ ํ•ด๊ฒฐ์ฑ…**์„ ์ œ์•ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - **์ฐธ๊ณ **: ๋งŒ์•ฝ ์ด์Šˆ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค๋ฉด, ์ €๋Š” ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์„ ๋„์™€๋‹ฌ๋ผ๊ณ  ๋ถ€ํƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜‰ +* ์งˆ๋ฌธ์„ ์ดํ•ดํ•  ์ˆ˜ ์—†๋‹ค๋ฉด, ๋” **์ž์„ธํ•œ ์ •๋ณด**๋ฅผ ์š”์ฒญํ•˜์„ธ์š”. - ## Pull Request๋ฅผ ๋งŒ๋“œ์‹ญ์‹œ์˜ค +### ๋ฌธ์ œ ์žฌํ˜„ํ•˜๊ธฐ - Pull Requests๋ฅผ ์ด์šฉํ•˜์—ฌ ์†Œ์Šค์ฝ”๋“œ์— [์ปจํŠธ๋ฆฌ๋ทฐํŠธ](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/contributing.md){.internal-link target=_blank} ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: +๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ, ์งˆ๋ฌธ์€ ์งˆ๋ฌธ์ž์˜ **์›๋ณธ ์ฝ”๋“œ**์™€ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. - - ๋ฌธ์„œ์—์„œ ์ฐพ์€ ์˜คํƒ€๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ. +๋งŽ์€ ๊ฒฝ์šฐ, ์ฝ”๋“œ์˜ ์ผ๋ถ€๋งŒ ๋ณต์‚ฌํ•ด์„œ ์˜ฌ๋ฆฌ์ง€๋งŒ, ๊ทธ๊ฒƒ๋งŒ์œผ๋กœ๋Š” **๋ฌธ์ œ๋ฅผ ์žฌํ˜„**ํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. - - FastAPI๋ฅผ [ํŽธ์ง‘ํ•˜์—ฌ](https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml) ์ž‘์„ฑํ–ˆ๊ฑฐ๋‚˜ ์ฐพ์€ ๋ฌธ์„œ, ๋น„๋””์˜ค ๋˜๋Š” ํŒŸ์บ์ŠคํŠธ๋ฅผ ๊ณต์œ ํ•  ๋•Œ. +* ์งˆ๋ฌธ์ž์—๊ฒŒ ์ตœ์†Œํ•œ์˜ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ์˜ˆ์ œ๋ฅผ ์ œ๊ณตํ•ด๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ **๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ**ํ•˜์—ฌ ์ง์ ‘ ์‹คํ–‰ํ•˜๊ณ , ๋™์ผํ•œ ์˜ค๋ฅ˜๋‚˜ ๋™์ž‘์„ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋” ์ž˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - - ํ•ด๋‹น ์„น์…˜์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. +* ๋„ˆ๊ทธ๋Ÿฌ์šด ๋งˆ์Œ์ด ๋“ ๋‹ค๋ฉด, ๋ฌธ์ œ ์„ค๋ช…๋งŒ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ง์ ‘ **์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด**๋ณผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ด๋Š” ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋จผ์ € ์งˆ๋ฌธ์„ ๋ช…ํ™•ํžˆ ํ•ด๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. - - ๋‹น์‹ ์˜ ์–ธ์–ด๋กœ [๋ฌธ์„œ ๋ฒˆ์—ญํ•˜๋Š”๋ฐ](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/contributing.md#translations){.internal-link target=_blank} ๊ธฐ์—ฌํ•  ๋•Œ. +### ํ•ด๊ฒฐ์ฑ… ์ œ์•ˆํ•˜๊ธฐ - - ๋˜ํ•œ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“  ๋ฒˆ์—ญ์„ ๊ฒ€ํ† ํ•˜๋Š”๋ฐ ๋„์›€์„ ์ค„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +* ์งˆ๋ฌธ์„ ์ถฉ๋ถ„ํžˆ ์ดํ•ดํ•œ ํ›„์—๋Š” ๊ฐ€๋Šฅํ•œ **๋‹ต๋ณ€**์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - - ์ƒˆ๋กœ์šด ๋ฌธ์„œ์˜ ์„น์…˜์„ ์ œ์•ˆํ•  ๋•Œ. +* ๋งŽ์€ ๊ฒฝ์šฐ, ์งˆ๋ฌธ์ž์˜ **๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋‚˜ ์‚ฌ์šฉ ์‚ฌ๋ก€**๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋“ค์ด ์‹œ๋„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋ณด๋‹ค ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. - - ๊ธฐ์กด ๋ฌธ์ œ/๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ. +### ํ•ด๊ฒฐ ์š”์ฒญํ•˜๊ธฐ - - ์ƒˆ๋กœ์šด feature๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ. +์งˆ๋ฌธ์ž๊ฐ€ ๋‹ต๋ณ€์„ ํ™•์ธํ•˜๊ณ  ๋‚˜๋ฉด, ๋‹น์‹ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค, **๋‹น์‹ ์€ ์˜์›…์ž…๋‹ˆ๋‹ค**! ๐Ÿฆธ - ## ์ฑ„ํŒ…์— ์ฐธ์—ฌํ•˜์‹ญ์‹œ์˜ค +* ์ด์ œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค๋ฉด, ์งˆ๋ฌธ์ž์—๊ฒŒ ๋‹ค์Œ์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ๐Ÿ‘ฅ [๋””์Šค์ฝ”๋“œ ์ฑ„ํŒ… ์„œ๋ฒ„](https://discord.gg/VQjSZaeJmf) ๐Ÿ‘ฅ ์— ๊ฐ€์ž…ํ•˜๊ณ  FastAPI ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ์–ด์šธ๋ฆฌ์„ธ์š”. + * GitHub ๋””์Šค์ปค์…˜์—์„œ: ๋Œ“๊ธ€์„ **๋‹ต๋ณ€**์œผ๋กœ ํ‘œ์‹œํ•˜๋„๋ก ์š”์ฒญํ•˜์„ธ์š”. + * GitHub ์ด์Šˆ์—์„œ: ์ด์Šˆ๋ฅผ **๋‹ซ์•„๋‹ฌ๋ผ๊ณ ** ์š”์ฒญํ•˜์„ธ์š”. - /// tip +## GitHub ์ €์žฅ์†Œ ๋ณด๊ธฐ - ์งˆ๋ฌธ์ด ์žˆ๋Š” ๊ฒฝ์šฐ, [GitHub ์ด์Šˆ ](https://github.com/fastapi/fastapi/issues/new/choose) ์—์„œ ์งˆ๋ฌธํ•˜์‹ญ์‹œ์˜ค, [FastAPI ์ „๋ฌธ๊ฐ€](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) ์˜ ๋„์›€์„ ๋ฐ›์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค{.internal-link target=_blank} . +GitHub์—์„œ FastAPI๋ฅผ "watch"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ watch ๋ฒ„ํŠผ์„ ํด๋ฆญ): https://github.com/fastapi/fastapi. ๐Ÿ‘€ - /// +"Releases only" ๋Œ€์‹  "Watching"์„ ์„ ํƒํ•˜๋ฉด, ์ƒˆ๋กœ์šด ์ด์Šˆ๋‚˜ ์งˆ๋ฌธ์ด ์ƒ์„ฑ๋  ๋•Œ ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ํŠน์ •ํ•˜๊ฒŒ ์ƒˆ๋กœ์šด ์ด์Šˆ, ๋””์Šค์ปค์…˜, PR ๋“ฑ๋งŒ ์•Œ๋ฆผ ๋ฐ›๋„๋ก ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. - ``` - ๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ ๋Œ€ํ™”์—์„œ๋งŒ ์ฑ„ํŒ…์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. - ``` +๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด๋Ÿฐ ์ด์Šˆ๋“ค์„ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ๊ธฐ์กด [์ง€ํ„ฐ ์ฑ„ํŒ…](https://gitter.im/fastapi/fastapi) ์ด ์žˆ์ง€๋งŒ ์ฑ„๋„๊ณผ ๊ณ ๊ธ‰๊ธฐ๋Šฅ์ด ์—†์–ด์„œ ๋Œ€ํ™”๋ฅผ ํ•˜๊ธฐ๊ฐ€ ์กฐ๊ธˆ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ์ง€๊ธˆ์€ ๋””์Šค์ฝ”๋“œ๊ฐ€ ๊ถŒ์žฅ๋˜๋Š” ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. +## ์ด์Šˆ ์ƒ์„ฑํ•˜๊ธฐ - ### ์งˆ๋ฌธ์„ ์œ„ํ•ด ์ฑ„ํŒ…์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค +GitHub ์ €์žฅ์†Œ์— ์ƒˆ๋กœ์šด ์ด์Šˆ ์ƒ์„ฑ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, ์˜ˆ๋ฅผ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: - ์ฑ„ํŒ…์€ ๋” ๋งŽ์€ "์ž์œ ๋กœ์šด ๋Œ€ํ™”"๋ฅผ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋„ˆ๋ฌด ์ผ๋ฐ˜์ ์ธ ์งˆ๋ฌธ์ด๋‚˜ ๋Œ€๋‹ตํ•˜๊ธฐ ์–ด๋ ค์šด ์งˆ๋ฌธ์„ ์‰ฝ๊ฒŒ ์งˆ๋ฌธ์„ ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋‹ต๋ณ€์„ ๋ฐ›์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* **์งˆ๋ฌธ**์„ ํ•˜๊ฑฐ๋‚˜ **๋ฌธ์ œ**์— ๋Œ€ํ•ด ์งˆ๋ฌธํ•ฉ๋‹ˆ๋‹ค. +* ์ƒˆ๋กœ์šด **๊ธฐ๋Šฅ**์„ ์ œ์•ˆ ํ•ฉ๋‹ˆ๋‹ค. - GitHub ์ด์Šˆ์—์„œ์˜ ํ…œํ”Œ๋ฆฟ์€ ์˜ฌ๋ฐ”๋ฅธ ์งˆ๋ฌธ์„ ์ž‘์„ฑํ•˜๋„๋ก ์•ˆ๋‚ดํ•˜์—ฌ ๋” ์‰ฝ๊ฒŒ ์ข‹์€ ๋‹ต๋ณ€์„ ์–ป๊ฑฐ๋‚˜ ์งˆ๋ฌธํ•˜๊ธฐ ์ „์— ์Šค์Šค๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub์—์„œ๋Š” ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ๊ฑธ๋ฆฌ๋”๋ผ๋„ ํ•ญ์ƒ ๋ชจ๋“  ๊ฒƒ์— ๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ๋Š” ๊ฐœ์ธ์ ์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… +**์ฐธ๊ณ **: ๋งŒ์•ฝ ์ด์Šˆ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค๋ฉด, ์ €๋Š” ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์„ ๋„์™€๋‹ฌ๋ผ๊ณ  ๋ถ€ํƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ˜‰ - ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ์˜ ๋Œ€ํ™” ๋˜ํ•œ GitHub์—์„œ ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€ํ™” ์ค‘์— ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์ด ์†์‹ค๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub ์ด์Šˆ์— ์žˆ๋Š” ๊ฒƒ๋งŒ [FastAPI ์ „๋ฌธ๊ฐ€](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts)๊ฐ€ ๋˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋˜๋ฏ€๋กœ{.internal-link target=_blank} , GitHub ์ด์Šˆ์—์„œ ๋” ๋งŽ์€ ๊ด€์‹ฌ์„ ๋ฐ›์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +## Pull Requests ๋ฆฌ๋ทฐํ•˜๊ธฐ - ๋ฐ˜๋ฉด, ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—๋Š” ์ˆ˜์ฒœ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ฑฐ์˜ ํ•ญ์ƒ ๋Œ€ํ™” ์ƒ๋Œ€๋ฅผ ์ฐพ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ๐Ÿ˜„ +๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ pull request๋ฅผ ๋ฆฌ๋ทฐํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ## ๊ฐœ๋ฐœ์ž ์Šคํฐ์„œ๊ฐ€ ๋˜์‹ญ์‹œ์˜ค +๋‹ค์‹œ ํ•œ๋ฒˆ ๋งํ•˜์ง€๋งŒ, ์ตœ๋Œ€ํ•œ ์นœ์ ˆํ•˜๊ฒŒ ๋ฆฌ๋ทฐํ•ด ์ฃผ์„ธ์š”. ๐Ÿค— - [GitHub ์Šคํฐ์„œ](https://github.com/sponsors/tiangolo) ๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž๋ฅผ ๊ฒฝ์ œ์ ์œผ๋กœ ์ง€์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +--- - ๊ฐ์‚ฌํ•˜๋‹ค๋Š” ๋ง๋กœ ์ปคํ”ผ๋ฅผ โ˜•๏ธ ํ•œ์ž” ์‚ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜„ +Pull Rrquest๋ฅผ ๋ฆฌ๋ทฐํ•  ๋•Œ ๊ณ ๋ คํ•ด์•ผ ํ•  ์‚ฌํ•ญ๊ณผ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: - ๋˜ํ•œ FastAPI์˜ ์‹ค๋ฒ„ ๋˜๋Š” ๊ณจ๋“œ ์Šคํฐ์„œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ…๐ŸŽ‰ +### ๋ฌธ์ œ ์ดํ•ดํ•˜๊ธฐ - ## FastAPI๋ฅผ ๊ฐ•ํ™”ํ•˜๋Š” ๋„๊ตฌ์˜ ์Šคํฐ์„œ๊ฐ€ ๋˜์‹ญ์‹œ์˜ค +* ๋จผ์ €, ํ•ด๋‹น pull request๊ฐ€ ํ•ด๊ฒฐํ•˜๋ ค๋Š” **๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•˜๋Š”์ง€** ํ™•์ธํ•˜์„ธ์š”. GitHub ๋””์Šค์ปค์…˜ ๋˜๋Š” ์ด์Šˆ์—์„œ ๋” ๊ธด ๋…ผ์˜๊ฐ€ ์žˆ์—ˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. - ๋ฌธ์„œ์—์„œ ๋ณด์•˜๋“ฏ์ด, FastAPI๋Š” Starlette๊ณผ Pydantic ๋ผ๋Š” ๊ฑฐ์ธ์˜ ์–ด๊นจ์— ํƒ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +* Pull request๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. **๋‹ค๋ฅธ ๋ฐฉ์‹**์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๊ทธ ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•˜๊ฑฐ๋‚˜ ์งˆ๋ฌธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ๋‹ค์Œ์˜ ์Šคํฐ์„œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค +### ์Šคํƒ€์ผ์— ๋„ˆ๋ฌด ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š๊ธฐ - - [Samuel Colvin (Pydantic)](https://github.com/sponsors/samuelcolvin) - - [Encode (Starlette, Uvicorn)](https://github.com/sponsors/encode) +* ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์Šคํƒ€์ผ ๊ฐ™์€ ๊ฒƒ์— ๋„ˆ๋ฌด ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. ์ €๋Š” ์ง์ ‘ ์ปค๋ฐ‹์„ ์ˆ˜์ •ํ•˜์—ฌ squash and merge๋ฅผ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. - ------ +* ์ฝ”๋“œ ์Šคํƒ€์ผ ๊ทœ์น™๋„ ๊ฑฑ์ •ํ•  ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ ์ž๋™ํ™”๋œ ๋„๊ตฌ๋“ค์ด ์ด๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๐Ÿš€ +์Šคํƒ€์ผ์ด๋‚˜ ์ผ๊ด€์„ฑ ๊ด€๋ จ ์š”์ฒญ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์ œ๊ฐ€ ์ง์ ‘ ์š”์ฒญํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ถ”๊ฐ€ ์ปค๋ฐ‹์œผ๋กœ ์ˆ˜์ •ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +### ์ฝ”๋“œ ํ™•์ธํ•˜๊ธฐ + +* ์ฝ”๋“œ๋ฅผ ์ฝ๊ณ , **๋…ผ๋ฆฌ์ ์œผ๋กœ ํƒ€๋‹น**ํ•œ์ง€ ํ™•์ธํ•œ ํ›„ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜์—ฌ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. + +* ๊ทธ๋Ÿฐ ๋‹ค์Œ, ํ™•์ธํ–ˆ๋‹ค๊ณ  **๋Œ“๊ธ€**์„ ๋‚จ๊ฒจ ์ฃผ์„ธ์š”. ๊ทธ๋ž˜์•ผ ์ œ๊ฐ€ ๊ฒ€ํ† ํ–ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// info + +๋ถˆํ–‰ํžˆ๋„, ์ œ๊ฐ€ ๋‹จ์ˆœํžˆ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์Šน์ธ๋งŒ์œผ๋กœ PR์„ ์‹ ๋ขฐํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. + +3๊ฐœ, 5๊ฐœ ์ด์ƒ์˜ ์Šน์ธ์ด ๋‹ฌ๋ฆฐ PR์ด ์‹ค์ œ๋กœ๋Š” ๊นจ์ ธ ์žˆ๊ฑฐ๋‚˜, ๋ฒ„๊ทธ๊ฐ€ ์žˆ๊ฑฐ๋‚˜, ์ฃผ์žฅํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… + +๋”ฐ๋ผ์„œ, ์ •๋ง๋กœ ์ฝ”๋“œ๋ฅผ ์ฝ๊ณ  ์‹คํ–‰ํ•œ ๋’ค, ๋Œ“๊ธ€๋กœ ํ™•์ธ ๋‚ด์šฉ์„ ๋‚จ๊ฒจ ์ฃผ๋Š” ๊ฒƒ์ด ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค“ + +/// + +* PR์„ ๋” ๋‹จ์ˆœํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋„ˆ๋ฌด ๊นŒ๋‹ค๋กœ์šธ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ฃผ๊ด€์ ์ธ ๊ฒฌํ•ด๊ฐ€ ๋งŽ์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค (๊ทธ๋ฆฌ๊ณ  ์ €๋„ ์ œ ๊ฒฌํ•ด๊ฐ€ ์žˆ์„ ๊ฑฐ์˜ˆ์š” ๐Ÿ™ˆ). ๋”ฐ๋ผ์„œ ํ•ต์‹ฌ์ ์ธ ๋ถ€๋ถ„์— ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. + +### ํ…Œ์ŠคํŠธ + +* PR์— **ํ…Œ์ŠคํŠธ**๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ฃผ์„ธ์š”. + +* PR์„ ์ ์šฉํ•˜๊ธฐ ์ „์— ํ…Œ์ŠคํŠธ๊ฐ€ **์‹คํŒจ**ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ๐Ÿšจ + +* PR์„ ์ ์šฉํ•œ ํ›„ ํ…Œ์ŠคํŠธ๊ฐ€ **ํ†ต๊ณผ**ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. โœ… + +* ๋งŽ์€ PR์—๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก **์ƒ๊ธฐ**์‹œ์ผœ์ค„ ์ˆ˜๋„ ์žˆ๊ณ , ์ง์ ‘ ํ…Œ์ŠคํŠธ๋ฅผ **์ œ์•ˆ**ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‹œ๊ฐ„์ด ๋งŽ์ด ์†Œ์š”๋˜๋Š” ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ๊ทธ ๋ถ€๋ถ„์„ ๋งŽ์ด ๋„์™€์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +* ๊ทธ๋ฆฌ๊ณ  ์‹œ๋„ํ•œ ๋‚ด์šฉ์„ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š”. ๊ทธ๋Ÿฌ๋ฉด ์ œ๊ฐ€ ํ™•์ธํ–ˆ๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ + +## Pull Request๋ฅผ ๋งŒ๋“œ์‹ญ์‹œ์˜ค + +Pull Requests๋ฅผ ์ด์šฉํ•˜์—ฌ ์†Œ์Šค์ฝ”๋“œ์— [์ปจํŠธ๋ฆฌ๋ทฐํŠธ](contributing.md){.internal-link target=\_blank} ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* ๋ฌธ์„œ์—์„œ ๋ฐœ๊ฒฌํ•œ ์˜คํƒ€๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ. +* FastAPI ๊ด€๋ จ ๋ฌธ์„œ, ๋น„๋””์˜ค ๋˜๋Š” ํŒŸ์บ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๊ฑฐ๋‚˜ ๋ฐœ๊ฒฌํ•˜์—ฌ ์ด ํŒŒ์ผ์„ ํŽธ์ง‘ํ•˜์—ฌ ๊ณต์œ ํ•  ๋•Œ. + * ํ•ด๋‹น ์„น์…˜์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +* ๋‹น์‹ ์˜ ์–ธ์–ด๋กœ [๋ฌธ์„œ ๋ฒˆ์—ญํ•˜๋Š”๋ฐ](contributing.md#translations){.internal-link target=\_blank} ๊ธฐ์—ฌํ•  ๋•Œ. + * ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์ž‘์„ฑํ•œ ๋ฒˆ์—ญ์„ ๊ฒ€ํ† ํ•˜๋Š” ๊ฒƒ๋„ ๋„์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +* ์ƒˆ๋กœ์šด ๋ฌธ์„œ์˜ ์„น์…˜์„ ์ œ์•ˆํ•  ๋•Œ. +* ๊ธฐ์กด ๋ฌธ์ œ/๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ. + * ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +* ์ƒˆ๋กœ์šด feature๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ. + * ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + * ๊ด€๋ จ ๋ฌธ์„œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +## FastAPI ์œ ์ง€ ๊ด€๋ฆฌ์— ๋„์›€ ์ฃผ๊ธฐ + +**FastAPI**์˜ ์œ ์ง€ ๊ด€๋ฆฌ๋ฅผ ๋„์™€์ฃผ์„ธ์š”! ๐Ÿค“ + +ํ•  ์ผ์ด ๋งŽ๊ณ , ๊ทธ ์ค‘ ๋Œ€๋ถ€๋ถ„์€ **์—ฌ๋Ÿฌ๋ถ„**์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ง€๊ธˆ ํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์š” ์ž‘์—…์€: + +* [GitHub์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์งˆ๋ฌธ์— ๋„์›€ ์ฃผ๊ธฐ](#github_1){.internal-link target=_blank} (์œ„์˜ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”). +* [Pull Request ๋ฆฌ๋ทฐํ•˜๊ธฐ](#pull-requests){.internal-link target=_blank} (์œ„์˜ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”). + +์ด ๋‘ ์ž‘์—…์ด **๊ฐ€์žฅ ๋งŽ์€ ์‹œ๊ฐ„์„ ์†Œ๋ชจ**ํ•˜๋Š” ์ผ์ž…๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด FastAPI ์œ ์ง€ ๊ด€๋ฆฌ์˜ ์ฃผ์š” ์ž‘์—…์ž…๋‹ˆ๋‹ค. + +์ด ์ž‘์—…์„ ๋„์™€์ฃผ์‹ ๋‹ค๋ฉด, **FastAPI ์œ ์ง€ ๊ด€๋ฆฌ์— ๋„์›€์„ ์ฃผ๋Š” ๊ฒƒ**์ด๋ฉฐ ๊ทธ๊ฒƒ์ด **๋” ๋น ๋ฅด๊ณ  ๋” ์ž˜ ๋ฐœ์ „ํ•˜๋Š” ๊ฒƒ**์„ ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿš€ + +## ์ฑ„ํŒ…์— ์ฐธ์—ฌํ•˜์‹ญ์‹œ์˜ค + +๐Ÿ‘ฅ ๋””์Šค์ฝ”๋“œ ์ฑ„ํŒ… ์„œ๋ฒ„ ๐Ÿ‘ฅ ์— ๊ฐ€์ž…ํ•˜๊ณ  FastAPI ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ์–ด์šธ๋ฆฌ์„ธ์š”. + +/// tip + +์งˆ๋ฌธ์ด ์žˆ๋Š” ๊ฒฝ์šฐ, GitHub ๋””์Šค์ปค์…˜ ์—์„œ ์งˆ๋ฌธํ•˜์‹ญ์‹œ์˜ค, [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank} ์˜ ๋„์›€์„ ๋ฐ›์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. + +๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ ๋Œ€ํ™”์—์„œ๋งŒ ์ฑ„ํŒ…์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. + +/// + +### ์งˆ๋ฌธ์„ ์œ„ํ•ด ์ฑ„ํŒ…์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค + +์ฑ„ํŒ…์€ ๋” ๋งŽ์€ "์ž์œ ๋กœ์šด ๋Œ€ํ™”"๋ฅผ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋„ˆ๋ฌด ์ผ๋ฐ˜์ ์ธ ์งˆ๋ฌธ์ด๋‚˜ ๋Œ€๋‹ตํ•˜๊ธฐ ์–ด๋ ค์šด ์งˆ๋ฌธ์„ ์‰ฝ๊ฒŒ ์งˆ๋ฌธ์„ ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋‹ต๋ณ€์„ ๋ฐ›์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +GitHub ์ด์Šˆ์—์„œ์˜ ํ…œํ”Œ๋ฆฟ์€ ์˜ฌ๋ฐ”๋ฅธ ์งˆ๋ฌธ์„ ์ž‘์„ฑํ•˜๋„๋ก ์•ˆ๋‚ดํ•˜์—ฌ ๋” ์‰ฝ๊ฒŒ ์ข‹์€ ๋‹ต๋ณ€์„ ์–ป๊ฑฐ๋‚˜ ์งˆ๋ฌธํ•˜๊ธฐ ์ „์— ์Šค์Šค๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub์—์„œ๋Š” ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ๊ฑธ๋ฆฌ๋”๋ผ๋„ ํ•ญ์ƒ ๋ชจ๋“  ๊ฒƒ์— ๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ๋Š” ๊ฐœ์ธ์ ์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜… + +์ฑ„ํŒ… ์‹œ์Šคํ…œ์—์„œ์˜ ๋Œ€ํ™” ๋˜ํ•œ GitHub์—์„œ ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€ํ™” ์ค‘์— ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์ด ์†์‹ค๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  GitHub ์ด์Šˆ์— ์žˆ๋Š” ๊ฒƒ๋งŒ [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}๊ฐ€ ๋˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋˜๋ฏ€๋กœ, GitHub ์ด์Šˆ์—์„œ ๋” ๋งŽ์€ ๊ด€์‹ฌ์„ ๋ฐ›์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋ฐ˜๋ฉด, ์ฑ„ํŒ… ์‹œ์Šคํ…œ์—๋Š” ์ˆ˜์ฒœ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ฑฐ์˜ ํ•ญ์ƒ ๋Œ€ํ™” ์ƒ๋Œ€๋ฅผ ์ฐพ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ๐Ÿ˜„ + +## ๊ฐœ๋ฐœ์ž ์Šคํฐ์„œ๊ฐ€ ๋˜์‹ญ์‹œ์˜ค + +GitHub ์Šคํฐ์„œ ๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž๋ฅผ ๊ฒฝ์ œ์ ์œผ๋กœ ์ง€์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ฐ์‚ฌํ•˜๋‹ค๋Š” ๋ง๋กœ ์ปคํ”ผ๋ฅผ โ˜•๏ธ ํ•œ์ž” ์‚ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜„ + +๋˜ํ•œ FastAPI์˜ ์‹ค๋ฒ„ ๋˜๋Š” ๊ณจ๋“œ ์Šคํฐ์„œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ…๐ŸŽ‰ + +## FastAPI๋ฅผ ๊ฐ•ํ™”ํ•˜๋Š” ๋„๊ตฌ์˜ ์Šคํฐ์„œ๊ฐ€ ๋˜์‹ญ์‹œ์˜ค + +๋ฌธ์„œ์—์„œ ๋ณด์•˜๋“ฏ์ด, FastAPI๋Š” Starlette๊ณผ Pydantic ๋ผ๋Š” ๊ฑฐ์ธ์˜ ์–ด๊นจ์— ํƒ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +๋‹ค์Œ์˜ ์Šคํฐ์„œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค + +* Samuel Colvin (Pydantic) +* Encode (Starlette, Uvicorn) + +--- + +๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๐Ÿš€ diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 000000000..ff174937d --- /dev/null +++ b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,275 @@ +# yield๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ + +FastAPI๋Š” ์ž‘์—… ์™„๋ฃŒ ํ›„ ์ถ”๊ฐ€ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ์˜์กด์„ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. + +์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด `return` ๋Œ€์‹  `yield`๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์ถ”๊ฐ€๋กœ ์‹คํ–‰ํ•  ๋‹จ๊ณ„ (์ฝ”๋“œ)๋ฅผ ๊ทธ ๋’ค์— ์ž‘์„ฑํ•˜์„ธ์š”. + +/// tip | ํŒ + +๊ฐ ์˜์กด์„ฑ๋งˆ๋‹ค `yield`๋Š” ํ•œ ๋ฒˆ๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +/// + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +๋‹ค์Œ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ํ•จ์ˆ˜: + +* `@contextlib.contextmanager` ๋˜๋Š” +* `@contextlib.asynccontextmanager` + +๋Š” **FastAPI**์˜ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์‚ฌ์‹ค, FastAPI๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ด ๋‘ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +/// + +## `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์˜์กด์„ฑ + +์˜ˆ๋ฅผ ๋“ค์–ด, ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์ž‘์—…์ด ๋๋‚œ ํ›„์— ์„ธ์…˜์„ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์‘๋‹ต์„ ์ƒ์„ฑํ•˜๊ธฐ ์ „์—๋Š” `yield`๋ฌธ์„ ํฌํ•จํ•˜์—ฌ ๊ทธ ์ด์ „์˜ ์ฝ”๋“œ๋งŒ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} + +yield๋œ ๊ฐ’์€ *๊ฒฝ๋กœ ์ž‘์—…* ๋ฐ ๋‹ค๋ฅธ ์˜์กด์„ฑ๋“ค์— ์ฃผ์ž…๋˜๋Š” ๊ฐ’ ์ž…๋‹ˆ๋‹ค: + +{* ../../docs_src/dependencies/tutorial007.py hl[4] *} + +`yield`๋ฌธ ๋‹ค์Œ์˜ ์ฝ”๋“œ๋Š” ์‘๋‹ต์„ ์ƒ์„ฑํ•œ ํ›„ ๋ณด๋‚ด๊ธฐ ์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} + +/// tip | ํŒ + +`async` ํ•จ์ˆ˜์™€ ์ผ๋ฐ˜ ํ•จ์ˆ˜ ๋ชจ๋‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ์ผ๋ฐ˜ ์˜์กด์„ฑ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ฐ๊ฐ์˜ ํ•จ์ˆ˜๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// + +## `yield`์™€ `try`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ + +`yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ `try` ๋ธ”๋ก์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๋„์ค‘ ๋ฐœ์ƒํ•œ ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ ์˜์กด์„ฑ์ด๋‚˜ *๊ฒฝ๋กœ ์ž‘์—…*์˜ ์ค‘๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŠธ๋žœ์žญ์…˜ "๋กค๋ฐฑ"์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์˜์กด์„ฑ์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ, ์˜์กด์„ฑ ๋‚ด์—์„œ `except SomeException`์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, `finally`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ ๋ฐœ์ƒ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„ ์—†์ด ์ข…๋ฃŒ ๋‹จ๊ณ„๊นŒ ์‹คํ–‰๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} + +## `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•˜์œ„ ์˜์กด์„ฑ + +๋ชจ๋“  ํฌ๊ธฐ์™€ ํ˜•ํƒœ์˜ ํ•˜์œ„ ์˜์กด์„ฑ๊ณผ ํ•˜์œ„ ์˜์กด์„ฑ์˜ "ํŠธ๋ฆฌ"๋„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋“ค ๋ชจ๋‘๊ฐ€ `yield`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**๋Š” `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ ์˜์กด์„ฑ์˜ "์ข…๋ฃŒ ์ฝ”๋“œ"๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, `dependency_c`๋Š” `dependency_b`์— ์˜์กดํ•  ์ˆ˜ ์žˆ๊ณ , `dependency_b`๋Š” `dependency_a`์— ์˜์กดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} + +์ด๋“ค ๋ชจ๋‘๋Š” `yield`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ๊ฒฝ์šฐ `dependency_c`๋Š” ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด, `dependency_b`์˜ ๊ฐ’ (์—ฌ๊ธฐ์„œ๋Š” `dep_b`๋กœ ๋ช…๋ช…)์ด ์—ฌ์ „ํžˆ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ , `dependency_b`๋Š” ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ์œ„ํ•ด `dependency_a`์˜ ๊ฐ’ (์—ฌ๊ธฐ์„œ๋Š” `dep_a`๋กœ ๋ช…๋ช…) ์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} + +๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ๊ณผ `return`์„ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋“ค ์ค‘ ์ผ๋ถ€๊ฐ€ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์— ์˜์กดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ฆฌ๊ณ  `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ ์˜์กด์„ฑ์„ ํ•„์š”๋กœ ํ•˜๋Š” ๋‹จ์ผ ์˜์กด์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์›ํ•˜๋Š” ์˜์กด์„ฑ์„ ์›ํ•˜๋Š” ๋Œ€๋กœ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ๋ชจ๋“  ๊ฒƒ์ด ์˜ฌ๋ฐ”๋ฅธ ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +/// note | ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +ํŒŒ์ด์ฌ์˜ Context Managers ๋•๋ถ„์— ์ด ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. + +**FastAPI**๋Š” ์ด๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. + +/// + +## `yield`์™€ `HTTPException`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ + +`yield`์™€ `try` ๋ธ”๋ก์ด ์žˆ๋Š” ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, `yield` ์ดํ›„์˜ ์ข…๋ฃŒ ์ฝ”๋“œ์—์„œ `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +์ด๋Š” ๋‹ค์†Œ ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ฒฝ๋กœ ์—ฐ์‚ฐ ํ•จ์ˆ˜ ๋“ฑ ๋‚˜๋จธ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋‚ด๋ถ€์—์„œ ์˜ˆ์™ธ (`HTTPException` ํฌํ•จ)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์‹ค์ œ๋กœ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค“ + +/// + +{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} + +์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ (๋˜๋Š” ์ถ”๊ฐ€๋กœ ๋‹ค๋ฅธ `HTTPException`์„ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด) ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์€ [์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. + +## `yield`์™€ `except`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ + +`yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ `except`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜๊ณ  ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ฑฐ๋‚˜ (๋˜๋Š” ์ƒˆ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉด), FastAPI๋Š” ํ•ด๋‹น ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๋ฐ˜์ ์ธ Python ๋ฐฉ์‹๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค: + +{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} + +์ด ๊ฒฝ์šฐ, `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ๋Š” HTTP 500 Internal Server Error ์‘๋‹ต์„ ๋ณด๊ฒŒ ๋˜์ง€๋งŒ, ์„œ๋ฒ„๋Š” ์–ด๋–ค ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ **๋กœ๊ทธ**๋‚˜ ๋‹ค๋ฅธ ํ‘œ์‹œ๋ฅผ ์ „ํ˜€ ๊ฐ€์ง€์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ˜ฑ + +### `yield`์™€ `except`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ํ•ญ์ƒ `raise` ํ•˜๊ธฐ + +`yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์—์„œ ์˜ˆ์™ธ๋ฅผ ์žก์•˜์„ ๋•Œ๋Š” `HTTPException`์ด๋‚˜ ์œ ์‚ฌํ•œ ์˜ˆ์™ธ๋ฅผ ์ƒˆ๋กœ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๋Š” ํ•œ, ๋ฐ˜๋“œ์‹œ ์›๋ž˜์˜ ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +`raise`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผํ•œ ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} + +์ด์ œ ํด๋ผ์ด์–ธํŠธ๋Š” ๋™์ผํ•œ *HTTP 500 Internal Server Error* ์˜ค๋ฅ˜ ์‘๋‹ต์„ ๋ฐ›๊ฒŒ ๋˜์ง€๋งŒ, ์„œ๋ฒ„ ๋กœ๊ทธ์—๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ์ธ `InternalError"๊ฐ€ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. ๐Ÿ˜Ž + +## `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์˜ ์‹คํ–‰ ์ˆœ์„œ + +์‹คํ–‰ ์ˆœ์„œ๋Š” ์•„๋ž˜ ๋‹ค์ด์–ด๊ทธ๋žจ๊ณผ ๊ฑฐ์˜ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ์‹œ๊ฐ„์€ ์œ„์—์„œ ์•„๋ž˜๋กœ ํ๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ ์—ด์€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,operation: Can raise exceptions, including HTTPException + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise Exception + dep -->> handler: Raise Exception + handler -->> client: HTTP error response + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise Exception (e.g. HTTPException) + opt handle + dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception + end + handler -->> client: HTTP error response + end + + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> tasks: Handle exceptions in the background task code + end +``` + +/// info | ์ •๋ณด + +ํด๋ผ์ด์–ธํŠธ์— **ํ•˜๋‚˜์˜ ์‘๋‹ต** ๋งŒ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์˜ค๋ฅ˜ ์‘๋‹ต ์ค‘ ํ•˜๋‚˜์ผ ์ˆ˜๋„ ์žˆ๊ณ ,*๊ฒฝ๋กœ ์ž‘์—…*์—์„œ ์ƒ์„ฑ๋œ ์‘๋‹ต์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Ÿฌํ•œ ์‘๋‹ต ์ค‘ ํ•˜๋‚˜๊ฐ€ ์ „์†ก๋œ ํ›„์—๋Š” ๋‹ค๋ฅธ ์‘๋‹ต์„ ๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +/// + +/// tip | ํŒ + +์ด ๋‹ค์ด์–ด๊ทธ๋žจ์€ `HTTPException`์„ ๋ณด์—ฌ์ฃผ์ง€๋งŒ, `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ์ฒ˜๋ฆฌํ•œ ์˜ˆ์™ธ๋‚˜ [์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•œ ๋‹ค๋ฅธ ์˜ˆ์™ธ๋„ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์–ด๋–ค ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋“ , `HTTPException`์„ ํฌํ•จํ•˜์—ฌ yield๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์œผ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋ฐœ์ƒ์‹œํ‚ค๊ฑฐ๋‚˜ ์ƒˆ๋กœ์šด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +/// + +## `yield`, `HTTPException`, `except` ๋ฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ + +/// warning | ๊ฒฝ๊ณ  + +์ด๋Ÿฌํ•œ ๊ธฐ์ˆ ์  ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ๋Œ€๋ถ€๋ถ„ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด ์„น์…˜์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์•„๋ž˜์—์„œ ๊ณ„์† ์ง„ํ–‰ํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค. + +์ด๋Ÿฌํ•œ ์„ธ๋ถ€ ์ •๋ณด๋Š” ์ฃผ๋กœ FastAPI 0.106.0 ์ด์ „ ๋ฒ„์ „์—์„œ `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ ๊ฒฝ์šฐ๋ฉ” ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. + +/// + +### `yield`์™€ `except`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ, ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +FastAPI 0.110.0 ์ด์ „์—๋Š” `yield`๊ฐ€ ํฌํ•จ๋œ ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•œ ํ›„ ํ•ด๋‹น ์˜์กด์„ฑ์—์„œ `except`๊ฐ€ ํฌํ•จ๋œ ์˜ˆ์™ธ๋ฅผ ์บก์ฒ˜ํ•˜๊ณ  ๋‹ค์‹œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ์ž๋™์œผ๋กœ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ ๋˜๋Š” ๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜ ํ•ธ๋“ค๋Ÿฌ๋กœ ๋ฐœ์ƒ/์ „๋‹ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ์ฒ˜๋ฆฌ๊ธฐ ์—†์ด ์ „๋‹ฌ๋œ ์˜ˆ์™ธ(๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜)์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ๋ฉ”๋ชจ๋ฆฌ ์†Œ๋น„๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์ผ๋ฐ˜ ํŒŒ์ด์ฌ ์ฝ”๋“œ์˜ ๋™์ž‘๊ณผ ์ผ์น˜ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด 0.110.0 ๋ฒ„์ „์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +### ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…๊ณผ `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ, ๊ธฐ์ˆ  ์„ธ๋ถ€์‚ฌํ•ญ + +FastAPI 0.106.0 ์ด์ „์—๋Š” `yield` ์ดํ›„์— ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” ์‘๋‹ต์ด ์ „์†ก๋œ ์ดํ›„์— ์‹คํ–‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, [์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}๊ฐ€ ์ด๋ฏธ ์‹คํ–‰๋œ ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ์ฃผ๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ๋‚ด์—์„œ ์˜์กด์„ฑ์—์„œ "yield๋œ" ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด ์™„๋ฃŒ๋œ ํ›„์— ์‹คํ–‰๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค + +ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฆฌ์†Œ์Šค๋ฅผ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์–‘๋ณดํ•œ ์˜์กด์„ฑ(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ)์—์„œ ๋ณด์œ ํ•˜๋ฉด์„œ ์‘๋‹ต์ด ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ์ด๋™ํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๊ธฐ ๋•Œ๋ฌธ์— FastAPI 0.106.0์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +/// tip | ํŒ + +๋˜ํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์ž์ฒด ๋ฆฌ์†Œ์Šค(์˜ˆ: ์ž์ฒด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๋…๋ฆฝ์ ์ธ ๋กœ์ง ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๋” ๊น”๋”ํ•ด์ง‘๋‹ˆ๋‹ค. + +/// + +๋งŒ์•ฝ ์ด์ „์— ์ด๋Ÿฌํ•œ ๋™์ž‘์— ์˜์กดํ–ˆ๋‹ค๋ฉด, ์ด์ œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ๋‚ด๋ถ€์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์œ„ํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ , `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์˜ ๋ฆฌ์†Œ์Šค์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ๋‚ด๋ถ€์—์„œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์ด ์ƒˆ๋กœ์šด ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ์ฒด๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ง์ ‘ ์ „๋‹ฌํ•˜๋Š” ๋Œ€์‹ , ํ•ด๋‹น ๊ฐ์ฒด์˜ ID๋ฅผ ์ „๋‹ฌํ•œ ๋‹ค์Œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค + +## ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž + +### "์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž"๋ž€? + +"์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž"๋Š” Python์—์„œ `with` ๋ฌธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. + +์˜ˆ๋ฅผ ๋“ค์–ด, `with`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +๋‚ด๋ถ€์ ์œผ๋กœ `open("./somefile.txt")` ๋Š” "์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž(Context Manager)"๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +`with` ๋ธ”๋ก์ด ๋๋‚˜๋ฉด, ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋”๋ผ๋„ ํŒŒ์ผ์„ ๋‹ซ๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +`yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์„ ์ƒ์„ฑํ•˜๋ฉด **FastAPI**๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฅผ ์œ„ํ•œ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค๋ฅธ ๊ด€๋ จ ๋„๊ตฌ๋“ค๊ณผ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค. + +### `yield`๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์—์„œ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž ์‚ฌ์šฉํ•˜๊ธฐ + +/// warning | ๊ฒฝ๊ณ  + +์ด๊ฒƒ์€ ์–ด๋Š ์ •๋„ "๊ณ ๊ธ‰" ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. + +**FastAPI**๋ฅผ ์ฒ˜์Œ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ ์ง€๊ธˆ์€ ์ด ๋ถ€๋ถ„์„ ๊ฑด๋„ˆ๋›ฐ์–ด๋„ ์ข‹์Šต๋‹ˆ๋‹ค. + +/// + +Python์—์„œ๋Š” ๋‹ค์Œ์„ ํ†ตํ•ด ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š” ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค: `__enter__()` and `__exit__()`. + +**FastAPI**์˜ `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ ๋‚ด์—์„œ +`with` ๋˜๋Š” `async with`๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋“ค์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: + +{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *} + +/// tip | ํŒ + +์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ์ž๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +* `@contextlib.contextmanager` ๋˜๋Š” +* `@contextlib.asynccontextmanager` + +์ด๋“ค์€ ๋‹จ์ผ `yield`๊ฐ€ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ๊พธ๋ฏธ๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +์ด๊ฒƒ์ด **FastAPI**๊ฐ€ `yield`๊ฐ€ ์žˆ๋Š” ์˜์กด์„ฑ์„ ์œ„ํ•ด ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ FastAPI ์˜์กด์„ฑ์—๋Š” ์ด๋Ÿฌํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค(๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉํ•ด์„œ๋„ ์•ˆ๋ฉ๋‹ˆ๋‹ค). + +FastAPI๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•ด ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +/// diff --git a/docs/ko/docs/tutorial/security/simple-oauth2.md b/docs/ko/docs/tutorial/security/simple-oauth2.md index ddc7430af..f10c4f588 100644 --- a/docs/ko/docs/tutorial/security/simple-oauth2.md +++ b/docs/ko/docs/tutorial/security/simple-oauth2.md @@ -32,7 +32,7 @@ OAuth2๋Š” (์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”) "ํŒจ์Šค์›Œ๋“œ ํ”Œ๋กœ์šฐ"์„ ์‚ฌ์šฉํ•  * `instagram_basic`์€ ํŽ˜์ด์Šค๋ถ/์ธ์Šคํƒ€๊ทธ๋žจ์—์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. * `https://www.googleapis.com/auth/drive`๋Š” Google์—์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -/// ์ •๋ณด +/// info | ์ •๋ณด OAuth2์—์„œ "๋ฒ”์œ„"๋Š” ํ•„์š”ํ•œ ํŠน์ • ๊ถŒํ•œ์„ ์„ ์–ธํ•˜๋Š” ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. @@ -61,7 +61,7 @@ OAuth2์˜ ๊ฒฝ์šฐ ๋ฌธ์ž์—ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. * `scope`๋Š” ์„ ํƒ์ ์ธ ํ•„๋“œ๋กœ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„๋œ ๋ฌธ์ž์—ด๋กœ ๊ตฌ์„ฑ๋œ ํฐ ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. * `grant_type`(์„ ํƒ์ ์œผ๋กœ ์‚ฌ์šฉ). -/// ํŒ +/// tip | ํŒ OAuth2 ์‚ฌ์–‘์€ ์‹ค์ œ๋กœ `password`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` ํ•„๋“œ๋ฅผ *์š”๊ตฌ*ํ•˜์ง€๋งŒ `OAuth2PasswordRequestForm`์€ ์ด๋ฅผ ๊ฐ•์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. @@ -72,7 +72,7 @@ OAuth2 ์‚ฌ์–‘์€ ์‹ค์ œ๋กœ `password`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` * `client_id`(์„ ํƒ์ ์œผ๋กœ ์‚ฌ์šฉ) (์˜ˆ์ œ์—์„œ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค). * `client_secret`(์„ ํƒ์ ์œผ๋กœ ์‚ฌ์šฉ) (์˜ˆ์ œ์—์„œ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค). -/// ์ •๋ณด +/// info | ์ •๋ณด `OAuth2PasswordRequestForm`์€ `OAuth2PasswordBearer`์™€ ๊ฐ™์ด **FastAPI**์— ๋Œ€ํ•œ ํŠน์ˆ˜ ํด๋ž˜์Šค๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. @@ -86,7 +86,7 @@ OAuth2 ์‚ฌ์–‘์€ ์‹ค์ œ๋กœ `password`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` ### ํผ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ -/// ํŒ +/// tip | ํŒ ์ข…์†์„ฑ ํด๋ž˜์Šค `OAuth2PasswordRequestForm`์˜ ์ธ์Šคํ„ด์Šค์—๋Š” ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„๋œ ๊ธด ๋ฌธ์ž์—ด์ด ์žˆ๋Š” `scope` ์†์„ฑ์ด ์—†๊ณ  ๋Œ€์‹  ์ „์†ก๋œ ๊ฐ ๋ฒ”์œ„์— ๋Œ€ํ•œ ์‹ค์ œ ๋ฌธ์ž์—ด ๋ชฉ๋ก์ด ์žˆ๋Š” `scopes` ์†์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. @@ -126,7 +126,7 @@ OAuth2 ์‚ฌ์–‘์€ ์‹ค์ œ๋กœ `password`๋ผ๋Š” ๊ณ ์ • ๊ฐ’์ด ์žˆ๋Š” `grant_type` ๋”ฐ๋ผ์„œ ํ•ด์ปค๋Š” ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์—์„œ ๋™์ผํ•œ ์•”ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ์‹œ๋„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(๋งŽ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋“  ๊ณณ์—์„œ ๋™์ผํ•œ ์•”ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ด๋Š” ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). -//// tab | PํŒŒ์ด์ฌ 3.7 ์ด์ƒ +//// tab | ํŒŒ์ด์ฌ 3.7 ์ด์ƒ {* ../../docs_src/security/tutorial003.py hl[80:83] *} @@ -150,7 +150,7 @@ UserInDB( ) ``` -/// ์ •๋ณด +/// info | ์ •๋ณด `**user_dict`์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ [**์ถ”๊ฐ€ ๋ชจ๋ธ** ๋ฌธ์„œ](../extra-models.md#about-user_indict){.internal-link target=_blank}๋ฅผ ๋‹ค์‹œ ์ฝ์–ด๋ด…์‹œ๋‹ค. @@ -166,7 +166,7 @@ UserInDB( ์ด ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์—์„œ๋Š” ์™„์ „ํžˆ ์•ˆ์ „ํ•˜์ง€ ์•Š๊ณ , ๋™์ผํ•œ `username`์„ ํ† ํฐ์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -/// ํŒ +/// tip | ํŒ ๋‹ค์Œ ์žฅ์—์„œ๋Š” ํŒจ์Šค์›Œ๋“œ ํ•ด์‹ฑ ๋ฐ JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ ๋ณด์•ˆ ๊ตฌํ˜„์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -176,7 +176,7 @@ UserInDB( {* ../../docs_src/security/tutorial003.py hl[85] *} -/// ํŒ +/// tip | ํŒ ์‚ฌ์–‘์— ๋”ฐ๋ผ ์ด ์˜ˆ์ œ์™€ ๋™์ผํ•˜๊ฒŒ `access_token` ๋ฐ `token_type`์ด ํฌํ•จ๋œ JSON์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. @@ -202,7 +202,7 @@ UserInDB( {* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *} -/// ์ •๋ณด +/// info | ์ •๋ณด ์—ฌ๊ธฐ์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ด `Bearer`์ธ ์ถ”๊ฐ€ ํ—ค๋” `WWW-Authenticate`๋„ ์‚ฌ์–‘์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. diff --git a/docs/pt/docs/deployment/https.md b/docs/pt/docs/deployment/https.md index 9a13977ec..9ad419934 100644 --- a/docs/pt/docs/deployment/https.md +++ b/docs/pt/docs/deployment/https.md @@ -14,38 +14,186 @@ Para aprender o bรกsico de HTTPS de uma perspectiva do usuรกrio, verifique SNI. - * Esta extensรฃo SNI permite que um รบnico servidor (com um รบnico endereรงo IP) tenha vรกrios certificados HTTPS e atenda a vรกrios domรญnios / aplicativos HTTPS. - * Para que isso funcione, um รบnico componente (programa) em execuรงรฃo no servidor, ouvindo no endereรงo IP pรบblico, deve ter todos os certificados HTTPS no servidor. -* Depois de obter uma conexรฃo segura, o protocolo de comunicaรงรฃo ainda รฉ HTTP. - * Os conteรบdos sรฃo criptografados, embora sejam enviados com o protocolo HTTP. + * No entanto, existe uma **soluรงรฃo** para isso. +* Hรก uma **extensรฃo** para o protocolo **TLS** (aquele que lida com a criptografia no nรญvel TCP, antes do HTTP) chamado **SNI**. + * Esta extensรฃo SNI permite que um รบnico servidor (com um **รบnico endereรงo IP**) tenha **vรกrios certificados HTTPS** e atenda a **vรกrios domรญnios / aplicativos HTTPS**. + * Para que isso funcione, um **รบnico** componente (programa) em execuรงรฃo no servidor, ouvindo no **endereรงo IP pรบblico**, deve ter **todos os certificados HTTPS** no servidor. +* **Depois** de obter uma conexรฃo segura, o protocolo de comunicaรงรฃo **ainda รฉ HTTP**. + * Os conteรบdos sรฃo **criptografados**, embora sejam enviados com o **protocolo HTTP**. -ร‰ uma prรกtica comum ter um programa/servidor HTTP em execuรงรฃo no servidor (mรกquina, host, etc.) e gerenciar todas as partes HTTPS: enviando as solicitaรงรตes HTTP descriptografadas para o aplicativo HTTP real em execuรงรฃo no mesmo servidor (a aplicaรงรฃo **FastAPI**, neste caso), pegue a resposta HTTP do aplicativo, criptografe-a usando o certificado apropriado e envie-a de volta ao cliente usando HTTPS. Este servidor รฉ frequentemente chamado de TLS Termination Proxy. +ร‰ uma prรกtica comum ter um **programa/servidor HTTP** em execuรงรฃo no servidor (mรกquina, host, etc.) e **gerenciar todas as partes HTTPS**: **recebendo as requisiรงรตes encriptadas**, enviando as **solicitaรงรตes HTTP descriptografadas** para o aplicativo HTTP real em execuรงรฃo no mesmo servidor (a aplicaรงรฃo **FastAPI**, neste caso), pegue a **resposta HTTP** do aplicativo, **criptografe-a** usando o **certificado HTTPS** apropriado e envie-a de volta ao cliente usando **HTTPS**. Este servidor รฉ frequentemente chamado de **Proxy de Terminaรงรฃo TLS**. + +Algumas das opรงรตes que vocรช pode usar como Proxy de Terminaรงรฃo TLS sรฃo: + +* Traefik (que tambรฉm pode gerenciar a renovaรงรฃo de certificados) +* Caddy (que tambรฉm pode gerenciar a renovaรงรฃo de certificados) +* Nginx +* HAProxy ## Let's Encrypt -Antes de Let's Encrypt, esses certificados HTTPS eram vendidos por terceiros confiรกveis. +Antes de Let's Encrypt, esses **certificados HTTPS** eram vendidos por terceiros confiรกveis. O processo de aquisiรงรฃo de um desses certificados costumava ser complicado, exigia bastante papelada e os certificados eram bastante caros. -Mas entรฃo Let's Encrypt foi criado. +Mas entรฃo o **Let's Encrypt** foi criado. -Ele รฉ um projeto da Linux Foundation que fornece certificados HTTPS gratuitamente. De forma automatizada. Esses certificados usam toda a seguranรงa criptogrรกfica padrรฃo e tรชm vida curta (cerca de 3 meses), entรฃo a seguranรงa รฉ realmente melhor por causa de sua vida รบtil reduzida. +Ele รฉ um projeto da Linux Foundation que fornece **certificados HTTPS gratuitamente** . De forma automatizada. Esses certificados usam toda a seguranรงa criptogrรกfica padrรฃo e tรชm vida curta (cerca de 3 meses), entรฃo a **seguranรงa รฉ, na verdade, melhor** por causa de sua vida รบtil reduzida. Os domรญnios sรฃo verificados com seguranรงa e os certificados sรฃo gerados automaticamente. Isso tambรฉm permite automatizar a renovaรงรฃo desses certificados. -A ideia รฉ automatizar a aquisiรงรฃo e renovaรงรฃo desses certificados, para que vocรช tenha HTTPS seguro, de graรงa e para sempre. +A ideia รฉ automatizar a aquisiรงรฃo e renovaรงรฃo desses certificados, para que vocรช tenha **HTTPS seguro, de graรงa e para sempre**. + +## HTTPS para Desenvolvedores + +Aqui estรก um exemplo de como uma API HTTPS poderia ser estruturada, passo a passo, com foco principal nas ideias relevantes para desenvolvedores. + +### Nome do domรญnio + +A etapa inicial provavelmente seria **adquirir** algum **nome de domรญnio**. Entรฃo, vocรช iria configurรก-lo em um servidor DNS (possivelmente no mesmo provedor em nuvem). + +Vocรช provavelmente usaria um servidor em nuvem (mรกquina virtual) ou algo parecido, e ele teria fixed **Endereรงo IP pรบblico**. + +No(s) servidor(es) DNS, vocรช configuraria um registro (`registro A`) para apontar **seu domรญnio** para o **endereรงo IP pรบblico do seu servidor**. + +Vocรช provavelmente farรก isso apenas uma vez, na primeira vez em que tudo estiver sendo configurado. + +/// tip | Dica + +Essa parte do Nome do Domรญnio se dรก muito antes do HTTPS, mas como tudo depende do domรญnio e endereรงo IP pรบblico, vale a pena mencionรก-la aqui. + +/// + +### DNS + +Agora vamos focar em todas as partes que realmente fazem parte do HTTPS. + +Primeiro, o navegador iria verificar com os **servidores DNS** qual o **IP do domรญnio**, nesse caso, `someapp.example.com`. + +Os servidores DNS iriam informar o navegador para utilizar algum **endereรงo IP** especรญfico. Esse seria o endereรงo IP pรบblico em uso no seu servidor, que vocรช configurou nos servidores DNS. + + + +### Inรญcio do Handshake TLS + +O navegador entรฃo irรก comunicar-se com esse endereรงo IP na **porta 443** (a porta HTTPS). + +A primeira parte dessa comunicaรงรฃo รฉ apenas para estabelecer a conexรฃo entre o cliente e o servidor e para decidir as chaves criptogrรกficas a serem utilizadas, etc. + + + +Esse interaรงรฃo entre o cliente e o servidor para estabelecer uma conexรฃo TLS รฉ chamada de **Handshake TLS**. + +### TLS com a Extensรฃo SNI + +**Apenas um processo** no servidor pode se conectar a uma **porta** em um **endereรงo IP**. Poderiam existir outros processos conectados em outras portas desse mesmo endereรงo IP, mas apenas um para cada combinaรงรฃo de endereรงo IP e porta. + +TLS (HTTPS) usa a porta `443` por padrรฃo. Entรฃo essa รฉ a porta que precisamos. + +Como apenas um รบnico processo pode se comunicar com essa porta, o processo que faria isso seria o **Proxy de Terminaรงรฃo TLS**. + +O Proxy de Terminaรงรฃo TLS teria acesso a um ou mais **certificados TLS** (certificados HTTPS). + +Utilizando a **extensรฃo SNI** discutida acima, o Proxy de Terminaรงรฃo TLS iria checar qual dos certificados TLS (HTTPS) disponรญveis deve ser usado para essa conexรฃo, utilizando o que corresponda ao domรญnio esperado pelo cliente. + +Nesse caso, ele usaria o certificado para `someapp.example.com`. + + + +O cliente jรก **confia** na entidade que gerou o certificado TLS (nesse caso, o Let's Encrypt, mas veremos sobre isso mais tarde), entรฃo ele pode **verificar** que o certificado รฉ vรกlido. + +Entรฃo, utilizando o certificado, o cliente e o Proxy de Terminaรงรฃo TLS **decidem como encriptar** o resto da **comunicaรงรฃo TCP**. Isso completa a parte do **Handshake TLS**. + +Apรณs isso, o cliente e o servidor possuem uma **conexรฃo TCP encriptada**, que รฉ provida pelo TLS. E entรฃo eles podem usar essa conexรฃo para comeรงar a **comunicaรงรฃo HTTP** propriamente dita. + +E isso resume o que รฉ **HTTPS**, apenas **HTTP** simples dentro de uma **conexรฃo TLS segura** em vez de uma conexรฃo TCP pura (nรฃo encriptada). + +/// tip | Dica + +Percebe que a encriptaรงรฃo da comunicaรงรฃo acontece no **nรญvel do TCP**, nรฃo no nรญvel do HTTP. + +/// + +### Solicitaรงรฃo HTTPS + +Agora que o cliente e servidor (especialmente o navegador e o Proxy de Terminaรงรฃo TLS) possuem uma **conexรฃo TCP encriptada**, eles podem iniciar a **comunicaรงรฃo HTTP**. + +Entรฃo, o cliente envia uma **solicitaรงรฃo HTTPS**. Que รฉ apenas uma solicitaรงรฃo HTTP sobre uma conexรฃo TLS encriptada. + + + +### Desencriptando a Solicitaรงรฃo + +O Proxy de Terminaรงรฃo TLS entรฃo usaria a encriptaรงรฃo combinada para **desencriptar a solicitaรงรฃo**, e transmitiria a **solicitaรงรฃo bรกsica (desencriptada)** para o processo executando a aplicaรงรฃo (por exemplo, um processo com Uvicorn executando a aplicaรงรฃo FastAPI). + + + +### Resposta HTTP + +A aplicaรงรฃo processaria a solicitaรงรฃo e retornaria uma **resposta HTTP bรกsica (nรฃo encriptada)** para o Proxy de Terminaรงรฃo TLS. + + + +### Resposta HTTPS + +O Proxy de Terminaรงรฃo TLS iria **encriptar a resposta** utilizando a criptografia combinada anteriormente (que foi definida com o certificado para `someapp.example.com`), e devolveria para o navegador. + +No prรณximo passo, o navegador verifica que a resposta รฉ vรกlida e encriptada com a chave criptogrรกfica correta, etc. E depois **desencripta a resposta** e a processa. + + + +O cliente (navegador) saberรก que a resposta vem do servidor correto por que ela usa a criptografia que foi combinada entre eles usando o **certificado HTTPS** anterior. + +### Mรบltiplas Aplicaรงรตes + +Podem existir **mรบltiplas aplicaรงรตes** em execuรงรฃo no mesmo servidor (ou servidores), por exemplo: outras APIs ou um banco de dados. + +Apenas um processo pode estar vinculado a um IP e porta (o Proxy de Terminaรงรฃo TLS, por exemplo), mas outras aplicaรงรตes/processos tambรฉm podem estar em execuรงรฃo no(s) servidor(es), desde que nรฃo tentem usar a mesma **combinaรงรฃo de IP pรบblico e porta**. + + + +Dessa forma, o Proxy de Terminaรงรฃo TLS pode gerenciar o HTTPS e os certificados de **mรบltiplos domรญnios**, para mรบltiplas aplicaรงรตes, e entรฃo transmitir as requisiรงรตes para a aplicaรงรฃo correta em cada caso. + +### Renovaรงรฃo de Certificados + +Em algum momento futuro, cada certificado irรก **expirar** (aproximadamente 3 meses apรณs a aquisiรงรฃo). + +E entรฃo, haverรก outro programa (em alguns casos pode ser o prรณprio Proxy de Terminaรงรฃo TLS) que irรก interagir com o Let's Encrypt e renovar o(s) certificado(s). + + + +Os **certificados TLS** sรฃo **associados com um nome de domรญnio**, e nรฃo a um endereรงo IP. + +Entรฃo para renovar os certificados, o programa de renovaรงรฃo precisa **provar** para a autoridade (Let's Encrypt) que ele realmente **possui e controla esse domรญnio**> + +Para fazer isso, e acomodar as necessidades de diferentes aplicaรงรตes, existem diferentes opรงรตes para esse programa. Algumas escolhas populares sรฃo: + +* **Modificar alguns registros DNS** + * Para isso, o programa de renovaรงรฃo precisa ter suporte as APIs do provedor DNS, entรฃo, dependendo do provedor DNS que vocรช utilize, isso pode ou nรฃo ser uma opรงรฃo viรกvel. +* **Executar como um servidor** (ao menos durante o processo de aquisiรงรฃo do certificado) no endereรงo IP pรบblico associado com o domรญnio. + * Como dito anteriormente, apenas um processo pode estar ligado a uma porta e IP especรญficos. + * Essa รฉ uma dos motivos que fazem utilizar o mesmo Proxy de Terminaรงรฃo TLS para gerenciar a renovaรงรฃo de certificados ser tรฃo รบtil. + * Caso contrรกrio, vocรช pode ter que parar a execuรงรฃo do Proxy de Terminaรงรฃo TLS momentaneamente, inicializar o programa de renovaรงรฃo para renovar os certificados, e entรฃo reiniciar o Proxy de Terminaรงรฃo TLS. Isso nรฃo รฉ o ideal, jรก que sua(s) aplicaรงรฃo(รตes) nรฃo vรฃo estar disponรญveis enquanto o Proxy de Terminaรงรฃo TLS estiver desligado. + +Todo esse processo de renovaรงรฃo, enquanto o aplicativo ainda funciona, รฉ uma das principais razรตes para preferir um **sistema separado para gerenciar HTTPS** com um Proxy de Terminaรงรฃo TLS em vez de usar os certificados TLS no servidor da aplicaรงรฃo diretamente (e.g. com o Uvicorn). + +## Recapitulando + +Possuir **HTTPS** habilitado na sua aplicaรงรฃo รฉ bastante importante, e atรฉ **crรญtico** na maioria dos casos. A maior parte do esforรงo que vocรช tem que colocar sobre o HTTPS como desenvolvedor estรก em **entender esses conceitos** e como eles funcionam. + +Mas uma vez que vocรช saiba o bรกsico de **HTTPS para desenvolvedores**, vocรช pode combinar e configurar diferentes ferramentas facilmente para gerenciar tudo de uma forma simples. + +Em alguns dos prรณximos capรญtulos, eu mostrarei para vocรช vรกrios exemplos concretos de como configurar o **HTTPS** para aplicaรงรตes **FastAPI**. ๐Ÿ”’ diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index bc23114dc..138048f06 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -11,15 +11,18 @@ Framework FastAPI, alta performance, fรกcil de aprender, fรกcil de codar, pronto para produรงรฃo

- - 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,6 +73,18 @@ Os recursos chave sรฃo: --- +"_Nรณs adotamos a biblioteca **FastAPI** para iniciar um servidor **REST** que pode ser consultado para obter **previsรตes**. [para o Ludwig]_" + +
Piero Molino, Yaroslav Dudin, e Sai Sumanth Miryala - Uber (ref)
+ +--- + +"_A **Netflix** tem o prazer de anunciar o lanรงamento open-source do nosso framework de orquestraรงรฃo de **gerenciamento de crises**: **Dispatch**! [criado com **FastAPI**]_" + +
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
+ +--- + "*Estou extremamente entusiasmado com o **FastAPI**. ร‰ tรฃo divertido!*"
Brian Okken - Python Bytes podcaster (ref)
@@ -90,9 +105,9 @@ Os recursos chave sรฃo: --- -"*Nรณs adotamos a biblioteca **FastAPI** para criar um servidor **REST** que possa ser chamado para obter **prediรงรตes**. [para o Ludwig]*" +"_Se alguรฉm estiver procurando construir uma API Python para produรงรฃo, eu recomendaria fortemente o **FastAPI**. Ele รฉ **lindamente projetado**, **simples de usar** e **altamente escalรกvel**. Ele se tornou um **componente chave** para a nossa estratรฉgia API first de desenvolvimento e estรก impulsionando diversas automaรงรตes e serviรงos, como o nosso Virtual TAC Engineer._" -
Piero Molino, Yaroslav Dudin e Sai Sumanth Miryala - Uber (ref)
+
Deon Pillsbury - Cisco (ref)
--- @@ -113,28 +128,20 @@ FastAPI estรก nos ombros de gigantes: ## Instalaรงรฃo -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -Vocรช tambรฉm precisarรก de um servidor ASGI para produรงรฃo, tal como Uvicorn ou Hypercorn. +Crie e ative um ambiente virtual, e entรฃo instale o FastAPI:
```console -$ pip install "uvicorn[standard]" +$ pip install "fastapi[standard]" ---> 100% ```
+**Nota**: Certifique-se de que vocรช colocou `"fastapi[standard]"` com aspas, para garantir que funcione em todos os terminais. + ## Exemplo ### Crie @@ -184,7 +191,7 @@ async def read_item(item_id: int, q: Union[str, None] = None): **Nota**: -Se vocรช nรฃo sabe, verifique a seรงรฃo _"In a hurry?"_ sobre `async` e `await` nas docs. +Se vocรช nรฃo sabe, verifique a seรงรฃo _"Com pressa?"_ sobre `async` e `await` nas docs. @@ -195,11 +202,24 @@ Rode o servidor com:
```console -$ uvicorn main:app --reload - +$ fastapi dev main.py + + โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Development mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ + โ”‚ โ”‚ + โ”‚ Serving at: http://127.0.0.1:8000 โ”‚ + โ”‚ โ”‚ + โ”‚ API docs: http://127.0.0.1:8000/docs โ”‚ + โ”‚ โ”‚ + โ”‚ Running in development mode, for production use: โ”‚ + โ”‚ โ”‚ + โ”‚ fastapi run โ”‚ + โ”‚ โ”‚ + โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] INFO: Waiting for application startup. INFO: Application startup complete. ``` @@ -207,13 +227,13 @@ INFO: Application startup complete.
-Sobre o comando uvicorn main:app --reload... +Sobre o comando fastapi dev main.py... -O comando `uvicorn main:app` se refere a: +O comando `fastapi dev` lรช o seu arquivo `main.py`, identifica o aplicativo **FastAPI** nele, e inicia um servidor usando o Uvicorn. -* `main`: o arquivo `main.py` (o "mรณdulo" Python). -* `app`: o objeto criado dentro de `main.py` com a linha `app = FastAPI()`. -* `--reload`: faz o servidor recarregar apรณs mudanรงas de cรณdigo. Somente faรงa isso para desenvolvimento. +Por padrรฃo, o `fastapi dev` iniciarรก com *auto-reload* habilitado para desenvolvimento local. + +Vocรช pode ler mais sobre isso na documentaรงรฃo do FastAPI CLI.
@@ -268,7 +288,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool] = None + is_offer: Union[bool, None] = None @app.get("/") @@ -286,7 +306,7 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -O servidor deverรก recarregar automaticamente (porquรช vocรช adicionou `--reload` ao comando `uvicorn` acima). +O servidor `fastapi dev` deverรก recarregar automaticamente. ### Evoluindo a Documentaรงรฃo Interativa da API @@ -316,7 +336,7 @@ E agora, vรก para Tutorial - Guia do Usuรกrio. +Para um exemplo mais completo incluindo mais recursos, veja Tutorial - Guia do Usuรกrio. **Alerta de Spoiler**: o tutorial - guia do usuรกrio inclui: @@ -416,9 +436,9 @@ Para um exemplo mais completo incluindo mais recursos, veja Injeรงรฃo de Dependรชncia**. * Seguranรงa e autenticaรงรฃo, incluindo suporte para **OAuth2** com autenticaรงรฃo **JWT tokens** e **HTTP Basic**. * Tรฉcnicas mais avanรงadas (mas igualmente fรกceis) para declaraรงรฃo de **modelos JSON profundamente aninhados** (graรงas ao Pydantic). +* Integraรงรตes **GraphQL** com o Strawberry e outras bibliotecas. * Muitos recursos extras (graรงas ao Starlette) como: * **WebSockets** - * **GraphQL** * testes extrememamente fรกceis baseados em HTTPX e `pytest` * **CORS** * **Cookie Sessions** @@ -428,30 +448,49 @@ Para um exemplo mais completo incluindo mais recursos, veja um dos _frameworks_ Python mais rรกpidos disponรญveis, somente atrรกs de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*) -Para entender mais sobre performance, veja a seรงรฃo Benchmarks. +Para entender mais sobre performance, veja a seรงรฃo Comparaรงรตes. + +## Dependรชncias + +O FastAPI depende do Pydantic e do Starlette. + -## Dependรชncias opcionais +### Dependรชncias `standard` -Usados por Pydantic: +Quando vocรช instala o FastAPI com `pip install "fastapi[standard]"`, ele vรชm com o grupo `standard` (padrรฃo) de dependรชncias opcionais: + +Utilizado pelo Pydantic: * email-validator - para validaรงรฃo de email. -Usados por Starlette: +Utilizado pelo Starlette: + +* httpx - Obrigatรณrio caso vocรช queira utilizar o `TestClient`. +* jinja2 - Obrigatรณrio se vocรช quer utilizar a configuraรงรฃo padrรฃo de templates. +* python-multipart - Obrigatรณrio se vocรช deseja suporte a "parsing" de formulรกrio, com `request.form()`. + +Utilizado pelo FastAPI / Starlette: + +* uvicorn - para o servidor que carrega e serve a sua aplicaรงรฃo. Isto inclui `uvicorn[standard]`, que inclui algumas dependรชncias (e.g. `uvloop`) necessรกrias para servir em alta performance. +* `fastapi-cli` - que disponibiliza o comando `fastapi`. + +### Sem as dependรชncias `standard` + +Se vocรช nรฃo deseja incluir as dependรชncias opcionais `standard`, vocรช pode instalar utilizando `pip install fastapi` ao invรฉs de `pip install "fastapi[standard]"`. + +### Dpendรชncias opcionais adicionais + +Existem algumas dependรชncias adicionais que vocรช pode querer instalar. -* httpx - Necessรกrio se vocรช quiser utilizar o `TestClient`. -* jinja2 - Necessรกrio se vocรช quiser utilizar a configuraรงรฃo padrรฃo de templates. -* python-multipart - Necessรกrio se vocรช quiser suporte com "parsing" de formulรกrio, com `request.form()`. -* itsdangerous - Necessรกrio para suporte a `SessionMiddleware`. -* pyyaml - Necessรกrio para suporte a `SchemaGenerator` da Starlette (vocรช provavelmente nรฃo precisarรก disso com o FastAPI). -* graphene - Necessรกrio para suporte a `GraphQLApp`. +Dependรชncias opcionais adicionais do Pydantic: -Usados por FastAPI / Starlette: +* pydantic-settings - para gerenciamento de configuraรงรตes. +* pydantic-extra-types - tipos extras para serem utilizados com o Pydantic. -* uvicorn - para o servidor que carrega e serve sua aplicaรงรฃo. -* orjson - Necessรกrio se vocรช quer utilizar `ORJSONResponse`. -* ujson - Necessรกrio se vocรช quer utilizar `UJSONResponse`. +Dependรชncias opcionais adicionais do FastAPI: -Vocรช pode instalar todas essas dependรชncias com `pip install fastapi[all]`. +* orjson - Obrigatรณrio se vocรช deseja utilizar o `ORJSONResponse`. +* ujson - Obrigatรณrio se vocรช deseja utilizar o `UJSONResponse`. ## Licenรงa diff --git a/docs/pt/docs/tutorial/request-forms.md b/docs/pt/docs/tutorial/request-forms.md index 756ceb581..572ddf003 100644 --- a/docs/pt/docs/tutorial/request-forms.md +++ b/docs/pt/docs/tutorial/request-forms.md @@ -6,7 +6,11 @@ Quando vocรช precisar receber campos de formulรกrio ao invรฉs de JSON, vocรช pod Para usar formulรกrios, primeiro instale `python-multipart`. -Ex: `pip install python-multipart`. +Lembre-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativรก-lo e entรฃo instalar a dependรชncia, por exemplo: + +```console +$ pip install python-multipart +``` /// diff --git a/docs/ru/docs/advanced/async-tests.md b/docs/ru/docs/advanced/async-tests.md new file mode 100644 index 000000000..7849ad109 --- /dev/null +++ b/docs/ru/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# ะัะธะฝั…ั€ะพะฝะฝะพะต ั‚ะตัั‚ะธั€ะพะฒะฐะฝะธะต + +ะ’ั‹ ัƒะถะต ะฒะธะดะตะปะธ ะบะฐะบ ั‚ะตัั‚ะธั€ะพะฒะฐั‚ัŒ **FastAPI** ะฟั€ะธะปะพะถะตะฝะธะต, ะธัะฟะพะปัŒะทัƒั ะธะผะตัŽั‰ะธะนัั ะบะปะฐัั `TestClient`. ะš ัั‚ะพะผัƒ ะผะพะผะตะฝั‚ัƒ ะฒั‹ ะฒะธะดะตะปะธ ั‚ะพะปัŒะบะพ ะบะฐะบ ะฟะธัะฐั‚ัŒ ั‚ะตัั‚ั‹ ะฒ ัะธะฝั…ั€ะพะฝะฝะพะผ ัั‚ะธะปะต ะฑะตะท ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั `async` ั„ัƒะฝะบั†ะธะน. + +ะ’ะพะทะผะพะถะฝะพัั‚ัŒ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะฐัะธะฝั…ั€ะพะฝะฝั‹ั… ั„ัƒะฝะบั†ะธะน ะฒ ะฒะฐัˆะธั… ั‚ะตัั‚ะฐั… ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฟะพะปะตะทะฝa, ะบะพะณะดะฐ, ะฝะฐะฟั€ะธะผะตั€, ะฒั‹ ะฐัะธะฝั…ั€ะพะฝะฝะพ ะพะฑั€ะฐั‰ะฐะตั‚ะตััŒ ะบ ะฒะฐัˆะตะน ะฑะฐะทะต ะดะฐะฝะฝั‹ั…. ะŸั€ะตะดัั‚ะฐะฒัŒั‚ะต, ั‡ั‚ะพ ะฒั‹ ั…ะพั‚ะธั‚ะต ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะทะฐะฟั€ะพัั‹ ะฒ ะฒะฐัˆะต FastAPI ะฟั€ะธะปะพะถะตะฝะธะต, ะฐ ะทะฐั‚ะตะผ ะฟั€ะธ ะฟะพะผะพั‰ะธ ะฐัะธะฝั…ั€ะพะฝะฝะพะน ะฑะธะฑะปะธะพั‚ะตะบะธ ะดะปั ั€ะฐะฑะพั‚ั‹ ั ะฑะฐะทะพะน ะดะฐะฝะฝั‹ั… ัƒะดะพัั‚ะพะฒะตั€ะธั‚ัŒัั, ั‡ั‚ะพ ะฒะฐัˆ ะฑะตะบัะฝะด ะบะพั€ั€ะตะบั‚ะฝะพ ะทะฐะฟะธัะฐะป ะดะฐะฝะฝั‹ะต ะฒ ะฑะฐะทัƒ ะดะฐะฝะฝั‹ั…. + +ะ”ะฐะฒะฐะนั‚ะต ั€ะฐััะผะพั‚ั€ะธะผ, ะบะฐะบ ะผั‹ ะผะพะถะตะผ ัั‚ะพ ั€ะตะฐะปะธะทะพะฒะฐั‚ัŒ. + +## pytest.mark.anyio + +ะ•ัะปะธ ะผั‹ ั…ะพั‚ะธะผ ะฒั‹ะทั‹ะฒะฐั‚ัŒ ะฐัะธะฝั…ั€ะพะฝะฝั‹ะต ั„ัƒะฝะบั†ะธะธ ะฒ ะฝะฐัˆะธั… ั‚ะตัั‚ะฐั…, ั‚ะพ ะฝะฐัˆะธ ั‚ะตัั‚ะพะฒั‹ะต ั„ัƒะฝะบั†ะธะธ ะดะพะปะถะฝั‹ ะฑั‹ั‚ัŒ ะฐัะธะฝั…ั€ะพะฝะฝั‹ะผะธ. AnyIO ะฟั€ะตะดะพัั‚ะฐะฒะปัะตั‚ ะดะปั ัั‚ะพะณะพ ะพั‚ะปะธั‡ะฝั‹ะน ะฟะปะฐะณะธะฝ, ะบะพั‚ะพั€ั‹ะน ะฟะพะทะฒะพะปัะตั‚ ะฝะฐะผ ัƒะบะฐะทั‹ะฒะฐั‚ัŒ, ะบะฐะบะธะต ั‚ะตัั‚ะพะฒั‹ะต ั„ัƒะฝะบั†ะธะธ ะดะพะปะถะฝั‹ ะฒั‹ะทั‹ะฒะฐั‚ัŒัั ะฐัะธะฝั…ั€ะพะฝะฝะพ. + +## HTTPX + +ะ”ะฐะถะต ะตัะปะธ **FastAPI** ะฟั€ะธะปะพะถะตะฝะธะต ะธัะฟะพะปัŒะทัƒะตั‚ ะพะฑั‹ั‡ะฝั‹ะต ั„ัƒะฝะบั†ะธะธ `def` ะฒะผะตัั‚ะพ `async def`, ัั‚ะพ ะฒัะต ั€ะฐะฒะฝะพ `async` ะฟั€ะธะปะพะถะตะฝะธะต 'ะฟะพะด ะบะฐะฟะพั‚ะพะผ'. + +ะงั‚ะพะฑั‹ ั€ะฐะฑะพั‚ะฐั‚ัŒ ั ะฐัะธะฝั…ั€ะพะฝะฝั‹ะผ FastAPI ะฟั€ะธะปะพะถะตะฝะธะตะผ ะฒ ะฒะฐัˆะธั… ะพะฑั‹ั‡ะฝั‹ั… ั‚ะตัั‚ะพะฒั‹ั… ั„ัƒะฝะบั†ะธัั… `def`, ะธัะฟะพะปัŒะทัƒั ัั‚ะฐะฝะดะฐั€ั‚ะฝั‹ะน pytest, `TestClient` ะฒะฝัƒั‚ั€ะธ ัะตะฑั ะดะตะปะฐะตั‚ ะฝะตะบะพั‚ะพั€ัƒัŽ ะผะฐะณะธัŽ. ะะพ ัั‚ะฐ ะผะฐะณะธั ะฟะตั€ะตัั‚ะฐะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ, ะบะพะณะดะฐ ะผั‹ ะธัะฟะพะปัŒะทัƒะตะผ ะตะณะพ ะฒะฝัƒั‚ั€ะธ ะฐัะธะฝั…ั€ะพะฝะฝั‹ั… ั„ัƒะฝะบั†ะธะน. ะ—ะฐะฟัƒัะบะฐั ะฝะฐัˆะธ ั‚ะตัั‚ั‹ ะฐัะธะฝั…ั€ะพะฝะฝะพ, ะผั‹ ะฑะพะปัŒัˆะต ะฝะต ะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `TestClient` ะฒะฝัƒั‚ั€ะธ ะฝะฐัˆะธั… ั‚ะตัั‚ะพะฒั‹ั… ั„ัƒะฝะบั†ะธะน. + +`TestClient` ะพัะฝะพะฒะฐะฝ ะฝะฐ HTTPX, ะธ, ะบ ัั‡ะฐัั‚ัŒัŽ, ะผั‹ ะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะตะณะพ (`HTTPX`) ะฝะฐะฟั€ัะผัƒัŽ ะดะปั ั‚ะตัั‚ะธั€ะพะฒะฐะฝะธั API. + +## ะŸั€ะธะผะตั€ + +ะ’ ะบะฐั‡ะตัั‚ะฒะต ะฟั€ะพัั‚ะพะณะพ ะฟั€ะธะผะตั€ะฐ, ะดะฐะฒะฐะนั‚ะต ั€ะฐััะผะพั‚ั€ะธะผ ั„ะฐะนะปะพะฒัƒัŽ ัั‚ั€ัƒะบั‚ัƒั€ัƒ, ัั…ะพะถัƒัŽ ั ะพะฟะธัะฐะฝะฝะพะน ะฒ [ะ‘ะพะปัŒัˆะธะต ะฟั€ะธะปะพะถะตะฝะธั](../tutorial/bigger-applications.md){.internal-link target=_blank} ะธ [ะขะตัั‚ะธั€ะพะฒะฐะฝะธะต](../tutorial/testing.md){.internal-link target=_blank}: + +``` +. +โ”œโ”€โ”€ app +โ”‚ย ย  โ”œโ”€โ”€ __init__.py +โ”‚ย ย  โ”œโ”€โ”€ main.py +โ”‚ย ย  โ””โ”€โ”€ test_main.py +``` + +ะคะฐะนะป `main.py`: + +{* ../../docs_src/async_tests/main.py *} + +ะคะฐะนะป `test_main.py` ัะพะดะตั€ะถะธั‚ ั‚ะตัั‚ั‹ ะดะปั `main.py`, ั‚ะตะฟะตั€ัŒ ะพะฝ ะผะพะถะตั‚ ะฒั‹ะณะปัะดะตั‚ัŒ ั‚ะฐะบ: + +{* ../../docs_src/async_tests/test_main.py *} + +## ะ—ะฐะฟัƒัะบ ั‚ะตัั‚ะพะฒ + +ะ’ั‹ ะผะพะถะตั‚ะต ะทะฐะฟัƒัั‚ะธั‚ัŒ ัะฒะพะธ ั‚ะตัั‚ั‹ ะบะฐะบ ะพะฑั‹ั‡ะฝะพ: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## ะŸะพะดั€ะพะฑะฝะตะต + +ะœะฐั€ะบะตั€ `@pytest.mark.anyio` ะณะพะฒะพั€ะธั‚ pytest, ั‡ั‚ะพ ั‚ะตัั‚ะพะฒะฐั ั„ัƒะฝะบั†ะธั ะดะพะปะถะฝะฐ ะฑั‹ั‚ัŒ ะฒั‹ะทะฒะฐะฝะฐ ะฐัะธะฝั…ั€ะพะฝะฝะพ: + +{* ../../docs_src/async_tests/test_main.py hl[7] *} + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ั‚ะตัั‚ะพะฒะฐั ั„ัƒะฝะบั†ะธั ั‚ะตะฟะตั€ัŒ `async def` ะฒะผะตัั‚ะพ ะฟั€ะพัั‚ะพะณะพ `def`, ะบะฐะบ ัั‚ะพ ะฑั‹ะปะพ ะฟั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ `TestClient`. + +/// + +ะ—ะฐั‚ะตะผ ะผั‹ ะผะพะถะตะผ ัะพะทะดะฐั‚ัŒ `AsyncClient` ัะพ ััั‹ะปะบะพะน ะฝะฐ ะฟั€ะธะปะพะถะตะฝะธะต ะธ ะฟะพัั‹ะปะฐั‚ัŒ ะฐัะธะฝั…ั€ะพะฝะฝั‹ะต ะทะฐะฟั€ะพัั‹, ะธัะฟะพะปัŒะทัƒั `await`. + +{* ../../docs_src/async_tests/test_main.py hl[9:12] *} + +ะญั‚ะพ ัะบะฒะธะฒะฐะปะตะฝั‚ะฝะพ ัะปะตะดัƒัŽั‰ะตะผัƒ: + +```Python +response = client.get('/') +``` + +...ะบะพั‚ะพั€ะพะต ะผั‹ ะธัะฟะพะปัŒะทะพะฒะฐะปะธ ะดะปั ะพั‚ะฟั€ะฐะฒะบะธ ะฝะฐัˆะธั… ะทะฐะฟั€ะพัะพะฒ ั `TestClient`. + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ะผั‹ ะธัะฟะพะปัŒะทัƒะตะผ async/await ั `AsyncClient` - ะทะฐะฟั€ะพั ะฐัะธะฝั…ั€ะพะฝะฝั‹ะน. + +/// + +/// warning | ะ’ะฝะธะผะฐะฝะธะต + +ะ•ัะปะธ ะฒะฐัˆะต ะฟั€ะธะปะพะถะตะฝะธะต ะฟะพะปะฐะณะฐะตั‚ัั ะฝะฐ lifespan ัะพะฑั‹ั‚ะธั, ั‚ะพ `AsyncClient` ะฝะต ะทะฐะฟัƒัั‚ะธั‚ ัั‚ะธ ัะพะฑั‹ั‚ะธั. ะงั‚ะพะฑั‹ ะพะฑะตัะฟะตั‡ะธั‚ัŒ ะธั… ัั€ะฐะฑะฐั‚ั‹ะฒะฐะฝะธะต ะธัะฟะพะปัŒะทัƒะนั‚ะต `LifespanManager` ะธะท florimondmanca/asgi-lifespan. + +/// + +## ะ’ั‹ะทะพะฒ ะดั€ัƒะณะธั… ะฐัะธะฝั…ั€ะพะฝะฝั‹ั… ั„ัƒะฝะบั†ะธะน + +ะขะตะฟะตั€ัŒ ั‚ะตัั‚ะพะฒะฐั ั„ัƒะฝะบั†ะธั ัั‚ะฐะปะฐ ะฐัะธะฝั…ั€ะพะฝะฝะพะน, ะฟะพัั‚ะพะผัƒ ะฒะฝัƒั‚ั€ะธ ะฝะตะต ะฒั‹ ะผะพะถะตั‚ะต ะฒั‹ะทั‹ะฒะฐั‚ัŒ ั‚ะฐะบะถะต ะธ ะดั€ัƒะณะธะต `async` ั„ัƒะฝะบั†ะธะธ, ะฝะต ัะฒัะทะฐะฝะฝั‹ะต ั ะพั‚ะฟั€ะฐะฒะปะตะฝะธะตะผ ะทะฐะฟั€ะพัะพะฒ ะฒ ะฒะฐัˆะต FastAPI ะฟั€ะธะปะพะถะตะฝะธะต. ะšะฐะบ ะตัะปะธ ะฑั‹ ะฒั‹ ะฒั‹ะทั‹ะฒะฐะปะธ ะธั… ะฒ ะปัŽะฑะพะผ ะดั€ัƒะณะพะผ ะผะตัั‚ะต ะฒะฐัˆะตะณะพ ะบะพะดะฐ. + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะ•ัะปะธ ะฒั‹ ัั‚ะพะปะบะฝัƒะปะธััŒ ั `RuntimeError: Task attached to a different loop` ะฟั€ะธ ะฒั‹ะทะพะฒะต ะฐัะธะฝั…ั€ะพะฝะฝั‹ั… ั„ัƒะฝะบั†ะธะน ะฒ ะฒะฐัˆะธั… ั‚ะตัั‚ะฐั… (ะฝะฐะฟั€ะธะผะตั€, ะฟั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ MongoDB's MotorClient), ั‚ะพ ะฝะต ะทะฐะฑั‹ะฒะฐะนั‚ะต ะธะฝะธั†ะธะฐะปะธะทะธั€ะพะฒะฐั‚ัŒ ะพะฑัŠะตะบั‚ั‹, ะบะพั‚ะพั€ั‹ะผ ะฝัƒะถะตะฝ ั†ะธะบะป ัะพะฑั‹ั‚ะธะน (event loop), ั‚ะพะปัŒะบะพ ะฒะฝัƒั‚ั€ะธ ะฐัะธะฝั…ั€ะพะฝะฝั‹ั… ั„ัƒะฝะบั†ะธะน, ะฝะฐะฟั€ะธะผะตั€, ะฒ `'@app.on_event("startup")` callback. + +/// diff --git a/docs/ru/docs/advanced/response-cookies.md b/docs/ru/docs/advanced/response-cookies.md new file mode 100644 index 000000000..e04ff577c --- /dev/null +++ b/docs/ru/docs/advanced/response-cookies.md @@ -0,0 +1,48 @@ + +# Cookies ะฒ ะพั‚ะฒะตั‚ะต + +## ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต ะฟะฐั€ะฐะผะตั‚ั€ะฐ `Response` + +ะ’ั‹ ะผะพะถะตั‚ะต ะพะฑัŠัะฒะธั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟะฐ `Response` ะฒ ะฒะฐัˆะตะน ั„ัƒะฝะบั†ะธะธ ัะฝะดะฟะพะธะฝั‚ะฐ. + +ะ—ะฐั‚ะตะผ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ cookies ะฒ ัั‚ะพะผ ะฒั€ะตะผะตะฝะฝะพะผ ะพะฑัŠะตะบั‚ะต ะพั‚ะฒะตั‚ะฐ. + +{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} + +ะŸะพัะปะต ัั‚ะพะณะพ ะผะพะถะฝะพ ะฒะตั€ะฝัƒั‚ัŒ ะปัŽะฑะพะน ะพะฑัŠะตะบั‚, ะบะฐะบ ะธ ั€ะฐะฝัŒัˆะต (ะฝะฐะฟั€ะธะผะตั€, `dict`, ะพะฑัŠะตะบั‚ ะผะพะดะตะปะธ ะฑะฐะทั‹ ะดะฐะฝะฝั‹ั… ะธ ั‚ะฐะบ ะดะฐะปะตะต). + +ะ•ัะปะธ ะฒั‹ ัƒะบะฐะทะฐะปะธ `response_model`, ะพะฝ ะฒัั‘ ั€ะฐะฒะฝะพ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะดะปั ั„ะธะปัŒั‚ั€ะฐั†ะธะธ ะธ ะฟั€ะตะพะฑั€ะฐะทะพะฒะฐะฝะธั ะฒะพะทะฒั€ะฐั‰ะฐะตะผะพะณะพ ะพะฑัŠะตะบั‚ะฐ. + +**FastAPI** ะธะทะฒะปะตั‡ะตั‚ cookies (ะฐ ั‚ะฐะบะถะต ะทะฐะณะพะปะพะฒะบะธ ะธ ะบะพะดั‹ ัะพัั‚ะพัะฝะธั) ะธะท ะฒั€ะตะผะตะฝะฝะพะณะพ ะพั‚ะฒะตั‚ะฐ ะธ ะฒะบะปัŽั‡ะธั‚ ะธั… ะฒ ะพะบะพะฝั‡ะฐั‚ะตะปัŒะฝั‹ะน ะพั‚ะฒะตั‚, ัะพะดะตั€ะถะฐั‰ะธะน ะฒะฐัˆะต ะฒะพะทะฒั€ะฐั‰ะฐะตะผะพะต ะทะฝะฐั‡ะตะฝะธะต, ะพั‚ั„ะธะปัŒั‚ั€ะพะฒะฐะฝะฝะพะต ั‡ะตั€ะตะท `response_model`. + +ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะพะฑัŠัะฒะธั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ ั‚ะธะฟะฐ Response ะฒ ะทะฐะฒะธัะธะผะพัั‚ัั… ะธ ัƒัั‚ะฐะฝะฐะฒะปะธะฒะฐั‚ัŒ cookies (ะธ ะทะฐะณะพะปะพะฒะบะธ) ั‚ะฐะผ. + +## ะ’ะพะทะฒั€ะฐั‰ะตะฝะธะต `Response` ะฝะฐะฟั€ัะผัƒัŽ + +ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ cookies, ะตัะปะธ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต `Response` ะฝะฐะฟั€ัะผัƒัŽ ะฒ ะฒะฐัˆะตะผ ะบะพะดะต. + +ะ”ะปั ัั‚ะพะณะพ ัะพะทะดะฐะนั‚ะต ะพะฑัŠะตะบั‚ `Response`, ะบะฐะบ ะพะฟะธัะฐะฝะพ ะฒ ั€ะฐะทะดะตะปะต [ะ’ะพะทะฒั€ะฐั‰ะตะฝะธะต ะพั‚ะฒะตั‚ะฐ ะฝะฐะฟั€ัะผัƒัŽ](response-directly.md){.target=_blank}. + +ะ—ะฐั‚ะตะผ ัƒัั‚ะฐะฝะพะฒะธั‚ะต cookies ะธ ะฒะตั€ะฝะธั‚ะต ัั‚ะพั‚ ะพะฑัŠะตะบั‚: + +{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} + +/// tip | ะŸั€ะธะผะตั‡ะฐะฝะธะต +ะ˜ะผะตะนั‚ะต ะฒ ะฒะธะดัƒ, ั‡ั‚ะพ ะตัะปะธ ะฒั‹ ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ะต ะพั‚ะฒะตั‚ ะฝะฐะฟั€ัะผัƒัŽ, ะฒะผะตัั‚ะพ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะฟะฐั€ะฐะผะตั‚ั€ะฐ `Response`, **FastAPI** ะพั‚ะฟั€ะฐะฒะธั‚ ะตะณะพ ะฑะตะท ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะน ะพะฑั€ะฐะฑะพั‚ะบะธ. + +ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒะฐัˆะธ ะดะฐะฝะฝั‹ะต ะธะผะตัŽั‚ ะบะพั€ั€ะตะบั‚ะฝั‹ะน ั‚ะธะฟ. ะะฐะฟั€ะธะผะตั€, ะพะฝะธ ะดะพะปะถะฝั‹ ะฑั‹ั‚ัŒ ัะพะฒะผะตัั‚ะธะผั‹ ั JSON, ะตัะปะธ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต `JSONResponse`. + +ะขะฐะบะถะต ัƒะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ะฝะต ะพั‚ะฟั€ะฐะฒะปัะตั‚ะต ะดะฐะฝะฝั‹ะต, ะบะพั‚ะพั€ั‹ะต ะดะพะปะถะฝั‹ ะฑั‹ะปะธ ะฑั‹ั‚ัŒ ะพั‚ั„ะธะปัŒั‚ั€ะพะฒะฐะฝั‹ ั‡ะตั€ะตะท `response_model`. +/// + +### ะ”ะพะฟะพะปะฝะธั‚ะตะปัŒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั + +/// note | ะขะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ +ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `from starlette.responses import Response` ะธะปะธ `from starlette.responses import JSONResponse`. + +**FastAPI** ะฟั€ะตะดะพัั‚ะฐะฒะปัะตั‚ `fastapi.responses`, ะบะพั‚ะพั€ั‹ะต ัะฒะปััŽั‚ัั ั‚ะตะผะธ ะถะต ะพะฑัŠะตะบั‚ะฐะผะธ, ั‡ั‚ะพ ะธ `starlette.responses`, ะฟั€ะพัั‚ะพ ะดะปั ัƒะดะพะฑัั‚ะฒะฐ. ะžะดะฝะฐะบะพ ะฑะพะปัŒัˆะธะฝัั‚ะฒะพ ะดะพัั‚ัƒะฟะฝั‹ั… ั‚ะธะฟะพะฒ ะพั‚ะฒะตั‚ะพะฒ ะฟะพัั‚ัƒะฟะฐะตั‚ ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะธะท **Starlette**. + +ะ”ะปั ัƒัั‚ะฐะฝะพะฒะบะธ ะทะฐะณะพะปะพะฒะบะพะฒ ะธ cookies `Response` ะธัะฟะพะปัŒะทัƒะตั‚ัั ั‡ะฐัั‚ะพ, ะฟะพัั‚ะพะผัƒ **FastAPI** ั‚ะฐะบะถะต ะฟั€ะตะดะพัั‚ะฐะฒะปัะตั‚ ะตะณะพ ั‡ะตั€ะตะท `fastapi.responses`. +/// + +ะงั‚ะพะฑั‹ ัƒะฒะธะดะตั‚ัŒ ะฒัะต ะดะพัั‚ัƒะฟะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะธ ะฝะฐัั‚ั€ะพะนะบะธ, ะพะทะฝะฐะบะพะผัŒั‚ะตััŒ ั ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะตะน Starlette. diff --git a/docs/ru/docs/advanced/websockets.md b/docs/ru/docs/advanced/websockets.md new file mode 100644 index 000000000..bc9dfcbff --- /dev/null +++ b/docs/ru/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# ะ’ะตะฑ-ัะพะบะตั‚ั‹ + +ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฒะตะฑ-ัะพะบะตั‚ั‹ ะฒ **FastAPI**. + +## ะฃัั‚ะฐะฝะพะฒะบะฐ `WebSockets` + +ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ [ะฒะธั€ั‚ัƒะฐะปัŒะฝะฐั ัั€ะตะดะฐ](../virtual-environments.md){.internal-link target=_blank} ัะพะทะดะฐะฝะฐ, ะฐะบั‚ะธะฒะธั€ัƒะนั‚ะต ะตั‘ ะธ ัƒัั‚ะฐะฝะพะฒะธั‚ะต `websockets`: + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## ะšะปะธะตะฝั‚ WebSockets + +### ะ ะฐะฑะพั‡ะตะต ะฟั€ะธะปะพะถะตะฝะธะต + +ะกะบะพั€ะตะต ะฒัะตะณะพ, ะฒ ะฒะฐัˆะตะน ั€ะตะฐะปัŒะฝะพะน ะฟั€ะพะดัƒะบั‚ะพะฒะพะน ัะธัั‚ะตะผะต ะตัั‚ัŒ ั„ั€ะพะฝั‚ะตะฝะด, ั€ะตะฐะปะธะทะพะฒะฐะฝะฝั‹ะน ะฟั€ะธ ะฟะพะผะพั‰ะธ ัะพะฒั€ะตะผะตะฝะฝั‹ั… ั„ั€ะตะนะผะฒะพั€ะบะพะฒ React, Vue.js ะธะปะธ Angular. + +ะ˜ ะฝะฐะฒะตั€ะฝัะบะฐ ะดะปั ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะธั ั ะฑะตะบะตะฝะดะพะผ ั‡ะตั€ะตะท ะฒะตะฑ-ัะพะบะตั‚ั‹ ะฒั‹ ะฑัƒะดะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั€ะตะดัั‚ะฒะฐ ั„ั€ะพะฝั‚ะตะฝะดะฐ. + +ะขะฐะบะถะต ัƒ ะฒะฐั ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฝะฐั‚ะธะฒะฝะพะต ะผะพะฑะธะปัŒะฝะพะต ะฟั€ะธะปะพะถะตะฝะธะต, ะบะพะผะผัƒะฝะธั†ะธั€ัƒัŽั‰ะตะต ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ั ะฒะตะฑ-ัะพะบะตั‚ะฐะผะธ ะฝะฐ ะฑะตะบะตะฝะด-ัะตั€ะฒะตั€ะต. + +ะ›ะธะฑะพ ะฒั‹ ะผะพะถะตั‚ะต ัะดะตะปะฐั‚ัŒ ะบะฐะบะพะน-ะปะธะฑะพ ะดั€ัƒะณะพะน ัะฟะพัะพะฑ ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะธั ั ะฒะตะฑ-ัะพะบะตั‚ะฐะผะธ. + +--- + +ะะพ ะดะปั ัั‚ะพะณะพ ะฟั€ะธะผะตั€ะฐ ะผั‹ ะฒะพัะฟะพะปัŒะทัƒะตะผัั ะพั‡ะตะฝัŒ ะฟั€ะพัั‚ั‹ะผ HTML ะดะพะบัƒะผะตะฝั‚ะพะผ ั ะฝะตะฑะพะปัŒัˆะธะผะธ ะฒัั‚ะฐะฒะบะฐะผะธ JavaScript ะบะพะดะฐ. + +ะšะพะฝะตั‡ะฝะพ ะถะต ัั‚ะพ ะฝะตะพะฟั‚ะธะผะฐะปัŒะฝะพ, ะธ ะฝะฐ ะฟั€ะฐะบั‚ะธะบะต ั‚ะฐะบ ะดะตะปะฐั‚ัŒ ะฝะต ัั‚ะพะธั‚. + +ะ’ ั€ะตะฐะปัŒะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธัั… ัั‚ะพะธั‚ ะฒะพัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั ะพะดะฝะธะผ ะธะท ะฒั‹ัˆะตัƒะฟะพะผัะฝัƒั‚ั‹ั… ัะฟะพัะพะฑะพะฒ. + +ะ”ะปั ะฟั€ะธะผะตั€ะฐ ะฝะฐะผ ะฝัƒะถะตะฝ ะฝะฐะธะฑะพะปะตะต ะฟั€ะพัั‚ะพะน ัะฟะพัะพะฑ, ะบะพั‚ะพั€ั‹ะน ะฟะพะทะฒะพะปะธั‚ ัะพัั€ะตะดะพั‚ะพั‡ะธั‚ัŒัั ะฝะฐ ัะตั€ะฒะตั€ะฝะพะน ั‡ะฐัั‚ะธ ะฒะตะฑ-ัะพะบะตั‚ะพะฒ ะธ ะฟะพะปัƒั‡ะธั‚ัŒ ั€ะฐะฑะพั‡ะธะน ะบะพะด: + +{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} + +## ะกะพะทะดะฐะฝะธะต `websocket` + +ะกะพะทะดะฐะนั‚ะต `websocket` ะฒ ัะฒะพะตะผ **FastAPI** ะฟั€ะธะปะพะถะตะฝะธะธ: + +{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} + +/// note | ะขะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ + +ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `from starlette.websockets import WebSocket`. + +**FastAPI** ะฝะฐะฟั€ัะผัƒัŽ ะฟั€ะตะดะพัั‚ะฐะฒะปัะตั‚ ั‚ะพั‚ ะถะต ัะฐะผั‹ะน `WebSocket` ะฟั€ะพัั‚ะพ ะดะปั ัƒะดะพะฑัั‚ะฒะฐ. ะะฐ ัะฐะผะพะผ ะดะตะปะต ัั‚ะพ `WebSocket` ะธะท Starlette. + +/// + +## ะžะถะธะดะฐะฝะธะต ะธ ะพั‚ะฟั€ะฐะฒะบะฐ ัะพะพะฑั‰ะตะฝะธะน + +ะงะตั€ะตะท ัะฝะดะฟะพะธะฝั‚ ะฒะตะฑ-ัะพะบะตั‚ะฐ ะฒั‹ ะผะพะถะตั‚ะต ะฟะพะปัƒั‡ะฐั‚ัŒ ะธ ะพั‚ะฟั€ะฐะฒะปัั‚ัŒ ัะพะพะฑั‰ะตะฝะธั. + +{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} + +ะ’ั‹ ะผะพะถะตั‚ะต ะฟะพะปัƒั‡ะฐั‚ัŒ ะธ ะพั‚ะฟั€ะฐะฒะปัั‚ัŒ ะดะฒะพะธั‡ะฝั‹ะต, ั‚ะตะบัั‚ะพะฒั‹ะต ะธ JSON ะดะฐะฝะฝั‹ะต. + +## ะŸั€ะพะฒะตั€ะบะฐ ะฒ ะดะตะนัั‚ะฒะธะธ + +ะ•ัะปะธ ะฒะฐัˆ ั„ะฐะนะป ะฝะฐะทั‹ะฒะฐะตั‚ัั `main.py`, ั‚ะพ ะทะฐะฟัƒัั‚ะธั‚ะต ะฟั€ะธะปะพะถะตะฝะธะต ะบะพะผะฐะฝะดะพะน: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +ะžั‚ะบั€ะพะนั‚ะต ะฑั€ะฐัƒะทะตั€ ะฟะพ ะฐะดั€ะตััƒ http://127.0.0.1:8000. + +ะ’ั‹ ัƒะฒะธะดะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะฟั€ะพัั‚ะตะฝัŒะบัƒัŽ ัั‚ั€ะฐะฝะธั†ัƒ: + + + +ะ’ั‹ ะผะพะถะตั‚ะต ะฝะฐะฑะธั€ะฐั‚ัŒ ัะพะพะฑั‰ะตะฝะธั ะฒ ะฟะพะปะต ะฒะฒะพะดะฐ ะธ ะพั‚ะฟั€ะฐะฒะปัั‚ัŒ ะธั…: + + + +ะ˜ ะฒะฐัˆะต **FastAPI** ะฟั€ะธะปะพะถะตะฝะธะต ั ะฒะตะฑ-ัะพะบะตั‚ะฐะผะธ ะพั‚ะฒะตั‚ะธั‚: + + + +ะ’ั‹ ะผะพะถะตั‚ะต ะพั‚ะฟั€ะฐะฒะปัั‚ัŒ ะธ ะฟะพะปัƒั‡ะฐั‚ัŒ ะผะฝะพะถะตัั‚ะฒะพ ัะพะพะฑั‰ะตะฝะธะน: + + + +ะ˜ ะฒัะต ะพะฝะธ ะฑัƒะดัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะดะฝะพ ะธ ั‚ะพ ะถะต ะฒะตะฑ-ัะพะบะตั‚ ัะพะตะดะธะฝะตะฝะธะต. + +## ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต `Depends` ะธ ะฝะต ั‚ะพะปัŒะบะพ + +ะ’ั‹ ะผะพะถะตั‚ะต ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธะท `fastapi` ะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฒ ัะฝะดะฟะพะธะฝั‚ะต ะฒะตะฑัะพะบะตั‚ะฐ: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +ะžะฝะธ ั€ะฐะฑะพั‚ะฐัŽั‚ ั‚ะฐะบ ะถะต, ะบะฐะบ ะธ ะฒ ะดั€ัƒะณะธั… FastAPI ัะฝะดะฟะพะธะฝั‚ะฐั…/*ะพะฟะตั€ะฐั†ะธัั… ะฟัƒั‚ะธ*: + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info | ะŸั€ะธะผะตั‡ะฐะฝะธะต + +ะ’ ะฒะตะฑ-ัะพะบะตั‚ะต ะฒั‹ะทั‹ะฒะฐั‚ัŒ `HTTPException` ะฝะต ะธะผะตะตั‚ ัะผั‹ัะปะฐ. ะ’ะผะตัั‚ะพ ัั‚ะพะณะพ ะฝัƒะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `WebSocketException`. + +ะ—ะฐะบั€ั‹ะฒะฐัŽั‰ะธะน ัั‚ะฐั‚ัƒั ะบะพะด ะผะพะถะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธะท valid codes defined in the specification. + +/// + +### ะ’ะตะฑ-ัะพะบะตั‚ั‹ ั ะทะฐะฒะธัะธะผะพัั‚ัะผะธ: ะฟั€ะพะฒะตั€ะบะฐ ะฒ ะดะตะนัั‚ะฒะธะธ + +ะ•ัะปะธ ะฒะฐัˆ ั„ะฐะนะป ะฝะฐะทั‹ะฒะฐะตั‚ัั `main.py`, ั‚ะพ ะทะฐะฟัƒัั‚ะธั‚ะต ะฟั€ะธะปะพะถะตะฝะธะต ะบะพะผะฐะฝะดะพะน: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +ะžั‚ะบั€ะพะนั‚ะต ะฑั€ะฐัƒะทะตั€ ะฟะพ ะฐะดั€ะตััƒ http://127.0.0.1:8000. + +ะขะฐะผ ะฒั‹ ะผะพะถะตั‚ะต ะทะฐะดะฐั‚ัŒ: + +* "Item ID", ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ะฒ ะฟัƒั‚ะธ. +* "Token", ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ะบะฐะบ query-ะฟะฐั€ะฐะผะตั‚ั€. + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ query-ะฟะฐั€ะฐะผะตั‚ั€ `token` ะฑัƒะดะตั‚ ะพะฑั€ะฐะฑะพั‚ะฐะฝ ะฒ ะทะฐะฒะธัะธะผะพัั‚ะธ. + +/// + +ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะฟะพะดะบะปัŽั‡ะธั‚ัŒัั ะบ ะฒะตะฑ-ัะพะบะตั‚ัƒ ะธ ะฝะฐั‡ะธะฝะฐั‚ัŒ ะพั‚ะฟั€ะฐะฒะบัƒ ะธ ะฟะพะปัƒั‡ะตะฝะธะต ัะพะพะฑั‰ะตะฝะธะน: + + + +## ะžะฑั€ะฐะฑะพั‚ะบะฐ ะพั‚ะบะปัŽั‡ะตะฝะธะน ะธ ั€ะฐะฑะพั‚ะฐ ั ะฝะตัะบะพะปัŒะบะธะผะธ ะบะปะธะตะฝั‚ะฐะผะธ + +ะ•ัะปะธ ะฒะตะฑ-ัะพะบะตั‚ ัะพะตะดะธะฝะตะฝะธะต ะทะฐะบั€ั‹ั‚ะพ, ั‚ะพ `await websocket.receive_text()` ะฒั‹ะทะพะฒะตั‚ ะธัะบะปัŽั‡ะตะฝะธะต `WebSocketDisconnect`, ะบะพั‚ะพั€ะพะต ะผะพะถะฝะพ ะฟะพะนะผะฐั‚ัŒ ะธ ะพะฑั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะฐะบ ะฒ ัั‚ะพะผ ะฟั€ะธะผะตั€ะต: + +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} + +ะงั‚ะพะฑั‹ ะฒะพัะฟั€ะพะธะทะฒะตัั‚ะธ ะฟั€ะธะผะตั€: + +* ะžั‚ะบั€ะพะนั‚ะต ะฟั€ะธะปะพะถะตะฝะธะต ะฒ ะฝะตัะบะพะปัŒะบะธั… ะฒะบะปะฐะดะบะฐั… ะฑั€ะฐัƒะทะตั€ะฐ. +* ะžั‚ะฟั€ะฐะฒัŒั‚ะต ะธะท ะฝะธั… ัะพะพะฑั‰ะตะฝะธั. +* ะ—ะฐั‚ะตะผ ะทะฐะบั€ะพะนั‚ะต ะพะดะฝัƒ ะธะท ะฒะบะปะฐะดะพะบ. + +ะญั‚ะพ ะฒั‹ะทะพะฒะตั‚ ะธัะบะปัŽั‡ะตะฝะธะต `WebSocketDisconnect`, ะธ ะฒัะต ะพัั‚ะฐะปัŒะฝั‹ะต ะบะปะธะตะฝั‚ั‹ ะฟะพะปัƒั‡ะฐั‚ ัะปะตะดัƒัŽั‰ะตะต ัะพะพะฑั‰ะตะฝะธะต: + +``` +Client #1596980209979 left the chat +``` + +/// tip | ะŸั€ะธะผะตั‡ะฐะฝะธะต + +ะŸั€ะธะปะพะถะตะฝะธะต ะฒั‹ัˆะต - ัั‚ะพ ะฒัะตะณะพ ะปะธัˆัŒ ะฟั€ะพัั‚ะพะน ะผะธะฝะธะผะฐะปัŒะฝั‹ะน ะฟั€ะธะผะตั€, ะดะตะผะพะฝัั‚ั€ะธั€ัƒัŽั‰ะธะน ะพะฑั€ะฐะฑะพั‚ะบัƒ ะธ ะฟะตั€ะตะดะฐั‡ัƒ ัะพะพะฑั‰ะตะฝะธะน ะฝะตัะบะพะปัŒะบะธะผ ะฒะตะฑ-ัะพะบะตั‚ ัะพะตะดะธะฝะตะฝะธัะผ. + +ะะพ ะธะผะตะนั‚ะต ะฒ ะฒะธะดัƒ, ั‡ั‚ะพ ัั‚ะพ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฒ ะพะดะฝะพะผ ะฟั€ะพั†ะตััะต ะธ ั‚ะพะปัŒะบะพ ะฟะพะบะฐ ะพะฝ ะฐะบั‚ะธะฒะตะฝ, ั‚ะฐะบ ะบะฐะบ ะฒัั‘ ะพะฑั€ะฐะฑะฐั‚ั‹ะฒะฐะตั‚ัั ะฒ ะฟั€ะพัั‚ะพะผ ัะฟะธัะบะต ะฒ ะพะฟะตั€ะฐั‚ะธะฒะฝะพะน ะฟะฐะผัั‚ะธ. + +ะ•ัะปะธ ะฝัƒะถะฝะพ ั‡ั‚ะพ-ั‚ะพ ะปะตะณะบะพ ะธะฝั‚ะตะณั€ะธั€ัƒะตะผะพะต ั FastAPI, ะฝะพ ะฑะพะปะตะต ะฝะฐะดะตะถะฝะพะต ะธ ั ะฟะพะดะดะตั€ะถะบะพะน Redis, PostgreSQL ะธะปะธ ะดั€ัƒะณะพะณะพ, ั‚ะพ ะผะพะถะฝะพ ะฒะพัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั encode/broadcaster. + +/// + +## ะ”ะพะฟะพะปะฝะธั‚ะตะปัŒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั + +ะ”ะปั ะฑะพะปะตะต ะณะปัƒะฑะพะบะพะณะพ ะธะทัƒั‡ะตะฝะธั ั‚ะตะผั‹ ะฒะพัะฟะพะปัŒะทัƒะนั‚ะตััŒ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะตะน Starlette: + +* The `WebSocket` class. +* Class-based WebSocket handling. diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md new file mode 100644 index 000000000..7c3dc288f --- /dev/null +++ b/docs/ru/docs/tutorial/bigger-applications.md @@ -0,0 +1,556 @@ +# ะ‘ะพะปัŒัˆะธะต ะฟั€ะธะปะพะถะตะฝะธั, ะฒ ะบะพั‚ะพั€ั‹ั… ะผะฝะพะณะพ ั„ะฐะนะปะพะฒ + +ะŸั€ะธ ะฟะพัั‚ั€ะพะตะฝะธะธ ะฟั€ะธะปะพะถะตะฝะธั ะธะปะธ ะฒะตะฑ-API ะฝะฐะผ ั€ะตะดะบะพ ัƒะดะฐะตั‚ัั ะฟะพะผะตัั‚ะธั‚ัŒ ะฒัั‘ ะฒ ะพะดะธะฝ ั„ะฐะนะป. + +**FastAPI** ะฟั€ะตะดะพัั‚ะฐะฒะปัะตั‚ ัƒะดะพะฑะฝั‹ะน ะธะฝัั‚ั€ัƒะผะตะฝั‚ะฐั€ะธะน, ะบะพั‚ะพั€ั‹ะน ะฟะพะทะฒะพะปัะตั‚ ะฝะฐะผ ัั‚ั€ัƒะบั‚ัƒั€ะธั€ะพะฒะฐั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธะต, ัะพั…ั€ะฐะฝัั ะฟั€ะธ ัั‚ะพะผ ะฒััŽ ะฝะตะพะฑั…ะพะดะธะผัƒัŽ ะณะธะฑะบะพัั‚ัŒ. + +/// info | ะŸั€ะธะผะตั‡ะฐะฝะธะต + +ะ•ัะปะธ ะฒั‹ ั€ะฐะฝัŒัˆะต ะธัะฟะพะปัŒะทะพะฒะฐะปะธ Flask, ั‚ะพ ัั‚ะพ ะฐะฝะฐะปะพะณ ัˆะฐะฑะปะพะฝะพะฒ Flask (Flask's Blueprints). + +/// + +## ะŸั€ะธะผะตั€ ัั‚ั€ัƒะบั‚ัƒั€ั‹ ะฟั€ะธะปะพะถะตะฝะธั + +ะ”ะฐะฒะฐะนั‚ะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ะฝะฐัˆะต ะฟั€ะธะปะพะถะตะฝะธะต ะธะผะตะตั‚ ัะปะตะดัƒัŽั‰ัƒัŽ ัั‚ั€ัƒะบั‚ัƒั€ัƒ: + +``` +. +โ”œโ”€โ”€ app +โ”‚ย ย  โ”œโ”€โ”€ __init__.py +โ”‚ย ย  โ”œโ”€โ”€ main.py +โ”‚ย ย  โ”œโ”€โ”€ dependencies.py +โ”‚ย ย  โ””โ”€โ”€ routers +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ items.py +โ”‚ย ย  โ”‚ โ””โ”€โ”€ users.py +โ”‚ย ย  โ””โ”€โ”€ internal +โ”‚ย ย  โ”œโ”€โ”€ __init__.py +โ”‚ย ย  โ””โ”€โ”€ admin.py +``` + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ะฒ ะบะฐะถะดะพะผ ะบะฐั‚ะฐะปะพะณะต ะธ ะฟะพะดะบะฐั‚ะฐะปะพะณะต ะธะผะตะตั‚ัั ั„ะฐะนะป `__init__.py` + +ะญั‚ะพ ะบะฐะบ ั€ะฐะท ั‚ะพ, ั‡ั‚ะพ ะฟะพะทะฒะพะปัะตั‚ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะบะพะด ะธะท ะพะดะฝะพะณะพ ั„ะฐะนะปะฐ ะฒ ะดั€ัƒะณะพะน. + +ะะฐะฟั€ะธะผะตั€, ะฒ ั„ะฐะนะปะต `app/main.py` ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ัะปะตะดัƒัŽั‰ะฐั ัั‚ั€ะพะบะฐ: + +``` +from app.routers import items +``` + +/// + +* ะ’ัั‘ ะฟะพะผะตั‰ะฐะตั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app`. ะ’ ะฝั‘ะผ ั‚ะฐะบะถะต ะฝะฐั…ะพะดะธั‚ัั ะฟัƒัั‚ะพะน ั„ะฐะนะป `app/__init__.py`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, `app` ัะฒะปัะตั‚ัั "Python-ะฟะฐะบะตั‚ะพะผ" (ะบะพะปะปะตะบั†ะธะตะน ะผะพะดัƒะปะตะน Python). +* ะžะฝ ัะพะดะตั€ะถะธั‚ ั„ะฐะนะป `app/main.py`. ะ”ะฐะฝะฝั‹ะน ั„ะฐะนะป ัะฒะปัะตั‚ัั ั‡ะฐัั‚ัŒัŽ ะฟะฐะบะตั‚ะฐ (ั‚.ะต. ะฝะฐั…ะพะดะธั‚ัั ะฒะฝัƒั‚ั€ะธ ะบะฐั‚ะฐะปะพะณะฐ, ัะพะดะตั€ะถะฐั‰ะตะณะพ ั„ะฐะนะป `__init__.py`), ะธ, ัะพะพั‚ะฒะตั‚ัั‚ะฒะตะฝะฝะพ, ะพะฝ ัะฒะปัะตั‚ัั ะผะพะดัƒะปะตะผ ะฟะฐะบะตั‚ะฐ: `app.main`. +* ะžะฝ ั‚ะฐะบะถะต ัะพะดะตั€ะถะธั‚ ั„ะฐะนะป `app/dependencies.py`, ะบะพั‚ะพั€ั‹ะน ั‚ะฐะบะถะต, ะบะฐะบ ะธ `app/main.py`, ัะฒะปัะตั‚ัั ะผะพะดัƒะปะตะผ: `app.dependencies`. +* ะ—ะดะตััŒ ั‚ะฐะบะถะต ะฝะฐั…ะพะดะธั‚ัั ะฟะพะดะบะฐั‚ะฐะปะพะณ `app/routers/`, ัะพะดะตั€ะถะฐั‰ะธะน `__init__.py`. ะžะฝ ัะฒะปัะตั‚ัั ััƒะฑ-ะฟะฐะบะตั‚ะพะผ: `app.routers`. +* ะคะฐะนะป `app/routers/items.py` ะฝะฐั…ะพะดะธั‚ัั ะฒะฝัƒั‚ั€ะธ ะฟะฐะบะตั‚ะฐ `app/routers/`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะพะฝ ัะฒะปัะตั‚ัั ััƒะฑ-ะผะพะดัƒะปะตะผ: `app.routers.items`. +* ะขะพั‡ะฝะพ ั‚ะฐะบะถะต `app/routers/users.py` ัะฒะปัะตั‚ัั ะตั‰ั‘ ะพะดะฝะธะผ ััƒะฑ-ะผะพะดัƒะปะตะผ: `app.routers.users`. +* ะŸะพะดะบะฐั‚ะฐะปะพะณ `app/internal/`, ัะพะดะตั€ะถะฐั‰ะธะน ั„ะฐะนะป `__init__.py`, ัะฒะปัะตั‚ัั ะตั‰ั‘ ะพะดะฝะธะผ ััƒะฑ-ะฟะฐะบะตั‚ะพะผ: `app.internal`. +* ะ ั„ะฐะนะป `app/internal/admin.py` ัะฒะปัะตั‚ัั ะตั‰ั‘ ะพะดะฝะธะผ ััƒะฑ-ะผะพะดัƒะปะตะผ: `app.internal.admin`. + + + +ะขะฐ ะถะต ัะฐะผะฐั ั„ะฐะนะปะพะฒะฐั ัั‚ั€ัƒะบั‚ัƒั€ะฐ ะฟั€ะธะปะพะถะตะฝะธั, ะฝะพ ั ะบะพะผะผะตะฝั‚ะฐั€ะธัะผะธ: + +``` +. +โ”œโ”€โ”€ app # "app" ะฟะฐะบะตั‚ +โ”‚ย ย  โ”œโ”€โ”€ __init__.py # ัั‚ะพั‚ ั„ะฐะนะป ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "app" ะฒ "Python-ะฟะฐะบะตั‚" +โ”‚ย ย  โ”œโ”€โ”€ main.py # ะผะพะดัƒะปัŒ "main", ะฝะฐะฟั€.: import app.main +โ”‚ย ย  โ”œโ”€โ”€ dependencies.py # ะผะพะดัƒะปัŒ "dependencies", ะฝะฐะฟั€.: import app.dependencies +โ”‚ย ย  โ””โ”€โ”€ routers # ััƒะฑ-ะฟะฐะบะตั‚ "routers" +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ __init__.py # ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "routers" ะฒ ััƒะฑ-ะฟะฐะบะตั‚ +โ”‚ย ย  โ”‚ โ”œโ”€โ”€ items.py # ััƒะฑ-ะผะพะดัƒะปัŒ "items", ะฝะฐะฟั€.: import app.routers.items +โ”‚ย ย  โ”‚ โ””โ”€โ”€ users.py # ััƒะฑ-ะผะพะดัƒะปัŒ "users", ะฝะฐะฟั€.: import app.routers.users +โ”‚ย ย  โ””โ”€โ”€ internal # ััƒะฑ-ะฟะฐะบะตั‚ "internal" +โ”‚ย ย  โ”œโ”€โ”€ __init__.py # ะฟั€ะตะฒั€ะฐั‰ะฐะตั‚ "internal" ะฒ ััƒะฑ-ะฟะฐะบะตั‚ +โ”‚ย ย  โ””โ”€โ”€ admin.py # ััƒะฑ-ะผะพะดัƒะปัŒ "admin", ะฝะฐะฟั€.: import app.internal.admin +``` + +## `APIRouter` + +ะ”ะฐะฒะฐะนั‚ะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ะดะปั ั€ะฐะฑะพั‚ั‹ ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผะธ ะธัะฟะพะปัŒะทัƒะตั‚ัั ะพั‚ะดะตะปัŒะฝั‹ะน ั„ะฐะนะป (ััƒะฑ-ะผะพะดัƒะปัŒ) `/app/routers/users.py`. + +ะ”ะปั ะปัƒั‡ัˆะตะน ะพั€ะณะฐะฝะธะทะฐั†ะธะธ ะฟั€ะธะปะพะถะตะฝะธั, ะฒั‹ ั…ะพั‚ะธั‚ะต ะพั‚ะดะตะปะธั‚ัŒ ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ, ัะฒัะทะฐะฝะฝั‹ะต ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผะธ, ะพั‚ ะพัั‚ะฐะปัŒะฝะพะณะพ ะบะพะดะฐ. + +ะะพ ั‚ะฐะบ, ั‡ั‚ะพะฑั‹ ัั‚ะธ ะพะฟะตั€ะฐั†ะธะธ ะฟะพ-ะฟั€ะตะถะฝะตะผัƒ ะพัั‚ะฐะฒะฐะปะธััŒ ั‡ะฐัั‚ัŒัŽ **FastAPI** ะฟั€ะธะปะพะถะตะฝะธั/ะฒะตะฑ-API (ั‡ะฐัั‚ัŒัŽ ะพะดะฝะพะณะพ ะฟะฐะบะตั‚ะฐ) + +ะก ะฟะพะผะพั‰ัŒัŽ `APIRouter` ะฒั‹ ะผะพะถะตั‚ะต ัะพะทะดะฐั‚ัŒ *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* (*ัะฝะดะฟะพะธะฝั‚ั‹*) ะดะปั ะดะฐะฝะฝะพะณะพ ะผะพะดัƒะปั. + + +### ะ˜ะผะฟะพั€ั‚ `APIRouter` + +ะขะพั‡ะฝะพ ั‚ะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะปัƒั‡ะฐะต ั ะบะปะฐััะพะผ `FastAPI`, ะฒะฐะผ ะฝัƒะถะฝะพ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธ ัะพะทะดะฐั‚ัŒ ะพะฑัŠะตะบั‚ ะบะปะฐััะฐ `APIRouter`. + +```Python hl_lines="1 3" title="app/routers/users.py" +{!../../docs_src/bigger_applications/app/routers/users.py!} +``` + +### ะกะพะทะดะฐะฝะธะต *ัะฝะดะฟะพะธะฝั‚ะพะฒ* ั ะฟะพะผะพั‰ัŒัŽ `APIRouter` + +ะ’ ะดะฐะปัŒะฝะตะนัˆะตะผ ะธัะฟะพะปัŒะทัƒะนั‚ะต `APIRouter` ะดะปั ะพะฑัŠัะฒะปะตะฝะธั *ัะฝะดะฟะพะธะฝั‚ะพะฒ*, ั‚ะพั‡ะฝะพ ั‚ะฐะบะถะต, ะบะฐะบ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ะบะปะฐัั `FastAPI`: + +```Python hl_lines="6 11 16" title="app/routers/users.py" +{!../../docs_src/bigger_applications/app/routers/users.py!} +``` + +ะ’ั‹ ะผะพะถะตั‚ะต ะดัƒะผะฐั‚ัŒ ะพะฑ `APIRouter` ะบะฐะบ ะพะฑ "ัƒะผะตะฝัŒัˆะตะฝะฝะพะน ะฒะตั€ัะธะธ" ะบะปะฐััะฐ FastAPI`. + +`APIRouter` ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะฒัะต ั‚ะต ะถะต ัะฐะผั‹ะต ะพะฟั†ะธะธ. + +`APIRouter` ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะฒัะต ั‚ะต ะถะต ัะฐะผั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹, ั‚ะฐะบะธะต ะบะฐะบ `parameters`, `responses`, `dependencies`, `tags`, ะธ ั‚. ะด. + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะ’ ะดะฐะฝะฝะพะผ ะฟั€ะธะผะตั€ะต, ะฒ ะบะฐั‡ะตัั‚ะฒะต ะฝะฐะทะฒะฐะฝะธั ะฟะตั€ะตะผะตะฝะฝะพะน ะธัะฟะพะปัŒะทัƒะตั‚ัั `router`, ะฝะพ ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะปัŽะฑะพะต ะดั€ัƒะณะพะต ะธะผั. + +/// + +ะœั‹ ัะพะฑะธั€ะฐะตะผัั ะฟะพะดะบะปัŽั‡ะธั‚ัŒ ะดะฐะฝะฝั‹ะน `APIRouter` ะบ ะฝะฐัˆะตะผัƒ ะพัะฝะพะฒะฝะพะผัƒ ะฟั€ะธะปะพะถะตะฝะธัŽ ะฝะฐ `FastAPI`, ะฝะพ ัะฝะฐั‡ะฐะปะฐ ะดะฐะฒะฐะนั‚ะต ะฟั€ะพะฒะตั€ะธะผ ะทะฐะฒะธัะธะผะพัั‚ะธ ะธ ัะพะทะดะฐะดะธะผ ะตั‰ั‘ ะพะดะธะฝ ะผะพะดัƒะปัŒ ั `APIRouter`. + +## ะ—ะฐะฒะธัะธะผะพัั‚ะธ + +ะะฐะผ ะฟะพะฝะฐะดะพะฑัั‚ัั ะฝะตะบะพั‚ะพั€ั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ, ะบะพั‚ะพั€ั‹ะต ะผั‹ ะฑัƒะดะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฒ ั€ะฐะทะฝั‹ั… ะผะตัั‚ะฐั… ะฝะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั. + +ะœั‹ ะฟะพะผะตัั‚ะธะผ ะธั… ะฒ ะพั‚ะดะตะปัŒะฝั‹ะน ะผะพะดัƒะปัŒ `dependencies` (`app/dependencies.py`). + +ะขะตะฟะตั€ัŒ ะผั‹ ะฒะพัะฟะพะปัŒะทัƒะตะผัั ะฟั€ะพัั‚ะพะน ะทะฐะฒะธัะธะผะพัั‚ัŒัŽ, ั‡ั‚ะพะฑั‹ ะฟั€ะพั‡ะธั‚ะฐั‚ัŒ ะบะฐัั‚ะพะผะธะทะธั€ะพะฒะฐะฝะฝั‹ะน `X-Token` ะธะท ะทะฐะณะพะปะพะฒะบะฐ: + +//// tab | Python 3.9+ + +```Python hl_lines="3 6-8" title="app/dependencies.py" +{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} +``` + +//// + +//// tab | Python 3.8+ + +```Python hl_lines="1 5-7" title="app/dependencies.py" +{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} +``` + +//// + +//// tab | Python 3.8+ non-Annotated + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะœั‹ ั€ะตะบะพะผะตะฝะดัƒะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฒะตั€ัะธัŽ `Annotated`, ะบะพะณะดะฐ ัั‚ะพ ะฒะพะทะผะพะถะฝะพ. + +/// + +```Python hl_lines="1 4-6" title="app/dependencies.py" +{!> ../../docs_src/bigger_applications/app/dependencies.py!} +``` + +//// + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะ”ะปั ะฟั€ะพัั‚ะพั‚ั‹ ะผั‹ ะฒะพัะฟะพะปัŒะทะพะฒะฐะปะธััŒ ะฝะตะบะธะผ ะฒะพะพะฑั€ะฐะถะฐะตะผั‹ะผ ะทะฐะณะพะปะพะฒะพะบะพะผ. + +ะ’ ั€ะตะฐะปัŒะฝั‹ั… ัะปัƒั‡ะฐัั… ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะฝะฐะธะปัƒั‡ัˆะธั… ั€ะตะทัƒะปัŒั‚ะฐั‚ะพะฒ ะธัะฟะพะปัŒะทัƒะนั‚ะต ะธะฝั‚ะตะณั€ะธั€ะพะฒะฐะฝะฝั‹ะต ัƒั‚ะธะปะธั‚ั‹ ะพะฑะตัะฟะตั‡ะตะฝะธั ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ [Security utilities](security/index.md){.internal-link target=_blank}. + +/// + +## ะ•ั‰ั‘ ะพะดะธะฝ ะผะพะดัƒะปัŒ ั `APIRouter` + +ะ”ะฐะฒะฐะนั‚ะต ั‚ะฐะบะถะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ัƒ ะฒะฐั ะตัั‚ัŒ *ัะฝะดะฟะพะธะฝั‚ั‹*, ะพั‚ะฒะตั‡ะฐัŽั‰ะธะต ะทะฐ ะพะฑั€ะฐะฑะพั‚ะบัƒ "items", ะธ ะพะฝะธ ะฝะฐั…ะพะดัั‚ัั ะฒ ะผะพะดัƒะปะต `app/routers/items.py`. + +ะฃ ะฒะฐั ะพะฟั€ะตะดะตะปะตะฝั‹ ัะปะตะดัƒัŽั‰ะธะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* (*ัะฝะดะฟะพะธะฝั‚ั‹*): + +* `/items/` +* `/items/{item_id}` + +ะขัƒั‚ ะฒัั‘ ั‚ะพั‡ะฝะพ ั‚ะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะธั‚ัƒะฐั†ะธะธ ั `app/routers/users.py`. + +ะะพ ั‚ะตะฟะตั€ัŒ ะผั‹ ั…ะพั‚ะธะผ ะฟะพัั‚ัƒะฟะธั‚ัŒ ะฝะตะผะฝะพะณะพ ัƒะผะฝะตะต ะธ ัะปะตะณะบะฐ ัƒะฟั€ะพัั‚ะธั‚ัŒ ะบะพะด. + +ะœั‹ ะทะฝะฐะตะผ, ั‡ั‚ะพ ะฒัะต *ัะฝะดะฟะพะธะฝั‚ั‹* ะดะฐะฝะฝะพะณะพ ะผะพะดัƒะปั ะธะผะตัŽั‚ ะฝะตะบะพั‚ะพั€ั‹ะต ะพะฑั‰ะธะต ัะฒะพะนัั‚ะฒะฐ: + +* ะŸั€ะตั„ะธะบั ะฟัƒั‚ะธ: `/items`. +* ะขะตะณะธ: (ะพะดะธะฝ ะตะดะธะฝัั‚ะฒะตะฝะฝั‹ะน ั‚ะตะณ: `items`). +* ะ”ะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะพั‚ะฒะตั‚ั‹ (responses) +* ะ—ะฐะฒะธัะธะผะพัั‚ะธ: ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต ัะพะทะดะฐะฝะฝะพะน ะฝะฐะผะธ ะทะฐะฒะธัะธะผะพัั‚ะธ `X-token` + +ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะฒะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะปัั‚ัŒ ะฒัะต ัั‚ะธ ัะฒะพะนัั‚ะฒะฐ ะฒ ั„ัƒะฝะบั†ะธัŽ ะบะฐะถะดะพะณะพ ะพั‚ะดะตะปัŒะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*, +ะผั‹ ะดะพะฑะฐะฒะธะผ ะธั… ะฒ `APIRouter`. + +```Python hl_lines="5-10 16 21" title="app/routers/items.py" +{!../../docs_src/bigger_applications/app/routers/items.py!} +``` + +ะขะฐะบ ะบะฐะบ ะบะฐะถะดั‹ะน *ัะฝะดะฟะพะธะฝั‚* ะฝะฐั‡ะธะฝะฐะตั‚ัั ั ัะธะผะฒะพะปะฐ `/`: + +```Python hl_lines="1" +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +...ั‚ะพ ะฟั€ะตั„ะธะบั ะฝะต ะดะพะปะถะตะฝ ะทะฐะบะฐะฝั‡ะธะฒะฐั‚ัŒัั ัะธะผะฒะพะปะพะผ `/`. + +ะ’ ะฝะฐัˆะตะผ ัะปัƒั‡ะฐะต ะฟั€ะตั„ะธะบัะพะผ ัะฒะปัะตั‚ัั `/items`. + +ะœั‹ ั‚ะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะฒ ะฝะฐัˆ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ (router) ัะฟะธัะพะบ `ั‚ะตะณะพะฒ` (`tags`) ะธ ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ั… `ะพั‚ะฒะตั‚ะพะฒ` (`responses`), ะบะพั‚ะพั€ั‹ะต ัะฒะปััŽั‚ัั ะพะฑั‰ะธะผะธ ะดะปั ะบะฐะถะดะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*. + +ะ˜ ะตั‰ั‘ ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะฒ ะฝะฐัˆ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ ัะฟะธัะพะบ `ะทะฐะฒะธัะธะผะพัั‚ะตะน`, ะบะพั‚ะพั€ั‹ะต ะดะพะปะถะฝั‹ ะฒั‹ะทั‹ะฒะฐั‚ัŒัั ะฟั€ะธ ะบะฐะถะดะพะผ ะพะฑั€ะฐั‰ะตะฝะธะธ ะบ *ัะฝะดะฟะพะธะฝั‚ะฐะผ*. + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ั‚ะฐะบะถะต, ะบะฐะบ ะธ ะฒ ัะปัƒั‡ะฐะต ั ะทะฐะฒะธัะธะผะพัั‚ัะผะธ ะฒ ะดะตะบะพั€ะฐั‚ะพั€ะฐั… *ัะฝะดะฟะพะธะฝั‚ะพะฒ* ([dependencies in *path operation decorators*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ะฝะธะบะฐะบะพะณะพ ะทะฝะฐั‡ะตะฝะธั ะฒ *ั„ัƒะฝะบั†ะธัŽ ัะฝะดะฟะพะธะฝั‚ะฐ* ะฟะตั€ะตะดะฐะฝะพ ะฝะต ะฑัƒะดะตั‚. + +/// + +ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ะผั‹ ะฟะพะปัƒั‡ะธะผ ัะปะตะดัƒัŽั‰ะธะต ัะฝะดะฟะพะธะฝั‚ั‹: + +* `/items/` +* `/items/{item_id}` + +...ะบะฐะบ ะผั‹ ะธ ะฟะปะฐะฝะธั€ะพะฒะฐะปะธ. + +* ะžะฝะธ ะฑัƒะดัƒั‚ ะฟะพะผะตั‡ะตะฝั‹ ั‚ะตะณะฐะผะธ ะธะท ะทะฐะดะฐะฝะฝะพะณะพ ัะฟะธัะบะฐ, ะฒ ะฝะฐัˆะตะผ ัะปัƒั‡ะฐะต ัั‚ะพ `"items"`. + * ะญั‚ะธ ั‚ะตะณะธ ะพัะพะฑะตะฝะฝะพ ะฟะพะปะตะทะฝั‹ ะดะปั ัะธัั‚ะตะผั‹ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะธะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ (ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ OpenAPI). +* ะšะฐะถะดั‹ะน ะธะท ะฝะธั… ะฑัƒะดะตั‚ ะฒะบะปัŽั‡ะฐั‚ัŒ ะฟั€ะตะดะพะฟั€ะตะดะตะปะตะฝะฝั‹ะต ะพั‚ะฒะตั‚ั‹ `responses`. +* ะšะฐะถะดั‹ะน *ัะฝะดะฟะพะธะฝั‚* ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ัะฟะธัะพะบ ะทะฐะฒะธัะธะผะพัั‚ะตะน (`dependencies`), ะธัะฟะพะปะฝัะตะผั‹ั… ะฟะตั€ะตะด ะฒั‹ะทะพะฒะพะผ *ัะฝะดะฟะพะธะฝั‚ะฐ*. + * ะ•ัะปะธ ะฒั‹ ะพะฟั€ะตะดะตะปะธะปะธ ะทะฐะฒะธัะธะผะพัั‚ะธ ะฒ ัะฐะผะพะน ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ, **ั‚ะพ ะพะฝะฐ ั‚ะฐะบะถะต ะฑัƒะดะตั‚ ะฒั‹ะฟะพะปะฝะตะฝะฐ**. + * ะกะฝะฐั‡ะฐะปะฐ ะฒั‹ะฟะพะปะฝััŽั‚ัั ะทะฐะฒะธัะธะผะพัั‚ะธ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ, ะทะฐั‚ะตะผ ะฒั‹ะทั‹ะฒะฐัŽั‚ัั ะทะฐะฒะธัะธะผะพัั‚ะธ, ะพะฟั€ะตะดะตะปะตะฝะฝั‹ะต ะฒ ะดะตะบะพั€ะฐั‚ะพั€ะต *ัะฝะดะฟะพะธะฝั‚ะฐ* ([`dependencies` in the decorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ะธ, ะฝะฐะบะพะฝะตั†, ะพะฑั‹ั‡ะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ะธั‡ะตัะบะธะต ะทะฐะฒะธัะธะผะพัั‚ะธ. + * ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ะทะฐะฒะธัะธะผะพัั‚ะธ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ ั ะพะฑะปะฐัั‚ัะผะธ ะฒะธะดะธะผะพัั‚ะธ (`scopes`) [`Security` dependencies with `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะะฐะฟั€ะธะผะตั€, ั ะฟะพะผะพั‰ัŒัŽ ะทะฐะฒะธัะธะผะพัั‚ะตะน ะฒ `APIRouter` ะผั‹ ะผะพะถะตะผ ะฟะพั‚ั€ะตะฑะพะฒะฐั‚ัŒ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ ะดะปั ะดะพัั‚ัƒะฟะฐ ะบะพ ะฒัะตะน ะณั€ัƒะฟะฟะต *ัะฝะดะฟะพะธะฝั‚ะพะฒ*, ะฝะต ัƒะบะฐะทั‹ะฒะฐั ะทะฐะฒะธัะธะผะพัั‚ะธ ะดะปั ะบะฐะถะดะพะน ะพั‚ะดะตะปัŒะฝะพะน ั„ัƒะฝะบั†ะธะธ *ัะฝะดะฟะพะธะฝั‚ะฐ*. + +/// + +/// check | ะ—ะฐะผะตั‚ะบะฐ + +ะŸะฐั€ะฐะผะตั‚ั€ั‹ `prefix`, `tags`, `responses` ะธ `dependencies` ะพั‚ะฝะพััั‚ัั ะบ ั„ัƒะฝะบั†ะธะพะฝะฐะปัƒ **FastAPI**, ะฟะพะผะพะณะฐัŽั‰ะตะผัƒ ะธะทะฑะตะถะฐั‚ัŒ ะดัƒะฑะปะธั€ะพะฒะฐะฝะธั ะบะพะดะฐ. + +/// + +### ะ˜ะผะฟะพั€ั‚ ะทะฐะฒะธัะธะผะพัั‚ะตะน + +ะะฐัˆ ะบะพะด ะฝะฐั…ะพะดะธั‚ัั ะฒ ะผะพะดัƒะปะต `app.routers.items` (ั„ะฐะนะป `app/routers/items.py`). + +ะ˜ ะฝะฐะผ ะฝัƒะถะฝะพ ะฒั‹ะทะฒะฐั‚ัŒ ั„ัƒะฝะบั†ะธัŽ ะทะฐะฒะธัะธะผะพัั‚ะธ ะธะท ะผะพะดัƒะปั `app.dependencies` (ั„ะฐะนะป `app/dependencies.py`). + +ะœั‹ ะธัะฟะพะปัŒะทัƒะตะผ ะพะฟะตั€ะฐั†ะธัŽ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ `..` ะดะปั ะธะผะฟะพั€ั‚ะฐ ะทะฐะฒะธัะธะผะพัั‚ะธ: + +```Python hl_lines="3" title="app/routers/items.py" +{!../../docs_src/bigger_applications/app/routers/items.py!} +``` + +#### ะšะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝั‹ะน ะธะผะฟะพั€ั‚? + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะ•ัะปะธ ะฒั‹ ะฟั€ะตะบั€ะฐัะฝะพ ะทะฝะฐะตั‚ะต, ะบะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚ ะฒ Python, ั‚ะพ ะฟะตั€ะตั…ะพะดะธั‚ะต ะบ ัะปะตะดัƒัŽั‰ะตะผัƒ ั€ะฐะทะดะตะปัƒ. + +/// + +ะžะดะฝะฐ ั‚ะพั‡ะบะฐ `.`, ะบะฐะบ ะฒ ะดะฐะฝะฝะพะผ ะฟั€ะธะผะตั€ะต: + +```Python +from .dependencies import get_token_header +``` +ะพะทะฝะฐั‡ะฐะตั‚: + +* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py` ั€ะฐัะฟะพะปะพะถะตะฝ ะฒ ะบะฐั‚ะฐะปะพะณะต `app/routers/`)... +* ... ะฝะฐะนะดะธั‚ะต ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/routers/dependencies.py`)... +* ... ะธ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. + +ะš ัะพะถะฐะปะตะฝะธัŽ, ั‚ะฐะบะพะณะพ ั„ะฐะนะปะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚, ะธ ะฝะฐัˆะธ ะทะฐะฒะธัะธะผะพัั‚ะธ ะฝะฐั…ะพะดัั‚ัั ะฒ ั„ะฐะนะปะต `app/dependencies.py`. + +ะ’ัะฟะพะผะฝะธั‚ะต, ะบะฐะบ ะฒั‹ะณะปัะดะธั‚ ั„ะฐะนะปะพะฒะฐั ัั‚ั€ัƒะบั‚ัƒั€ะฐ ะฝะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั: + + + +--- + +ะ”ะฒะต ั‚ะพั‡ะบะธ `..`, ะบะฐะบ ะฒ ะดะฐะฝะฝะพะผ ะฟั€ะธะผะตั€ะต: + +```Python +from ..dependencies import get_token_header +``` + +ะพะทะฝะฐั‡ะฐัŽั‚: + +* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py` ะฝะฐั…ะพะดะธั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app/routers/`)... +* ... ะฟะตั€ะตะนะดะธั‚ะต ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ (ะบะฐั‚ะฐะปะพะณ `app/`)... +* ... ะฝะฐะนะดะธั‚ะต ะฒ ะฝั‘ะผ ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/dependencies.py`)... +* ... ะธ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. + +ะญั‚ะพ ั€ะฐะฑะพั‚ะฐะตั‚ ะฒะตั€ะฝะพ! ๐ŸŽ‰ + +--- + +ะะฝะฐะปะพะณะธั‡ะฝะพ, ะตัะปะธ ะฑั‹ ะผั‹ ะธัะฟะพะปัŒะทะพะฒะฐะปะธ ั‚ั€ะธ ั‚ะพั‡ะบะธ `...`, ะบะฐะบ ะทะดะตััŒ: + +```Python +from ...dependencies import get_token_header +``` + +ั‚ะพ ัั‚ะพ ะฑั‹ ะพะทะฝะฐั‡ะฐะปะพ: + +* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ะฝะฐั…ะพะดะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/routers/items.py` ะฝะฐั…ะพะดะธั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app/routers/`)... +* ... ะฟะตั€ะตะนะดะธั‚ะต ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ (ะบะฐั‚ะฐะปะพะณ `app/`)... +* ... ะทะฐั‚ะตะผ ะฟะตั€ะตะนะดะธั‚ะต ะฒ ั€ะพะดะธั‚ะตะปัŒัะบะธะน ะฟะฐะบะตั‚ ั‚ะตะบัƒั‰ะตะณะพ ะฟะฐะบะตั‚ะฐ (ั‚ะฐะบะพะณะพ ะฟะฐะบะตั‚ะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚, `app` ะฝะฐั…ะพะดะธั‚ัั ะฝะฐ ัะฐะผะพะผ ะฒะตั€ั…ะฝะตะผ ัƒั€ะพะฒะฝะต ๐Ÿ˜ฑ)... +* ... ะฝะฐะนะดะธั‚ะต ะฒ ะฝั‘ะผ ะผะพะดัƒะปัŒ `dependencies` (ั„ะฐะนะป `app/dependencies.py`)... +* ... ะธ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ะธะท ะฝะตะณะพ ั„ัƒะฝะบั†ะธัŽ `get_token_header`. + +ะญั‚ะพ ะฑัƒะดะตั‚ ะพั‚ะฝะพัะธั‚ัŒัั ะบ ะฝะตะบะพั‚ะพั€ะพะผัƒ ะฟะฐะบะตั‚ัƒ, ะฝะฐั…ะพะดัั‰ะตะผัƒัั ะฝะฐ ะพะดะธะฝ ัƒั€ะพะฒะตะฝัŒ ะฒั‹ัˆะต ั‡ะตะผ `app/` ะธ ัะพะดะตั€ะถะฐั‰ะตะผัƒ ัะฒะพะน ัะพะฑัั‚ะฒะตะฝะฝั‹ะน ั„ะฐะนะป `__init__.py`. ะะพ ะฝะธั‡ะตะณะพ ั‚ะฐะบะพะณะพ ัƒ ะฝะฐั ะฝะตั‚. ะŸะพัั‚ะพะผัƒ ัั‚ะพ ะฟั€ะธะฒะตะดะตั‚ ะบ ะพัˆะธะฑะบะต ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะผะตั€ะต. ๐Ÿšจ + +ะขะตะฟะตั€ัŒ ะฒั‹ ะทะฝะฐะตั‚ะต, ะบะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚ ะฒ Python, ะธ ัะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะต ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะฝะธะต ะฒ ัะฒะพะธั… ัะพะฑัั‚ะฒะตะฝะฝั‹ั… ะฟั€ะธะปะพะถะตะฝะธัั… ะปัŽะฑะพะณะพ ัƒั€ะพะฒะฝั ัะปะพะถะฝะพัั‚ะธ. ๐Ÿค“ + +### ะ”ะพะฑะฐะฒะปะตะฝะธะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธั… ั‚ะตะณะพะฒ (`tags`), ะพั‚ะฒะตั‚ะพะฒ (`responses`) ะธ ะทะฐะฒะธัะธะผะพัั‚ะตะน (`dependencies`) + +ะœั‹ ะฝะต ะฑัƒะดะตะผ ะดะพะฑะฐะฒะปัั‚ัŒ ะฟั€ะตั„ะธะบั `/items` ะธ ัะฟะธัะพะบ ั‚ะตะณะพะฒ `tags=["items"]` ะดะปั ะบะฐะถะดะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*, ั‚.ะบ. ะผั‹ ัƒะถะต ะธั… ะดะพะฑะฐะฒะธะปะธ ั ะฟะพะผะพั‰ัŒัŽ `APIRouter`. + +ะะพ ะฟะพะผะธะผะพ ัั‚ะพะณะพ ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะฝะพะฒั‹ะต ั‚ะตะณะธ ะดะปั ะบะฐะถะดะพะณะพ ะพั‚ะดะตะปัŒะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*, ะฐ ั‚ะฐะบะถะต ะฝะตะบะพั‚ะพั€ั‹ะต ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝั‹ะต ะพั‚ะฒะตั‚ั‹ (`responses`), ั…ะฐั€ะฐะบั‚ะตั€ะฝั‹ะต ะดะปั ะดะฐะฝะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ*: + +```Python hl_lines="30-31" title="app/routers/items.py" +{!../../docs_src/bigger_applications/app/routers/items.py!} +``` + +/// tip | ะŸะพะดัะบะฐะทะบะฐ + +ะŸะพัะปะตะดะฝะธะน *ัะฝะดะฟะพะธะฝั‚* ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฑะธะฝะฐั†ะธัŽ ั‚ะตะณะพะฒ: `["items", "custom"]`. + +ะ ั‚ะฐะบะถะต ะฒ ะตะณะพ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ ะฑัƒะดัƒั‚ ัะพะดะตั€ะถะฐั‚ัŒัั ะพะฑะฐ ะพั‚ะฒะตั‚ะฐ: ะพะดะธะฝ ะดะปั `404` ะธ ะดั€ัƒะณะพะน ะดะปั `403`. + +/// + +## ะœะพะดัƒะปัŒ main ะฒ `FastAPI` + +ะขะตะฟะตั€ัŒ ะดะฐะฒะฐะนั‚ะต ะฟะพัะผะพั‚ั€ะธะผ ะฝะฐ ะผะพะดัƒะปัŒ `app/main.py`. + +ะ˜ะผะตะฝะฝะพ ััŽะดะฐ ะฒั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตั‚ะต ะธ ะธะผะตะฝะฝะพ ะทะดะตััŒ ะฒั‹ ะธัะฟะพะปัŒะทัƒะตั‚ะต ะบะปะฐัั `FastAPI`. + +ะญั‚ะพ ะพัะฝะพะฒะฝะพะน ั„ะฐะนะป ะฒะฐัˆะตะณะพ ะฟั€ะธะปะพะถะตะฝะธั, ะบะพั‚ะพั€ั‹ะน ะพะฑัŠะตะดะธะฝัะตั‚ ะฒัั‘ ะฒ ะพะดะฝะพ ั†ะตะปะพะต. + +ะ˜ ั‚ะตะฟะตั€ัŒ, ะบะพะณะดะฐ ะฑะพะปัŒัˆะฐั ั‡ะฐัั‚ัŒ ะปะพะณะธะบะธ ะฟั€ะธะปะพะถะตะฝะธั ั€ะฐะทะดะตะปะตะฝะฐ ะฝะฐ ะพั‚ะดะตะปัŒะฝั‹ะต ะผะพะดัƒะปะธ, ะพัะฝะพะฒะฝะพะน ั„ะฐะนะป `app/main.py` ะฑัƒะดะตั‚ ะดะพัั‚ะฐั‚ะพั‡ะฝะพ ะฟั€ะพัั‚ั‹ะผ. + +### ะ˜ะผะฟะพั€ั‚ `FastAPI` + +ะ’ั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตั‚ะต ะธ ัะพะทะดะฐะตั‚ะต ะบะปะฐัั `FastAPI` ะบะฐะบ ะพะฑั‹ั‡ะฝะพ. + +ะœั‹ ะดะฐะถะต ะผะพะถะตะผ ะพะฑัŠัะฒะธั‚ัŒ ะณะปะพะฑะฐะปัŒะฝั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}, ะบะพั‚ะพั€ั‹ะต ะฑัƒะดัƒั‚ ะพะฑัŠะตะดะธะฝะตะฝั‹ ั ะทะฐะฒะธัะธะผะพัั‚ัะผะธ ะดะปั ะบะฐะถะดะพะณะพ ะพั‚ะดะตะปัŒะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ: + +```Python hl_lines="1 3 7" title="app/main.py" +{!../../docs_src/bigger_applications/app/main.py!} +``` + +### ะ˜ะผะฟะพั€ั‚ `APIRouter` + +ะขะตะฟะตั€ัŒ ะผั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ ะดั€ัƒะณะธะต ััƒะฑ-ะผะพะดัƒะปะธ, ัะพะดะตั€ะถะฐั‰ะธะต `APIRouter`: + +```Python hl_lines="4-5" title="app/main.py" +{!../../docs_src/bigger_applications/app/main.py!} +``` + +ะขะฐะบ ะบะฐะบ ั„ะฐะนะปั‹ `app/routers/users.py` ะธ `app/routers/items.py` ัะฒะปััŽั‚ัั ััƒะฑ-ะผะพะดัƒะปัะผะธ ะพะดะฝะพะณะพ ะธ ั‚ะพะณะพ ะถะต Python-ะฟะฐะบะตั‚ะฐ `app`, ั‚ะพ ะผั‹ ัะผะพะถะตะผ ะธั… ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ, ะฒะพัะฟะพะปัŒะทะพะฒะฐะฒัˆะธััŒ ะพะฟะตั€ะฐั†ะธะตะน ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ `.`. + +### ะšะฐะบ ั€ะฐะฑะพั‚ะฐะตั‚ ะธะผะฟะพั€ั‚? + +ะ”ะฐะฝะฝะฐั ัั‚ั€ะพะบะฐ ะบะพะดะฐ: + +```Python +from .routers import items, users +``` + +ะพะทะฝะฐั‡ะฐะตั‚: + +* ะะฐั‡ะฝะธั‚ะต ั ะฟะฐะบะตั‚ะฐ, ะฒ ะบะพั‚ะพั€ะพะผ ัะพะดะตั€ะถะธั‚ัั ะดะฐะฝะฝั‹ะน ะผะพะดัƒะปัŒ (ั„ะฐะนะป `app/main.py` ัะพะดะตั€ะถะธั‚ัั ะฒ ะบะฐั‚ะฐะปะพะณะต `app/`)... +* ... ะฝะฐะนะดะธั‚ะต ััƒะฑ-ะฟะฐะบะตั‚ `routers` (ะบะฐั‚ะฐะปะพะณ `app/routers/`)... +* ... ะธ ะธะท ะฝะตะณะพ ะธะผะฟะพั€ั‚ะธั€ัƒะนั‚ะต ััƒะฑ-ะผะพะดัƒะปะธ `items` (ั„ะฐะนะป `app/routers/items.py`) ะธ `users` (ั„ะฐะนะป `app/routers/users.py`)... + +ะ’ ะผะพะดัƒะปะต `items` ัะพะดะตั€ะถะธั‚ัั ะฟะตั€ะตะผะตะฝะฝะฐั `router` (`items.router`), ั‚ะฐ ัะฐะผะฐั, ะบะพั‚ะพั€ัƒัŽ ะผั‹ ัะพะทะดะฐะปะธ ะฒ ั„ะฐะนะปะต `app/routers/items.py`, ะพะฝะฐ ัะฒะปัะตั‚ัั ะพะฑัŠะตะบั‚ะพะผ ะบะปะฐััะฐ `APIRouter`. + +ะ˜ ะทะฐั‚ะตะผ ะผั‹ ัะดะตะปะฐะตะผ ั‚ะพ ะถะต ัะฐะผะพะต ะดะปั ะผะพะดัƒะปั `users`. + +ะœั‹ ั‚ะฐะบะถะต ะผะพะณะปะธ ะฑั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะธ ะดั€ัƒะณะธะผ ะผะตั‚ะพะดะพะผ: + +```Python +from app.routers import items, users +``` + +/// info | ะŸั€ะธะผะตั‡ะฐะฝะธะต + +ะŸะตั€ะฒะฐั ะฒะตั€ัะธั ัะฒะปัะตั‚ัั ะฟั€ะธะผะตั€ะพะผ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ: + +```Python +from .routers import items, users +``` + +ะ’ั‚ะพั€ะฐั ะฒะตั€ัะธั ัะฒะปัะตั‚ัั ะฟั€ะธะผะตั€ะพะผ ะฐะฑัะพะปัŽั‚ะฝะพะณะพ ะธะผะฟะพั€ั‚ะฐ: + +```Python +from app.routers import items, users +``` + +ะฃะทะฝะฐั‚ัŒ ะฑะพะปัŒัˆะต ะพ ะฟะฐะบะตั‚ะฐั… ะธ ะผะพะดัƒะปัั… ะฒ Python ะฒั‹ ะผะพะถะตั‚ะต ะธะท ะพั„ะธั†ะธะฐะปัŒะฝะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ Python ะพ ะผะพะดัƒะปัั… + +/// + +### ะ˜ะทะฑะตะณะฐะนั‚ะต ะบะพะฝั„ะปะธะบั‚ะพะฒ ะธะผะตะฝ + +ะ’ะผะตัั‚ะพ ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ ั‚ะพะปัŒะบะพ ะฟะตั€ะตะผะตะฝะฝัƒัŽ `router`, ะผั‹ ะธะผะฟะพั€ั‚ะธั€ัƒะตะผ ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ััƒะฑ-ะผะพะดัƒะปัŒ `items`. + +ะœั‹ ะดะตะปะฐะตะผ ัั‚ะพ ะฟะพั‚ะพะผัƒ, ั‡ั‚ะพ ัƒ ะฝะฐั ะตัั‚ัŒ ะตั‰ั‘ ะพะดะฝะฐ ะฟะตั€ะตะผะตะฝะฝะฐั `router` ะฒ ััƒะฑ-ะผะพะดัƒะปะต `users`. + +ะ•ัะปะธ ะฑั‹ ะผั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะปะธ ะธั… ะพะดะฝัƒ ะทะฐ ะดั€ัƒะณะพะน, ะบะฐะบ ะฟะพะบะฐะทะฐะฝะพ ะฒ ะฟั€ะธะผะตั€ะต: + +```Python +from .routers.items import router +from .routers.users import router +``` + +ั‚ะพ ะฟะตั€ะตะผะตะฝะฝะฐั `router` ะธะท `users` ะฟะตั€ะตะฟะธัะฐะป ะฑั‹ ะฟะตั€ะตะผะตะฝะฝัƒัŽ `router` ะธะท `items`, ะธ ัƒ ะฝะฐั ะฝะต ะฑั‹ะปะพ ะฑั‹ ะฒะพะทะผะพะถะฝะพัั‚ะธ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะธั… ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ. + +ะŸะพัั‚ะพะผัƒ, ะดะปั ั‚ะพะณะพ ั‡ั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะพะฑะต ัั‚ะธ ะฟะตั€ะตะผะตะฝะฝั‹ะต ะฒ ะพะดะฝะพะผ ั„ะฐะนะปะต, ะผั‹ ะธะผะฟะพั€ั‚ะธั€ะพะฒะฐะปะธ ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‰ะธะต ััƒะฑ-ะผะพะดัƒะปะธ: + +```Python hl_lines="5" title="app/main.py" +{!../../docs_src/bigger_applications/app/main.py!} +``` + +### ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะฒ (`APIRouter`) ะดะปั `users` ะธ ะดะปั `items` + +ะ”ะฐะฒะฐะนั‚ะต ะฟะพะดะบะปัŽั‡ะธะผ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ั‹ (`router`) ะธะท ััƒะฑ-ะผะพะดัƒะปะตะน `users` ะธ `items`: + +```Python hl_lines="10-11" title="app/main.py" +{!../../docs_src/bigger_applications/app/main.py!} +``` + +/// info | ะŸั€ะธะผะตั‡ะฐะฝะธะต + +`users.router` ัะพะดะตั€ะถะธั‚ `APIRouter` ะธะท ั„ะฐะนะปะฐ `app/routers/users.py`. + +ะ `items.router` ัะพะดะตั€ะถะธั‚ `APIRouter` ะธะท ั„ะฐะนะปะฐ `app/routers/items.py`. + +/// + +ะก ะฟะพะผะพั‰ัŒัŽ `app.include_router()` ะผั‹ ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ ะบะฐะถะดั‹ะน ะธะท ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะฒ (`APIRouter`) ะฒ ะพัะฝะพะฒะฝะพะต ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`. + +ะžะฝ ะฟะพะดะบะปัŽั‡ะธั‚ ะฒัะต ะผะฐั€ัˆั€ัƒั‚ั‹ ะทะฐะดะฐะฝะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ ะบ ะฝะฐัˆะตะผัƒ ะฟั€ะธะปะพะถะตะฝะธัŽ. + +/// note | ะขะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ + +ะคะฐะบั‚ะธั‡ะตัะบะธ, ะฒะฝัƒั‚ั€ะธ ะพะฝ ัะพะทะดะฐัั‚ ะฒัะต *ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ* ะดะปั ะบะฐะถะดะพะน ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ ะพะฑัŠัะฒะปะตะฝะฝะพะน ะฒ `APIRouter`. + +ะ˜ ะฟะพะด ะบะฐะฟะพั‚ะพะผ ะฒัั‘ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ั‚ะฐะบ, ะบะฐะบ ะฑัƒะดั‚ะพ ะฑั‹ ะผั‹ ะธะผะตะตะผ ะดะตะปะพ ั ะพะดะฝะธะผ ั„ะฐะนะปะพะผ ะฟั€ะธะปะพะถะตะฝะธั. + +/// + +/// check | ะ—ะฐะผะตั‚ะบะฐ + +ะŸั€ะธ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะพะฒ ะฝะต ัั‚ะพะธั‚ ะฑะตัะฟะพะบะพะธั‚ัŒัั ะพ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ะธ. + +ะžะฟะตั€ะฐั†ะธั ะฟะพะดะบะปัŽั‡ะตะฝะธั ะทะฐะนะผั‘ั‚ ะผะธะบั€ะพัะตะบัƒะฝะดั‹ ะธ ะฟะพะฝะฐะดะพะฑะธั‚ัั ั‚ะพะปัŒะบะพ ะฟั€ะธ ะทะฐะฟัƒัะบะต ะฟั€ะธะปะพะถะตะฝะธั. + +ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ัั‚ะพ ะฝะต ะฟะพะฒะปะธัะตั‚ ะฝะฐ ะฟั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ. โšก + +/// + +### ะŸะพะดะบะปัŽั‡ะตะฝะธะต `APIRouter` ั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะผะธ ะฟั€ะตั„ะธะบัะพะผ (`prefix`), ั‚ะตะณะฐะผะธ (`tags`), ะพั‚ะฒะตั‚ะฐะผะธ (`responses`), ะธ ะทะฐะฒะธัะธะผะพัั‚ัะผะธ (`dependencies`) + +ะขะตะฟะตั€ัŒ ะดะฐะฒะฐะนั‚ะต ะฟั€ะตะดัั‚ะฐะฒะธะผ, ั‡ั‚ะพ ะฒะฐัˆะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั ะฟะตั€ะตะดะฐะปะฐ ะฒะฐะผ ั„ะฐะนะป `app/internal/admin.py`. + +ะžะฝ ัะพะดะตั€ะถะธั‚ `APIRouter` ั ะฝะตะบะพั‚ะพั€ั‹ะผะธ *ัะฝะดะฟะพะธั‚ะฐะผะธ* ะฐะดะผะธะฝะธัั‚ั€ะธั€ะพะฒะฐะฝะธั, ะบะพั‚ะพั€ั‹ะต ะฒะฐัˆะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั ะธัะฟะพะปัŒะทัƒะตั‚ ะดะปั ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะพะฒ. + +ะ’ ะดะฐะฝะฝะพะผ ะฟั€ะธะผะตั€ะต ัั‚ะพ ัะดะตะปะฐั‚ัŒ ะพั‡ะตะฝัŒ ะฟั€ะพัั‚ะพ. ะะพ ะดะฐะฒะฐะนั‚ะต ะฟั€ะตะดะฟะพะปะพะถะธะผ, ั‡ั‚ะพ ะฟะพัะบะพะปัŒะบัƒ ั„ะฐะนะป ะธัะฟะพะปัŒะทัƒะตั‚ัั ะดะปั ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะพะฒ, +ั‚ะพ ะผั‹ ะฝะต ะผะพะถะตะผ ะผะพะดะธั„ะธั†ะธั€ะพะฒะฐั‚ัŒ ะตะณะพ, ะดะพะฑะฐะฒะปัั ะฟั€ะตั„ะธะบัั‹ (`prefix`), ะทะฐะฒะธัะธะผะพัั‚ะธ (`dependencies`), ั‚ะตะณะธ (`tags`), ะธ ั‚.ะด. ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะฒ `APIRouter`: + +```Python hl_lines="3" title="app/internal/admin.py" +{!../../docs_src/bigger_applications/app/internal/admin.py!} +``` + +ะะพ, ะฝะตัะผะพั‚ั€ั ะฝะฐ ัั‚ะพ, ะผั‹ ั…ะพั‚ะธะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะบะฐัั‚ะพะผะฝั‹ะน ะฟั€ะตั„ะธะบั (`prefix`) ะดะปั ะฟะพะดะบะปัŽั‡ะตะฝะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ (`APIRouter`), ะฒ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต ั‡ะตะณะพ, ะบะฐะถะดะฐั *ะพะฟะตั€ะฐั†ะธั ะฟัƒั‚ะธ* ะฑัƒะดะตั‚ ะฝะฐั‡ะธะฝะฐั‚ัŒัั ั `/admin`. ะขะฐะบะถะต ะผั‹ ั…ะพั‚ะธะผ ะทะฐั‰ะธั‚ะธั‚ัŒ ะฝะฐัˆ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ ั ะฟะพะผะพั‰ัŒัŽ ะทะฐะฒะธัะธะผะพัั‚ะตะน, ัะพะทะดะฐะฝะฝั‹ั… ะดะปั ะฝะฐัˆะตะณะพ ะฟั€ะพะตะบั‚ะฐ. ะ˜ ะตั‰ั‘ ะผั‹ ั…ะพั‚ะธะผ ะฒะบะปัŽั‡ะธั‚ัŒ ั‚ะตะณะธ (`tags`) ะธ ะพั‚ะฒะตั‚ั‹ (`responses`). + +ะœั‹ ะผะพะถะตะผ ะฟั€ะธะผะตะฝะธั‚ัŒ ะฒัะต ะฒั‹ัˆะตะฟะตั€ะตั‡ะธัะปะตะฝะฝั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ, ะฝะต ะธะทะผะตะฝัั ะฝะฐั‡ะฐะปัŒะฝั‹ะน `APIRouter`. ะะฐะผ ะฒัะตะณะพ ะปะธัˆัŒ ะฝัƒะถะฝะพ ะฟะตั€ะตะดะฐั‚ัŒ ะฝัƒะถะฝั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ ะฒ `app.include_router()`. + +```Python hl_lines="14-17" title="app/main.py" +{!../../docs_src/bigger_applications/app/main.py!} +``` + +ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะพั€ะธะณะธะฝะฐะปัŒะฝั‹ะน `APIRouter` ะฝะต ะฑัƒะดะตั‚ ะผะพะดะธั„ะธั†ะธั€ะพะฒะฐะฝ, ะธ ะผั‹ ัะผะพะถะตะผ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั„ะฐะนะป `app/internal/admin.py` ัั€ะฐะทัƒ ะฒ ะฝะตัะบะพะปัŒะบะธั… ะฟั€ะพะตะบั‚ะฐั… ะพั€ะณะฐะฝะธะทะฐั†ะธะธ. + +ะ’ ั€ะตะทัƒะปัŒั‚ะฐั‚ะต, ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะปะพะถะตะฝะธะธ ะบะฐะถะดั‹ะน *ัะฝะดะฟะพะธะฝั‚* ะผะพะดัƒะปั `admin` ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ: + +* ะŸั€ะตั„ะธะบั `/admin`. +* ะขะตะณ `admin`. +* ะ—ะฐะฒะธัะธะผะพัั‚ัŒ `get_token_header`. +* ะžั‚ะฒะตั‚ `418`. ๐Ÿต + +ะญั‚ะพ ะฑัƒะดะตั‚ ะธะผะตั‚ัŒ ะผะตัั‚ะพ ะธัะบะปัŽั‡ะธั‚ะตะปัŒะฝะพ ะดะปั `APIRouter` ะฒ ะฝะฐัˆะตะผ ะฟั€ะธะปะพะถะตะฝะธะธ, ะธ ะฝะต ะทะฐั‚ั€ะพะฝะตั‚ ะปัŽะฑะพะน ะดั€ัƒะณะพะน ะบะพะด, ะธัะฟะพะปัŒะทัƒัŽั‰ะธะน ะตะณะพ. + +ะะฐะฟั€ะธะผะตั€, ะดั€ัƒะณะธะต ะฟั€ะพะตะบั‚ั‹, ะผะพะณัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ั‚ะพั‚ ะถะต ัะฐะผั‹ะน `APIRouter` ั ะดั€ัƒะณะธะผะธ ะผะตั‚ะพะดะฐะผะธ ะฐัƒั‚ะตะฝั‚ะธั„ะธะบะฐั†ะธะธ. + +### ะŸะพะดะบะปัŽั‡ะตะฝะธะต ะพั‚ะดะตะปัŒะฝะพะณะพ *ัะฝะดะฟะพะธะฝั‚ะฐ* + +ะœั‹ ั‚ะฐะบะถะต ะผะพะถะตะผ ะดะพะฑะฐะฒะธั‚ัŒ *ัะฝะดะฟะพะธะฝั‚* ะฝะตะฟะพัั€ะตะดัั‚ะฒะตะฝะฝะพ ะฒ ะพัะฝะพะฒะฝะพะต ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`. + +ะ—ะดะตััŒ ะผั‹ ัั‚ะพ ะดะตะปะฐะตะผ ... ะฟั€ะพัั‚ะพ, ั‡ั‚ะพะฑั‹ ะฟะพะบะฐะทะฐั‚ัŒ, ั‡ั‚ะพ ัั‚ะพ ะฒะพะทะผะพะถะฝะพ ๐Ÿคท: + +```Python hl_lines="21-23" title="app/main.py" +{!../../docs_src/bigger_applications/app/main.py!} +``` + +ะธ ัั‚ะพ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ ะบะพั€ั€ะตะบั‚ะฝะพ ะฒะผะตัั‚ะต ั ะดั€ัƒะณะธะผะธ *ัะฝะดะฟะพะธะฝั‚ะฐะผะธ*, ะดะพะฑะฐะฒะปะตะฝะฝั‹ะผะธ ั ะฟะพะผะพั‰ัŒัŽ `app.include_router()`. + +/// info | ะกะปะพะถะฝั‹ะต ั‚ะตั…ะฝะธั‡ะตัะบะธะต ะดะตั‚ะฐะปะธ + +**ะŸั€ะธะผะตั‡ะฐะฝะธะต**: ัั‚ะพ ัะปะพะถะฝะฐั ั‚ะตั…ะฝะธั‡ะตัะบะฐั ะดะตั‚ะฐะปัŒ, ะบะพั‚ะพั€ัƒัŽ, ัะบะพั€ะตะต ะฒัะตะณะพ, **ะฒั‹ ะผะพะถะตั‚ะต ะฟั€ะพะฟัƒัั‚ะธั‚ัŒ**. + +--- + +ะœะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ั‹ (`APIRouter`) ะฝะต "ะผะพะฝั‚ะธั€ัƒัŽั‚ัั" ะฟะพ-ะพั‚ะดะตะปัŒะฝะพัั‚ะธ ะธ ะฝะต ะธะทะพะปะธั€ัƒัŽั‚ัั ะพั‚ ะพัั‚ะฐะปัŒะฝะพะณะพ ะฟั€ะธะปะพะถะตะฝะธั. + +ะญั‚ะพ ะฟั€ะพะธัั…ะพะดะธั‚ ะฟะพั‚ะพะผัƒ, ั‡ั‚ะพ ะฝัƒะถะฝะพ ะฒะบะปัŽั‡ะธั‚ัŒ ะธั… *ัะฝะดะฟะพะธะฝั‚ั‹* ะฒ OpenAPI ัั…ะตะผัƒ ะธ ะฒ ะธะฝั‚ะตั€ั„ะตะนั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั. + +ะ’ ัะธะปัƒ ั‚ะพะณะพ, ั‡ั‚ะพ ะผั‹ ะฝะต ะผะพะถะตะผ ะธั… ะธะทะพะปะธั€ะพะฒะฐั‚ัŒ ะธ "ะฟั€ะธะผะพะฝั‚ะธั€ะพะฒะฐั‚ัŒ" ะฝะตะทะฐะฒะธัะธะผะพ ะพั‚ ะพัั‚ะฐะปัŒะฝั‹ั…, *ัะฝะดะฟะพะธะฝั‚ั‹* ะบะปะพะฝะธั€ัƒัŽั‚ัั (ะฟะตั€ะตัะพะทะดะฐัŽั‚ัั) ะธ ะฝะต ะฟะพะดะบะปัŽั‡ะฐัŽั‚ัั ะฝะฐะฟั€ัะผัƒัŽ. + +/// + +## ะŸั€ะพะฒะตั€ะบะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะพะน ะดะพะบัƒะผะตะฝั‚ะฐั†ะธะธ API + +ะขะตะฟะตั€ัŒ ะทะฐะฟัƒัั‚ะธั‚ะต ะฟั€ะธะปะพะถะตะฝะธะต: + +
+ +```console +$ fastapi dev app/main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +ะžั‚ะบั€ะพะนั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ ะฟะพ ะฐะดั€ะตััƒ http://127.0.0.1:8000/docs. + +ะ’ั‹ ัƒะฒะธะดะธั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบัƒัŽ API ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัŽ. ะžะฝะฐ ะฒะบะปัŽั‡ะฐะตั‚ ะฒ ัะตะฑั ะผะฐั€ัˆั€ัƒั‚ั‹ ะธะท ััƒะฑ-ะผะพะดัƒะปะตะน, ะธัะฟะพะปัŒะทัƒั ะฒะตั€ะฝั‹ะต ะผะฐั€ัˆั€ัƒั‚ั‹, ะฟั€ะตั„ะธะบัั‹ ะธ ั‚ะตะณะธ: + + + +## ะŸะพะดะบะปัŽั‡ะตะฝะธะต ััƒั‰ะตัั‚ะฒัƒัŽั‰ะตะณะพ ะผะฐั€ัˆั€ัƒั‚ะฐ ั‡ะตั€ะตะท ะฝะพะฒั‹ะน ะฟั€ะตั„ะธะบั (`prefix`) + +ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `.include_router()` ะฝะตัะบะพะปัŒะบะพ ั€ะฐะท ั ะพะดะฝะธะผ ะธ ั‚ะตะผ ะถะต ะผะฐั€ัˆั€ัƒั‚ะพะผ, ะฟั€ะธะผะตะฝะธะฒ ั€ะฐะทะปะธั‡ะฝั‹ะต ะฟั€ะตั„ะธะบัั‹. + +ะญั‚ะพ ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะฟะพะปะตะทะฝั‹ะผ, ะตัะปะธ ะฝัƒะถะฝะพ ะฟั€ะตะดะพัั‚ะฐะฒะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ะพะดะฝะพะผัƒ ะธ ั‚ะพะผัƒ ะถะต API ั‡ะตั€ะตะท ั€ะฐะทะปะธั‡ะฝั‹ะต ะฟั€ะตั„ะธะบัั‹, ะฝะฐะฟั€ะธะผะตั€, `/api/v1` ะธ `/api/latest`. + +ะญั‚ะพ ะฟั€ะพะดะฒะธะฝัƒั‚ั‹ะน ัะฟะพัะพะฑ, ะบะพั‚ะพั€ั‹ะน ะฒะฐะผ ะผะพะถะตั‚ ะธ ะฝะต ะฟั€ะธะณะพะดะธั‚ัั. ะœั‹ ะฟั€ะธะฒะพะดะธะผ ะตะณะพ ะฝะฐ ัะปัƒั‡ะฐะน, ะตัะปะธ ะฒะดั€ัƒะณ ะฒะฐะผ ัั‚ะพ ะฟะพะฝะฐะดะพะฑะธั‚ัั. + +## ะ’ะบะปัŽั‡ะตะฝะธะต ะพะดะฝะพะณะพ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ (`APIRouter`) ะฒ ะดั€ัƒะณะพะน + +ะขะพั‡ะฝะพ ั‚ะฐะบ ะถะต, ะบะฐะบ ะฒั‹ ะฒะบะปัŽั‡ะฐะตั‚ะต `APIRouter` ะฒ ะฟั€ะธะปะพะถะตะฝะธะต `FastAPI`, ะฒั‹ ะผะพะถะตั‚ะต ะฒะบะปัŽั‡ะธั‚ัŒ `APIRouter` ะฒ ะดั€ัƒะณะพะน `APIRouter`: + +```Python +router.include_router(other_router) +``` + +ะฃะดะพัั‚ะพะฒะตั€ัŒั‚ะตััŒ, ั‡ั‚ะพ ะฒั‹ ัะดะตะปะฐะปะธ ัั‚ะพ ะดะพ ั‚ะพะณะพ, ะบะฐะบ ะฟะพะดะบะปัŽั‡ะธั‚ัŒ ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ (`router`) ะบ ะฒะฐัˆะตะผัƒ `FastAPI` ะฟั€ะธะปะพะถะตะฝะธัŽ, ะธ *ัะฝะดะฟะพะธะฝั‚ั‹* ะผะฐั€ัˆั€ัƒั‚ะธะทะฐั‚ะพั€ะฐ `other_router` ะฑั‹ะปะธ ั‚ะฐะบะถะต ะฟะพะดะบะปัŽั‡ะตะฝั‹. diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index f9b9dec25..0e4eb95be 100644 --- a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -18,7 +18,7 @@ ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะธะท dependencies ะฒั‹ะฟะพะปะฝัั‚ัั ั‚ะฐะบ ะถะต, ะบะฐะบ ะธ ะพะฑั‹ั‡ะฝั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ. ะะพ ะธั… ะทะฝะฐั‡ะตะฝะธั (ะตัะปะธ ะพะฝะธ ะฑั‹ะปะธ) ะฝะต ะฑัƒะดัƒั‚ ะฟะตั€ะตะดะฐะฝั‹ ะฒ *ั„ัƒะฝะบั†ะธัŽ ะพะฟะตั€ะฐั†ะธะธ ะฟัƒั‚ะธ*. -/// ะฟะพะดัะบะฐะทะบะฐ +/// tip | ะŸะพะดัะบะฐะทะบะฐ ะะตะบะพั‚ะพั€ั‹ะต ั€ะตะดะฐะบั‚ะพั€ั‹ ะบะพะดะฐ ะพะฟั€ะตะดะตะปััŽั‚ ะฝะตะธัะฟะพะปัŒะทัƒะตะผั‹ะต ะฟะฐั€ะฐะผะตั‚ั€ั‹ ั„ัƒะฝะบั†ะธะน ะธ ะฟะพะดัะฒะตั‡ะธะฒะฐัŽั‚ ะธั… ะบะฐะบ ะพัˆะธะฑะบัƒ. @@ -28,7 +28,7 @@ /// -/// ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะฐั | ะธะฝั„ะพั€ะผะฐั†ะธั +/// info | ะŸั€ะธะผะตั‡ะฐะฝะธะต ะ’ ัั‚ะพะผ ะฟั€ะธะผะตั€ะต ะผั‹ ะธัะฟะพะปัŒะทัƒะตะผ ะฒั‹ะดัƒะผะฐะฝะฝั‹ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะทะฐะณะพะปะพะฒะบะธ `X-Key` ะธ `X-Token`. diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md index 32a98ff22..13b7015db 100644 --- a/docs/ru/docs/tutorial/query-params-str-validations.md +++ b/docs/ru/docs/tutorial/query-params-str-validations.md @@ -291,22 +291,6 @@ q: Union[str, None] = Query(default=None, min_length=3) {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### ะžะฑัะทะฐั‚ะตะปัŒะฝั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ั Ellipsis (`...`) - -ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝั‹ะน ัะฟะพัะพะฑ ัƒะบะฐะทะฐั‚ัŒ ะพะฑัะทะฐั‚ะตะปัŒะฝะพัั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ะฐ ะทะฐะฟั€ะพัะฐ - ัั‚ะพ ัƒะบะฐะทะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `default` ั‡ะตั€ะตะท ะผะฝะพะณะพั‚ะพั‡ะธะต `...`: - -{* ../../docs_src/query_params_str_validations/tutorial006b_an_py39.py hl[9] *} - -/// info | ะ”ะพะฟะพะปะฝะธั‚ะตะปัŒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั - -ะ•ัะปะธ ะฒั‹ ั€ะฐะฝะตะต ะฝะต ัั‚ะฐะปะบะธะฒะฐะปะธััŒ ั `...`: ัั‚ะพ ัะฟะตั†ะธะฐะปัŒะฝะพะต ะทะฝะฐั‡ะตะฝะธะต, ั‡ะฐัั‚ัŒ ัะทั‹ะบะฐ Python ะธ ะฝะฐะทั‹ะฒะฐะตั‚ัั "Ellipsis". - -ะ˜ัะฟะพะปัŒะทัƒะตั‚ัั ะฒ Pydantic ะธ FastAPI ะดะปั ะพะฟั€ะตะดะตะปะตะฝะธั, ั‡ั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ั‚ั€ะตะฑัƒะตั‚ัั ะพะฑัะทะฐั‚ะตะปัŒะฝะพ. - -/// - -ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, **FastAPI** ะพะฟั€ะตะดะตะปัะตั‚, ั‡ั‚ะพ ะฟะฐั€ะฐะผะตั‚ั€ ัะฒะปัะตั‚ัั ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ. - ### ะžะฑัะทะฐั‚ะตะปัŒะฝั‹ะน ะฟะฐั€ะฐะผะตั‚ั€ ั `None` ะ’ั‹ ะผะพะถะตั‚ะต ะพะฟั€ะตะดะตะปะธั‚ัŒ, ั‡ั‚ะพ ะฟะฐั€ะฐะผะตั‚ั€ ะผะพะถะตั‚ ะฟั€ะธะฝะธะผะฐั‚ัŒ `None`, ะฝะพ ะฒัั‘ ะตั‰ั‘ ัะฒะปัะตั‚ัั ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ. ะญั‚ะพ ะผะพะถะตั‚ ะฟะพั‚ั€ะตะฑะพะฒะฐั‚ัŒัั ะดะปั ั‚ะพะณะพ, ั‡ั‚ะพะฑั‹ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะธ ัะฒะฝะพ ัƒะบะฐะทะฐะปะธ ะฟะฐั€ะฐะผะตั‚ั€, ะดะฐะถะต ะตัะปะธ ะตะณะพ ะทะฝะฐั‡ะตะฝะธะต ะฑัƒะดะตั‚ `None`. @@ -321,18 +305,6 @@ Pydantic, ะผะพั‰ัŒ ะบะพั‚ะพั€ะพะณะพ ะธัะฟะพะปัŒะทัƒะตั‚ัั ะฒ FastAPI ะดะปั /// -### ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต Pydantic's `Required` ะฒะผะตัั‚ะพ Ellipsis (`...`) - -ะ•ัะปะธ ะฒะฐั ัะผัƒั‰ะฐะตั‚ `...`, ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `Required` ะธะท Pydantic: - -{* ../../docs_src/query_params_str_validations/tutorial006d_an_py39.py hl[4,10] *} - -/// tip | ะŸะพะดัะบะฐะทะบะฐ - -ะ—ะฐะฟะพะผะฝะธั‚ะต, ะบะพะณะดะฐ ะฒะฐะผ ะฝะตะพะฑั…ะพะดะธะผะพ ะพะฑัŠัะฒะธั‚ัŒ query-ะฟะฐั€ะฐะผะตั‚ั€ ะพะฑัะทะฐั‚ะตะปัŒะฝั‹ะผ, ะฒั‹ ะผะพะถะตั‚ะต ะฟั€ะพัั‚ะพ ะฝะต ัƒะบะฐะทั‹ะฒะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ `default`. ะขะฐะบะธะผ ะพะฑั€ะฐะทะพะผ, ะฒะฐะผ ั€ะตะดะบะพ ะฟั€ะธะดั‘ั‚ัั ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ `...` ะธะปะธ `Required`. - -/// - ## ะœะฝะพะถะตัั‚ะฒะพ ะทะฝะฐั‡ะตะฝะธะน ะดะปั query-ะฟะฐั€ะฐะผะตั‚ั€ะฐ ะ”ะปั query-ะฟะฐั€ะฐะผะตั‚ั€ะฐ `Query` ะผะพะถะฝะพ ัƒะบะฐะทะฐั‚ัŒ, ั‡ั‚ะพ ะพะฝ ะฟั€ะธะฝะธะผะฐะตั‚ ัะฟะธัะพะบ ะทะฝะฐั‡ะตะฝะธะน (ะผะฝะพะถะตัั‚ะฒะพ ะทะฝะฐั‡ะตะฝะธะน). diff --git a/docs/uk/docs/features.md b/docs/uk/docs/features.md new file mode 100644 index 000000000..7d679d8ee --- /dev/null +++ b/docs/uk/docs/features.md @@ -0,0 +1,189 @@ +# ะคัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝั– ะผะพะถะปะธะฒะพัั‚ั– + +## ะคัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝั– ะผะพะถะปะธะฒะพัั‚ั– FastAPI + +**FastAPI** ะฝะฐะดะฐั” ะฒะฐะผ ั‚ะฐะบั– ะผะพะถะปะธะฒะพัั‚ั–: + +### ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะฒั–ะดะบั€ะธั‚ะธั… ัั‚ะฐะฝะดะฐั€ั‚ั–ะฒ + +* OpenAPI ะดะปั ัั‚ะฒะพั€ะตะฝะฝั API, ะฒะบะปัŽั‡ะฐัŽั‡ะธ ะพะณะพะปะพัˆะตะฝะฝั ัˆะปัั…ั–ะฒ, ะพะฟะตั€ะฐั†ั–ะน, ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ, ั‚ั–ะป ะทะฐะฟะธั‚ั–ะฒ, ะฑะตะทะฟะตะบะธ ั‚ะพั‰ะพ. +* ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ะผะพะดะตะปะตะน ะดะฐะฝะธั… ะทะฐ ะดะพะฟะพะผะพะณะพัŽ JSON Schema (ะพัะบั–ะปัŒะบะธ OpenAPI ะฑะฐะทัƒั”ั‚ัŒัั ัะฐะผะต ะฝะฐ JSON Schema). +* ะ ะพะทั€ะพะฑะปะตะฝะพ ะฝะฐ ะพัะฝะพะฒั– ั†ะธั… ัั‚ะฐะฝะดะฐั€ั‚ั–ะฒ ะฟั–ัะปั ั€ะตั‚ะตะปัŒะฝะพะณะพ ะฐะฝะฐะปั–ะทัƒ, ะฐ ะฝะต ัะบ ะดะพะดะฐั‚ะบะพะฒะธะน ั€ั–ะฒะตะฝัŒ ะฟะพะฒะตั€ั… ะพัะฝะพะฒะฝะพั— ะฐั€ั…ั–ั‚ะตะบั‚ัƒั€ะธ. +* ะฆะต ั‚ะฐะบะพะถ ะดะฐั” ะทะผะพะณัƒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ **ะณะตะฝะตั€ัƒะฒะฐั‚ะธ ะบะพะด ะบะปั–ั”ะฝั‚ะฐ** ะฑะฐะณะฐั‚ัŒะผะฐ ะผะพะฒะฐะผะธ. + +### ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะณะตะฝะตั€ะฐั†ั–ั ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— + +ะ†ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั API ั‚ะฐ ะฒะตะฑั–ะฝั‚ะตั€ั„ะตะนั ะดะปั ะนะพะณะพ ะดะพัะปั–ะดะถะตะฝะฝั. ะžัะบั–ะปัŒะบะธ ั„ั€ะตะนะผะฒะพั€ะบ ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ OpenAPI, ั” ะบั–ะปัŒะบะฐ ะฒะฐั€ั–ะฐะฝั‚ั–ะฒ, ะดะฒะฐ ะท ัะบะธั… ะฒะบะปัŽั‡ะตะฝั– ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ. + +* Swagger UI โ€” ะดะพะทะฒะพะปัั” ั–ะฝั‚ะตั€ะฐะบั‚ะธะฒะฝะพ ะฟะตั€ะตะณะปัะดะฐั‚ะธ API, ะฒะธะบะปะธะบะฐั‚ะธ ั‚ะฐ ั‚ะตัั‚ัƒะฒะฐั‚ะธ ะนะพะณะพ ะฟั€ัะผะพ ัƒ ะฑั€ะฐัƒะทะตั€ั–. + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* ะะปัŒั‚ะตั€ะฝะฐั‚ะธะฒะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั API ะทะฐ ะดะพะฟะพะผะพะณะพัŽ ReDoc. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### ะขั–ะปัŒะบะธ ััƒั‡ะฐัะฝะธะน Python + +FastAPI ะฒะธะบะพั€ะธัั‚ะพะฒัƒั” ัั‚ะฐะฝะดะฐั€ั‚ะฝั– **ั‚ะธะฟะธ Python** (ะทะฐะฒะดัะบะธ Pydantic). ะ’ะฐะผ ะฝะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฒะธะฒั‡ะฐั‚ะธ ะฝะพะฒะธะน ัะธะฝั‚ะฐะบัะธั โ€” ะปะธัˆะต ัั‚ะฐะฝะดะฐั€ั‚ะฝะธะน ััƒั‡ะฐัะฝะธะน Python. + +ะฏะบั‰ะพ ะฒะฐะผ ะฟะพั‚ั€ั–ะฑะฝะต ะบะพั€ะพั‚ะบะต ะฝะฐะณะฐะดัƒะฒะฐะฝะฝั ะฟั€ะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั‚ะธะฟั–ะฒ ัƒ Python (ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะธ ะฝะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต FastAPI), ะฟะตั€ะตะณะปัะฝัŒั‚ะต ะบะพั€ะพั‚ะบะธะน ะฟั–ะดั€ัƒั‡ะฝะธะบ: [ะ’ัั‚ัƒะฟ ะดะพ ั‚ะธะฟั–ะฒ Python](python-types.md){.internal-link target=_blank}. + +ะžััŒ ะฟั€ะธะบะปะฐะด ัั‚ะฐะฝะดะฐั€ั‚ะฝะพะณะพ Python-ะบะพะดัƒ ะท ั‚ะธะฟะฐะผะธ: + +```Python +from datetime import date +from pydantic import BaseModel + +# ะžะณะพะปะพัˆะตะฝะฝั ะทะผั–ะฝะฝะพั— ัะบ str +# ะท ะฟั–ะดั‚ั€ะธะผะบะพัŽ ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั ัƒ ั€ะตะดะฐะบั‚ะพั€ั– +def main(user_id: str): + return user_id + +# ะœะพะดะตะปัŒ Pydantic +class User(BaseModel): + id: int + name: str + joined: date +``` + +ะŸั€ะธะบะปะฐะด ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ั†ั–ั”ั— ะผะพะดะตะปั–: + +```Python +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +/// info | ะ†ะฝั„ะพั€ะผะฐั†ั–ั + +`**second_user_data` ะพะทะฝะฐั‡ะฐั”: + +ะŸะตั€ะตะดะฐั‚ะธ ะบะปัŽั‡ั– ั‚ะฐ ะทะฝะฐั‡ะตะฝะฝั ัะปะพะฒะฝะธะบะฐ `second_user_data` ัะบ ะฐั€ะณัƒะผะตะฝั‚ะธ ัƒ ะฒะธะณะปัะดั– "ะบะปัŽั‡-ะทะฝะฐั‡ะตะฝะฝั", ะตะบะฒั–ะฒะฐะปะตะฝั‚ะฝะพ `User(id=4, name="Mary", joined="2018-11-30")`. + +/// + +### ะŸั–ะดั‚ั€ะธะผะบะฐ ั€ะตะดะฐะบั‚ะพั€ั–ะฒ (IDE) + +ะคั€ะตะนะผะฒะพั€ะบ ัะฟั€ะพั”ะบั‚ะพะฒะฐะฝะธะน ั‚ะฐะบ, ั‰ะพะฑ ะฑัƒั‚ะธ ะปะตะณะบะธะผ ั– ั–ะฝั‚ัƒั—ั‚ะธะฒะฝะพ ะทั€ะพะทัƒะผั–ะปะธะผ. ะฃัั– ั€ั–ัˆะตะฝะฝั ั‚ะตัั‚ัƒะฒะฐะปะธัั ัƒ ั€ั–ะทะฝะธั… ั€ะตะดะฐะบั‚ะพั€ะฐั… ั‰ะต ะดะพ ะฟะพั‡ะฐั‚ะบัƒ ั€ะพะทั€ะพะฑะบะธ, ั‰ะพะฑ ะทะฐะฑะตะทะฟะตั‡ะธั‚ะธ ะฝะฐะนะบั€ะฐั‰ะธะน ะดะพัะฒั–ะด ะฟั€ะพะณั€ะฐะผัƒะฒะฐะฝะฝั. + +ะ—ะฐ ั€ะตะทัƒะปัŒั‚ะฐั‚ะฐะผะธ ะพะฟะธั‚ัƒะฒะฐะฝัŒ ั€ะพะทั€ะพะฑะฝะธะบั–ะฒ Python ะพะดะฝั–ั”ัŽ ะท ะฝะฐะนะฟะพะฟัƒะปัั€ะฝั–ัˆะธั… ั„ัƒะฝะบั†ั–ะน ั” "ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั". + +**FastAPI** ะฟะพะฒะฝั–ัั‚ัŽ ะฟั–ะดั‚ั€ะธะผัƒั” ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั ัƒ ะฒัั–ั… ะผั–ัั†ัั…, ั‚ะพะผัƒ ะฒะฐะผ ั€ั–ะดะบะพ ะดะพะฒะตะดะตั‚ัŒัั ะฟะพะฒะตั€ั‚ะฐั‚ะธัั ะดะพ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั—. + +ะŸั€ะธะบะปะฐะด ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั ัƒ ั€ะตะดะฐะบั‚ะพั€ะฐั…: + +* ัƒ Visual Studio Code: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +* ัƒ PyCharm: + +![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) + +### ะšะพั€ะพั‚ะบะธะน ะบะพะด +FastAPI ะผะฐั” ั€ะพะทัƒะผะฝั– ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั **ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ**, ะฐะปะต ะฒัั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะผะพะถะฝะฐ ะฝะฐะปะฐัˆั‚ะพะฒัƒะฒะฐั‚ะธ ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ะฒะฐัˆะธั… ะฟะพั‚ั€ะตะฑ. ะžะดะฝะฐะบ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ ะฒัะต "ะฟั€ะพัั‚ะพ ะฟั€ะฐั†ัŽั”". + +### ะ’ะฐะปั–ะดะฐั†ั–ั +* ะŸั–ะดั‚ั€ะธะผะบะฐ ะฒะฐะปั–ะดะฐั†ั–ั— ะดะปั ะฑั–ะปัŒัˆะพัั‚ั– (ะฐะฑะพ ะฒัั–ั…?) **ั‚ะธะฟั–ะฒ ะดะฐะฝะธั… Python**, ะทะพะบั€ะตะผะฐ: + * JSON-ะพะฑ'ั”ะบั‚ั–ะฒ (`dict`). + * JSON-ัะฟะธัะบั–ะฒ (`list`) ะท ะฒะธะทะฝะฐั‡ะตะฝะฝัะผ ั‚ะธะฟั–ะฒ ะตะปะตะผะตะฝั‚ั–ะฒ. + * ะ ัะดะบั–ะฒ (`str`) ั–ะท ะผั–ะฝั–ะผะฐะปัŒะฝะพัŽ ั‚ะฐ ะผะฐะบัะธะผะฐะปัŒะฝะพัŽ ะดะพะฒะถะธะฝะพัŽ. + * ะงะธัะตะป (`int`, `float`) ะท ะพะฑะผะตะถะตะฝะฝัะผะธ ะผั–ะฝั–ะผะฐะปัŒะฝะธั… ั‚ะฐ ะผะฐะบัะธะผะฐะปัŒะฝะธั… ะทะฝะฐั‡ะตะฝัŒ ั‚ะพั‰ะพ. + +* ะ’ะฐะปั–ะดะฐั†ั–ั ัะบะปะฐะดะฝั–ัˆะธั… ั‚ะธะฟั–ะฒ, ั‚ะฐะบะธั… ัะบ: + * URL. + * Email. + * UUID. + * ...ั‚ะฐ ั–ะฝัˆั–. + +ะฃัั ะฒะฐะปั–ะดะฐั†ั–ั ะฒะธะบะพะฝัƒั”ั‚ัŒัั ั‡ะตั€ะตะท ะฝะฐะดั–ะนะฝะธะน ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะตะฝะธะน **Pydantic**. + +### ะ‘ะตะทะฟะตะบะฐ ั‚ะฐ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั + +**FastAPI** ะฟั–ะดั‚ั€ะธะผัƒั” ะฒะฑัƒะดะพะฒะฐะฝัƒ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ัŽ ั‚ะฐ ะฐะฒั‚ะพั€ะธะทะฐั†ั–ัŽ, ะฑะตะท ะฟั€ะธะฒโ€™ัะทะบะธ ะดะพ ะบะพะฝะบั€ะตั‚ะฝะธั… ะฑะฐะท ะดะฐะฝะธั… ั‡ะธ ะผะพะดะตะปะตะน ะดะฐะฝะธั…. + +ะŸั–ะดั‚ั€ะธะผัƒัŽั‚ัŒัั ะฒัั– ัั…ะตะผะธ ะฑะตะทะฟะตะบะธ OpenAPI, ะฒะบะปัŽั‡ะฐัŽั‡ะธ: + +* HTTP Basic. +* **OAuth2** (ั‚ะฐะบะพะถ ั–ะท ะฟั–ะดั‚ั€ะธะผะบะพัŽ **JWT-ั‚ะพะบะตะฝั–ะฒ**). ะ”ะธะฒ. ะฟั–ะดั€ัƒั‡ะฝะธะบ: [OAuth2 ั–ะท JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* ะšะปัŽั‡ั– API ะฒ: + * ะ—ะฐะณะพะปะพะฒะบะฐั…. + * ะŸะฐั€ะฐะผะตั‚ั€ะฐั… ะทะฐะฟะธั‚ัƒ. + * Cookies ั‚ะพั‰ะพ. + +ะ ั‚ะฐะบะพะถ ัƒัั– ะผะพะถะปะธะฒะพัั‚ั– ะฑะตะทะฟะตะบะธ ะฒั–ะด Starlette (ะทะพะบั€ะตะผะฐ **ัะตัั–ะนะฝั– cookies**). + +ะฃัั– ะฒะพะฝะธ ัั‚ะฒะพั€ะตะฝั– ัะบ ะฑะฐะณะฐั‚ะพั€ะฐะทะพะฒั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ั‚ะฐ ะบะพะผะฟะพะฝะตะฝั‚ะธ, ัะบั– ะปะตะณะบะพ ั–ะฝั‚ะตะณั€ัƒัŽั‚ัŒัั ะท ะฒะฐัˆะธะผะธ ัะธัั‚ะตะผะฐะผะธ, ัั…ะพะฒะธั‰ะฐะผะธ ะดะฐะฝะธั…, ั€ะตะปัั†ั–ะนะฝะธะผะธ ั‚ะฐ NoSQL ะฑะฐะทะฐะผะธ ะดะฐะฝะธั… ั‚ะพั‰ะพ. + +### ะ’ะฟั€ะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพัั‚ะตะน + +**FastAPI** ะผั–ัั‚ะธั‚ัŒ ะฝะฐะดะทะฒะธั‡ะฐะนะฝะพ ะฟั€ะพัั‚ัƒ ัƒ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั–, ะฐะปะต ะฟะพั‚ัƒะถะฝัƒ ัะธัั‚ะตะผัƒ ะฒะฟั€ะพะฒะฐะดะถะตะฝะฝั ะทะฐะปะตะถะฝะพัั‚ะตะน. + +* ะ—ะฐะปะตะถะฝะพัั‚ั– ะผะพะถัƒั‚ัŒ ะผะฐั‚ะธ ะฒะปะฐัะฝั– ะทะฐะปะตะถะฝะพัั‚ั–, ัƒั‚ะฒะพั€ัŽัŽั‡ะธ ั–ั”ั€ะฐั€ั…ั–ัŽ ะฐะฑะพ **"ะณั€ะฐั„ ะทะฐะปะตะถะฝะพัั‚ะตะน"**. +* ะฃัั– ะทะฐะปะตะถะฝะพัั‚ั– ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะบะตั€ัƒัŽั‚ัŒัั ั„ั€ะตะนะผะฒะพั€ะบะพะผ. +* ะฃัั– ะทะฐะปะตะถะฝะพัั‚ั– ะผะพะถัƒั‚ัŒ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะดะฐะฝั– ะท ะทะฐะฟะธั‚ั–ะฒ ั– ั€ะพะทัˆะธั€ัŽะฒะฐั‚ะธ **ะพะฑะผะตะถะตะฝะฝั ะพะฟะตั€ะฐั†ั–ั— ะทะฐ ัˆะปัั…ะพะผ** ั‚ะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝัƒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ัŽ. +* **ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะฐ ะฒะฐะปั–ะดะฐั†ั–ั** ะฝะฐะฒั–ั‚ัŒ ะดะปั ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ*, ะฒะธะทะฝะฐั‡ะตะฝะธั… ัƒ ะทะฐะปะตะถะฝะพัั‚ัั…. +* ะŸั–ะดั‚ั€ะธะผะบะฐ ัะบะปะฐะดะฝะธั… ัะธัั‚ะตะผ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะฐั†ั–ั— ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ, **ะท'ั”ะดะฝะฐะฝัŒ ั–ะท ะฑะฐะทะฐะผะธ ะดะฐะฝะธั…** ั‚ะพั‰ะพ. +* **ะ–ะพะดะฝะธั… ะพะฑะผะตะถะตะฝัŒ** ั‰ะพะดะพ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะฑะฐะท ะดะฐะฝะธั…, ั„ั€ะพะฝั‚ะตะฝะดั–ะฒ ั‚ะพั‰ะพ, ะฐะปะต ะฒะพะดะฝะพั‡ะฐั ะฟั€ะพัั‚ะฐ ั–ะฝั‚ะตะณั€ะฐั†ั–ั ะท ัƒัั–ะผะฐ ะฝะธะผะธ. + +### ะะตะผะฐั” ะพะฑะผะตะถะตะฝัŒ ะฝะฐ "ะฟะปะฐะณั–ะฝะธ" + +ะะฑะพ ั–ะฝัˆะธะผะธ ัะปะพะฒะฐะผะธ, ะฒะพะฝะธ ะฝะต ะฟะพั‚ั€ั–ะฑะฝั– โ€“ ะฟั€ะพัั‚ะพ ั–ะผะฟะพั€ั‚ัƒะนั‚ะต ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฝะตะพะฑั…ั–ะดะฝะธะน ะบะพะด. + +ะ‘ัƒะดัŒ-ัะบะฐ ั–ะฝั‚ะตะณั€ะฐั†ั–ั ัะฟั€ะพั”ะบั‚ะพะฒะฐะฝะฐ ะฝะฐัั‚ั–ะปัŒะบะธ ะฟั€ะพัั‚ะพ (ะท ะฒะธะบะพั€ะธัั‚ะฐะฝะฝัะผ ะทะฐะปะตะถะฝะพัั‚ะตะน), ั‰ะพ ะฒะธ ะผะพะถะตั‚ะต ัั‚ะฒะพั€ะธั‚ะธ "ะฟะปะฐะณั–ะฝ" ะดะปั ัะฒะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ ะฒััŒะพะณะพ ัƒ 2 ั€ัะดะบะฐั… ะบะพะดัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ั‚ัƒ ัะฐะผัƒ ัั‚ั€ัƒะบั‚ัƒั€ัƒ ั‚ะฐ ัะธะฝั‚ะฐะบัะธั, ั‰ะพ ะน ะดะปั ะฒะฐัˆะธั… *ะพะฟะตั€ะฐั†ั–ะน ัˆะปัั…ัƒ*. + +### ะŸั€ะพั‚ะตัั‚ะพะฒะฐะฝะพ + +* 100% ะฟะพะบั€ะธั‚ั‚ั ั‚ะตัั‚ะฐะผะธ. +* 100% ะฐะฝะพั‚ะพะฒะฐะฝะฐ ั‚ะธะฟะฐะผะธ ะบะพะดะพะฒะฐ ะฑะฐะทะฐ. +* ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ัƒ ั€ะพะฑะพั‡ะธั… ัะตั€ะตะดะพะฒะธั‰ะฐั…. + +## ะœะพะถะปะธะฒะพัั‚ั– Starlette + +**FastAPI** ะฟะพะฒะฝั–ัั‚ัŽ ััƒะผั–ัะฝะธะน ั–ะท (ั‚ะฐ ะฟะพะฑัƒะดะพะฒะฐะฝะธะน ะฝะฐ ะพัะฝะพะฒั–) Starlette. ะขะพะผัƒ ะฑัƒะดัŒ-ัะบะธะน ะดะพะดะฐั‚ะบะพะฒะธะน ะบะพะด Starlette, ัะบะธะน ะฒะธ ะผะฐั”ั‚ะต, ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต. + +**FastAPI** ั„ะฐะบั‚ะธั‡ะฝะพ ั” ะฟั–ะดะบะปะฐัะพะผ **Starlette**. ะขะพะผัƒ, ัะบั‰ะพ ะฒะธ ะฒะถะต ะทะฝะฐะนะพะผั– ะทั– Starlette ะฐะฑะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ะต ะนะพะณะพ, ะฑั–ะปัŒัˆั–ัั‚ัŒ ั„ัƒะฝะบั†ั–ะพะฝะฐะปัŒะฝะพัั‚ั– ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต ั‚ะฐะบ ัะฐะผะพ. + +ะ— **FastAPI** ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฒัั– ะผะพะถะปะธะฒะพัั‚ั– **Starlette** (ะฐะดะถะต FastAPI โ€” ั†ะต, ะฟะพ ััƒั‚ั–, Starlette ะฝะฐ ัั‚ะตั€ะพั—ะดะฐั…): + +* ะ ะฐะทัŽั‡ะฐ ะฟั€ะพะดัƒะบั‚ะธะฒะฝั–ัั‚ัŒ. ะฆะต ะพะดะธะฝ ั–ะท ะฝะฐะนัˆะฒะธะดัˆะธั… ั„ั€ะตะนะผะฒะพั€ะบั–ะฒ ะฝะฐ Python, ะฝะฐ ั€ั–ะฒะฝั– ะท **NodeJS** ั– **Go**. +* ะŸั–ะดั‚ั€ะธะผะบะฐ **WebSocket**. +* ะคะพะฝะพะฒั– ะทะฐะดะฐั‡ั– ัƒ ะฟั€ะพั†ะตัั–. +* ะŸะพะดั–ั— ะทะฐะฟัƒัะบัƒ ั‚ะฐ ะทะฐะฒะตั€ัˆะตะฝะฝั ั€ะพะฑะพั‚ะธ. +* ะšะปั–ั”ะฝั‚ ะดะปั ั‚ะตัั‚ัƒะฒะฐะฝะฝั, ะฟะพะฑัƒะดะพะฒะฐะฝะธะน ะฝะฐ HTTPX. +* ะŸั–ะดั‚ั€ะธะผะบะฐ **CORS**, **GZip**, ัั‚ะฐั‚ะธั‡ะฝะธั… ั„ะฐะนะปั–ะฒ, ะฟะพั‚ะพะบะพะฒะธั… ะฒั–ะดะฟะพะฒั–ะดะตะน. +* ะŸั–ะดั‚ั€ะธะผะบะฐ **ัะตัั–ะน** ั– **cookie**. +* 100% ะฟะพะบั€ะธั‚ั‚ั ั‚ะตัั‚ะฐะผะธ. +* 100% ะฐะฝะพั‚ะพะฒะฐะฝะฐ ั‚ะธะฟะฐะผะธ ะบะพะดะพะฒะฐ ะฑะฐะทะฐ. + +## ะœะพะถะปะธะฒะพัั‚ั– Pydantic + +**FastAPI** ะฟะพะฒะฝั–ัั‚ัŽ ััƒะผั–ัะฝะธะน ั–ะท (ั‚ะฐ ะฟะพะฑัƒะดะพะฒะฐะฝะธะน ะฝะฐ ะพัะฝะพะฒั–) Pydantic. ะขะพะผัƒ ะฑัƒะดัŒ-ัะบะธะน ะดะพะดะฐั‚ะบะพะฒะธะน ะบะพะด Pydantic, ัะบะธะน ะฒะธ ะผะฐั”ั‚ะต, ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽะฒะฐั‚ะธะผะต. + +ะ’ะบะปัŽั‡ะฐัŽั‡ะธ ะทะพะฒะฝั–ัˆะฝั– ะฑั–ะฑะปั–ะพั‚ะตะบะธ, ะฟะพะฑัƒะดะพะฒะฐะฝั– ั‚ะฐะบะพะถ ะฝะฐ Pydantic, ั‚ะฐะบั– ัะบ ORM, ODM ะดะปั ะฑะฐะท ะดะฐะฝะธั…. + +ะฆะต ั‚ะฐะบะพะถ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒ ะฑะฐะณะฐั‚ัŒะพั… ะฒะธะฟะฐะดะบะฐั… ะฒะธ ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ั‚ะพะน ัะฐะผะธะน ะพะฑ'ั”ะบั‚, ัะบะธะน ะพั‚ั€ะธะผัƒั”ั‚ะต ะท ะทะฐะฟะธั‚ัƒ, **ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฒ ะฑะฐะทัƒ ะดะฐะฝะธั…**, ะพัะบั–ะปัŒะบะธ ะฒัะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะตั€ะตะฒั–ั€ัั”ั‚ัŒัั. + +ะขะต ะถ ัะฐะผะต ะฒั–ะดะฑัƒะฒะฐั”ั‚ัŒัั ะน ัƒ ะทะฒะพั€ะพั‚ะฝะพะผัƒ ะฝะฐะฟั€ัะผะบัƒ โ€” ัƒ ะฑะฐะณะฐั‚ัŒะพั… ะฒะธะฟะฐะดะบะฐั… ะฒะธ ะผะพะถะตั‚ะต ะฟั€ะพัั‚ะพ ะฟะตั€ะตะดะฐั‚ะธ ะพะฑ'ั”ะบั‚, ัะบะธะน ะพั‚ั€ะธะผัƒั”ั‚ะต ะท ะฑะฐะทะธ ะดะฐะฝะธั…, **ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะบะปั–ั”ะฝั‚ัƒ**. + +ะ— **FastAPI** ะฒะธ ะพั‚ั€ะธะผัƒั”ั‚ะต ะฒัั– ะผะพะถะปะธะฒะพัั‚ั– **Pydantic** (ะฐะดะถะต FastAPI ะฑะฐะทัƒั”ั‚ัŒัั ะฝะฐ Pydantic ะดะปั ะพะฑั€ะพะฑะบะธ ะฒัั–ั… ะดะฐะฝะธั…): + +* **ะั–ัะบะพั— ะฟะปัƒั‚ะฐะฝะธะฝะธ** : + * ะะต ะฟะพั‚ั€ั–ะฑะฝะพ ะฒั‡ะธั‚ะธ ะฝะพะฒัƒ ะผะพะฒัƒ ะดะปั ะฒะธะทะฝะฐั‡ะตะฝะฝั ัั…ะตะผ. + * ะฏะบั‰ะพ ะฒะธ ะทะฝะฐั”ั‚ะต ั‚ะธะฟะธ Python, ะฒะธ ะทะฝะฐั”ั‚ะต, ัะบ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ Pydantic. +* ะ›ะตะณะบะพ ะฟั€ะฐั†ัŽั” ะท ะฒะฐัˆะธะผ **IDE/ะปั–ะฝั‚ะตั€ะพะผ/ะผะพะทะบะพะผ**: + * ะžัะบั–ะปัŒะบะธ ัั‚ั€ัƒะบั‚ัƒั€ะธ ะดะฐะฝะธั… Pydantic ั” ะฟั€ะพัั‚ะพ ะตะบะทะตะผะฟะปัั€ะฐะผะธ ะบะปะฐัั–ะฒ, ัะบั– ะฒะธ ะฒะธะทะฝะฐั‡ะฐั”ั‚ะต; ะฐะฒั‚ะพะดะพะฟะพะฒะฝะตะฝะฝั, ะปั–ะฝั‚ะธะฝะณ, mypy ั– ะฒะฐัˆะฐ ั–ะฝั‚ัƒั—ั†ั–ั ะฟะพะฒะธะฝะฝั– ะดะพะฑั€ะต ะฟั€ะฐั†ัŽะฒะฐั‚ะธ ะท ะฒะฐัˆะธะผะธ ะฟะตั€ะตะฒั–ั€ะตะฝะธะผะธ ะดะฐะฝะธะผะธ. +* ะ’ะฐะปั–ะดะฐั†ั–ั **ัะบะปะฐะดะฝะธั… ัั‚ั€ัƒะบั‚ัƒั€**: + * ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ั–ั”ั€ะฐั€ั…ั–ั‡ะฝะธั… ะผะพะดะตะปะตะน Pydantic. Python `typing`, `List` ั– `Dict` ั‚ะพั‰ะพ. + * ะ’ะฐะปั–ะดะฐั‚ะพั€ะธ ะดะพะทะฒะพะปััŽั‚ัŒ ั‡ั–ั‚ะบะพ ั– ะฟั€ะพัั‚ะพ ะฒะธะทะฝะฐั‡ะฐั‚ะธ, ะฟะตั€ะตะฒั–ั€ัั‚ะธ ะน ะดะพะบัƒะผะตะฝั‚ัƒะฒะฐั‚ะธ ัะบะปะฐะดะฝั– ัั…ะตะผะธ ะดะฐะฝะธั… ัƒ ะฒะธะณะปัะดั– JSON-ัั…ะตะผะธ. + * ะ’ะธ ะผะพะถะตั‚ะต ะผะฐั‚ะธ ะณะปะธะฑะพะบะพ **ะฒะบะปะฐะดะตะฝั– JSON ะพะฑ'ั”ะบั‚ะธ** ั‚ะฐ ะฟะตั€ะตะฒั–ั€ะธั‚ะธ ั‚ะฐ ะฐะฝะพั‚ัƒะฒะฐั‚ะธ ั—ั… ะฒัั–. +* **ะ ะพะทัˆะธั€ัŽะฒะฐะฝั–ัั‚ัŒ**: + * Pydantic ะดะพะทะฒะพะปัั” ะฒะธะทะฝะฐั‡ะฐั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั†ัŒะบั– ั‚ะธะฟะธ ะดะฐะฝะธั… ะฐะฑะพ ั€ะพะทัˆะธั€ัŽะฒะฐั‚ะธ ะฒะฐะปั–ะดะฐั†ั–ัŽ ะผะตั‚ะพะดะฐะผะธ ะฒ ะผะพะดะตะปั– ะดะตะบะพั€ะฐั‚ะพั€ะพะผ `validator`. +* 100% ะฟะพะบั€ะธั‚ั‚ั ั‚ะตัั‚ะฐะผะธ. diff --git a/docs/uk/docs/learn/index.md b/docs/uk/docs/learn/index.md new file mode 100644 index 000000000..7f9f21e57 --- /dev/null +++ b/docs/uk/docs/learn/index.md @@ -0,0 +1,5 @@ +# ะะฐะฒั‡ะฐะฝะฝั + +ะฃ ั†ัŒะพะผัƒ ั€ะพะทะดั–ะปั– ะฝะฐะดะฐะฝั– ะฒัั‚ัƒะฟะฝั– ั‚ะฐ ะฝะฐะฒั‡ะฐะปัŒะฝั– ะผะฐั‚ะตั€ั–ะฐะปะธ ะดะปั ะฒะธะฒั‡ะตะฝะฝั FastAPI. + +ะฆะต ะผะพะถะฝะฐ ั€ะพะทะณะปัะดะฐั‚ะธ ัะบ **ะบะฝะธะณัƒ**, **ะบัƒั€ั**, ะฐะฑะพ **ะพั„ั–ั†ั–ะนะฝะธะน** ั‚ะฐ ั€ะตะบะพะผะตะฝะดะพะฒะฐะฝะธะน ัะฟะพัั–ะฑ ะพัะฒะพั—ั‚ะธ FastAPI. ๐Ÿ˜Ž diff --git a/docs/uk/docs/tutorial/static-files.md b/docs/uk/docs/tutorial/static-files.md new file mode 100644 index 000000000..a84782d8f --- /dev/null +++ b/docs/uk/docs/tutorial/static-files.md @@ -0,0 +1,40 @@ +# ะกั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ + +ะ’ะธ ะผะพะถะตั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฝะฐะดะฐะฒะฐั‚ะธ ัั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ ะท ะบะฐั‚ะฐะปะพะณัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ `StaticFiles`. + +## ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั `StaticFiles` + +* ะ†ะผะฟะพั€ั‚ัƒะนั‚ะต `StaticFiles`. +* "ะŸั–ะด'ั”ะดะฝะฐั‚ะธ" ะตะบะทะตะผะฟะปัั€ `StaticFiles()` ะท ะฒะบะฐะทะฐะฝะฝัะผ ะฝะตะพะฑั…ั–ะดะฝะพะณะพ ัˆะปัั…ัƒ. + +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} + +/// note | ะขะตั…ะฝั–ั‡ะฝั– ะดะตั‚ะฐะปั– + +ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ `from starlette.staticfiles import StaticFiles`. + +**FastAPI** ะฝะฐะดะฐั” ั‚ะพะน ัะฐะผะธะน `starlette.staticfiles`, ั‰ะพ ะน `fastapi.staticfiles` ะดะปั ะทั€ัƒั‡ะฝะพัั‚ั– ั€ะพะทั€ะพะฑะฝะธะบั–ะฒ. ะะปะต ั„ะฐะบั‚ะธั‡ะฝะพ ะฒั–ะฝ ะฑะตะทะฟะพัะตั€ะตะดะฝัŒะพ ะฟะพั…ะพะดะธั‚ัŒ ั–ะท Starlette. + +/// + +### ะฉะพ ั‚ะฐะบะต "ะŸั–ะด'ั”ะดะฝะฐะฝะฝั" + +"ะŸั–ะด'ั”ะดะฝะฐะฝะฝั" ะพะทะฝะฐั‡ะฐั” ะดะพะดะฐะฒะฐะฝะฝั ะฟะพะฒะฝะพั†ั–ะฝะฝะพะณะพ "ะฝะตะทะฐะปะตะถะฝะพะณะพ" ะทะฐัั‚ะพััƒะฝะบัƒ ะทะฐ ะฟะตะฒะฝะธะผ ัˆะปัั…ะพะผ, ัะบะธะน ะฟะพั‚ั–ะผ ะพะฑั€ะพะฑะปัั” ะฒัั– ะฟั–ะด ัˆะปัั…ะธ. + +ะฆะต ะฒั–ะดั€ั–ะทะฝัั”ั‚ัŒัั ะฒั–ะด ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั `APIRouter`, ะพัะบั–ะปัŒะบะธ ะฟั–ะด'ั”ะดะฝะฐะฝะธะน ะทะฐัั‚ะพััƒะฝะพะบ ั” ะฟะพะฒะฝั–ัั‚ัŽ ะฝะตะทะฐะปะตะถะฝะธะผ. OpenAPI ั‚ะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั ะฒะฐัˆะพะณะพ ะพัะฝะพะฒะฝะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ ะฝะต ะฑัƒะดัƒั‚ัŒ ะทะฝะฐั‚ะธ ะฝั–ั‡ะพะณะพ ะฟั€ะพ ะฒะฐัˆ ะฟั–ะด'ั”ะดะฝะฐะฝะธะน ะทะฐัั‚ะพััƒะฝะพะบ. + +ะ’ะธ ะผะพะถะตั‚ะต ะดั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต ะฟั€ะพ ั†ะต ะฒ [ะŸะพัั–ะฑะฝะธะบัƒ ะดะปั ะฟั€ะพััƒะฝัƒั‚ะธั… ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ](../advanced/index.md){.internal-link target=_blank}. + +## ะ”ะตั‚ะฐะปั– + +ะŸะตั€ัˆะต `"/static"` ะฒะบะฐะทัƒั” ะฝะฐ ะฟั–ะด ัˆะปัั…, ะทะฐ ัะบะธะผ ะฑัƒะดะต "ะฟั–ะด'ั”ะดะฝะฐะฝะพ" ั†ะตะน ะฝะพะฒะธะน "ะทะฐัั‚ะพััƒะฝะพะบ". ะขะพะผัƒ ะฑัƒะดัŒ-ัะบะธะน ัˆะปัั…, ัะบะธะน ะฟะพั‡ะธะฝะฐั”ั‚ัŒัั ะท `"/static"`, ะฑัƒะดะต ะพะฑั€ะพะฑะปัั‚ะธัั ะฝะธะผ. + +`directory="static"` ะฒะธะทะฝะฐั‡ะฐั” ะบะฐั‚ะฐะปะพะณ, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ะฒะฐัˆั– ัั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ. + +`name="static"` ั†ะต ั–ะผ'ั, ัะบะต ะผะพะถะฝะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒัะตั€ะตะดะธะฝั– **FastAPI**. + +ะฃัั– ั†ั– ะฟะฐั€ะฐะผะตั‚ั€ะธ ะผะพะถัƒั‚ัŒ ะฑัƒั‚ะธ ะทะผั–ะฝะตะฝั– ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ะฟะพั‚ั€ะตะฑ ั– ะพัะพะฑะปะธะฒะพัั‚ะตะน ะฒะฐัˆะพะณะพ ะทะฐัั‚ะพััƒะฝะบัƒ. + +## ะ”ะพะดะฐั‚ะบะพะฒะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั + +ะ”ะตั‚ะฐะปัŒะฝั–ัˆะต ะฟั€ะพ ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ั‚ะฐ ะผะพะถะปะธะฒะพัั‚ั– ะผะพะถะฝะฐ ะดั–ะทะฝะฐั‚ะธัั ะฒ ะดะพะบัƒะผะตะฝั‚ะฐั†ั–ั— Starlette ะฟั€ะพ ัั‚ะฐั‚ะธั‡ะฝั– ั„ะฐะนะปะธ. diff --git a/docs/vi/docs/environment-variables.md b/docs/vi/docs/environment-variables.md new file mode 100644 index 000000000..dd06f8959 --- /dev/null +++ b/docs/vi/docs/environment-variables.md @@ -0,0 +1,300 @@ +# Biแบฟn mรดi trฦฐแปng (Environment Variables) + +/// tip + +Nแบฟu bแบกn ฤ‘รฃ biแบฟt vแป "biแบฟn mรดi trฦฐแปng" vร  cรกch sแปญ dแปฅng chรบng, bแบกn cรณ thแปƒ bแป qua phแบงn nร y. + +/// + +Mแป™t biแบฟn mรดi trฦฐแปng (cรฒn ฤ‘ฦฐแปฃc gแปi lร  "**env var**") lร  mแป™t biแบฟn mร  tแป“n tแบกi **bรชn ngoร i** ฤ‘oแบกn mรฃ Python, แปŸ trong **hแป‡ ฤ‘iแปu hร nh**, vร  cรณ thแปƒ ฤ‘ฦฐแปฃc ฤ‘แปc bแปŸi ฤ‘oแบกn mรฃ Python cแปงa bแบกn (hoแบทc bแปŸi cรกc chฦฐฦกng trรฌnh khรกc). + +Cรกc biแบฟn mรดi trฦฐแปng cรณ thแปƒ ฤ‘ฦฐแปฃc sแปญ dแปฅng ฤ‘แปƒ xแปญ lรญ **cรกc thiแบฟt lแบญp** cแปงa แปฉng dแปฅng, nhฦฐ mแป™t phแบงn cแปงa **cรกc quรก trรฌnh cร i ฤ‘แบทt** Python, v.v. + +## Tแบกo vร  Sแปญ dแปฅng cรกc Biแบฟn Mรดi Trฦฐแปng + +Bแบกn cรณ thแปƒ **tแบกo** vร  sแปญ dแปฅng cรกc biแบฟn mรดi trฦฐแปng trong **shell (terminal)**, mร  khรดng cแบงn sแปญ dแปฅng Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Bแบกn cรณ thแปƒ tแบกo mแป™t biแบฟn mรดi trฦฐแปng MY_NAME vแป›i +$ export MY_NAME="Wade Wilson" + +// Sau ฤ‘รณ bแบกn cรณ thแปƒ sแปญ dแปฅng nรณ vแป›i cรกc chฦฐฦกng trรฌnh khรกc, nhฦฐ +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Tแบกo mแป™t biแบฟn mรดi trฦฐแปng MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Sแปญ dแปฅng nรณ vแป›i cรกc chฦฐฦกng trรฌnh khรกc, nhฦฐ lร  +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## ฤแปc cรกc Biแบฟn Mรดi Trฦฐแปng trong Python + +Bแบกn cลฉng cรณ thแปƒ tแบกo cรกc biแบฟn mรดi trฦฐแปng **bรชn ngoร i** ฤ‘oแบกn mรฃ Python, trong terminal (hoแบทc bแบฑng bแบฅt kแปณ phฦฐฦกng phรกp nร o khรกc), vร  sau ฤ‘รณ **ฤ‘แปc chรบng trong Python**. + +Vรญ dแปฅ, bแบกn cรณ mแป™t file `main.py` vแป›i: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip + +Tham sแป‘ thแปฉ hai cho `os.getenv()` lร  giรก trแป‹ mแบทc ฤ‘แป‹nh ฤ‘แปƒ trแบฃ vแป. + +Nแบฟu khรดng ฤ‘ฦฐแปฃc cung cแบฅp, nรณ mแบทc ฤ‘แป‹nh lร  `None`, แปŸ ฤ‘รขy chรบng ta cung cแบฅp `"World"` lร  giรก trแป‹ mแบทc ฤ‘แป‹nh ฤ‘แปƒ sแปญ dแปฅng. + +/// + +Sau ฤ‘รณ bแบกn cรณ thแปƒ gแปi chฦฐฦกng trรฌnh Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// แปž ฤ‘รขy chรบng ta chฦฐa cร i ฤ‘แบทt biแบฟn mรดi trฦฐแปng +$ python main.py + +// Vรฌ chรบng ta chฦฐa cร i ฤ‘แบทt biแบฟn mรดi trฦฐแปng, chรบng ta nhแบญn ฤ‘ฦฐแปฃc giรก trแป‹ mแบทc ฤ‘แป‹nh + +Hello World from Python + +// Nhฦฐng nแบฟu chรบng ta tแบกo mแป™t biแบฟn mรดi trฦฐแปng trฦฐแป›c ฤ‘รณ +$ export MY_NAME="Wade Wilson" + +// Vร  sau ฤ‘รณ gแปi chฦฐฦกng trรฌnh lแบกi +$ python main.py + +// Bรขy giแป nรณ cรณ thแปƒ ฤ‘แปc biแบฟn mรดi trฦฐแปng + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// แปž ฤ‘รขy chรบng ta chฦฐa cร i ฤ‘แบทt biแบฟn mรดi trฦฐแปng +$ python main.py + +// Vรฌ chรบng ta chฦฐa cร i ฤ‘แบทt biแบฟn mรดi trฦฐแปng, chรบng ta nhแบญn ฤ‘ฦฐแปฃc giรก trแป‹ mแบทc ฤ‘แป‹nh + +Hello World from Python + +// Nhฦฐng nแบฟu chรบng ta tแบกo mแป™t biแบฟn mรดi trฦฐแปng trฦฐแป›c ฤ‘รณ +$ $Env:MY_NAME = "Wade Wilson" + +// Vร  sau ฤ‘รณ gแปi chฦฐฦกng trรฌnh lแบกi +$ python main.py + +// Bรขy giแป nรณ cรณ thแปƒ ฤ‘แปc biแบฟn mรดi trฦฐแปng + +Hello Wade Wilson from Python +``` + +
+ +//// + +Vรฌ cรกc biแบฟn mรดi trฦฐแปng cรณ thแปƒ ฤ‘ฦฐแปฃc tแบกo bรชn ngoร i ฤ‘oแบกn mรฃ Python, nhฦฐng cรณ thแปƒ ฤ‘ฦฐแปฃc ฤ‘แปc bแปŸi ฤ‘oแบกn mรฃ Python, vร  khรดng cแบงn ฤ‘ฦฐแปฃc lฦฐu trแปฏ (commit vร o `git`) cรนng vแป›i cรกc file khรกc, nรชn chรบng thฦฐแปng ฤ‘ฦฐแปฃc sแปญ dแปฅng ฤ‘แปƒ lฦฐu cรกc thiแบฟt lแบญp hoแบทc **cแบฅu hรฌnh**. + +Bแบกn cลฉng cรณ thแปƒ tแบกo ra mแป™t biแบฟn mรดi trฦฐแปng dร nh riรชng cho mแป™t **lแบงn gแปi chฦฐฦกng trรฌnh**, chแป‰ cรณ thแปƒ ฤ‘ฦฐแปฃc sแปญ dแปฅng bแปŸi chฦฐฦกng trรฌnh ฤ‘รณ, vร  chแป‰ trong thแปi gian chแบกy cแปงa chฦฐฦกng trรฌnh. + +ฤแปƒ lร m ฤ‘iแปu nร y, tแบกo nรณ ngay trฦฐแป›c chฦฐฦกng trรฌnh ฤ‘รณ, trรชn cรนng mแป™t dรฒng: + +
+ +```console +// Tแบกo mแป™t biแบฟn mรดi trฦฐแปng MY_NAME cho lแบงn gแปi chฦฐฦกng trรฌnh nร y +$ MY_NAME="Wade Wilson" python main.py + +// Bรขy giแป nรณ cรณ thแปƒ ฤ‘แปc biแบฟn mรดi trฦฐแปng + +Hello Wade Wilson from Python + +// Biแบฟn mรดi trฦฐแปng khรดng cรฒn tแป“n tแบกi sau ฤ‘รณ +$ python main.py + +Hello World from Python +``` + +
+ +/// tip + +Bแบกn cรณ thแปƒ ฤ‘แปc thรชm vแป ฤ‘iแปu nร y tแบกi The Twelve-Factor App: Config. + +/// + +## Cรกc Kiแปƒu (Types) vร  Kiแปƒm tra (Validation) + +Cรกc biแบฟn mรดi trฦฐแปng cรณ thแปƒ chแป‰ xแปญ lรญ **chuแป—i kรฝ tแปฑ**, vรฌ chรบng nแบฑm bรชn ngoร i ฤ‘oแบกn mรฃ Python vร  phแบฃi tฦฐฦกng thรญch vแป›i cรกc chฦฐฦกng trรฌnh khรกc vร  phแบงn cรฒn lแบกi cแปงa hแป‡ thแป‘ng (vร  thแบญm chรญ vแป›i cรกc hแป‡ ฤ‘iแปu hร nh khรกc, nhฦฐ Linux, Windows, macOS). + +ฤiแปu nร y cรณ nghฤฉa lร  **bแบฅt kแปณ giรก trแป‹ nร o** ฤ‘ฦฐแปฃc ฤ‘แปc trong Python tแปซ mแป™t biแบฟn mรดi trฦฐแปng **sแบฝ lร  mแป™t `str`**, vร  bแบฅt kแปณ hร nh ฤ‘แป™ng chuyแปƒn ฤ‘แป•i sang kiแปƒu dแปฏ liแป‡u khรกc hoแบทc hร nh ฤ‘แป™ng kiแปƒm tra nร o cลฉng phแบฃi ฤ‘ฦฐแปฃc thแปฑc hiแป‡n trong ฤ‘oแบกn mรฃ. + +Bแบกn sแบฝ hแปc thรชm vแป viแป‡c sแปญ dแปฅng biแบฟn mรดi trฦฐแปng ฤ‘แปƒ xแปญ lรญ **cรกc thiแบฟt lแบญp แปฉng dแปฅng** trong [Hฦฐแป›ng dแบซn nรขng cao - Cรกc thiแบฟt lแบญp vร  biแบฟn mรดi trฦฐแปng](./advanced/settings.md){.internal-link target=_blank}. + +## Biแบฟn mรดi trฦฐแปng `PATH` + +Cรณ mแป™t biแบฟn mรดi trฦฐแปng **ฤ‘แบทc biแป‡t** ฤ‘ฦฐแปฃc gแปi lร  **`PATH`** ฤ‘ฦฐแปฃc sแปญ dแปฅng bแปŸi cรกc hแป‡ ฤ‘iแปu hร nh (Linux, macOS, Windows) nhแบฑm tรฌm cรกc chฦฐฦกng trรฌnh ฤ‘แปƒ thแปฑc thi. + +Giรก trแป‹ cแปงa biแบฟn mรดi trฦฐแปng `PATH` lร  mแป™t chuแป—i dร i ฤ‘ฦฐแปฃc tแบกo bแปŸi cรกc thฦฐ mแปฅc ฤ‘ฦฐแปฃc phรขn tรกch bแปŸi dแบฅu hai chแบฅm `:` trรชn Linux vร  macOS, vร  bแปŸi dแบฅu chแบฅm phแบฉy `;` trรชn Windows. + +Vรญ dแปฅ, biแบฟn mรดi trฦฐแปng `PATH` cรณ thแปƒ cรณ dแบกng nhฦฐ sau: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +ฤiแปu nร y cรณ nghฤฉa lร  hแป‡ thแป‘ng sแบฝ tรฌm kiแบฟm cรกc chฦฐฦกng trรฌnh trong cรกc thฦฐ mแปฅc: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +ฤiแปu nร y cรณ nghฤฉa lร  hแป‡ thแป‘ng sแบฝ tรฌm kiแบฟm cรกc chฦฐฦกng trรฌnh trong cรกc thฦฐ mแปฅc: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +Khi bแบกn gรต mแป™t **lแป‡nh** trong terminal, hแป‡ ฤ‘iแปu hร nh **tรฌm kiแบฟm** chฦฐฦกng trรฌnh trong **mแป—i thฦฐ mแปฅc** ฤ‘ฦฐแปฃc liแป‡t kรช trong biแบฟn mรดi trฦฐแปng `PATH`. + +Vรญ dแปฅ, khi bแบกn gรต `python` trong terminal, hแป‡ ฤ‘iแปu hร nh tรฌm kiแบฟm mแป™t chฦฐฦกng trรฌnh ฤ‘ฦฐแปฃc gแปi `python` trong **thฦฐ mแปฅc ฤ‘แบงu tiรชn** trong danh sรกch ฤ‘รณ. + +Nแบฟu tรฌm thแบฅy, nรณ sแบฝ **sแปญ dแปฅng** nรณ. Nแบฟu khรดng tรฌm thแบฅy, nรณ sแบฝ tiแบฟp tแปฅc tรฌm kiแบฟm trong **cรกc thฦฐ mแปฅc khรกc**. + +### Cร i ฤ‘แบทt Python vร  cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH` + +Khi bแบกn cร i ฤ‘แบทt Python, bแบกn cรณ thแปƒ ฤ‘ฦฐแปฃc hแปi nแบฟu bแบกn muแป‘n cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH`. + +//// tab | Linux, macOS + +Giแบฃ sแปญ bแบกn cร i ฤ‘แบทt Python vร o thฦฐ mแปฅc `/opt/custompython/bin`. + +Nแบฟu bแบกn chแปn cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH`, thรฌ cร i ฤ‘แบทt sแบฝ thรชm `/opt/custompython/bin` vร o biแบฟn mรดi trฦฐแปng `PATH`. + +Nรณ cรณ thแปƒ cรณ dแบกng nhฦฐ sau: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +Nhฦฐ vแบญy, khi bแบกn gรต `python` trong terminal, hแป‡ thแป‘ng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong `/opt/custompython/bin` (thฦฐ mแปฅc cuแป‘i) vร  sแปญ dแปฅng nรณ. + +//// + +//// tab | Windows + +Giแบฃ sแปญ bแบกn cร i ฤ‘แบทt Python vร o thฦฐ mแปฅc `C:\opt\custompython\bin`. + +Nแบฟu bแบกn chแปn cแบญp nhแบญt biแบฟn mรดi trฦฐแปng `PATH`, thรฌ cร i ฤ‘แบทt sแบฝ thรชm `C:\opt\custompython\bin` vร o biแบฟn mรดi trฦฐแปng `PATH`. + +Nรณ cรณ thแปƒ cรณ dแบกng nhฦฐ sau: + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +Nhฦฐ vแบญy, khi bแบกn gรต `python` trong terminal, hแป‡ thแป‘ng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong `C:\opt\custompython\bin` (thฦฐ mแปฅc cuแป‘i) vร  sแปญ dแปฅng nรณ. + +//// + +Vแบญy, nแบฟu bแบกn gรต: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +Hแป‡ thแป‘ng sแบฝ **tรฌm kiแบฟm** chฦฐฦกng trรฌnh `python` trong `/opt/custompython/bin` vร  thแปฑc thi nรณ. + +Nรณ tฦฐฦกng ฤ‘ฦฐฦกng vแป›i viแป‡c bแบกn gรต: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +Hแป‡ thแป‘ng sแบฝ **tรฌm kiแบฟm** chฦฐฦกng trรฌnh `python` trong `C:\opt\custompython\bin\python` vร  thแปฑc thi nรณ. + +Nรณ tฦฐฦกng ฤ‘ฦฐฦกng vแป›i viแป‡c bแบกn gรต: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +Thรดng tin nร y sแบฝ hแปฏu รญch khi bแบกn hแปc vแป [Mรดi trฦฐแปng แบฃo](virtual-environments.md){.internal-link target=_blank}. + +## Kแบฟt luแบญn + +Vแป›i nhแปฏng thรดng tin nร y, bแบกn cรณ thแปƒ hiแปƒu ฤ‘ฦฐแปฃc **cรกc biแบฟn mรดi trฦฐแปng lร  gรฌ** vร  **cรกch sแปญ dแปฅng chรบng trong Python**. + +Bแบกn cรณ thแปƒ ฤ‘แปc thรชm vแป chรบng tแบกi Wikipedia cho Biแบฟn mรดi trฦฐแปng. + +Trong nhiแปu trฦฐแปng hแปฃp, cรกch cรกc biแบฟn mรดi trฦฐแปng trแปŸ nรชn hแปฏu รญch vร  cรณ thแปƒ รกp dแปฅng khรดng thแปฑc sแปฑ rรต rร ng ngay tแปซ ฤ‘แบงu, nhฦฐng chรบng sแบฝ liรชn tแปฅc xuแบฅt hiแป‡n trong rแบฅt nhiแปu tรฌnh huแป‘ng khi bแบกn phรกt triแปƒn แปฉng dแปฅng, vรฌ vแบญy viแป‡c hiแปƒu biแบฟt vแป chรบng lร  hแปฏu รญch. + +Chแบณng hแบกn, bแบกn sแบฝ cแบงn nhแปฏng thรดng tin nร y khi bแบกn hแปc vแป [Mรดi trฦฐแปng แบฃo](virtual-environments.md). diff --git a/docs/vi/docs/fastapi-cli.md b/docs/vi/docs/fastapi-cli.md new file mode 100644 index 000000000..d9e315ae4 --- /dev/null +++ b/docs/vi/docs/fastapi-cli.md @@ -0,0 +1,75 @@ +# FastAPI CLI + +**FastAPI CLI** lร  mแป™t chฦฐฦกng trรฌnh dรฒng lแป‡nh cรณ thแปƒ ฤ‘ฦฐแปฃc sแปญ dแปฅng ฤ‘แปƒ phแปฅc vแปฅ แปฉng dแปฅng FastAPI cแปงa bแบกn, quแบฃn lรฝ dแปฑ รกn FastAPI cแปงa bแบกn vร  nhiแปu hoแบกt ฤ‘แป™ng khรกc. + +Khi bแบกn cร i ฤ‘แบทt FastAPI (vd vแป›i `pip install "fastapi[standard]"`), nรณ sแบฝ bao gแป“m mแป™t gรณi ฤ‘ฦฐแปฃc gแปi lร  `fastapi-cli`, gรณi nร y cung cแบฅp lแป‡nh `fastapi` trong terminal. + +ฤแปƒ chแบกy แปฉng dแปฅng FastAPI cแปงa bแบกn cho quรก trรฌnh phรกt triแปƒn (development), bแบกn cรณ thแปƒ sแปญ dแปฅng lแป‡nh `fastapi dev`: + +
+ +```console +$ fastapi dev main.py + + FastAPI Starting development server ๐Ÿš€ + + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp + + module ๐Ÿ main.py + + code Importing the FastAPI app object from the module with the + following code: + + from main import app + + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to + quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. +``` + +
+ +Chฦฐฦกng trรฌnh dรฒng lแป‡nh `fastapi` lร  **FastAPI CLI**. + +FastAPI CLI nhแบญn ฤ‘ฦฐแปng dแบซn ฤ‘แบฟn chฦฐฦกng trรฌnh Python cแปงa bแบกn (vd `main.py`) vร  tแปฑ ฤ‘แป™ng phรกt hiแป‡n ฤ‘แป‘i tฦฐแปฃng `FastAPI` (thฦฐแปng ฤ‘ฦฐแปฃc gแปi lร  `app`), xรกc ฤ‘แป‹nh quรก trรฌnh nhแบญp ฤ‘รบng, vร  sau ฤ‘รณ chแบกy nรณ (serve). + +ฤแป‘i vแป›i vแบญn hร nh thแปฑc tแบฟ (production), bแบกn sแบฝ sแปญ dแปฅng `fastapi run` thay thแบฟ. ๐Ÿš€ + +แปž bรชn trong, **FastAPI CLI** sแปญ dแปฅng Uvicorn, mแป™t server ASGI cรณ hiแป‡u suแบฅt cao, sแบตn sร ng cho vแบญn hร nh thแปฑc tแบฟ (production). ๐Ÿ˜Ž + +## `fastapi dev` + +Chแบกy `fastapi dev` sแบฝ khแปŸi ฤ‘แป™ng quรก trรฌnh phรกt triแปƒn. + +Mแบทc ฤ‘แป‹nh, **auto-reload** ฤ‘ฦฐแปฃc bแบญt, tแปฑ ฤ‘แป™ng tแบฃi lแบกi server khi bแบกn thay ฤ‘แป•i code cแปงa bแบกn. ฤiแปu nร y tแป‘n nhiแปu tร i nguyรชn vร  cรณ thแปƒ kรฉm แป•n ฤ‘แป‹nh hฦกn khi nรณ bแป‹ tแบฏt. Bแบกn nรชn sแปญ dแปฅng nรณ cho quรก trรฌnh phรกt triแปƒn. Nรณ cลฉng lแบฏng nghe ฤ‘แป‹a chแป‰ IP `127.0.0.1`, ฤ‘รณ lร  ฤ‘แป‹a chแป‰ IP cแปงa mรกy tรญnh ฤ‘แปƒ tแปฑ giao tiแบฟp vแป›i chรญnh nรณ (`localhost`). + +## `fastapi run` + +Chแบกy `fastapi run` mแบทc ฤ‘แป‹nh sแบฝ khแปŸi ฤ‘แป™ng FastAPI cho quรก trรฌnh vแบญn hร nh thแปฑc tแบฟ. + +Mแบทc ฤ‘แป‹nh, **auto-reload** bแป‹ tแบฏt. Nรณ cลฉng lแบฏng nghe ฤ‘แป‹a chแป‰ IP `0.0.0.0`, ฤ‘รณ lร  tแบฅt cแบฃ cรกc ฤ‘แป‹a chแป‰ IP cรณ sแบตn, nhฦฐ vแบญy nรณ sแบฝ ฤ‘ฦฐแปฃc truy cแบญp cรดng khai bแปŸi bแบฅt kแปณ ai cรณ thแปƒ giao tiแบฟp vแป›i mรกy tรญnh. ฤรขy lร  cรกch bแบกn thฦฐแปng chแบกy nรณ trong sแบฃn phแบฉm hoร n thiแป‡n, vรญ dแปฅ trong mแป™t container. + +Trong hแบงu hแบฟt cรกc trฦฐแปng hแปฃp, bแบกn sแบฝ (vร  nรชn) cรณ mแป™t "proxy ฤ‘iแปƒm cuแป‘i (termination proxy)" xแปญ lรฝ HTTPS cho bแบกn, ฤ‘iแปu nร y sแบฝ phแปฅ thuแป™c vร o cรกch bแบกn triแปƒn khai แปฉng dแปฅng cแปงa bแบกn, nhร  cung cแบฅp cรณ thแปƒ lร m ฤ‘iแปu nร y cho bแบกn, hoแบทc bแบกn cรณ thแปƒ cแบงn thiแบฟt lแบญp nรณ. + +/// tip + +Bแบกn cรณ thแปƒ tรฌm hiแปƒu thรชm vแป FastAPI CLI trong [tร i liแป‡u triแปƒn khai](deployment/index.md){.internal-link target=_blank}. + +/// diff --git a/docs/vi/docs/tutorial/static-files.md b/docs/vi/docs/tutorial/static-files.md new file mode 100644 index 000000000..ecf8c2485 --- /dev/null +++ b/docs/vi/docs/tutorial/static-files.md @@ -0,0 +1,40 @@ +# Tแป‡p tฤฉnh (Static Files) + +Bแบกn cรณ thแปƒ triแปƒn khai tแป‡p tฤฉnh tแปฑ ฤ‘แป™ng tแปซ mแป™t thฦฐ mแปฅc bแบฑng cรกch sแปญ dแปฅng StaticFiles. + +## Sแปญ dแปฅng `Tแป‡p tฤฉnh` + +- Nhแบญp `StaticFiles`. +- "Mount" a `StaticFiles()` instance in a specific path. + +{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} + +/// note | Chi tiแบฟt kแปน thuแบญt + +Bแบกn cลฉng cรณ thแปƒ sแปญ dแปฅng `from starlette.staticfiles import StaticFiles`. + +**FastAPI** cung cแบฅp cรนng `starlette.staticfiles` nhฦฐ `fastapi.staticfiles` giรบp ฤ‘ฦกn giแบฃn hรณa viแป‡c sแปญ dแปฅng, nhฦฐng nรณ thแปฑc sแปฑ ฤ‘แบฟn tแปซ Starlette. + +/// + +### "Mounting" lร  gรฌ + +"Mounting" cรณ nghฤฉa lร  thรชm mแป™t แปฉng dแปฅng "ฤ‘แป™c lแบญp" hoร n chแป‰nh vร o mแป™t ฤ‘ฦฐแปng dแบซn cแปฅ thแปƒ, sau ฤ‘รณ แปฉng dแปฅng ฤ‘รณ sแบฝ chแป‹u trรกch nhiแป‡m xแปญ lรฝ tแบฅt cแบฃ cรกc ฤ‘ฦฐแปng dแบซn con. + +ฤiแปu nร y khรกc vแป›i viแป‡c sแปญ dแปฅng `APIRouter` vรฌ mแป™t แปฉng dแปฅng ฤ‘ฦฐแปฃc gแบฏn kแบฟt lร  hoร n toร n ฤ‘แป™c lแบญp. OpenAPI vร  tร i liแป‡u tแปซ แปฉng dแปฅng chรญnh cแปงa bแบกn sแบฝ khรดng bao gแป“m bแบฅt kแปณ thแปฉ gรฌ tแปซ แปฉng dแปฅng ฤ‘ฦฐแปฃc gแบฏn kแบฟt, v.v. + +Bแบกn cรณ thแปƒ ฤ‘แปc thรชm vแป ฤ‘iแปu nร y trong [Hฦฐแป›ng dแบซn Ngฦฐแปi dรนng Nรขng cao](../advanced/index.md){.internal-link target=\_blank}. + +## Chi tiแบฟt + +ฤฦฐแปng dแบซn ฤ‘แบงu tiรชn `"/static"` lร  ฤ‘ฦฐแปng dแบซn con mร  "แปฉng dแปฅng con" nร y sแบฝ ฤ‘ฦฐแปฃc "gแบฏn" vร o. Vรฌ vแบญy, bแบฅt kแปณ ฤ‘ฦฐแปng dแบซn nร o bแบฏt ฤ‘แบงu bแบฑng `"/static"` sแบฝ ฤ‘ฦฐแปฃc xแปญ lรฝ bแปŸi nรณ. + +ฤฦฐแปng dแบซn `directory="static"` lร  tรชn cแปงa thฦฐ mแปฅc chแปฉa tแป‡p tฤฉnh cแปงa bแบกn. + +Tham sแป‘ `name="static"` ฤ‘แบทt tรชn cho nรณ ฤ‘แปƒ cรณ thแปƒ ฤ‘ฦฐแปฃc sแปญ dแปฅng bรชn trong **FastAPI**. + +Tแบฅt cแบฃ cรกc tham sแป‘ nร y cรณ thแปƒ khรกc vแป›i `static`, ฤ‘iแปu chแป‰nh chรบng vแป›i phรน hแปฃp vแป›i แปฉng dแปฅng cแปงa bแบกn. + +## Thรดng tin thรชm + +ฤแปƒ biแบฟt thรชm chi tiแบฟt vร  tรนy chแปn, hรฃy xem Starlette's docs about Static Files. diff --git a/docs/vi/docs/virtual-environments.md b/docs/vi/docs/virtual-environments.md new file mode 100644 index 000000000..22d8e153e --- /dev/null +++ b/docs/vi/docs/virtual-environments.md @@ -0,0 +1,842 @@ +# Mรดi trฦฐแปng แบฃo (Virtual Environments) + +Khi bแบกn lร m viแป‡c trong cรกc dแปฑ รกn Python, bแบกn cรณ thแปƒ sแปญ dแปฅng mแป™t **mรดi trฦฐแปng แบฃo** (hoแบทc mแป™t cฦก chแบฟ tฦฐฦกng tแปฑ) ฤ‘แปƒ cรกch ly cรกc gรณi bแบกn cร i ฤ‘แบทt cho mแป—i dแปฑ รกn. + +/// info +Nแบฟu bแบกn ฤ‘รฃ biแบฟt vแป cรกc mรดi trฦฐแปng แบฃo, cรกch tแบกo chรบng vร  sแปญ dแปฅng chรบng, bแบกn cรณ thแปƒ bแป qua phแบงn nร y. ๐Ÿค“ + +/// + +/// tip + +Mแป™t **mรดi trฦฐแปng แบฃo** khรกc vแป›i mแป™t **biแบฟn mรดi trฦฐแปng (environment variable)**. + +Mแป™t **biแบฟn mรดi trฦฐแปng** lร  mแป™t biแบฟn trong hแป‡ thแป‘ng cรณ thแปƒ ฤ‘ฦฐแปฃc sแปญ dแปฅng bแปŸi cรกc chฦฐฦกng trรฌnh. + +Mแป™t **mรดi trฦฐแปng แบฃo** lร  mแป™t thฦฐ mแปฅc vแป›i mแป™t sแป‘ tแป‡p trong ฤ‘รณ. + +/// + +/// info + +Trang nร y sแบฝ hฦฐแป›ng dแบซn bแบกn cรกch sแปญ dแปฅng cรกc **mรดi trฦฐแปng แบฃo** vร  cรกch chรบng hoแบกt ฤ‘แป™ng. + +Nแบฟu bแบกn ฤ‘รฃ sแบตn sร ng sแปญ dแปฅng mแป™t **cรดng cแปฅ cรณ thแปƒ quแบฃn lรฝ tแบฅt cแบฃ mแปi thแปฉ** cho bแบกn (bao gแป“m cแบฃ viแป‡c cร i ฤ‘แบทt Python), hรฃy thแปญ uv. + +/// + +## Tแบกo mแป™t Dแปฑ รกn + +ฤแบงu tiรชn, tแบกo mแป™t thฦฐ mแปฅc cho dแปฑ รกn cแปงa bแบกn. + +Cรกch tรดi thฦฐแปng lร m lร  tแบกo mแป™t thฦฐ mแปฅc cรณ tรชn `code` trong thฦฐ mแปฅc `home/user`. + +Vร  trong thฦฐ mแปฅc ฤ‘รณ, tรดi tแบกo mแป™t thฦฐ mแปฅc cho mแป—i dแปฑ รกn. + +
+ +```console +// ฤi ฤ‘แบฟn thฦฐ mแปฅc home +$ cd +// Tแบกo mแป™t thฦฐ mแปฅc cho tแบฅt cแบฃ cรกc dแปฑ รกn cแปงa bแบกn +$ mkdir code +// Vร o thฦฐ mแปฅc code +$ cd code +// Tแบกo mแป™t thฦฐ mแปฅc cho dแปฑ รกn nร y +$ mkdir awesome-project +// Vร o thฦฐ mแปฅc dแปฑ รกn +$ cd awesome-project +``` + +
+ +## Tแบกo mแป™t Mรดi trฦฐแปng แบฃo + +Khi bแบกn bแบฏt ฤ‘แบงu lร m viแป‡c vแป›i mแป™t dแปฑ รกn Python **trong lแบงn ฤ‘แบงu**, hรฃy tแบกo mแป™t mรดi trฦฐแปng แบฃo **trong thฦฐ mแปฅc dแปฑ รกn cแปงa bแบกn**. + +/// tip + +Bแบกn cแบงn lร m ฤ‘iแปu nร y **mแป™t lแบงn cho mแป—i dแปฑ รกn**, khรดng phแบฃi mแป—i khi bแบกn lร m viแป‡c. +/// + +//// tab | `venv` + +ฤแปƒ tแบกo mแป™t mรดi trฦฐแปng แบฃo, bแบกn cรณ thแปƒ sแปญ dแปฅng module `venv` cรณ sแบตn cแปงa Python. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | Cรกch cรกc lแป‡nh hoแบกt ฤ‘แป™ng + +* `python`: sแปญ dแปฅng chฦฐฦกng trรฌnh `python` +* `-m`: gแปi mแป™t module nhฦฐ mแป™t script, chรบng ta sแบฝ nรณi vแป module ฤ‘รณ sau +* `venv`: sแปญ dแปฅng module `venv` ฤ‘ฦฐแปฃc cร i ฤ‘แบทt sแบตn cแปงa Python +* `.venv`: tแบกo mรดi trฦฐแปng แบฃo trong thฦฐ mแปฅc mแป›i `.venv` + +/// + +//// + +//// tab | `uv` + +Nแบฟu bแบกn cรณ `uv` ฤ‘ฦฐแปฃc cร i ฤ‘แบทt, bแบกn cรณ thแปƒ sแปญ dแปฅng nรณ ฤ‘แปƒ tแบกo mแป™t mรดi trฦฐแปng แบฃo. + +
+ +```console +$ uv venv +``` + +
+ +/// tip + +Mแบทc ฤ‘แป‹nh, `uv` sแบฝ tแบกo mแป™t mรดi trฦฐแปng แบฃo trong mแป™t thฦฐ mแปฅc cรณ tรชn `.venv`. + +Nhฦฐng bแบกn cรณ thแปƒ tรนy chแป‰nh nรณ bแบฑng cรกch thรชm mแป™t ฤ‘แป‘i sแป‘ vแป›i tรชn thฦฐ mแปฅc. + +/// + +//// + +Lแป‡nh nร y tแบกo mแป™t mรดi trฦฐแปng แบฃo mแป›i trong mแป™t thฦฐ mแปฅc cรณ tรชn `.venv`. + +/// details | `.venv` hoแบทc tรชn khรกc + +Bแบกn cรณ thแปƒ tแบกo mรดi trฦฐแปng แบฃo trong mแป™t thฦฐ mแปฅc khรกc, nhฦฐng thฦฐแปng ngฦฐแปi ta quy ฦฐแป›c ฤ‘แบทt nรณ lร  `.venv`. + +/// + +## Kรญch hoแบกt Mรดi trฦฐแปng แบฃo + +Kรญch hoแบกt mรดi trฦฐแปng แบฃo mแป›i ฤ‘แปƒ bแบฅt kแปณ lแป‡nh Python nร o bแบกn chแบกy hoแบทc gรณi nร o bแบกn cร i ฤ‘แบทt sแบฝ sแปญ dแปฅng nรณ. + +/// tip + +Lร m ฤ‘iแปu nร y **mแป—i khi** bแบกn bแบฏt ฤ‘แบงu mแป™t **phiรชn terminal mแป›i** ฤ‘แปƒ lร m viแป‡c trรชn dแปฑ รกn. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Nแบฟu bแบกn sแปญ dแปฅng Bash cho Windows (vรญ dแปฅ: Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip + +Mแป—i khi bแบกn cร i ฤ‘แบทt thรชm mแป™t **package mแป›i** trong mรดi trฦฐแปng ฤ‘รณ, hรฃy **kรญch hoแบกt** mรดi trฦฐแปng ฤ‘รณ lแบกi. + +ฤiแปu nร y ฤ‘แบฃm bแบฃo rแบฑng khi bแบกn sแปญ dแปฅng mแป™t **chฦฐฦกng trรฌnh dรฒng lแป‡nh (CLI)** ฤ‘ฦฐแปฃc cร i ฤ‘แบทt tแปซ gรณi ฤ‘รณ, bแบกn sแบฝ dรนng bแบฃn cร i ฤ‘แบทt tแปซ mรดi trฦฐแปng แบฃo cแปงa mรฌnh thay vรฌ bแบฃn ฤ‘ฦฐแปฃc cร i ฤ‘แบทt toร n cแปฅc khรกc cรณ thแปƒ cรณ phiรชn bแบฃn khรกc vแป›i phiรชn bแบฃn bแบกn cแบงn. + +/// + +## Kiแปƒm tra xem Mรดi trฦฐแปng แบฃo ฤ‘รฃ ฤ‘ฦฐแปฃc Kรญch hoแบกt chฦฐa + +Kiแปƒm tra xem mรดi trฦฐแปng แบฃo ฤ‘รฃ ฤ‘ฦฐแปฃc kรญch hoแบกt chฦฐa (lแป‡nh trฦฐแป›c ฤ‘รณ ฤ‘รฃ hoแบกt ฤ‘แป™ng). + +/// tip + +ฤiแปu nร y lร  **khรดng bแบฏt buแป™c**, nhฦฐng nรณ lร  mแป™t cรกch tแป‘t ฤ‘แปƒ **kiแปƒm tra** rแบฑng mแปi thแปฉ ฤ‘ang hoแบกt ฤ‘แป™ng nhฦฐ mong ฤ‘แปฃi vร  bแบกn ฤ‘ang sแปญ dแปฅng ฤ‘รบng mรดi trฦฐแปng แบฃo mร  bแบกn ฤ‘รฃ ฤ‘แป‹nh. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +Nแบฟu nรณ hiแปƒn thแป‹ `python` binary tแบกi `.venv/bin/python`, trong dแปฑ รกn cแปงa bแบกn (trong trฦฐแปng hแปฃp `awesome-project`), thรฌ tแปฉc lร  nรณ hoแบกt ฤ‘แป™ng. ๐ŸŽ‰ + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +Nแบฟu nรณ hiแปƒn thแป‹ `python` binary tแบกi `.venv\Scripts\python`, trong dแปฑ รกn cแปงa bแบกn (trong trฦฐแปng hแปฃp `awesome-project`), thรฌ tแปฉc lร  nรณ hoแบกt ฤ‘แป™ng. ๐ŸŽ‰ + +//// + +## Nรขng cแบฅp `pip` + +/// tip + +Nแบฟu bแบกn sแปญ dแปฅng `uv` bแบกn sแปญ dแปฅng nรณ ฤ‘แปƒ cร i ฤ‘แบทt thay vรฌ `pip`, thรฌ bแบกn khรดng cแบงn cแบญp nhแบญt `pip`. ๐Ÿ˜Ž + +/// + +Nแบฟu bแบกn sแปญ dแปฅng `pip` ฤ‘แปƒ cร i ฤ‘แบทt gรณi (nรณ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt mแบทc ฤ‘แป‹nh vแป›i Python), bแบกn nรชn **nรขng cแบฅp** nรณ lรชn phiรชn bแบฃn mแป›i nhแบฅt. + +Nhiแปu lแป—i khรกc nhau trong khi cร i ฤ‘แบทt gรณi ฤ‘ฦฐแปฃc giแบฃi quyแบฟt chแป‰ bแบฑng cรกch nรขng cแบฅp `pip` trฦฐแป›c. + +/// tip + +Bแบกn thฦฐแปng lร m ฤ‘iแปu nร y **mแป™t lแบงn**, ngay sau khi bแบกn tแบกo mรดi trฦฐแปng แบฃo. + +/// + +ฤแบฃm bแบฃo rแบฑng mรดi trฦฐแปng แบฃo ฤ‘รฃ ฤ‘ฦฐแปฃc kรญch hoแบกt (vแป›i lแป‡nh trรชn) vร  sau ฤ‘รณ chแบกy: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## Thรชm `.gitignore` + +Nแบฟu bแบกn sแปญ dแปฅng **Git** (nรชn lร m), hรฃy thรชm mแป™t file `.gitignore` ฤ‘แปƒ Git bแป qua mแปi thแปฉ trong `.venv`. + +/// tip + +Nแบฟu bแบกn sแปญ dแปฅng `uv` ฤ‘แปƒ tแบกo mรดi trฦฐแปng แบฃo, nรณ ฤ‘รฃ tแปฑ ฤ‘แป™ng lร m ฤ‘iแปu nร y cho bแบกn, bแบกn cรณ thแปƒ bแป qua bฦฐแป›c nร y. ๐Ÿ˜Ž + +/// + +/// tip + +Lร m ฤ‘iแปu nร y **mแป™t lแบงn**, ngay sau khi bแบกn tแบกo mรดi trฦฐแปng แบฃo. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | Cรกch lแป‡nh hoแบกt ฤ‘แป™ng + +* `echo "*"`: sแบฝ "in" vฤƒn bแบฃn `*` trong terminal (phแบงn tiแบฟp theo sแบฝ thay ฤ‘แป•i ฤ‘iแปu ฤ‘รณ mแป™t chรบt) +* `>`: bแบฅt kแปณ vฤƒn bแบฃn nร o ฤ‘ฦฐแปฃc in ra terminal bแปŸi lแป‡nh trฦฐแป›c `>` khรดng ฤ‘ฦฐแปฃc in ra mร  thay vร o ฤ‘รณ ฤ‘ฦฐแปฃc viแบฟt vร o file แปŸ phรญa bรชn phแบฃi cแปงa `>` +* `.gitignore`: tรชn cแปงa file mร  vฤƒn bแบฃn sแบฝ ฤ‘ฦฐแปฃc viแบฟt vร o + +Vร  `*` vแป›i Git cรณ nghฤฉa lร  "mแปi thแปฉ". Vรฌ vแบญy, nรณ sแบฝ bแป qua mแปi thแปฉ trong thฦฐ mแปฅc `.venv`. + +Lแป‡nh nร y sแบฝ tแบกo mแป™t file `.gitignore` vแป›i nแป™i dung: + +```gitignore +* +``` + +/// + +## Cร i ฤ‘แบทt gรณi (packages) + +Sau khi kรญch hoแบกt mรดi trฦฐแปng, bแบกn cรณ thแปƒ cร i ฤ‘แบทt cรกc gรณi trong ฤ‘รณ. + +/// tip + +Thแปฑc hiแป‡n ฤ‘iแปu nร y **mแป™t lแบงn** khi cร i ฤ‘แบทt hoแบทc cแบญp nhแบญt gรณi cแบงn thiแบฟt cho dแปฑ รกn cแปงa bแบกn. + +Nแบฟu bแบกn cแบงn cแบญp nhแบญt phiรชn bแบฃn hoแบทc thรชm mแป™t gรณi mแป›i, bแบกn sแบฝ **thแปฑc hiแป‡n ฤ‘iแปu nร y lแบกi**. + +/// + +### Cร i ฤ‘แบทt gรณi trแปฑc tiแบฟp + +Nแบฟu bแบกn cแบงn cแบญp nhแบญt phiรชn bแบฃn hoแบทc thรชm mแป™t gรณi mแป›i, bแบกn sแบฝ **thแปฑc hiแป‡n ฤ‘iแปu nร y lแบกi**. + +/// tip +ฤแปƒ quแบฃn lรฝ dแปฑ รกn tแป‘t hฦกn, hรฃy liแป‡t kรช tแบฅt cแบฃ cรกc gรณi vร  phiรชn bแบฃn cแบงn thiแบฟt trong mแป™t file (vรญ dแปฅ `requirements.txt` hoแบทc `pyproject.toml`). + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Nแบฟu bแบกn cรณ `uv`: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### Cร i ฤ‘แบทt tแปซ `requirements.txt` + +Nแบฟu bแบกn cรณ mแป™t tแป‡p `requirements.txt`, bแบกn cรณ thแปƒ sแปญ dแปฅng nรณ ฤ‘แปƒ cร i ฤ‘แบทt cรกc gรณi. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Nแบฟu bแบกn cรณ `uv`: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +Mแป™t tแป‡p `requirements.txt` vแป›i mแป™t sแป‘ gรณi sแบฝ trรดng nhฦฐ thแบฟ nร y: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## Chแบกy Chฦฐฦกng trรฌnh cแปงa bแบกn + +Sau khi kรญch hoแบกt mรดi trฦฐแปng แบฃo, bแบกn cรณ thแปƒ chแบกy chฦฐฦกng trรฌnh cแปงa mรฌnh, nรณ sแบฝ sแปญ dแปฅng Python trong mรดi trฦฐแปng แบฃo cแปงa bแบกn vแป›i cรกc gรณi bแบกn ฤ‘รฃ cร i ฤ‘แบทt. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Cแบฅu hรฌnh Trรฌnh soแบกn thแบฃo cแปงa bแบกn + +Nแบฟu bแบกn sแปญ dแปฅng mแป™t trรฌnh soแบกn thแบฃo, hรฃy ฤ‘แบฃm bแบฃo bแบกn cแบฅu hรฌnh nรณ ฤ‘แปƒ sแปญ dแปฅng cรนng mรดi trฦฐแปng แบฃo mร  bแบกn ฤ‘รฃ tแบกo (trรฌnh soแบกn thแบฃo sแบฝ tแปฑ ฤ‘แป™ng phรกt hiแป‡n mรดi trฦฐแปng แบฃo) ฤ‘แปƒ bแบกn cรณ thแปƒ nhแบญn ฤ‘ฦฐแปฃc tรญnh nฤƒng tแปฑ ฤ‘แป™ng hoร n thร nh cรขu lแป‡nh (autocomplete) vร  in lแป—i trแปฑc tiแบฟp trong trรฌnh soแบกn thแบฃo (inline errors). + +Vรญ dแปฅ: + +* VS Code +* PyCharm + +/// tip + +Bแบกn thฦฐแปng chแป‰ cแบงn lร m ฤ‘iแปu nร y **mแป™t lแบงn**, khi bแบกn tแบกo mรดi trฦฐแปng แบฃo. + +/// + +## Huแปท kรญch hoแบกt Mรดi trฦฐแปng แบฃo + +Khi bแบกn hoร n tแบฅt viแป‡c lร m trรชn dแปฑ รกn cแปงa bแบกn, bแบกn cรณ thแปƒ **huแปท kรญch hoแบกt** mรดi trฦฐแปng แบฃo. + +
+ +```console +$ deactivate +``` + +
+ +Nhฦฐ vแบญy, khi bแบกn chแบกy `python`, nรณ sแบฝ khรดng chแบกy tแปซ mรดi trฦฐแปng แบฃo ฤ‘รณ vแป›i cรกc gรณi ฤ‘รฃ cร i ฤ‘แบทt. + +## Sแบตn sร ng ฤ‘แปƒ Lร m viแป‡c + +Bรขy giแป bแบกn ฤ‘รฃ sแบตn sร ng ฤ‘แปƒ lร m viแป‡c trรชn dแปฑ รกn cแปงa mรฌnh rแป“i ฤ‘แบฅy. + +/// tip + +Bแบกn muแป‘n hiแปƒu tแบฅt cแบฃ nhแปฏng gรฌ แปŸ trรชn? + +Tiแบฟp tแปฅc ฤ‘แปc. ๐Ÿ‘‡๐Ÿค“ + +/// + +## Tแบกi sao cแบงn Mรดi trฦฐแปng แบฃo + +ฤแปƒ lร m viแป‡c vแป›i FastAPI, bแบกn cแบงn cร i ฤ‘แบทt Python. + +Sau ฤ‘รณ, bแบกn sแบฝ cแบงn **cร i ฤ‘แบทt** FastAPI vร  bแบฅt kแปณ **gรณi** nร o mร  bแบกn muแป‘n sแปญ dแปฅng. + +ฤแปƒ cร i ฤ‘แบทt gรณi, bแบกn thฦฐแปng sแปญ dแปฅng lแป‡nh `pip` cรณ sแบตn vแป›i Python (hoแบทc cรกc phiรชn bแบฃn tฦฐฦกng tแปฑ). + +Tuy nhiรชn, nแบฟu bแบกn sแปญ dแปฅng `pip` trแปฑc tiแบฟp, cรกc gรณi sแบฝ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt trong **mรดi trฦฐแปng Python toร n cแปฅc** cแปงa bแบกn (phแบงn cร i ฤ‘แบทt toร n cแปฅc cแปงa Python). + +### Vแบฅn ฤ‘แป + +Vแบญy, vแบฅn ฤ‘แป gรฌ khi cร i ฤ‘แบทt gรณi trong mรดi trฦฐแปng Python toร n cแปฅc? + +Trong mแป™t vร i thแปi ฤ‘iแปƒm, bแบกn sแบฝ phแบฃi viแบฟt nhiแปu chฦฐฦกng trรฌnh khรกc nhau phแปฅ thuแป™c vร o **cรกc gรณi khรกc nhau**. Vร  mแป™t sแป‘ dแปฑ รกn bแบกn thแปฑc hiแป‡n lแบกi phแปฅ thuแป™c vร o **cรกc phiรชn bแบฃn khรกc nhau** cแปงa cรนng mแป™t gรณi. ๐Ÿ˜ฑ + +Vรญ dแปฅ, bแบกn cรณ thแปƒ tแบกo mแป™t dแปฑ รกn ฤ‘ฦฐแปฃc gแปi lร  `philosophers-stone`, chฦฐฦกng trรฌnh nร y phแปฅ thuแป™c vร o mแป™t gรณi khรกc ฤ‘ฦฐแปฃc gแปi lร  **`harry`, sแปญ dแปฅng phiรชn bแบฃn `1`**. Vรฌ vแบญy, bแบกn cแบงn cร i ฤ‘แบทt `harry`. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|phแปฅ thuแป™c| harry-1[harry v1] +``` + +Sau ฤ‘รณ, vร o mแป™t vร i thแปi ฤ‘iแปƒm sau, bแบกn tแบกo mแป™t dแปฑ รกn khรกc ฤ‘ฦฐแปฃc gแปi lร  `prisoner-of-azkaban`, vร  dแปฑ รกn nร y cลฉng phแปฅ thuแป™c vร o `harry`, nhฦฐng dแปฑ รกn nร y cแบงn **`harry` phiรชn bแบฃn `3`**. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |phแปฅ thuแป™c| harry-3[harry v3] +``` + +Bรขy giแป, vแบฅn ฤ‘แป lร , nแบฟu bแบกn cร i ฤ‘แบทt cรกc gรณi toร n cแปฅc (trong mรดi trฦฐแปng toร n cแปฅc) thay vรฌ trong mแป™t **mรดi trฦฐแปng แบฃo cแปฅc bแป™**, bแบกn sแบฝ phแบฃi chแปn phiรชn bแบฃn `harry` nร o ฤ‘แปƒ cร i ฤ‘แบทt. + +Nแบฟu bแบกn muแป‘n chแบกy `philosophers-stone` bแบกn sแบฝ cแบงn phแบฃi cร i ฤ‘แบทt `harry` phiรชn bแบฃn `1`, vรญ dแปฅ vแป›i: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +Vร  sau ฤ‘รณ bแบกn sแบฝ cรณ `harry` phiรชn bแบฃn `1` ฤ‘ฦฐแปฃc cร i ฤ‘แบทt trong mรดi trฦฐแปng Python toร n cแปฅc cแปงa bแบกn. + +```mermaid +flowchart LR + subgraph global[mรดi trฦฐแปng toร n cแปฅc] + harry-1[harry v1] + end + subgraph stone-project[dแปฑ รกn philosophers-stone ] + stone(philosophers-stone) -->|phแปฅ thuแป™c| harry-1 + end +``` + +Nhฦฐng sau ฤ‘รณ, nแบฟu bแบกn muแป‘n chแบกy `prisoner-of-azkaban`, bแบกn sแบฝ cแบงn phแบฃi gแปก bแป `harry` phiรชn bแบฃn `1` vร  cร i ฤ‘แบทt `harry` phiรชn bแบฃn `3` (hoแบทc chแป‰ cแบงn cร i ฤ‘แบทt phiรชn bแบฃn `3` sแบฝ tแปฑ ฤ‘แป™ng gแปก bแป phiรชn bแบฃn `1`). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +Vร  sau ฤ‘รณ bแบกn sแบฝ cรณ `harry` phiรชn bแบฃn `3` ฤ‘ฦฐแปฃc cร i ฤ‘แบทt trong mรดi trฦฐแปng Python toร n cแปฅc cแปงa bแบกn. + +Vร  nแบฟu bแบกn cแป‘ gแบฏng chแบกy `philosophers-stone` lแบกi, cรณ khแบฃ nฤƒng nรณ sแบฝ **khรดng hoแบกt ฤ‘แป™ng** vรฌ nรณ cแบงn `harry` phiรชn bแบฃn `1`. + +```mermaid +flowchart LR + subgraph global[mรดi trฦฐแปng toร n cแปฅc] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[dแปฑ รกn philosophers-stone ] + stone(philosophers-stone) -.-x|โ›”๏ธ| harry-1 + end + subgraph azkaban-project[dแปฑ รกn prisoner-of-azkaban ] + azkaban(prisoner-of-azkaban) --> |phแปฅ thuแป™c| harry-3 + end +``` + +/// tip + +Mแบทc dรน cรกc gรณi Python thฦฐแปng cแป‘ gแบฏng **trรกnh cรกc thay ฤ‘แป•i lร m hแปng code** trong **phiรชn bแบฃn mแป›i**, nhฦฐng ฤ‘แปƒ ฤ‘แบฃm bแบฃo an toร n, bแบกn nรชn chแปง ฤ‘แป™ng cร i ฤ‘แบทt phiรชn bแบฃn mแป›i vร  chแบกy kiแปƒm thแปญ ฤ‘แปƒ xรกc nhแบญn mแปi thแปฉ vแบซn hoแบกt ฤ‘แป™ng ฤ‘รบng. + +/// + +Bรขy giแป, hรฃy hรฌnh dung vแป **nhiแปu** gรณi khรกc nhau mร  tแบฅt cแบฃ cรกc dแปฑ รกn cแปงa bแบกn phแปฅ thuแป™c vร o. Rรต rร ng rแบฅt khรณ ฤ‘แปƒ quแบฃn lรฝ. ฤiแปu nร y dแบซn tแป›i viแป‡c lร  bแบกn sแบฝ cรณ nhiแปu dแปฑ รกn vแป›i **cรกc phiรชn bแบฃn khรดng tฦฐฦกng thรญch** cแปงa cรกc gรณi, vร  bแบกn cรณ thแปƒ khรดng biแบฟt tแบกi sao mแป™t sแป‘ thแปฉ khรดng hoแบกt ฤ‘แป™ng. + +Hฦกn nแปฏa, tuแปณ vร o hแป‡ ฤ‘iแปu hร nh cแปงa bแบกn (vd Linux, Windows, macOS), cรณ thแปƒ ฤ‘รฃ cรณ Python ฤ‘ฦฐแปฃc cร i ฤ‘แบทt sแบตn. Trong trฦฐแปng hแปฃp แบฅy, mแป™t vร i gรณi nhiแปu khแบฃ nฤƒng ฤ‘รฃ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt trฦฐแป›c vแป›i cรกc phiรชn bแบฃn **cแบงn thiแบฟt cho hแป‡ thแป‘ng cแปงa bแบกn**. Nแบฟu bแบกn cร i ฤ‘แบทt cรกc gรณi trong mรดi trฦฐแปng Python toร n cแปฅc, bแบกn cรณ thแปƒ sแบฝ **phรก vแปก** mแป™t sแป‘ chฦฐฦกng trรฌnh ฤ‘รฃ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt sแบตn cรนng hแป‡ thแป‘ng. + +## Nฦกi cรกc Gรณi ฤ‘ฦฐแปฃc Cร i ฤ‘แบทt + +Khi bแบกn cร i ฤ‘แบทt Python, nรณ sแบฝ tแบกo ra mแป™t vร i thฦฐ mแปฅc vร  tแป‡p trong mรกy tรญnh cแปงa bแบกn. + +Mแป™t vร i thฦฐ mแปฅc nร y lร  nhแปฏng thฦฐ mแปฅc chแป‹u trรกch nhiแป‡m cรณ tแบฅt cแบฃ cรกc gรณi bแบกn cร i ฤ‘แบทt. + +Khi bแบกn chแบกy: + +
+ +```console +// ฤแปซng chแบกy lแป‡nh nร y ngay, ฤ‘รขy chแป‰ lร  mแป™t vรญ dแปฅ ๐Ÿค“ +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +Lแป‡nh nร y sแบฝ tแบฃi xuแป‘ng mแป™t tแป‡p nรฉn vแป›i mรฃ nguแป“n FastAPI, thฦฐแปng lร  tแปซ PyPI. + +Nรณ cลฉng sแบฝ **tแบฃi xuแป‘ng** cรกc tแป‡p cho cรกc gรณi khรกc mร  FastAPI phแปฅ thuแป™c vร o. + +Sau ฤ‘รณ, nรณ sแบฝ **giแบฃi nรฉn** tแบฅt cแบฃ cรกc tแป‡p ฤ‘รณ vร  ฤ‘ฦฐa chรบng vร o mแป™t thฦฐ mแปฅc trong mรกy tรญnh cแปงa bแบกn. + +Mแบทc ฤ‘แป‹nh, nรณ sแบฝ ฤ‘ฦฐa cรกc tแป‡p ฤ‘รฃ tแบฃi xuแป‘ng vร  giแบฃi nรฉn vร o thฦฐ mแปฅc ฤ‘ฦฐแปฃc cร i ฤ‘แบทt cรนng Python cแปงa bแบกn, ฤ‘รณ lร  **mรดi trฦฐแปng toร n cแปฅc**. + +## Nhแปฏng Mรดi trฦฐแปng แบฃo lร  gรฌ? + +Cรกch giแบฃi quyแบฟt cho vแบฅn ฤ‘แป cรณ tแบฅt cแบฃ cรกc gรณi trong mรดi trฦฐแปng toร n cแปฅc lร  sแปญ dแปฅng mแป™t **mรดi trฦฐแปng แบฃo cho mแป—i dแปฑ รกn** bแบกn lร m viแป‡c. + +Mแป™t mรดi trฦฐแปng แบฃo lร  mแป™t **thฦฐ mแปฅc**, rแบฅt giแป‘ng vแป›i mรดi trฦฐแปng toร n cแปฅc, trong ฤ‘รณ bแบกn cรณ thแปƒ cร i ฤ‘แบทt cรกc gรณi cho mแป™t dแปฑ รกn. + +Vรฌ vแบญy, mแป—i dแปฑ รกn sแบฝ cรณ mแป™t mรดi trฦฐแปng แบฃo riรชng cแปงa nรณ (thฦฐ mแปฅc `.venv`) vแป›i cรกc gรณi riรชng cแปงa nรณ. + +```mermaid +flowchart TB + subgraph stone-project[dแปฑ รกn philosophers-stone ] + stone(philosophers-stone) --->|phแปฅ thuแป™c| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[dแปฑ รกn prisoner-of-azkaban ] + azkaban(prisoner-of-azkaban) --->|phแปฅ thuแป™c| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## Kรญch hoแบกt Mรดi trฦฐแปng แบฃo nghฤฉa lร  gรฌ + +Khi bแบกn kรญch hoแบกt mแป™t mรดi trฦฐแปng แบฃo, vรญ dแปฅ vแป›i: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Nแบฟu bแบกn sแปญ dแปฅng Bash cho Windows (vรญ dแปฅ Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +Lแป‡nh nร y sแบฝ tแบกo hoแบทc sแปญa ฤ‘แป•i mแป™t sแป‘ [biแบฟn mรดi trฦฐแปng](environment-variables.md){.internal-link target=_blank} mร  sแบฝ ฤ‘ฦฐแปฃc sแปญ dแปฅng cho cรกc lแป‡nh tiแบฟp theo. + +Mแป™t trong sแป‘ ฤ‘รณ lร  biแบฟn `PATH`. + +/// tip + +Bแบกn cรณ thแปƒ tรฌm hiแปƒu thรชm vแป biแบฟn `PATH` trong [Biแบฟn mรดi trฦฐแปng](environment-variables.md#path-environment-variable){.internal-link target=_blank} section. + +/// + +Kรญch hoแบกt mรดi trฦฐแปng แบฃo thรชm ฤ‘ฦฐแปng dแบซn `.venv/bin` (trรชn Linux vร  macOS) hoแบทc `.venv\Scripts` (trรชn Windows) vร o biแบฟn `PATH`. + +Giแบฃ sแปญ rแบฑng trฦฐแป›c khi kรญch hoแบกt mรดi trฦฐแปng, biแบฟn `PATH` nhฦฐ sau: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +Nghฤฉa lร  hแป‡ thแป‘ng sแบฝ tรฌm kiแบฟm chฦฐฦกng trรฌnh trong: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +Nghฤฉa lร  hแป‡ thแป‘ng sแบฝ tรฌm kiแบฟm chฦฐฦกng trรฌnh trong: + +* `C:\Windows\System32` + +//// + +Sau khi kรญch hoแบกt mรดi trฦฐแปng แบฃo, biแบฟn `PATH` sแบฝ nhฦฐ sau: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Nghฤฉa lร  hแป‡ thแป‘ng sแบฝ bแบฏt ฤ‘แบงu tรฌm kiแบฟm chฦฐฦกng trรฌnh trong: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +trฦฐแป›c khi tรฌm kiแบฟm trong cรกc thฦฐ mแปฅc khรกc. + +Vรฌ vแบญy, khi bแบกn gรต `python` trong terminal, hแป‡ thแป‘ng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong: + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +vร  sแปญ dแปฅng chฦฐฦกng trรฌnh ฤ‘รณ. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +Nghฤฉa lร  hแป‡ thแป‘ng sแบฝ bแบฏt ฤ‘แบงu tรฌm kiแบฟm chฦฐฦกng trรฌnh trong: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +trฦฐแป›c khi tรฌm kiแบฟm trong cรกc thฦฐ mแปฅc khรกc. + +Vรฌ vแบญy, khi bแบกn gรต `python` trong terminal, hแป‡ thแป‘ng sแบฝ tรฌm thแบฅy chฦฐฦกng trรฌnh Python trong: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +vร  sแปญ dแปฅng chฦฐฦกng trรฌnh ฤ‘รณ. + +//// + +Mแป™t chi tiแบฟt quan trแปng lร  nรณ sแบฝ ฤ‘ฦฐa ฤ‘แป‹a chแป‰ cแปงa mรดi trฦฐแปng แบฃo vร o **ฤ‘แบงu** cแปงa biแบฟn `PATH`. Hแป‡ thแป‘ng sแบฝ tรฌm kiแบฟm nรณ **trฦฐแป›c** khi tรฌm kiแบฟm bแบฅt kแปณ Python nร o khรกc cรณ sแบตn. Vรฌ vแบญy, khi bแบกn chแบกy `python`, nรณ sแบฝ sแปญ dแปฅng Python **tแปซ mรดi trฦฐแปng แบฃo** thay vรฌ bแบฅt kแปณ Python nร o khรกc (vรญ dแปฅ, Python tแปซ mรดi trฦฐแปng toร n cแปฅc). + +Kรญch hoแบกt mแป™t mรดi trฦฐแปng แบฃo cลฉng thay ฤ‘แป•i mแป™t vร i thแปฉ khรกc, nhฦฐng ฤ‘รขy lร  mแป™t trong nhแปฏng ฤ‘iแปu quan trแปng nhแบฅt mร  nรณ thแปฑc hiแป‡n. + +## Kiแปƒm tra mแป™t Mรดi trฦฐแปng แบฃo + +Khi bแบกn kiแปƒm tra mแป™t mรดi trฦฐแปng แบฃo ฤ‘รฃ ฤ‘ฦฐแปฃc kรญch hoแบกt chฦฐa, vรญ dแปฅ vแป›i: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + + +ฤiแปu ฤ‘รณ cรณ nghฤฉa lร  chฦฐฦกng trรฌnh `python` sแบฝ ฤ‘ฦฐแปฃc sแปญ dแปฅng lร  chฦฐฦกng trรฌnh **trong mรดi trฦฐแปng แบฃo**. + +Bแบกn sแปญ dแปฅng `which` trรชn Linux vร  macOS vร  `Get-Command` trรชn Windows PowerShell. + +Cรกch hoแบกt ฤ‘แป™ng cแปงa lแป‡nh nร y lร  nรณ sแบฝ ฤ‘i vร  kiแปƒm tra biแบฟn `PATH`, ฤ‘i qua **mแป—i ฤ‘ฦฐแปng dแบซn theo thแปฉ tแปฑ**, tรฌm kiแบฟm chฦฐฦกng trรฌnh ฤ‘ฦฐแปฃc gแปi lร  `python`. Khi nรณ tรฌm thแบฅy nรณ, nรณ sแบฝ **hiแปƒn thแป‹ cho bแบกn ฤ‘ฦฐแปng dแบซn** ฤ‘แบฟn chฦฐฦกng trรฌnh ฤ‘รณ. + +ฤiแปu quan trแปng nhแบฅt lร  khi bแบกn gแปi `python`, ฤ‘รณ chรญnh lร  chฦฐฦกng trรฌnh `python` ฤ‘ฦฐแปฃc thแปฑc thi. + +Vรฌ vแบญy, bแบกn cรณ thแปƒ xรกc nhแบญn nแบฟu bแบกn ฤ‘ang แปŸ trong mรดi trฦฐแปng แบฃo ฤ‘รบng. + +/// tip + +Dแป… dร ng kรญch hoแบกt mแป™t mรดi trฦฐแปng แบฃo, cร i ฤ‘แบทt Python, vร  sau ฤ‘รณ **chuyแปƒn ฤ‘แบฟn mแป™t dแปฑ รกn khรกc**. + +Vร  dแปฑ รกn thแปฉ hai **sแบฝ khรดng hoแบกt ฤ‘แป™ng** vรฌ bแบกn ฤ‘ang sแปญ dแปฅng **Python khรดng ฤ‘รบng**, tแปซ mแป™t mรดi trฦฐแปng แบฃo cho mแป™t dแปฑ รกn khรกc. + +Thแบญt tiแป‡n lแปฃi khi cรณ thแปƒ kiแปƒm tra `python` nร o ฤ‘ang ฤ‘ฦฐแปฃc sแปญ dแปฅng ๐Ÿค“ + +/// + +## Tแบกi sao lแบกi Huแปท kรญch hoแบกt mแป™t Mรดi trฦฐแปng แบฃo + +Vรญ dแปฅ, bแบกn cรณ thแปƒ lร m viแป‡c trรชn mแป™t dแปฑ รกn `philosophers-stone`, **kรญch hoแบกt mรดi trฦฐแปng แบฃo**, cร i ฤ‘แบทt cรกc gรณi vร  lร m viแป‡c vแป›i mรดi trฦฐแปng แบฃo ฤ‘รณ. + +Sau ฤ‘รณ, bแบกn muแป‘n lร m viแป‡c trรชn **dแปฑ รกn khรกc** `prisoner-of-azkaban`. + +Bแบกn ฤ‘i ฤ‘แบฟn dแปฑ รกn ฤ‘รณ: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +Nแบฟu bแบกn khรดng tแบฏt mรดi trฦฐแปng แบฃo cho `philosophers-stone`, khi bแบกn chแบกy `python` trong terminal, nรณ sแบฝ cแป‘ gแบฏng sแปญ dแปฅng Python tแปซ `philosophers-stone`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Lแป—i khi import sirius, nรณ khรดng ฤ‘ฦฐแปฃc cร i ฤ‘แบทt ๐Ÿ˜ฑ +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +Nแบฟu bแบกn huแปท kรญch hoแบกt mรดi trฦฐแปng แบฃo hiแป‡n tแบกi vร  kรญch hoแบกt mรดi trฦฐแปng แบฃo mแป›i cho `prisoner-of-azkaban`, khi bแบกn chแบกy `python`, nรณ sแบฝ sแปญ dแปฅng Python tแปซ mรดi trฦฐแปng แบฃo trong `prisoner-of-azkaban`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// Bแบกn khรดng cแบงn phแบฃi แปŸ trong thฦฐ mแปฅc trฦฐแป›c ฤ‘แปƒ huแปท kรญch hoแบกt, bแบกn cรณ thแปƒ lร m ฤ‘iแปu ฤ‘รณ แปŸ bแบฅt kแปณ ฤ‘รขu, ngay cแบฃ sau khi ฤ‘i ฤ‘แบฟn dแปฑ รกn khรกc ๐Ÿ˜Ž +$ deactivate + +// Kรญch hoแบกt mรดi trฦฐแปng แบฃo trong prisoner-of-azkaban/.venv ๐Ÿš€ +$ source .venv/bin/activate + +// Bรขy giแป khi bแบกn chแบกy python, nรณ sแบฝ tรฌm thแบฅy gรณi sirius ฤ‘ฦฐแปฃc cร i ฤ‘แบทt trong mรดi trฦฐแปng แบฃo nร y โœจ +$ python main.py + +I solemnly swear ๐Ÿบ + +(Tรดi long trแปng thแป ๐Ÿบ - cรขu nร y ฤ‘ฦฐแปฃc lแบฅy tแปซ Harry Potter, chรบ thรญch cแปงa ngฦฐแปi dแป‹ch) +``` + +
+ +## Cรกc cรกch lร m tฦฐฦกng tแปฑ + +ฤรขy lร  mแป™t hฦฐแป›ng dแบซn ฤ‘ฦกn giแบฃn ฤ‘แปƒ bแบกn cรณ thแปƒ bแบฏt ฤ‘แบงu vร  hiแปƒu cรกch mแปi thแปฉ hoแบกt ฤ‘แป™ng **bรชn trong**. + +Cรณ nhiแปu **cรกch khรกc nhau** ฤ‘แปƒ quแบฃn lรญ cรกc mรดi trฦฐแปng แบฃo, cรกc gรณi phแปฅ thuแป™c (requirements), vร  cรกc dแปฑ รกn. + +Mแป™t khi bแบกn ฤ‘รฃ sแบตn sร ng vร  muแป‘n sแปญ dแปฅng mแป™t cรดng cแปฅ ฤ‘แปƒ **quแบฃn lรญ cแบฃ dแปฑ รกn**, cรกc gรณi phแปฅ thuแป™c, cรกc mรดi trฦฐแปng แบฃo, v.v. Tรดi sแบฝ khuyรชn bแบกn nรชn thแปญ uv. + +`uv` cรณ thแปƒ lร m nhiแปu thแปฉ, chแบณng hแบกn: + +* **Cร i ฤ‘แบทt Python** cho bแบกn, bao gแป“m nhiแปu phiรชn bแบฃn khรกc nhau +* Quแบฃn lรญ **cรกc mรดi trฦฐแปng แบฃo** cho cรกc dแปฑ รกn cแปงa bแบกn +* Cร i ฤ‘แบทt **cรกc gรณi (packages)** +* Quแบฃn lรญ **cรกc thร nh phแบงn phแปฅ thuแป™c vร  phiรชn bแบฃn** cแปงa cรกc gรณi cho dแปฑ รกn cแปงa bแบกn +* ฤแบฃm bแบฃo rแบฑng bแบกn cรณ mแป™t **tแบญp hแปฃp chรญnh xรกc** cรกc gรณi vร  phiรชn bแบฃn ฤ‘แปƒ cร i ฤ‘แบทt, bao gแป“m cรกc thร nh phแบงn phแปฅ thuแป™c cแปงa chรบng, ฤ‘แปƒ bแบกn cรณ thแปƒ ฤ‘แบฃm bแบฃo rแบฑng bแบกn cรณ thแปƒ chแบกy dแปฑ รกn cแปงa bแบกn trong sแบฃn xuแบฅt chรญnh xรกc nhฦฐ trong mรกy tรญnh cแปงa bแบกn trong khi phรกt triแปƒn, ฤ‘iแปu nร y ฤ‘ฦฐแปฃc gแปi lร  **locking** +* Vร  cรฒn nhiแปu thแปฉ khรกc nแปฏa + +## Kแบฟt luแบญn + +Nแบฟu bแบกn ฤ‘รฃ ฤ‘แปc vร  hiแปƒu hแบฟt nhแปฏng ฤ‘iแปu nร y, khรก chแบฏc lร  bรขy giแป bแบกn ฤ‘รฃ **biแบฟt nhiแปu hฦกn** vแป mรดi trฦฐแปng แบฃo so vแป›i kha khรก lแบญp trรฌnh viรชn khรกc ฤ‘แบฅy. ๐Ÿค“ + +Nhแปฏng hiแปƒu biแบฟt chi tiแบฟt nร y cรณ thแปƒ sแบฝ hแปฏu รญch vแป›i bแบกn trong tฦฐฦกng lai khi mร  bแบกn cแบงn gแปก lแป—i mแป™t vร i thแปฉ phแปฉc tแบกp, vร  bแบกn ฤ‘รฃ cรณ nhแปฏng hiแปƒu biแบฟt vแป **ngแปn ngร nh gแป‘c rแป… cรกch nรณ hoแบกt ฤ‘แป™ng**. ๐Ÿ˜Ž diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md index 2fba671f7..c2f9a7e9f 100644 --- a/docs/zh/docs/tutorial/query-params-str-validations.md +++ b/docs/zh/docs/tutorial/query-params-str-validations.md @@ -108,21 +108,6 @@ q: Union[str, None] = Query(default=None, min_length=3) {* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} -### ไฝฟ็”จ็œ็•ฅๅท(`...`)ๅฃฐๆ˜Žๅฟ…้œ€ๅ‚ๆ•ฐ - -ๆœ‰ๅฆไธ€็งๆ–นๆณ•ๅฏไปฅๆ˜พๅผ็š„ๅฃฐๆ˜Žไธ€ไธชๅ€ผๆ˜ฏๅฟ…้œ€็š„๏ผŒๅณๅฐ†้ป˜่ฎคๅ‚ๆ•ฐ็š„้ป˜่ฎคๅ€ผ่ฎพไธบ `...` ๏ผš - -{* ../../docs_src/query_params_str_validations/tutorial006b.py hl[7] *} - -/// info - -ๅฆ‚ๆžœไฝ ไน‹ๅ‰ๆฒก่ง่ฟ‡ `...` ่ฟ™็ง็”จๆณ•๏ผšๅฎƒๆ˜ฏไธ€ไธช็‰นๆฎŠ็š„ๅ•็‹ฌๅ€ผ๏ผŒๅฎƒๆ˜ฏ Python ็š„ไธ€้ƒจๅˆ†ๅนถไธ”่ขซ็งฐไธบโ€œEllipsisโ€๏ผˆๆ„ไธบ็œ็•ฅๅท โ€”โ€” ่ฏ‘่€…ๆณจ๏ผ‰ใ€‚ -Pydantic ๅ’Œ FastAPI ไฝฟ็”จๅฎƒๆฅๆ˜พๅผ็š„ๅฃฐๆ˜Ž้œ€่ฆไธ€ไธชๅ€ผใ€‚ - -/// - -่ฟ™ๅฐ†ไฝฟ **FastAPI** ็Ÿฅ้“ๆญคๆŸฅ่ฏขๅ‚ๆ•ฐๆ˜ฏๅฟ…้œ€็š„ใ€‚ - ### ไฝฟ็”จ`None`ๅฃฐๆ˜Žๅฟ…้œ€ๅ‚ๆ•ฐ ไฝ ๅฏไปฅๅฃฐๆ˜Žไธ€ไธชๅ‚ๆ•ฐๅฏไปฅๆŽฅๆ”ถ`None`ๅ€ผ๏ผŒไฝ†ๅฎƒไป็„ถๆ˜ฏๅฟ…้œ€็š„ใ€‚่ฟ™ๅฐ†ๅผบๅˆถๅฎขๆˆท็ซฏๅ‘้€ไธ€ไธชๅ€ผ๏ผŒๅณไฝฟ่ฏฅๅ€ผๆ˜ฏ`None`ใ€‚ @@ -137,18 +122,6 @@ Pydantic ๆ˜ฏ FastAPI ไธญๆ‰€ๆœ‰ๆ•ฐๆฎ้ชŒ่ฏๅ’Œๅบๅˆ—ๅŒ–็š„ๆ ธๅฟƒ๏ผŒๅฝ“ไฝ ๅœจๆฒก /// -### ไฝฟ็”จPydanticไธญ็š„`Required`ไปฃๆ›ฟ็œ็•ฅๅท(`...`) - -ๅฆ‚ๆžœไฝ ่ง‰ๅพ—ไฝฟ็”จ `...` ไธ่ˆ’ๆœ๏ผŒไฝ ไนŸๅฏไปฅไปŽ Pydantic ๅฏผๅ…ฅๅนถไฝฟ็”จ `Required`๏ผš - -{* ../../docs_src/query_params_str_validations/tutorial006d.py hl[2,8] *} - -/// tip - -่ฏท่ฎฐไฝ๏ผŒๅœจๅคงๅคšๆ•ฐๆƒ…ๅ†ตไธ‹๏ผŒๅฝ“ไฝ ้œ€่ฆๆŸไบ›ไธœ่ฅฟๆ—ถ๏ผŒๅฏไปฅ็ฎ€ๅ•ๅœฐ็œ็•ฅ `default` ๅ‚ๆ•ฐ๏ผŒๅ› ๆญคไฝ ้€šๅธธไธๅฟ…ไฝฟ็”จ `...` ๆˆ– `Required` - -/// - ## ๆŸฅ่ฏขๅ‚ๆ•ฐๅˆ—่กจ / ๅคšไธชๅ€ผ ๅฝ“ไฝ ไฝฟ็”จ `Query` ๆ˜พๅผๅœฐๅฎšไน‰ๆŸฅ่ฏขๅ‚ๆ•ฐๆ—ถ๏ผŒไฝ ่ฟ˜ๅฏไปฅๅฃฐๆ˜ŽๅฎƒๅŽปๆŽฅๆ”ถไธ€็ป„ๅ€ผ๏ผŒๆˆ–ๆขๅฅ่ฏๆฅ่ฏด๏ผŒๆŽฅๆ”ถๅคšไธชๅ€ผใ€‚ diff --git a/docs_src/body/tutorial002.py b/docs_src/body/tutorial002.py index 7f5183908..5cd86216b 100644 --- a/docs_src/body/tutorial002.py +++ b/docs_src/body/tutorial002.py @@ -17,7 +17,7 @@ app = FastAPI() @app.post("/items/") async def create_item(item: Item): item_dict = item.dict() - if item.tax: + if item.tax is not None: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) return item_dict diff --git a/docs_src/body/tutorial002_py310.py b/docs_src/body/tutorial002_py310.py index 8928b72b8..454c45c88 100644 --- a/docs_src/body/tutorial002_py310.py +++ b/docs_src/body/tutorial002_py310.py @@ -15,7 +15,7 @@ app = FastAPI() @app.post("/items/") async def create_item(item: Item): item_dict = item.dict() - if item.tax: + if item.tax is not None: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) return item_dict diff --git a/docs_src/configure_swagger_ui/tutorial002.py b/docs_src/configure_swagger_ui/tutorial002.py index cc569ce45..cc75c2196 100644 --- a/docs_src/configure_swagger_ui/tutorial002.py +++ b/docs_src/configure_swagger_ui/tutorial002.py @@ -1,6 +1,6 @@ from fastapi import FastAPI -app = FastAPI(swagger_ui_parameters={"syntaxHighlight.theme": "obsidian"}) +app = FastAPI(swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}}) @app.get("/users/{username}") diff --git a/docs_src/query_params_str_validations/tutorial006b.py b/docs_src/query_params_str_validations/tutorial006b.py deleted file mode 100644 index a8d69c889..000000000 --- a/docs_src/query_params_str_validations/tutorial006b.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str = Query(default=..., min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006b_an.py b/docs_src/query_params_str_validations/tutorial006b_an.py deleted file mode 100644 index ea3b02583..000000000 --- a/docs_src/query_params_str_validations/tutorial006b_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006b_an_py39.py b/docs_src/query_params_str_validations/tutorial006b_an_py39.py deleted file mode 100644 index 687a9f544..000000000 --- a/docs_src/query_params_str_validations/tutorial006b_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006c.py b/docs_src/query_params_str_validations/tutorial006c.py index 2ac148c94..0a0e820da 100644 --- a/docs_src/query_params_str_validations/tutorial006c.py +++ b/docs_src/query_params_str_validations/tutorial006c.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Union[str, None] = Query(default=..., min_length=3)): +async def read_items(q: Union[str, None] = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py index 10bf26a57..55c4f4adc 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an.py +++ b/docs_src/query_params_str_validations/tutorial006c_an.py @@ -7,7 +7,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...): +async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py310.py b/docs_src/query_params_str_validations/tutorial006c_an_py310.py index 1ab0a7d53..2995d9c97 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py310.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py310.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[str | None, Query(min_length=3)] = ...): +async def read_items(q: Annotated[str | None, Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py39.py b/docs_src/query_params_str_validations/tutorial006c_an_py39.py index ac1273331..76a1cd49a 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial006c_an_py39.py @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...): +async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_py310.py b/docs_src/query_params_str_validations/tutorial006c_py310.py index 82dd9e5d7..88b499c7a 100644 --- a/docs_src/query_params_str_validations/tutorial006c_py310.py +++ b/docs_src/query_params_str_validations/tutorial006c_py310.py @@ -4,7 +4,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: str | None = Query(default=..., min_length=3)): +async def read_items(q: str | None = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006d.py b/docs_src/query_params_str_validations/tutorial006d.py deleted file mode 100644 index a8d69c889..000000000 --- a/docs_src/query_params_str_validations/tutorial006d.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str = Query(default=..., min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006d_an.py b/docs_src/query_params_str_validations/tutorial006d_an.py deleted file mode 100644 index ea3b02583..000000000 --- a/docs_src/query_params_str_validations/tutorial006d_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006d_an_py39.py b/docs_src/query_params_str_validations/tutorial006d_an_py39.py deleted file mode 100644 index 687a9f544..000000000 --- a/docs_src/query_params_str_validations/tutorial006d_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = ...): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/security/tutorial004.py b/docs_src/security/tutorial004.py index 91d161b8a..222589618 100644 --- a/docs_src/security/tutorial004.py +++ b/docs_src/security/tutorial004.py @@ -95,7 +95,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py index df50754af..e2221cd39 100644 --- a/docs_src/security/tutorial004_an.py +++ b/docs_src/security/tutorial004_an.py @@ -96,7 +96,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py index eff54ef01..a3f74fc0e 100644 --- a/docs_src/security/tutorial004_an_py310.py +++ b/docs_src/security/tutorial004_an_py310.py @@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py index 0455b500c..b33d677ed 100644 --- a/docs_src/security/tutorial004_an_py39.py +++ b/docs_src/security/tutorial004_an_py39.py @@ -95,7 +95,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py index 78bee22a3..d46ce26bf 100644 --- a/docs_src/security/tutorial004_py310.py +++ b/docs_src/security/tutorial004_py310.py @@ -94,7 +94,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)): ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py index 5b67cb145..2e8bb3bdb 100644 --- a/docs_src/security/tutorial005_an.py +++ b/docs_src/security/tutorial005_an.py @@ -117,7 +117,7 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_scopes = payload.get("scopes", []) diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py index 297193e35..90781587f 100644 --- a/docs_src/security/tutorial005_an_py310.py +++ b/docs_src/security/tutorial005_an_py310.py @@ -116,7 +116,7 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_scopes = payload.get("scopes", []) diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py index 1acf47bdc..a5192d8d6 100644 --- a/docs_src/security/tutorial005_an_py39.py +++ b/docs_src/security/tutorial005_an_py39.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta, timezone -from typing import Annotated, List, Union +from typing import Annotated, Union import jwt from fastapi import Depends, FastAPI, HTTPException, Security, status @@ -44,7 +44,7 @@ class Token(BaseModel): class TokenData(BaseModel): username: Union[str, None] = None - scopes: List[str] = [] + scopes: list[str] = [] class User(BaseModel): @@ -116,7 +116,7 @@ async def get_current_user( ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") + username = payload.get("sub") if username is None: raise credentials_exception token_scopes = payload.get("scopes", []) diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 823957822..e3e0200ae 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.115.6" +__version__ = "0.115.8" from starlette import status as status diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py index d68bdb037..70c2dca8a 100644 --- a/fastapi/security/api_key.py +++ b/fastapi/security/api_key.py @@ -9,7 +9,15 @@ from typing_extensions import Annotated, Doc class APIKeyBase(SecurityBase): - pass + @staticmethod + def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]: + if not api_key: + if auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + return None + return api_key class APIKeyQuery(APIKeyBase): @@ -101,14 +109,7 @@ class APIKeyQuery(APIKeyBase): async def __call__(self, request: Request) -> Optional[str]: api_key = request.query_params.get(self.model.name) - if not api_key: - if self.auto_error: - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - else: - return None - return api_key + return self.check_api_key(api_key, self.auto_error) class APIKeyHeader(APIKeyBase): @@ -196,14 +197,7 @@ class APIKeyHeader(APIKeyBase): async def __call__(self, request: Request) -> Optional[str]: api_key = request.headers.get(self.model.name) - if not api_key: - if self.auto_error: - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - else: - return None - return api_key + return self.check_api_key(api_key, self.auto_error) class APIKeyCookie(APIKeyBase): @@ -291,11 +285,4 @@ class APIKeyCookie(APIKeyBase): async def __call__(self, request: Request) -> Optional[str]: api_key = request.cookies.get(self.model.name) - if not api_key: - if self.auto_error: - raise HTTPException( - status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - else: - return None - return api_key + return self.check_api_key(api_key, self.auto_error) diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py index 6adc55bfe..5ffad5986 100644 --- a/fastapi/security/oauth2.py +++ b/fastapi/security/oauth2.py @@ -63,7 +63,7 @@ class OAuth2PasswordRequestForm: *, grant_type: Annotated[ Union[str, None], - Form(pattern="password"), + Form(pattern="^password$"), Doc( """ The OAuth2 spec says it is required and MUST be the fixed string @@ -217,7 +217,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): self, grant_type: Annotated[ str, - Form(pattern="password"), + Form(pattern="^password$"), Doc( """ The OAuth2 spec says it is required and MUST be the fixed string diff --git a/pyproject.toml b/pyproject.toml index edfa81522..51d63fd44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ "Framework :: FastAPI", "Framework :: Pydantic", "Framework :: Pydantic :: 1", + "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3 :: Only", @@ -37,11 +38,12 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP", ] dependencies = [ - "starlette>=0.40.0,<0.42.0", + "starlette>=0.40.0,<0.46.0", "pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0", "typing-extensions>=4.8.0", ] @@ -60,9 +62,9 @@ standard = [ # For the test client "httpx >=0.23.0", # For templates - "jinja2 >=2.11.2", + "jinja2 >=3.1.5", # For forms and file uploads - "python-multipart >=0.0.7", + "python-multipart >=0.0.18", # To validate email fields "email-validator >=2.0.0", # Uvicorn with uvloop @@ -79,9 +81,9 @@ all = [ # # For the test client "httpx >=0.23.0", # For templates - "jinja2 >=2.11.2", + "jinja2 >=3.1.5", # For forms and file uploads - "python-multipart >=0.0.7", + "python-multipart >=0.0.18", # For Starlette's SessionMiddleware, not commonly used with FastAPI "itsdangerous >=1.1.0", # For Starlette's schema generation, would not be used with FastAPI @@ -161,6 +163,8 @@ filterwarnings = [ # Ref: https://github.com/python-trio/trio/pull/3054 # Remove once there's a new version of Trio 'ignore:The `hash` argument is deprecated*:DeprecationWarning:trio', + # Ignore flaky coverage / pytest warning about SQLite connection, only applies to Python 3.13 and Pydantic v1 + 'ignore:Exception ignored in. =1.4.1,<2.0.0 mkdocs-redirects>=1.2.1,<1.3.0 typer == 0.12.5 @@ -8,7 +8,7 @@ pyyaml >=5.3.1,<7.0.0 # For Material for MkDocs, Chinese search jieba==0.42.1 # For image processing by Material for MkDocs -pillow==11.0.0 +pillow==11.1.0 # For image processing by Material for MkDocs cairosvg==2.7.1 mkdocstrings[python]==0.26.1 diff --git a/requirements-tests.txt b/requirements-tests.txt index 5be052307..4a15844e4 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -6,11 +6,11 @@ mypy ==1.8.0 dirty-equals ==0.8.0 sqlmodel==0.0.22 flask >=1.1.2,<4.0.0 -anyio[trio] >=3.2.1,<4.0.0 +anyio[trio] >=3.2.1,<5.0.0 PyJWT==2.8.0 pyyaml >=5.3.1,<7.0.0 passlib[bcrypt] >=1.7.2,<2.0.0 -inline-snapshot==0.18.1 +inline-snapshot==0.19.3 # types types-ujson ==5.10.0.20240515 types-orjson ==3.6.2 diff --git a/.github/actions/notify-translations/app/main.py b/scripts/notify_translations.py similarity index 88% rename from .github/actions/notify-translations/app/main.py rename to scripts/notify_translations.py index 716232d49..c300624db 100644 --- a/.github/actions/notify-translations/app/main.py +++ b/scripts/notify_translations.py @@ -7,12 +7,13 @@ from typing import Any, Dict, List, Union, cast import httpx from github import Github -from pydantic import BaseModel, BaseSettings, SecretStr +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings awaiting_label = "awaiting-review" lang_all_label = "lang-all" approved_label = "approved-1" -translations_path = Path(__file__).parent / "translations.yml" + github_graphql_url = "https://api.github.com/graphql" questions_translations_category_id = "DIC_kwDOCZduT84CT5P9" @@ -175,20 +176,23 @@ class AllDiscussionsResponse(BaseModel): class Settings(BaseSettings): + model_config = {"env_ignore_empty": True} + github_repository: str - input_token: SecretStr + github_token: SecretStr github_event_path: Path github_event_name: Union[str, None] = None httpx_timeout: int = 30 - input_debug: Union[bool, None] = False + debug: Union[bool, None] = False + number: int | None = None class PartialGitHubEventIssue(BaseModel): - number: int + number: int | None = None class PartialGitHubEvent(BaseModel): - pull_request: PartialGitHubEventIssue + pull_request: PartialGitHubEventIssue | None = None def get_graphql_response( @@ -202,9 +206,7 @@ def get_graphql_response( comment_id: Union[str, None] = None, body: Union[str, None] = None, ) -> Dict[str, Any]: - headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} - # some fields are only used by one query, but GraphQL allows unused variables, so - # keep them here for simplicity + headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} variables = { "after": after, "category_id": category_id, @@ -228,37 +230,40 @@ def get_graphql_response( data = response.json() if "errors" in data: logging.error(f"Errors in response, after: {after}, category_id: {category_id}") + logging.error(data["errors"]) logging.error(response.text) raise RuntimeError(response.text) return cast(Dict[str, Any], data) -def get_graphql_translation_discussions(*, settings: Settings): +def get_graphql_translation_discussions( + *, settings: Settings +) -> List[AllDiscussionsDiscussionNode]: data = get_graphql_response( settings=settings, query=all_discussions_query, category_id=questions_translations_category_id, ) - graphql_response = AllDiscussionsResponse.parse_obj(data) + graphql_response = AllDiscussionsResponse.model_validate(data) return graphql_response.data.repository.discussions.nodes def get_graphql_translation_discussion_comments_edges( *, settings: Settings, discussion_number: int, after: Union[str, None] = None -): +) -> List[CommentsEdge]: data = get_graphql_response( settings=settings, query=translation_discussion_query, discussion_number=discussion_number, after=after, ) - graphql_response = CommentsResponse.parse_obj(data) + graphql_response = CommentsResponse.model_validate(data) return graphql_response.data.repository.discussion.comments.edges def get_graphql_translation_discussion_comments( *, settings: Settings, discussion_number: int -): +) -> list[Comment]: comment_nodes: List[Comment] = [] discussion_edges = get_graphql_translation_discussion_comments_edges( settings=settings, discussion_number=discussion_number @@ -276,43 +281,49 @@ def get_graphql_translation_discussion_comments( return comment_nodes -def create_comment(*, settings: Settings, discussion_id: str, body: str): +def create_comment(*, settings: Settings, discussion_id: str, body: str) -> Comment: data = get_graphql_response( settings=settings, query=add_comment_mutation, discussion_id=discussion_id, body=body, ) - response = AddCommentResponse.parse_obj(data) + response = AddCommentResponse.model_validate(data) return response.data.addDiscussionComment.comment -def update_comment(*, settings: Settings, comment_id: str, body: str): +def update_comment(*, settings: Settings, comment_id: str, body: str) -> Comment: data = get_graphql_response( settings=settings, query=update_comment_mutation, comment_id=comment_id, body=body, ) - response = UpdateCommentResponse.parse_obj(data) + response = UpdateCommentResponse.model_validate(data) return response.data.updateDiscussionComment.comment -if __name__ == "__main__": +def main() -> None: settings = Settings() - if settings.input_debug: + if settings.debug: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) - logging.debug(f"Using config: {settings.json()}") - g = Github(settings.input_token.get_secret_value()) + logging.debug(f"Using config: {settings.model_dump_json()}") + g = Github(settings.github_token.get_secret_value()) repo = g.get_repo(settings.github_repository) if not settings.github_event_path.is_file(): raise RuntimeError( f"No github event file available at: {settings.github_event_path}" ) contents = settings.github_event_path.read_text() - github_event = PartialGitHubEvent.parse_raw(contents) + github_event = PartialGitHubEvent.model_validate_json(contents) + logging.info(f"Using GitHub event: {github_event}") + number = ( + github_event.pull_request and github_event.pull_request.number + ) or settings.number + if number is None: + raise RuntimeError("No PR number available") # Avoid race conditions with multiple labels sleep_time = random.random() * 10 # random number between 0 and 10 seconds @@ -323,8 +334,8 @@ if __name__ == "__main__": time.sleep(sleep_time) # Get PR - logging.debug(f"Processing PR: #{github_event.pull_request.number}") - pr = repo.get_pull(github_event.pull_request.number) + logging.debug(f"Processing PR: #{number}") + pr = repo.get_pull(number) label_strs = {label.name for label in pr.get_labels()} langs = [] for label in label_strs: @@ -415,3 +426,7 @@ if __name__ == "__main__": f"There doesn't seem to be anything to be done about PR #{pr.number}" ) logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/scripts/people.py b/scripts/people.py new file mode 100644 index 000000000..f61fd31c9 --- /dev/null +++ b/scripts/people.py @@ -0,0 +1,401 @@ +import logging +import secrets +import subprocess +import time +from collections import Counter +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any, Container, Union + +import httpx +import yaml +from github import Github +from pydantic import BaseModel, SecretStr +from pydantic_settings import BaseSettings + +github_graphql_url = "https://api.github.com/graphql" +questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0" + +discussions_query = """ +query Q($after: String, $category_id: ID) { + repository(name: "fastapi", owner: "fastapi") { + discussions(first: 100, after: $after, categoryId: $category_id) { + edges { + cursor + node { + number + author { + login + avatarUrl + url + } + createdAt + comments(first: 50) { + totalCount + nodes { + createdAt + author { + login + avatarUrl + url + } + isAnswer + replies(first: 10) { + totalCount + nodes { + createdAt + author { + login + avatarUrl + url + } + } + } + } + } + } + } + } + } +} +""" + + +class Author(BaseModel): + login: str + avatarUrl: str | None = None + url: str | None = None + + +class CommentsNode(BaseModel): + createdAt: datetime + author: Union[Author, None] = None + + +class Replies(BaseModel): + totalCount: int + nodes: list[CommentsNode] + + +class DiscussionsCommentsNode(CommentsNode): + replies: Replies + + +class DiscussionsComments(BaseModel): + totalCount: int + nodes: list[DiscussionsCommentsNode] + + +class DiscussionsNode(BaseModel): + number: int + author: Union[Author, None] = None + title: str | None = None + createdAt: datetime + comments: DiscussionsComments + + +class DiscussionsEdge(BaseModel): + cursor: str + node: DiscussionsNode + + +class Discussions(BaseModel): + edges: list[DiscussionsEdge] + + +class DiscussionsRepository(BaseModel): + discussions: Discussions + + +class DiscussionsResponseData(BaseModel): + repository: DiscussionsRepository + + +class DiscussionsResponse(BaseModel): + data: DiscussionsResponseData + + +class Settings(BaseSettings): + github_token: SecretStr + github_repository: str + httpx_timeout: int = 30 + + +def get_graphql_response( + *, + settings: Settings, + query: str, + after: Union[str, None] = None, + category_id: Union[str, None] = None, +) -> dict[str, Any]: + headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} + variables = {"after": after, "category_id": category_id} + response = httpx.post( + github_graphql_url, + headers=headers, + timeout=settings.httpx_timeout, + json={"query": query, "variables": variables, "operationName": "Q"}, + ) + if response.status_code != 200: + logging.error( + f"Response was not 200, after: {after}, category_id: {category_id}" + ) + logging.error(response.text) + raise RuntimeError(response.text) + data = response.json() + if "errors" in data: + logging.error(f"Errors in response, after: {after}, category_id: {category_id}") + logging.error(data["errors"]) + logging.error(response.text) + raise RuntimeError(response.text) + return data + + +def get_graphql_question_discussion_edges( + *, + settings: Settings, + after: Union[str, None] = None, +) -> list[DiscussionsEdge]: + data = get_graphql_response( + settings=settings, + query=discussions_query, + after=after, + category_id=questions_category_id, + ) + graphql_response = DiscussionsResponse.model_validate(data) + return graphql_response.data.repository.discussions.edges + + +class DiscussionExpertsResults(BaseModel): + commenters: Counter[str] + last_month_commenters: Counter[str] + three_months_commenters: Counter[str] + six_months_commenters: Counter[str] + one_year_commenters: Counter[str] + authors: dict[str, Author] + + +def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]: + discussion_nodes: list[DiscussionsNode] = [] + discussion_edges = get_graphql_question_discussion_edges(settings=settings) + + while discussion_edges: + for discussion_edge in discussion_edges: + discussion_nodes.append(discussion_edge.node) + last_edge = discussion_edges[-1] + # Handle GitHub secondary rate limits, requests per minute + time.sleep(5) + discussion_edges = get_graphql_question_discussion_edges( + settings=settings, after=last_edge.cursor + ) + return discussion_nodes + + +def get_discussions_experts( + discussion_nodes: list[DiscussionsNode], +) -> DiscussionExpertsResults: + commenters = Counter[str]() + last_month_commenters = Counter[str]() + three_months_commenters = Counter[str]() + six_months_commenters = Counter[str]() + one_year_commenters = Counter[str]() + authors: dict[str, Author] = {} + + now = datetime.now(tz=timezone.utc) + one_month_ago = now - timedelta(days=30) + three_months_ago = now - timedelta(days=90) + six_months_ago = now - timedelta(days=180) + one_year_ago = now - timedelta(days=365) + + for discussion in discussion_nodes: + discussion_author_name = None + if discussion.author: + authors[discussion.author.login] = discussion.author + discussion_author_name = discussion.author.login + discussion_commentors: dict[str, datetime] = {} + for comment in discussion.comments.nodes: + if comment.author: + authors[comment.author.login] = comment.author + if comment.author.login != discussion_author_name: + author_time = discussion_commentors.get( + comment.author.login, comment.createdAt + ) + discussion_commentors[comment.author.login] = max( + author_time, comment.createdAt + ) + for reply in comment.replies.nodes: + if reply.author: + authors[reply.author.login] = reply.author + if reply.author.login != discussion_author_name: + author_time = discussion_commentors.get( + reply.author.login, reply.createdAt + ) + discussion_commentors[reply.author.login] = max( + author_time, reply.createdAt + ) + for author_name, author_time in discussion_commentors.items(): + commenters[author_name] += 1 + if author_time > one_month_ago: + last_month_commenters[author_name] += 1 + if author_time > three_months_ago: + three_months_commenters[author_name] += 1 + if author_time > six_months_ago: + six_months_commenters[author_name] += 1 + if author_time > one_year_ago: + one_year_commenters[author_name] += 1 + discussion_experts_results = DiscussionExpertsResults( + authors=authors, + commenters=commenters, + last_month_commenters=last_month_commenters, + three_months_commenters=three_months_commenters, + six_months_commenters=six_months_commenters, + one_year_commenters=one_year_commenters, + ) + return discussion_experts_results + + +def get_top_users( + *, + counter: Counter[str], + authors: dict[str, Author], + skip_users: Container[str], + min_count: int = 2, +) -> list[dict[str, Any]]: + users: list[dict[str, Any]] = [] + for commenter, count in counter.most_common(50): + if commenter in skip_users: + continue + if count >= min_count: + author = authors[commenter] + users.append( + { + "login": commenter, + "count": count, + "avatarUrl": author.avatarUrl, + "url": author.url, + } + ) + return users + + +def get_users_to_write( + *, + counter: Counter[str], + authors: dict[str, Author], + min_count: int = 2, +) -> list[dict[str, Any]]: + users: dict[str, Any] = {} + users_list: list[dict[str, Any]] = [] + for user, count in counter.most_common(60): + if count >= min_count: + author = authors[user] + user_data = { + "login": user, + "count": count, + "avatarUrl": author.avatarUrl, + "url": author.url, + } + users[user] = user_data + users_list.append(user_data) + return users_list + + +def update_content(*, content_path: Path, new_content: Any) -> bool: + old_content = content_path.read_text(encoding="utf-8") + + new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True) + if old_content == new_content: + logging.info(f"The content hasn't changed for {content_path}") + return False + content_path.write_text(new_content, encoding="utf-8") + logging.info(f"Updated {content_path}") + return True + + +def main() -> None: + logging.basicConfig(level=logging.INFO) + settings = Settings() + logging.info(f"Using config: {settings.model_dump_json()}") + g = Github(settings.github_token.get_secret_value()) + repo = g.get_repo(settings.github_repository) + + discussion_nodes = get_discussion_nodes(settings=settings) + experts_results = get_discussions_experts(discussion_nodes=discussion_nodes) + + authors = experts_results.authors + maintainers_logins = {"tiangolo"} + maintainers = [] + for login in maintainers_logins: + user = authors[login] + maintainers.append( + { + "login": login, + "answers": experts_results.commenters[login], + "avatarUrl": user.avatarUrl, + "url": user.url, + } + ) + + experts = get_users_to_write( + counter=experts_results.commenters, + authors=authors, + ) + last_month_experts = get_users_to_write( + counter=experts_results.last_month_commenters, + authors=authors, + ) + three_months_experts = get_users_to_write( + counter=experts_results.three_months_commenters, + authors=authors, + ) + six_months_experts = get_users_to_write( + counter=experts_results.six_months_commenters, + authors=authors, + ) + one_year_experts = get_users_to_write( + counter=experts_results.one_year_commenters, + authors=authors, + ) + + people = { + "maintainers": maintainers, + "experts": experts, + "last_month_experts": last_month_experts, + "three_months_experts": three_months_experts, + "six_months_experts": six_months_experts, + "one_year_experts": one_year_experts, + } + + # For local development + # people_path = Path("../docs/en/data/people.yml") + people_path = Path("./docs/en/data/people.yml") + + updated = update_content(content_path=people_path, new_content=people) + + if not updated: + logging.info("The data hasn't changed, finishing.") + return + + logging.info("Setting up GitHub Actions git user") + subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run( + ["git", "config", "user.email", "github-actions@github.com"], check=True + ) + branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}" + logging.info(f"Creating a new branch {branch_name}") + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + logging.info("Adding updated file") + subprocess.run(["git", "add", str(people_path)], check=True) + logging.info("Committing updated file") + message = "๐Ÿ‘ฅ Update FastAPI People - Experts" + subprocess.run(["git", "commit", "-m", message], check=True) + logging.info("Pushing branch") + subprocess.run(["git", "push", "origin", branch_name], check=True) + logging.info("Creating PR") + pr = repo.create_pull(title=message, body=message, base="master", head=branch_name) + logging.info(f"Created PR: {pr.number}") + logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py index 7d914d034..2b7e3457a 100644 --- a/tests/test_security_oauth2.py +++ b/tests/test_security_oauth2.py @@ -1,3 +1,4 @@ +import pytest from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -137,10 +138,18 @@ def test_strict_login_no_grant_type(): ) -def test_strict_login_incorrect_grant_type(): +@pytest.mark.parametrize( + argnames=["grant_type"], + argvalues=[ + pytest.param("incorrect", id="incorrect value"), + pytest.param("passwordblah", id="password with suffix"), + pytest.param("blahpassword", id="password with prefix"), + ], +) +def test_strict_login_incorrect_grant_type(grant_type: str): response = client.post( "/login", - data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, + data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 assert response.json() == IsDict( @@ -149,9 +158,9 @@ def test_strict_login_incorrect_grant_type(): { "type": "string_pattern_mismatch", "loc": ["body", "grant_type"], - "msg": "String should match pattern 'password'", - "input": "incorrect", - "ctx": {"pattern": "password"}, + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, } ] } @@ -161,9 +170,9 @@ def test_strict_login_incorrect_grant_type(): "detail": [ { "loc": ["body", "grant_type"], - "msg": 'string does not match regex "password"', + "msg": 'string does not match regex "^password$"', "type": "value_error.str.regex", - "ctx": {"pattern": "password"}, + "ctx": {"pattern": "^password$"}, } ] } @@ -248,7 +257,7 @@ def test_openapi_schema(): "properties": { "grant_type": { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", }, "username": {"title": "Username", "type": "string"}, diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index 0da3b911e..046ac5763 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -1,5 +1,6 @@ from typing import Optional +import pytest from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -141,10 +142,18 @@ def test_strict_login_no_grant_type(): ) -def test_strict_login_incorrect_grant_type(): +@pytest.mark.parametrize( + argnames=["grant_type"], + argvalues=[ + pytest.param("incorrect", id="incorrect value"), + pytest.param("passwordblah", id="password with suffix"), + pytest.param("blahpassword", id="password with prefix"), + ], +) +def test_strict_login_incorrect_grant_type(grant_type: str): response = client.post( "/login", - data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, + data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 assert response.json() == IsDict( @@ -153,9 +162,9 @@ def test_strict_login_incorrect_grant_type(): { "type": "string_pattern_mismatch", "loc": ["body", "grant_type"], - "msg": "String should match pattern 'password'", - "input": "incorrect", - "ctx": {"pattern": "password"}, + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, } ] } @@ -165,9 +174,9 @@ def test_strict_login_incorrect_grant_type(): "detail": [ { "loc": ["body", "grant_type"], - "msg": 'string does not match regex "password"', + "msg": 'string does not match regex "^password$"', "type": "value_error.str.regex", - "ctx": {"pattern": "password"}, + "ctx": {"pattern": "^password$"}, } ] } @@ -252,7 +261,7 @@ def test_openapi_schema(): "properties": { "grant_type": { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", }, "username": {"title": "Username", "type": "string"}, diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index 85a9f9b39..629cddca2 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -1,5 +1,6 @@ from typing import Optional +import pytest from dirty_equals import IsDict from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -142,10 +143,18 @@ def test_strict_login_no_grant_type(): ) -def test_strict_login_incorrect_grant_type(): +@pytest.mark.parametrize( + argnames=["grant_type"], + argvalues=[ + pytest.param("incorrect", id="incorrect value"), + pytest.param("passwordblah", id="password with suffix"), + pytest.param("blahpassword", id="password with prefix"), + ], +) +def test_strict_login_incorrect_grant_type(grant_type: str): response = client.post( "/login", - data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"}, + data={"username": "johndoe", "password": "secret", "grant_type": grant_type}, ) assert response.status_code == 422 assert response.json() == IsDict( @@ -154,9 +163,9 @@ def test_strict_login_incorrect_grant_type(): { "type": "string_pattern_mismatch", "loc": ["body", "grant_type"], - "msg": "String should match pattern 'password'", - "input": "incorrect", - "ctx": {"pattern": "password"}, + "msg": "String should match pattern '^password$'", + "input": grant_type, + "ctx": {"pattern": "^password$"}, } ] } @@ -166,9 +175,9 @@ def test_strict_login_incorrect_grant_type(): "detail": [ { "loc": ["body", "grant_type"], - "msg": 'string does not match regex "password"', + "msg": 'string does not match regex "^password$"', "type": "value_error.str.regex", - "ctx": {"pattern": "password"}, + "ctx": {"pattern": "^password$"}, } ] } @@ -253,7 +262,7 @@ def test_openapi_schema(): "properties": { "grant_type": { "title": "Grant Type", - "pattern": "password", + "pattern": "^password$", "type": "string", }, "username": {"title": "Username", "type": "string"}, diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py index c26f8b89b..d18ceae48 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -1,13 +1,26 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 + -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003 import app +@pytest.fixture( + name="client", + params=[ + "tutorial003", + pytest.param("tutorial003_py310", marks=needs_py310), + "tutorial003_an", + pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}") - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py deleted file mode 100644 index 62c7e2fad..000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py +++ /dev/null @@ -1,273 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_an import app - - client = TestClient(app) - return client - - -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py deleted file mode 100644 index f46430fb5..000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py +++ /dev/null @@ -1,279 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -@needs_py310 -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py deleted file mode 100644 index 29071cddc..000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py +++ /dev/null @@ -1,279 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -@needs_py39 -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py deleted file mode 100644 index 133afe9b5..000000000 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py +++ /dev/null @@ -1,279 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.body_multiple_params.tutorial003_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_body_valid(client: TestClient): - response = client.put( - "/items/5", - json={ - "importance": 2, - "item": {"name": "Foo", "price": 50.5}, - "user": {"username": "Dave"}, - }, - ) - assert response.status_code == 200 - assert response.json() == { - "item_id": 5, - "importance": 2, - "item": { - "name": "Foo", - "price": 50.5, - "description": None, - "tax": None, - }, - "user": {"username": "Dave", "full_name": None}, - } - - -@needs_py310 -def test_post_body_no_data(client: TestClient): - response = client.put("/items/5", json=None) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_post_body_empty_list(client: TestClient): - response = client.put("/items/5", json=[]) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "item"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "user"], - "msg": "Field required", - "input": None, - }, - { - "type": "missing", - "loc": ["body", "importance"], - "msg": "Field required", - "input": None, - }, - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "item"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "user"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "importance"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - "price": {"title": "Price", "type": "number"}, - "tax": IsDict( - { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Tax", "type": "number"} - ), - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": IsDict( - { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Full Name", "type": "string"} - ), - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py index 166901188..d06a385b5 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py @@ -12,7 +12,7 @@ def test_swagger_ui(): '"syntaxHighlight": false' not in response.text ), "not used parameters should not be included" assert ( - '"syntaxHighlight.theme": "obsidian"' in response.text + '"syntaxHighlight": {"theme": "obsidian"}' in response.text ), "parameters with middle dots should be included in a JSON compatible way" assert ( '"dom_id": "#swagger-ui"' in response.text diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index 4f52d6ff7..e08e16963 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -1,14 +1,29 @@ +import importlib + import pytest from dirty_equals import IsDict from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi.testclient import TestClient +from ...utils import needs_py39, needs_py310 + -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010 import app +@pytest.fixture( + name="client", + params=[ + "tutorial010", + pytest.param("tutorial010_py310", marks=needs_py310), + "tutorial010_an", + pytest.param("tutorial010_an_py39", marks=needs_py39), + pytest.param("tutorial010_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py deleted file mode 100644 index 5daca1e70..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py +++ /dev/null @@ -1,168 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an import app - - client = TestClient(app) - return client - - -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py deleted file mode 100644 index 89da4d82e..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py +++ /dev/null @@ -1,175 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py310 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py deleted file mode 100644 index f5f692b06..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py +++ /dev/null @@ -1,175 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py39 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py39 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py39 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py deleted file mode 100644 index 5b62c969f..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py +++ /dev/null @@ -1,175 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial010_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_query_params_str_validations_no_query(client: TestClient): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_fixedquery(client: TestClient): - response = client.get("/items/", params={"item-query": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == { - "items": [{"item_id": "Foo"}, {"item_id": "Bar"}], - "q": "fixedquery", - } - - -@needs_py310 -def test_query_params_str_validations_q_fixedquery(client: TestClient): - response = client.get("/items/", params={"q": "fixedquery"}) - assert response.status_code == 200 - assert response.json() == {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - - -@needs_py310 -def test_query_params_str_validations_item_query_nonregexquery(client: TestClient): - response = client.get("/items/", params={"item-query": "nonregexquery"}) - assert response.status_code == 422 - assert response.json() == IsDict( - { - "detail": [ - { - "type": "string_pattern_mismatch", - "loc": ["query", "item-query"], - "msg": "String should match pattern '^fixedquery$'", - "input": "nonregexquery", - "ctx": {"pattern": "^fixedquery$"}, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "ctx": {"pattern": "^fixedquery$"}, - "loc": ["query", "item-query"], - "msg": 'string does not match regex "^fixedquery$"', - "type": "value_error.str.regex", - } - ] - } - ) - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": IsDict( - { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Query string", - "maxLength": 50, - "minLength": 3, - "pattern": "^fixedquery$", - "type": "string", - "description": "Query string for the items to search in the database that have a good match", - } - ), - "name": "item-query", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index 5ba39b05d..f4da25752 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -1,26 +1,47 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial011 import app +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial011", + pytest.param("tutorial011_py39", marks=needs_py310), + pytest.param("tutorial011_py310", marks=needs_py310), + "tutorial011_an", + pytest.param("tutorial011_an_py39", marks=needs_py39), + pytest.param("tutorial011_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_multi_query_values(): +def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_query_no_values(): +def test_query_no_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": None} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py deleted file mode 100644 index 3942ea77a..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py +++ /dev/null @@ -1,108 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial011_an import app - -client = TestClient(app) - - -def test_multi_query_values(): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_query_no_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py deleted file mode 100644 index f2ec38c95..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py310 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py deleted file mode 100644 index cd7b15679..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py deleted file mode 100644 index bdc729516..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py310 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py deleted file mode 100644 index 26ac56b2f..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial011_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": None} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": IsDict( - { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - } - ), - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index 1436db384..549a90519 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -1,25 +1,44 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial012 import app +from ...utils import needs_py39 + + +@pytest.fixture( + name="client", + params=[ + "tutorial012", + pytest.param("tutorial012_py39", marks=needs_py39), + "tutorial012_an", + pytest.param("tutorial012_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_default_query_values(): +def test_default_query_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_multi_query_values(): +def test_multi_query_values(client: TestClient): url = "/items/?q=baz&q=foobar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["baz", "foobar"]} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py deleted file mode 100644 index 270763f1d..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py +++ /dev/null @@ -1,96 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial012_an import app - -client = TestClient(app) - - -def test_default_query_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_multi_query_values(): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py deleted file mode 100644 index 548391683..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial012_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_default_query_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py deleted file mode 100644 index e7d745154..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial012_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_default_query_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=baz&q=foobar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["baz", "foobar"]} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index 1ba1fdf61..f2f5f7a85 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -1,25 +1,43 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial013 import app +from ...utils import needs_py39 + + +@pytest.fixture( + name="client", + params=[ + "tutorial013", + "tutorial013_an", + pytest.param("tutorial013_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_multi_query_values(): +def test_multi_query_values(client: TestClient): url = "/items/?q=foo&q=bar" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": ["foo", "bar"]} -def test_query_no_values(): +def test_query_no_values(client: TestClient): url = "/items/" response = client.get(url) assert response.status_code == 200, response.text assert response.json() == {"q": []} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py deleted file mode 100644 index 343261748..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py +++ /dev/null @@ -1,96 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial013_an import app - -client = TestClient(app) - - -def test_multi_query_values(): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -def test_query_no_values(): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": []} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py deleted file mode 100644 index 537d6325b..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py +++ /dev/null @@ -1,106 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial013_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_multi_query_values(client: TestClient): - url = "/items/?q=foo&q=bar" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": ["foo", "bar"]} - - -@needs_py39 -def test_query_no_values(client: TestClient): - url = "/items/" - response = client.get(url) - assert response.status_code == 200, response.text - assert response.json() == {"q": []} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], - } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index 7bce7590c..edd40bb1a 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -1,23 +1,43 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.query_params_str_validations.tutorial014 import app +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial014", + pytest.param("tutorial014_py310", marks=needs_py310), + "tutorial014_an", + pytest.param("tutorial014_an_py39", marks=needs_py39), + pytest.param("tutorial014_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.query_params_str_validations.{request.param}" + ) -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_hidden_query(): +def test_hidden_query(client: TestClient): response = client.get("/items?hidden_query=somevalue") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "somevalue"} -def test_no_hidden_query(): +def test_no_hidden_query(client: TestClient): response = client.get("/items") assert response.status_code == 200, response.text assert response.json() == {"hidden_query": "Not found"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py deleted file mode 100644 index 2182e87b7..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py +++ /dev/null @@ -1,81 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.query_params_str_validations.tutorial014_an import app - -client = TestClient(app) - - -def test_hidden_query(): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -def test_no_hidden_query(): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py deleted file mode 100644 index 344004d01..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py deleted file mode 100644 index 5d4f6df3d..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py deleted file mode 100644 index dad49fb12..000000000 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.query_params_str_validations.tutorial014_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_hidden_query(client: TestClient): - response = client.get("/items?hidden_query=somevalue") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "somevalue"} - - -@needs_py310 -def test_no_hidden_query(client: TestClient): - response = client.get("/items") - assert response.status_code == 200, response.text - assert response.json() == {"hidden_query": "Not found"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index f5817593b..b06919961 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -1,23 +1,28 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.request_files.tutorial001 import app +from ...utils import needs_py39 -client = TestClient(app) +@pytest.fixture( + name="client", + params=[ + "tutorial001", + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") -file_required = { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] -} + client = TestClient(mod.app) + return client -def test_post_form_no_body(): +def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -45,7 +50,7 @@ def test_post_form_no_body(): ) -def test_post_body_json(): +def test_post_body_json(client: TestClient): response = client.post("/files/", json={"file": "Foo"}) assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -73,41 +78,38 @@ def test_post_body_json(): ) -def test_post_file(tmp_path): +def test_post_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": 14} -def test_post_large_file(tmp_path): +def test_post_large_file(tmp_path, client: TestClient): default_pydantic_max_size = 2**16 path = tmp_path / "test.txt" path.write_bytes(b"x" * (default_pydantic_max_size + 1)) - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": default_pydantic_max_size + 1} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index 42f75442a..9075a1756 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -1,46 +1,63 @@ +import importlib +from pathlib import Path + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.request_files.tutorial001_02 import app +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial001_02", + pytest.param("tutorial001_02_py310", marks=needs_py310), + "tutorial001_02_an", + pytest.param("tutorial001_02_an_py39", marks=needs_py39), + pytest.param("tutorial001_02_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_post_form_no_body(): +def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 200, response.text assert response.json() == {"message": "No file sent"} -def test_post_uploadfile_no_body(): +def test_post_uploadfile_no_body(client: TestClient): response = client.post("/uploadfile/") assert response.status_code == 200, response.text assert response.json() == {"message": "No upload file sent"} -def test_post_file(tmp_path): +def test_post_file(tmp_path: Path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": 14} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path: Path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py deleted file mode 100644 index f63eb339c..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py +++ /dev/null @@ -1,208 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.request_files.tutorial001_02_an import app - -client = TestClient(app) - - -def test_post_form_no_body(): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -def test_post_uploadfile_no_body(): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -def test_post_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -def test_post_upload_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py deleted file mode 100644 index 94b6ac67e..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py +++ /dev/null @@ -1,220 +0,0 @@ -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_02_an_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -@needs_py310 -def test_post_uploadfile_no_body(client: TestClient): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -@needs_py310 -def test_post_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py310 -def test_post_upload_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py deleted file mode 100644 index fcb39f8f1..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py +++ /dev/null @@ -1,220 +0,0 @@ -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_02_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -@needs_py39 -def test_post_uploadfile_no_body(client: TestClient): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -@needs_py39 -def test_post_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py39 -def test_post_upload_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py deleted file mode 100644 index a700752a3..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py +++ /dev/null @@ -1,220 +0,0 @@ -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_02_py310 import app - - client = TestClient(app) - return client - - -@needs_py310 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No file sent"} - - -@needs_py310 -def test_post_uploadfile_no_body(client: TestClient): - response = client.post("/uploadfile/") - assert response.status_code == 200, response.text - assert response.json() == {"message": "No upload file sent"} - - -@needs_py310 -def test_post_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py310 -def test_post_upload_file(tmp_path: Path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py310 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": IsDict( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ], - "title": "Body", - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - ) - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "type": "object", - "properties": { - "file": IsDict( - { - "title": "File", - "anyOf": [ - {"type": "string", "format": "binary"}, - {"type": "null"}, - ], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "File", "type": "string", "format": "binary"} - ) - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index f02170814..9fbe2166c 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -1,33 +1,47 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.request_files.tutorial001_03 import app +from ...utils import needs_py39 + + +@pytest.fixture( + name="client", + params=[ + "tutorial001_03", + "tutorial001_03_an", + pytest.param("tutorial001_03_an_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") -client = TestClient(app) + client = TestClient(mod.app) + return client -def test_post_file(tmp_path): +def test_post_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/files/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"file_size": 14} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path, client: TestClient): path = tmp_path / "test.txt" path.write_bytes(b"") - client = TestClient(app) with path.open("rb") as file: response = client.post("/uploadfile/", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"filename": "test.txt"} -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py deleted file mode 100644 index acfb749ce..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py +++ /dev/null @@ -1,159 +0,0 @@ -from fastapi.testclient import TestClient - -from docs_src.request_files.tutorial001_03_an import app - -client = TestClient(app) - - -def test_post_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -def test_post_upload_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as bytes", - "format": "binary", - } - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as UploadFile", - "format": "binary", - } - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py deleted file mode 100644 index 36e5faac1..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py +++ /dev/null @@ -1,167 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_03_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py39 -def test_post_upload_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as bytes", - "format": "binary", - } - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as UploadFile", - "format": "binary", - } - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_an.py deleted file mode 100644 index 1c78e3679..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_an.py +++ /dev/null @@ -1,218 +0,0 @@ -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from docs_src.request_files.tutorial001_an import app - -client = TestClient(app) - - -def test_post_form_no_body(): - response = client.post("/files/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_body_json(): - response = client.post("/files/", json={"file": "Foo"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -def test_post_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -def test_post_large_file(tmp_path): - default_pydantic_max_size = 2**16 - path = tmp_path / "test.txt" - path.write_bytes(b"x" * (default_pydantic_max_size + 1)) - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": default_pydantic_max_size + 1} - - -def test_post_upload_file(tmp_path): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - client = TestClient(app) - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -def test_openapi_schema(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - } - }, - "/uploadfile/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py deleted file mode 100644 index 843fcec28..000000000 --- a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py +++ /dev/null @@ -1,228 +0,0 @@ -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="client") -def get_client(): - from docs_src.request_files.tutorial001_an_py39 import app - - client = TestClient(app) - return client - - -@needs_py39 -def test_post_form_no_body(client: TestClient): - response = client.post("/files/") - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_post_body_json(client: TestClient): - response = client.post("/files/", json={"file": "Foo"}) - assert response.status_code == 422, response.text - assert response.json() == IsDict( - { - "detail": [ - { - "type": "missing", - "loc": ["body", "file"], - "msg": "Field required", - "input": None, - } - ] - } - ) | IsDict( - # TODO: remove when deprecating Pydantic v1 - { - "detail": [ - { - "loc": ["body", "file"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } - ) - - -@needs_py39 -def test_post_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": 14} - - -@needs_py39 -def test_post_large_file(tmp_path, client: TestClient): - default_pydantic_max_size = 2**16 - path = tmp_path / "test.txt" - path.write_bytes(b"x" * (default_pydantic_max_size + 1)) - - with path.open("rb") as file: - response = client.post("/files/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"file_size": default_pydantic_max_size + 1} - - -@needs_py39 -def test_post_upload_file(tmp_path, client: TestClient): - path = tmp_path / "test.txt" - path.write_bytes(b"") - - with path.open("rb") as file: - response = client.post("/uploadfile/", files={"file": file}) - assert response.status_code == 200, response.text - assert response.json() == {"filename": "test.txt"} - - -@needs_py39 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - } - }, - "/uploadfile/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index db1552e5c..446a87657 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -1,12 +1,35 @@ +import importlib + +import pytest from dirty_equals import IsDict +from fastapi import FastAPI from fastapi.testclient import TestClient -from docs_src.request_files.tutorial002 import app +from ...utils import needs_py39 + + +@pytest.fixture( + name="app", + params=[ + "tutorial002", + "tutorial002_an", + pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_an_py39", marks=needs_py39), + ], +) +def get_app(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.request_files.{request.param}") -client = TestClient(app) + return mod.app + + +@pytest.fixture(name="client") +def get_client(app: FastAPI): + client = TestClient(app) + return client -def test_post_form_no_body(): +def test_post_form_no_body(client: TestClient): response = client.post("/files/") assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -34,7 +57,7 @@ def test_post_form_no_body(): ) -def test_post_body_json(): +def test_post_body_json(client: TestClient): response = client.post("/files/", json={"file": "Foo"}) assert response.status_code == 422, response.text assert response.json() == IsDict( @@ -62,7 +85,7 @@ def test_post_body_json(): ) -def test_post_files(tmp_path): +def test_post_files(tmp_path, app: FastAPI): path = tmp_path / "test.txt" path.write_bytes(b"") path2 = tmp_path / "test2.txt" @@ -81,7 +104,7 @@ def test_post_files(tmp_path): assert response.json() == {"file_sizes": [14, 15]} -def test_post_upload_file(tmp_path): +def test_post_upload_file(tmp_path, app: FastAPI): path = tmp_path / "test.txt" path.write_bytes(b"") path2 = tmp_path / "test2.txt" @@ -100,14 +123,14 @@ def test_post_upload_file(tmp_path): assert response.json() == {"filenames": ["test.txt", "test2.txt"]} -def test_get_root(): +def test_get_root(app: FastAPI): client = TestClient(app) response = client.get("/") assert response.status_code == 200, response.text assert b"") - 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" 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 e53fc3224..000000000 --- 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 c974e5dc1..000000000 --- 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 71f99726c..000000000 --- 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 e30cdc073..000000000 --- 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_dependencies/test_tutorial001.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py index af26307f5..00ee6ab1e 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 fc1f9149a..000000000 --- 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 a3d27f47f..000000000 --- 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 f03ed5e07..000000000 --- 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 776b916ff..000000000 --- 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}, - }