Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions[bot]
11225f8552 🎨 Auto format 2026-01-11 01:07:49 +00:00
github-actions[bot]
0fd671269e 🌐 Update translations for zh (update-outdated) 2026-01-11 01:07:25 +00:00
177 changed files with 7560 additions and 11746 deletions

View File

@@ -91,13 +91,13 @@ jobs:
run: uv sync --locked --no-dev --group docs
- name: Update Languages
run: uv run ./scripts/docs.py update-languages
- uses: actions/cache@v5
- uses: actions/cache@v4
with:
key: mkdocs-cards-${{ matrix.lang }}-${{ github.ref }}
path: docs/${{ matrix.lang }}/.cache
- name: Build Docs
run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }}
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v5
with:
name: docs-site-${{ matrix.lang }}
path: ./site/**

View File

@@ -45,7 +45,7 @@ jobs:
run: |
rm -rf ./site
mkdir ./site
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v6
with:
path: ./site/
pattern: docs-site-*

View File

@@ -41,15 +41,11 @@ jobs:
"message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.",
"reminder": {
"before": "P3D",
"message": "Heads-up: this will be closed in 3 days unless there's new activity."
"message": "Heads-up: this will be closed in 3 days unless theres new activity."
}
},
"invalid": {
"delay": 0,
"message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details."
},
"maybe-ai": {
"delay": 0,
"message": "This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI."
}
}

View File

@@ -28,7 +28,7 @@ jobs:
pyproject.toml
uv.lock
- run: uv sync --locked --no-dev --group github-actions
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v6
with:
name: coverage-html
path: htmlcov

View File

@@ -84,7 +84,7 @@ jobs:
# Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow
- name: Store coverage files
if: matrix.coverage == 'coverage'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }}
path: coverage
@@ -112,7 +112,7 @@ jobs:
- name: Install Dependencies
run: uv sync --locked --no-dev --group tests --extra all
- name: Get coverage files
uses: actions/download-artifact@v7
uses: actions/download-artifact@v6
with:
pattern: coverage-*
path: coverage
@@ -121,7 +121,7 @@ jobs:
- run: uv run coverage combine coverage
- run: uv run coverage html --title "Coverage for ${{ github.sha }}"
- name: Store coverage HTML
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: coverage-html
path: htmlcov

View File

@@ -164,6 +164,8 @@ $ pip install "fastapi[standard]"
Create a file `main.py` with:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@@ -175,7 +177,7 @@ def read_root():
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
@@ -184,7 +186,9 @@ def read_item(item_id: int, q: str | None = None):
If your code uses `async` / `await`, use `async def`:
```Python hl_lines="7 12"
```Python hl_lines="9 14"
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@@ -196,7 +200,7 @@ async def read_root():
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
@@ -287,7 +291,9 @@ Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.
```Python hl_lines="2 7-10 23-25"
```Python hl_lines="4 9-12 25-27"
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
@@ -297,7 +303,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: bool | None = None
is_offer: Union[bool, None] = None
@app.get("/")
@@ -306,7 +312,7 @@ def read_root():
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}

View File

@@ -189,7 +189,7 @@ Siehe Abschnitt `### Links` im allgemeinen Prompt in `scripts/translate.py`.
////
## HTML-„abbr“-Elemente { #html-abbr-elements }
## HTML „abbr“-Elemente { #html-abbr-elements }
//// tab | Test

View File

@@ -56,19 +56,19 @@ from app.routers import items
Die gleiche Dateistruktur mit Kommentaren:
```bash
```
.
├── app # "app" ist ein Python-Package
│   ├── __init__.py # diese Datei macht "app" zu einem "Python-Package"
│   ├── main.py # "main"-Modul, z. B. import app.main
│   ├── dependencies.py # "dependencies"-Modul, z. B. import app.dependencies
│   └── routers # "routers" ist ein "Python-Subpackage"
│   │ ├── __init__.py # macht "routers" zu einem "Python-Subpackage"
│   │ ├── items.py # "items"-Submodul, z. B. import app.routers.items
│   │ └── users.py # "users"-Submodul, z. B. import app.routers.users
│   └── internal # "internal" ist ein "Python-Subpackage"
│   ├── __init__.py # macht "internal" zu einem "Python-Subpackage"
│   └── admin.py # "admin"-Submodul, z. B. import app.internal.admin
├── app # app ist ein Python-Package
│   ├── __init__.py # diese Datei macht app zu einem Python-Package
│   ├── main.py # main-Modul, z. B. import app.main
│   ├── dependencies.py # dependencies-Modul, z. B. import app.dependencies
│   └── routers # routers ist ein Python-Subpackage
│   │ ├── __init__.py # macht routers zu einem Python-Subpackage
│   │ ├── items.py # items-Submodul, z. B. import app.routers.items
│   │ └── users.py # users-Submodul, z. B. import app.routers.users
│   └── internal # internal ist ein Python-Subpackage
│   ├── __init__.py # macht internal zu einem Python-Subpackage
│   └── admin.py # admin-Submodul, z. B. import app.internal.admin
```
## `APIRouter` { #apirouter }
@@ -479,7 +479,7 @@ $ fastapi dev app/main.py
</div>
Und öffnen Sie die Dokumentation unter <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
und öffnen Sie die Dokumentation unter <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Sie sehen die automatische API-Dokumentation, einschließlich der Pfade aller Submodule, mit den richtigen Pfaden (und Präfixen) und den richtigen Tags:

View File

@@ -65,6 +65,9 @@ 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://www.testmu.ai/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage
title: TestMu AI. The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering.
img: https://fastapi.tiangolo.com/img/sponsors/testmu.png
- url: https://lambdatest.com/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage
title: LambdaTest, AI-Powered Cloud-based Test Orchestration Platform
img: https://fastapi.tiangolo.com/img/sponsors/lambdatest.png
- url: https://requestly.com/fastapi
title: All-in-one platform to Test, Mock and Intercept APIs. Built for speed, privacy and offline support.
img: https://fastapi.tiangolo.com/img/sponsors/requestly.png

View File

@@ -177,81 +177,252 @@ as Uvicorn by default will use the port `8000`, the documentation on port `8008`
### Translations
/// warning | Attention
**Update on Translations**
We're updating the way we handle documentation translations.
Until now, we invited community members to translate pages via pull requests, which were then reviewed by at least two native speakers. While this has helped bring FastAPI to many more users, weve also run into several challenges - some languages have only a few translated pages, others are outdated and hard to maintain over time.
To improve this, were working on automation tools 🤖 to manage translations more efficiently. Once ready, documentation will be machine-translated and still reviewed by at least two native speakers ✅ before publishing. This will allow us to keep translations up-to-date while reducing the review burden on maintainers.
Whats changing now:
* 🚫 Were no longer accepting new community-submitted translation PRs.
* ⏳ Existing open PRs will be reviewed and can still be merged if completed within the next 3 weeks (since July 11 2025).
* 🌐 In the future, we will only support languages where at least three active native speakers are available to review and maintain translations.
This transition will help us keep translations more consistent and timely while better supporting our contributors 🙌. Thank you to everyone who has contributed so far — your help has been invaluable! 💖
///
Help with translations is VERY MUCH appreciated! And it can't be done without the help from the community. 🌎 🚀
Here are the steps to help with translations.
#### Review Translation PRs
Translation pull requests are made by LLMs guided with prompts designed by the FastAPI team together with the community of native speakers for each supported language.
These translations are normally still reviewed by native speakers, and here's where you can help!
#### Tips and guidelines
* Check the currently <a href="https://github.com/fastapi/fastapi/pulls" class="external-link" target="_blank">existing pull requests</a> for your language. You can filter the pull requests by the ones with the label for your language. For example, for Spanish, the label is <a href="https://github.com/fastapi/fastapi/pulls?q=is%3Aopen+sort%3Aupdated-desc+label%3Alang-es+label%3Aawaiting-review" class="external-link" target="_blank">`lang-es`</a>.
* When reviewing a pull request, it's better not to suggest changes in the same pull request, because it is LLM generated, and it won't be possible to make sure that small individual changes are replicated in other similar sections, or that they are preserved when translating the same content again.
* Instead of adding suggestions to the translation PR, make the suggestions to the LLM prompt file for that language, in a new PR. For example, for Spanish, the LLM prompt file is at: <a href="https://github.com/fastapi/fastapi/blob/master/docs/es/llm-prompt.md" class="external-link" target="_blank">`docs/es/llm-prompt.md`</a>.
* Review those pull requests, requesting changes or approving them. For the languages I don't speak, I'll wait for several others to review the translation before merging.
/// tip
You can <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request" class="external-link" target="_blank">add comments with change suggestions</a> to existing pull requests.
Check the docs about <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-reviews" class="external-link" target="_blank">adding a pull request review</a> to approve it or request changes.
///
#### Subscribe to Notifications for Your Language
* Check if there's a <a href="https://github.com/fastapi/fastapi/discussions/categories/translations" class="external-link" target="_blank">GitHub Discussion</a> to coordinate translations for your language. You can subscribe to it, and when there's a new pull request to review, an automatic comment will be added to the discussion.
* If you translate pages, add a single pull request per page translated. That will make it much easier for others to review it.
* To check the 2-letter code for the language you want to translate, you can use the table <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" class="external-link" target="_blank">List of ISO 639-1 codes</a>.
#### Existing language
Let's say you want to translate a page for a language that already has translations for some pages, like Spanish.
In the case of Spanish, the 2-letter code is `es`. So, the directory for Spanish translations is located at `docs/es/`.
/// tip
The main ("official") language is English, located at `docs/en/`.
///
Now run the live server for the docs in Spanish:
<div class="termy">
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
</div>
/// tip
Alternatively, you can perform the same steps that scripts does manually.
Go into the language directory, for the Spanish translations it's at `docs/es/`:
```console
$ cd docs/es/
```
Then run `mkdocs` in that directory:
```console
$ mkdocs serve --dev-addr 127.0.0.1:8008
```
///
Now you can go to <a href="http://127.0.0.1:8008" class="external-link" target="_blank">http://127.0.0.1:8008</a> and see your changes live.
You will see that every language has all the pages. But some pages are not translated and have an info box at the top, about the missing translation.
Now let's say that you want to add a translation for the section [Features](features.md){.internal-link target=_blank}.
* Copy the file at:
```
docs/en/docs/features.md
```
* Paste it in exactly the same location but for the language you want to translate, e.g.:
```
docs/es/docs/features.md
```
/// tip
Notice that the only change in the path and file name is the language code, from `en` to `es`.
///
If you go to your browser you will see that now the docs show your new section (the info box at the top is gone). 🎉
Now you can translate it all and see how it looks as you save the file.
#### Don't Translate these Pages
🚨 Don't translate:
* Files under `reference/`
* `release-notes.md`
* `fastapi-people.md`
* `external-links.md`
* `newsletter.md`
* `management-tasks.md`
* `management.md`
* `contributing.md`
Some of these files are updated very frequently and a translation would always be behind, or they include the main content from English source files, etc.
#### Request a New Language
Let's say that you want to request translations for a language that is not yet translated, not even some pages. For example, Latin.
* The first step would be for you to find other 2 people that would be willing to be reviewing translation PRs for that language with you.
* Once there are at least 3 people that would be willing to commit to help maintain that language, you can continue the next steps.
If there is no discussion for that language, you can start by requesting the new language. For that, you can follow these steps:
* Create a new discussion following the template.
* Tag the other 2 people that will help with the language, and ask them to confirm there they will help.
* Get a few native speakers to comment on the discussion and commit to help review translations for that language.
Once there are several people in the discussion, the FastAPI team can evaluate it and can make it an official translation.
Then the docs will be automatically translated using LLMs, and the team of native speakers can review the translation, and help tweak the LLM prompts.
Then the docs will be automatically translated using AI, and the team of native speakers can review the translation, and help tweak the AI prompts.
Once there's a new translation, for example if docs are updated or there's a new section, there will be a comment in the same discussion with the link to the new translation to review.
## Automated Code and AI
#### New Language
You are encouraged to use all the tools you want to do your work and contribute as efficiently as possible, this includes AI (LLM) tools, etc. Nevertheless, contributions should have meaningful human intervention, judgement, context, etc.
/// note
If the **human effort** put in a PR, e.g. writing LLM prompts, is **less** than the **effort we would need to put** to **review it**, please **don't** submit the PR.
These steps will be performed by the FastAPI team.
Think of it this way: we can already write LLM prompts or run automated tools ourselves, and that would be faster than reviewing external PRs.
///
### Closing Automated and AI PRs
Checking the link from above (List of ISO 639-1 codes), you can see that the 2-letter code for Latin is `la`.
If we see PRs that seem AI generated or automated in similar ways, we'll flag them and close them.
Now you can create a new directory for the new language, running the following script:
The same applies to comments and descriptions, please don't copy paste the content generated by an LLM.
<div class="termy">
### Human Effort Denial of Service
```console
// Use the command new-lang, pass the language code as a CLI argument
$ python ./scripts/docs.py new-lang la
Using automated tools and AI to submit PRs or comments that we have to carefully review and handle would be the equivalent of a <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack" class="external-link" target="_blank">Denial-of-service attack</a> on our human effort.
Successfully initialized: docs/la
```
It would be very little effort from the person submitting the PR (an LLM prompt) that generates a large amount of effort on our side (carefully reviewing code).
</div>
Please don't do that.
Now you can check in your code editor the newly created directory `docs/la/`.
We'll need to block accounts that spam us with repeated automated PRs or comments.
That command created a file `docs/la/mkdocs.yml` with a simple config that inherits everything from the `en` version:
### Use Tools Wisely
```yaml
INHERIT: ../en/mkdocs.yml
```
As Uncle Ben said:
/// tip
<blockquote>
With great <strike>power</strike> <strong>tools</strong> comes great responsibility.
</blockquote>
You could also simply create that file with those contents manually.
Avoid inadvertently doing harm.
///
You have amazing tools at hand, use them wisely to help effectively.
That command also created a dummy file `docs/la/index.md` for the main page, you can start by translating that one.
You can continue with the previous instructions for an "Existing Language" for that process.
You can make the first pull request with those two files, `docs/la/mkdocs.yml` and `docs/la/index.md`. 🎉
#### Preview the result
As already mentioned above, you can use the `./scripts/docs.py` with the `live` command to preview the results (or `mkdocs serve`).
Once you are done, you can also test it all as it would look online, including all the other languages.
To do that, first build all the docs:
<div class="termy">
```console
// Use the command "build-all", this will take a bit
$ python ./scripts/docs.py build-all
Building docs for: en
Building docs for: es
Successfully built docs for: es
```
</div>
This builds all those independent MkDocs sites for each language, combines them, and generates the final output at `./site/`.
Then you can serve that with the command `serve`:
<div class="termy">
```console
// Use the command "serve" after running "build-all"
$ python ./scripts/docs.py serve
Warning: this is a very simple server. For development, use mkdocs serve instead.
This is here only to preview a site with translations already built.
Make sure you run the build-all command first.
Serving at: http://127.0.0.1:8008
```
</div>
#### Translation specific tips and guidelines
* Translate only the Markdown documents (`.md`). Do not translate the code examples at `./docs_src`.
* In code blocks within the Markdown document, translate comments (`# a comment`), but leave the rest unchanged.
* Do not change anything enclosed in "``" (inline code).
* In lines starting with `///` translate only the text part after `|`. Leave the rest unchanged.
* You can translate info boxes like `/// warning` with for example `/// warning | Achtung`. But do not change the word immediately after the `///`, it determines the color of the info box.
* Do not change the paths in links to images, code files, Markdown documents.
* However, when a Markdown document is translated, the `#hash-parts` in links to its headings may change. Update these links if possible.
* Search for such links in the translated document using the regex `#[^# ]`.
* Search in all documents already translated into your language for `your-translated-document.md`. For example VS Code has an option "Edit" -> "Find in Files".
* When translating a document, do not "pre-translate" `#hash-parts` that link to headings in untranslated documents.

View File

@@ -145,6 +145,8 @@ There are other formats and tools to define and install package dependencies.
* Create a `main.py` file with:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@@ -156,7 +158,7 @@ def read_root():
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```

View File

@@ -196,11 +196,31 @@ They have contributed source code, documentation, etc. 📦
There are hundreds of other contributors, you can see them all in the <a href="https://github.com/fastapi/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷
## Top Translators
These are the **Top Translators**. 🌐
These users have created the most Pull Requests with [translations to other languages](contributing.md#translations){.internal-link target=_blank} that have been *merged*.
<div class="user-list user-list-center">
{% for user in (translators.values() | list)[:50] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Translations: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
## Top Translation Reviewers
These users are the **Top Translation Reviewers**. 🕵️
Translation reviewers have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
I only speak a few languages (and not very well 😅). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
<div class="user-list user-list-center">
{% for user in (translation_reviewers.values() | list)[:50] %}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -161,6 +161,8 @@ $ pip install "fastapi[standard]"
Create a file `main.py` with:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@@ -172,7 +174,7 @@ def read_root():
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
@@ -181,7 +183,9 @@ def read_item(item_id: int, q: str | None = None):
If your code uses `async` / `await`, use `async def`:
```Python hl_lines="7 12"
```Python hl_lines="9 14"
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@@ -193,7 +197,7 @@ async def read_root():
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
@@ -284,7 +288,9 @@ Now modify the file `main.py` to receive a body from a `PUT` request.
Declare the body using standard Python types, thanks to Pydantic.
```Python hl_lines="2 7-10 23-25"
```Python hl_lines="4 9-12 25-27"
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
@@ -294,7 +300,7 @@ app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: bool | None = None
is_offer: Union[bool, None] = None
@app.get("/")
@@ -303,7 +309,7 @@ def read_root():
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}

View File

@@ -81,14 +81,8 @@ function setupTermynal() {
}
}
saveBuffer();
const inputCommands = useLines
.filter(line => line.type === "input")
.map(line => line.value)
.join("\n");
node.textContent = inputCommands;
const div = document.createElement("div");
node.style.display = "none";
node.after(div);
node.replaceWith(div);
const termynal = new Termynal(div, {
lineData: useLines,
noInit: true,

View File

@@ -74,7 +74,7 @@ Make sure you use a supported label from the <a href="https://github.com/tiangol
* `refactor`: Refactors
* This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc.
* `upgrade`: Upgrades
* This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies or GitHub Action versions should be marked as `internal`, not `upgrade`.
* This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies, normally in `requirements.txt` files or GitHub Action versions should be marked as `internal`, not `upgrade`.
* `docs`: Docs
* Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations.
* You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`.
@@ -106,25 +106,135 @@ This way, we can notice when there are new translations ready, because they have
## Merge Translation PRs
Translations are generated automatically with LLMs and scripts.
For Spanish, as I'm a native speaker and it's a language close to me, I will give it a final review myself and in most cases tweak the PR a bit before merging it.
There's one GitHub Action that can be manually run to add or update translations for a language: <a href="https://github.com/fastapi/fastapi/actions/workflows/translate.yml" class="external-link" target="_blank">`translate.yml`</a>.
For the other languages, confirm that:
For these language translation PRs, confirm that:
* The PR was automated (authored by @tiangolo), not made by another user.
* The title is correct following the instructions above.
* It has the labels `lang-all` and `lang-{lang code}`.
* If the PR is approved by at least one native speaker, you can merge it.
* The PR changes only one Markdown file adding a translation.
* Or in some cases, at most two files, if they are small, for the same language, and people reviewed them.
* If it's the first translation for that language, it will have additional `mkdocs.yml` files, for those cases follow the instructions below.
* The PR doesn't add any additional or extraneous files.
* The translation seems to have a similar structure as the original English file.
* The translation doesn't seem to change the original content, for example with obvious additional documentation sections.
* The translation doesn't use different Markdown structures, for example adding HTML tags when the original didn't have them.
* The "admonition" sections, like `tip`, `info`, etc. are not changed or translated. For example:
```
/// tip
This is a tip.
///
```
looks like this:
/// tip
This is a tip.
///
...it could be translated as:
```
/// tip
Esto es un consejo.
///
```
...but needs to keep the exact `tip` keyword. If it was translated to `consejo`, like:
```
/// consejo
Esto es un consejo.
///
```
it would change the style to the default one, it would look like:
/// consejo
Esto es un consejo.
///
Those don't have to be translated, but if they are, they need to be written as:
```
/// tip | consejo
Esto es un consejo.
///
```
Which looks like:
/// tip | consejo
Esto es un consejo.
///
## First Translation PR
When there's a first translation for a language, it will have a `docs/{lang code}/docs/index.md` translated file and a `docs/{lang code}/mkdocs.yml`.
For example, for Bosnian, it would be:
* `docs/bs/docs/index.md`
* `docs/bs/mkdocs.yml`
The `mkdocs.yml` file will have only the following content:
```YAML
INHERIT: ../en/mkdocs.yml
```
The language code would normally be in the <a href="https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes" class="external-link" target="_blank">ISO 639-1 list of language codes</a>.
In any case, the language code should be in the file <a href="https://github.com/fastapi/fastapi/blob/master/docs/language_names.yml" class="external-link" target="_blank">docs/language_names.yml</a>.
There won't be yet a label for the language code, for example, if it was Bosnian, there wouldn't be a `lang-bs`. Before creating the label and adding it to the PR, create the GitHub Discussion:
* Go to the <a href="https://github.com/fastapi/fastapi/discussions/categories/translations" class="external-link" target="_blank">Translations GitHub Discussions</a>
* Create a new discussion with the title `Bosnian Translations` (or the language name in English)
* A description of:
```Markdown
## Bosnian translations
This is the issue to track translations of the docs to Bosnian. 🚀
Here are the [PRs to review with the label `lang-bs`](https://github.com/fastapi/fastapi/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc+label%3Alang-bs+label%3A%22awaiting-review%22). 🤓
```
Update "Bosnian" with the new language.
And update the search link to point to the new language label that will be created, like `lang-bs`.
Create and add the label to that new Discussion just created, like `lang-bs`.
Then go back to the PR, and add the label, like `lang-bs`, and `lang-all` and `awaiting-review`.
Now the GitHub action will automatically detect the label `lang-bs` and will post in that Discussion that this PR is waiting to be reviewed.
## Review PRs
* If a PR doesn't explain what it does or why, if it seems like it could be useful, ask for more information. Otherwise, feel free to close it.
If a PR doesn't explain what it does or why, ask for more information.
* If a PR seems to be spam, meaningless, only to change statistics (to appear as "contributor") or similar, you can simply mark it as `invalid`, and it will be automatically closed.
* If a PR seems to be AI generated, and seems like reviewing it would take more time from you than the time it took to write the prompt, mark it as `maybe-ai`, and it will be automatically closed.
* A PR should have a specific use case that it is solving.
A PR should have a specific use case that it is solving.
* If the PR is for a feature, it should have docs.
* Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use.
@@ -144,12 +254,27 @@ Every month, a GitHub Action updates the FastAPI People data. Those PRs look lik
If the tests are passing, you can merge it right away.
## External Links PRs
When people add external links they edit this file <a href="https://github.com/fastapi/fastapi/blob/master/docs/en/data/external_links.yml" class="external-link" target="_blank">external_links.yml</a>.
* Make sure the new link is in the correct category (e.g. "Podcasts") and language (e.g. "Japanese").
* A new link should be at the top of its list.
* The link URL should work (it should not return a 404).
* The content of the link should be about FastAPI.
* The new addition should have these fields:
* `author`: The name of the author.
* `link`: The URL with the content.
* `title`: The title of the link (the title of the article, podcast, etc).
After checking all these things and ensuring the PR has the right labels, you can merge it.
## Dependabot PRs
Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others.
* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml` in the main dependencies, **don't merge it**. 😱 Let me check it first. There's a good chance that some additional tweaks or updates are needed.
* If the PR updates one of the internal dependencies, for example the group `dev` in `pyproject.toml`, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. 😎
* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml`, **don't merge it**. 😱 Let me check it first. There's a good chance that some additional tweaks or updates are needed.
* If the PR updates one of the internal dependencies, for example it's modifying `requirements.txt` files, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. 😎
## Mark GitHub Discussions Answers

View File

@@ -9,24 +9,10 @@ hide:
### Docs
* 🐛 Fix copy button in custom.js. PR [#14722](https://github.com/fastapi/fastapi/pull/14722) by [@fcharrier](https://github.com/fcharrier).
* 📝 Add contribution instructions about LLM generated code and comments and automated tools for PRs. PR [#14706](https://github.com/fastapi/fastapi/pull/14706) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update docs for management tasks. PR [#14705](https://github.com/fastapi/fastapi/pull/14705) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update docs about managing translations. PR [#14704](https://github.com/fastapi/fastapi/pull/14704) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update docs for contributing with translations. PR [#14701](https://github.com/fastapi/fastapi/pull/14701) by [@tiangolo](https://github.com/tiangolo).
* 📝 Specify language code for code block. PR [#14656](https://github.com/fastapi/fastapi/pull/14656) by [@YuriiMotov](https://github.com/YuriiMotov).
### Translations
* 🌐 Update translations for de (update-outdated). PR [#14690](https://github.com/fastapi/fastapi/pull/14690) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update LLM prompt for Russian translations. PR [#14733](https://github.com/fastapi/fastapi/pull/14733) by [@YuriiMotov](https://github.com/YuriiMotov).
* 🌐 Update translations for ru (update-outdated). PR [#14693](https://github.com/fastapi/fastapi/pull/14693) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for pt (update-outdated). PR [#14724](https://github.com/fastapi/fastapi/pull/14724) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update Korean LLM prompt. PR [#14740](https://github.com/fastapi/fastapi/pull/14740) by [@hard-coders](https://github.com/hard-coders).
* 🌐 Improve LLM prompt for Turkish translations. PR [#14728](https://github.com/fastapi/fastapi/pull/14728) by [@Kadermiyanyedi](https://github.com/Kadermiyanyedi).
* 🌐 Update portuguese llm-prompt.md. PR [#14702](https://github.com/fastapi/fastapi/pull/14702) by [@ceb10n](https://github.com/ceb10n).
* 🌐 Update LLM prompt instructions file for French. PR [#14618](https://github.com/fastapi/fastapi/pull/14618) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for ko (add-missing). PR [#14699](https://github.com/fastapi/fastapi/pull/14699) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for ko (update-outdated). PR [#14589](https://github.com/fastapi/fastapi/pull/14589) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for uk (update-outdated). PR [#14587](https://github.com/fastapi/fastapi/pull/14587) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for es (update-outdated). PR [#14686](https://github.com/fastapi/fastapi/pull/14686) by [@tiangolo](https://github.com/tiangolo).
@@ -36,11 +22,6 @@ hide:
### Internal
* 🔧 Update sponsors: remove Requestly. PR [#14735](https://github.com/fastapi/fastapi/pull/14735) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update sponsors, LambdaTest changes to TestMu AI. PR [#14734](https://github.com/fastapi/fastapi/pull/14734) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/cache from 4 to 5. PR [#14511](https://github.com/fastapi/fastapi/pull/14511) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump actions/upload-artifact from 5 to 6. PR [#14525](https://github.com/fastapi/fastapi/pull/14525) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump actions/download-artifact from 6 to 7. PR [#14526](https://github.com/fastapi/fastapi/pull/14526) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 👷 Tweak CI input names. PR [#14688](https://github.com/fastapi/fastapi/pull/14688) by [@tiangolo](https://github.com/tiangolo).
* 🔨 Refactor translation script to allow committing in place. PR [#14687](https://github.com/fastapi/fastapi/pull/14687) by [@tiangolo](https://github.com/tiangolo).
* 🐛 Fix translation script path. PR [#14685](https://github.com/fastapi/fastapi/pull/14685) by [@tiangolo](https://github.com/tiangolo).

View File

@@ -102,16 +102,15 @@ Of course, you can also declare additional query parameters whenever you need, a
As, by default, singular values are interpreted as query parameters, you don't have to explicitly add a `Query`, you can just do:
```Python
q: str | None = None
```
Or in Python 3.9:
```Python
q: Union[str, None] = None
```
Or in Python 3.10 and above:
```Python
q: str | None = None
```
For example:

View File

@@ -6,127 +6,123 @@ Language code: fr.
### Grammar to use when talking to the reader
Use the formal grammar (use `vous` instead of `tu`).
Additionally, in instructional sentences, prefer the present tense for obligations:
- Prefer `vous devez …` over `vous devrez …`, unless the English source explicitly refers to a future requirement.
- When translating “make sure (that) … is …”, prefer the indicative after `vous assurer que` (e.g. `Vous devez vous assurer qu'il est …`) instead of the subjunctive (e.g. `qu'il soit …`).
Use the formal grammar (use «vous» instead of «tu»).
### Quotes
- Convert neutral double quotes (`"`) to French guillemets (`«` and `»`).
1) Convert neutral double quotes («"») and English double typographic quotes («“» and «”») to French guillemets (««» and «»»).
- Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks.
2) In the French docs, guillemets are written without extra spaces: use «texte», not « texte ».
3) Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks.
Examples:
Source (English):
Source (English):
```
"Hello world"
“Hello Universe”
"He said: 'Hello'"
"The module is `__main__`"
```
«««
"Hello world"
“Hello Universe”
"He said: 'Hello'"
"The module is `__main__`"
»»»
Result (French):
Result (French):
```
"Hello world"
Hello Universe
"He said: 'Hello'"
"The module is `__main__`"
```
«««
«Hello world»
«Hello Universe»
«He said: 'Hello'»
«The module is `__main__`»
»»»
### Ellipsis
- Make sure there is a space between an ellipsis and a word following or preceding the ellipsis.
1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis.
Examples:
Source (English):
Source (English):
```
...as we intended.
...this would work:
...etc.
others...
More to come...
```
«««
...as we intended.
...this would work:
...etc.
others...
More to come...
»»»
Result (French):
Result (French):
```
... comme prévu.
... cela fonctionnerait :
... etc.
D'autres ...
La suite ...
```
«««
... comme prévu.
... cela fonctionnerait :
... etc.
D'autres ...
La suite ...
»»»
- This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there.
### Headings
- Prefer translating headings using the infinitive form (as is common in the existing French docs): `Créer…`, `Utiliser…`, `Ajouter…`.
1) Prefer translating headings using the infinitive form (as is common in the existing French docs): «Créer…», «Utiliser…», «Ajouter…».
- For headings that are instructions written in imperative in English (e.g. `Go check …`), keep them in imperative in French, using the formal grammar (e.g. `Allez voir …`).
2) For headings that are instructions written in imperative in English (e.g. Go check …), keep them in imperative in French, using the formal grammar (e.g. «Allez voir …»).
3) Keep heading punctuation as in the source. In particular, keep occurrences of literal « - » (space-hyphen-space) as « - » (the existing French docs use a hyphen here).
### French instructions about technical terms
Do not try to translate everything. In particular, keep common programming terms (e.g. `framework`, `endpoint`, `plug-in`, `payload`).
Do not try to translate everything. In particular, keep common programming terms when that is the established usage in the French docs (e.g. «framework», «endpoint», «plug-in», «payload»). Use French where the existing docs already consistently use French (e.g. «requête», «réponse»).
Keep class names, function names, modules, file names, and CLI commands unchanged.
### List of English terms and their preferred French translations
Below is a list of English terms and their preferred French translations, separated by a colon (:). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them.
Below is a list of English terms and their preferred French translations, separated by a colon («:»). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them.
- /// note | Technical Details»: /// note | Détails techniques
- /// note: /// note | Remarque
- /// tip: /// tip | Astuce
- /// warning: /// warning | Alertes
- /// check: /// check | Vérifications
- /// info: /// info
* «/// note | Technical Details»: «/// note | Détails techniques»
* «/// note»: «/// note | Remarque»
* «/// tip»: «/// tip | Astuce»
* «/// warning»: «/// warning | Attention»
* «/// check»: «/// check | vérifier»
* «/// info»: «/// info»
- the docs: les documents
- the documentation: la documentation
* «the docs»: «les documents»
* «the documentation»: «la documentation»
- Exclude from OpenAPI: Exclusion d'OpenAPI
* «framework»: «framework» (do not translate to «cadre»)
* «performance»: «performance»
- framework: framework (do not translate to cadre)
- performance: performance
* «type hints»: «annotations de type»
* «type annotations»: «annotations de type»
- type hints: annotations de type
- type annotations: annotations de type
* «autocomplete»: «autocomplétion»
* «autocompletion»: «autocomplétion»
- autocomplete: autocomplétion
- autocompletion: autocomplétion
* «the request» (what the client sends to the server): «la requête»
* «the response» (what the server sends back to the client): «la réponse»
- the request (what the client sends to the server): la requête
- the response (what the server sends back to the client): la réponse
* «the request body»: «le corps de la requête»
* «the response body»: «le corps de la réponse»
- the request body: le corps de la requête
- the response body: le corps de la réponse
* «path operation»: «opération de chemin»
* «path operations» (plural): «opérations de chemin»
* «path operation function»: «fonction de chemin»
* «path operation decorator»: «décorateur d'opération de chemin»
- path operation: chemin d'accès
- path operations (plural): chemins d'accès
- path operation function: fonction de chemin d'accès
- path operation decorator: décorateur de chemin d'accès
* «path parameter»: «paramètre de chemin»
* «query parameter»: «paramètre de requête»
- path parameter: paramètre de chemin
- query parameter: paramètre de requête
* «the `Request`»: «`Request`» (keep as code identifier)
* «the `Response`»: «`Response`» (keep as code identifier)
- the `Request`: `Request` (keep as code identifier)
- the `Response`: `Response` (keep as code identifier)
* «deployment»: «déploiement»
* «to upgrade»: «mettre à niveau»
- deployment: déploiement
- to upgrade: mettre à niveau
* «deprecated»: «déprécié»
* «to deprecate»: «déprécier»
- deprecated: déprécié
- to deprecate: déprécier
- cheat sheet: aide-mémoire
- plug-in: plug-in
* «cheat sheet»: «aide-mémoire»
* «plug-in»: «plug-in»

View File

@@ -1,503 +0,0 @@
# LLM 테스트 파일 { #llm-test-file }
이 문서는 문서를 번역하는 <abbr title="Large Language Model - 대규모 언어 모델">LLM</abbr>이 `scripts/translate.py``general_prompt``docs/{language code}/llm-prompt.md`의 언어별 프롬프트를 이해하는지 테스트합니다. 언어별 프롬프트는 `general_prompt`에 추가됩니다.
여기에 추가된 테스트는 언어별 프롬프트를 설계하는 모든 사람이 보게 됩니다.
사용 방법은 다음과 같습니다:
* 언어별 프롬프트 `docs/{language code}/llm-prompt.md`를 준비합니다.
* 이 문서를 원하는 대상 언어로 새로 번역합니다(예: `translate.py``translate-page` 명령). 그러면 `docs/{language code}/docs/_llm-test.md` 아래에 번역이 생성됩니다.
* 번역에서 문제가 없는지 확인합니다.
* 필요하다면 언어별 프롬프트, 일반 프롬프트, 또는 영어 문서를 개선합니다.
* 그런 다음 번역에서 남아 있는 문제를 수동으로 수정해 좋은 번역이 되게 합니다.
* 좋은 번역을 둔 상태에서 다시 번역합니다. 이상적인 결과는 LLM이 더 이상 번역에 변경을 만들지 않는 것입니다. 이는 일반 프롬프트와 언어별 프롬프트가 가능한 한 최선이라는 뜻입니다(때때로 몇 가지 seemingly random 변경을 할 수 있는데, 그 이유는 <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM은 결정론적 알고리즘이 아니기 때문</a>입니다).
테스트:
## 코드 스니펫 { #code-snippets }
//// tab | 테스트
다음은 코드 스니펫입니다: `foo`. 그리고 이것은 또 다른 코드 스니펫입니다: `bar`. 그리고 또 하나: `baz quux`.
////
//// tab | 정보
코드 스니펫의 내용은 그대로 두어야 합니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Content of code snippets` 섹션을 참고하세요.
////
## 따옴표 { #quotes }
//// tab | 테스트
어제 제 친구가 이렇게 썼습니다: "If you spell incorrectly correctly, you have spelled it incorrectly". 이에 저는 이렇게 답했습니다: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"".
/// note | 참고
LLM은 아마 이것을 잘못 번역할 것입니다. 흥미로운 점은 재번역할 때 고정된 번역을 유지하는지 여부뿐입니다.
///
////
//// tab | 정보
프롬프트 설계자는 중립 따옴표를 타이포그래피 따옴표로 변환할지 선택할 수 있습니다. 그대로 두어도 괜찮습니다.
예를 들어 `docs/de/llm-prompt.md``### Quotes` 섹션을 참고하세요.
////
## 코드 스니펫의 따옴표 { #quotes-in-code-snippets }
//// tab | 테스트
`pip install "foo[bar]"`
코드 스니펫에서 문자열 리터럴의 예: `"this"`, `'that'`.
코드 스니펫에서 문자열 리터럴의 어려운 예: `f"I like {'oranges' if orange else "apples"}"`
하드코어: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"`
////
//// tab | 정보
... 하지만 코드 스니펫 안의 따옴표는 그대로 유지되어야 합니다.
////
## 코드 블록 { #code-blocks }
//// tab | 테스트
Bash 코드 예시...
```bash
# 우주에 인사말 출력
echo "Hello universe"
```
...그리고 콘솔 코드 예시...
```console
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting server
Searching for package file structure
```
...그리고 또 다른 콘솔 코드 예시...
```console
// "Code" 디렉터리 생성
$ mkdir code
// 해당 디렉터리로 이동
$ cd code
```
...그리고 Python 코드 예시...
```Python
wont_work() # 이건 동작하지 않습니다 😱
works(foo="bar") # 이건 동작합니다 🎉
```
...이상입니다.
////
//// tab | 정보
코드 블록의 코드는(주석을 제외하고) 수정하면 안 됩니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Content of code blocks` 섹션을 참고하세요.
////
## 탭과 색상 박스 { #tabs-and-colored-boxes }
//// tab | 테스트
/// info | 정보
일부 텍스트
///
/// note | 참고
일부 텍스트
///
/// note Technical details | 기술 세부사항
일부 텍스트
///
/// check | 확인
일부 텍스트
///
/// tip | 팁
일부 텍스트
///
/// warning | 경고
일부 텍스트
///
/// danger | 위험
일부 텍스트
///
////
//// tab | 정보
탭과 `Info`/`Note`/`Warning`/등의 블록은 제목 번역을 수직 막대(`|`) 뒤에 추가해야 합니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Special blocks``### Tab blocks` 섹션을 참고하세요.
////
## 웹 및 내부 링크 { #web-and-internal-links }
//// tab | 테스트
링크 텍스트는 번역되어야 하고, 링크 주소는 변경되지 않아야 합니다:
* [위의 제목으로 가는 링크](#code-snippets)
* [내부 링크](index.md#installation){.internal-link target=_blank}
* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">외부 링크</a>
* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">스타일로 가는 링크</a>
* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">스크립트로 가는 링크</a>
* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">이미지로 가는 링크</a>
링크 텍스트는 번역되어야 하고, 링크 주소는 번역 페이지를 가리켜야 합니다:
* <a href="https://fastapi.tiangolo.com/ko/" class="external-link" target="_blank">FastAPI 링크</a>
////
//// tab | 정보
링크는 번역되어야 하지만, 주소는 변경되지 않아야 합니다. 예외는 FastAPI 문서 페이지로 향하는 절대 링크이며, 이 경우 번역 페이지로 연결되어야 합니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Links` 섹션을 참고하세요.
////
## HTML "abbr" 요소 { #html-abbr-elements }
//// tab | 테스트
여기 HTML "abbr" 요소로 감싼 몇 가지가 있습니다(일부는 임의로 만든 것입니다):
### abbr가 전체 문구를 제공 { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done - 일을 끝내는 방법론">GTD</abbr>
* <abbr title="less than - 보다 작음"><code>lt</code></abbr>
* <abbr title="XML Web Token - XML 웹 토큰">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - 병렬 서버 게이트웨이 인터페이스">PSGI</abbr>
### abbr가 설명을 제공 { #the-abbr-gives-an-explanation }
* <abbr title="어떤 방식으로든 서로 연결되고 함께 작동하도록 구성된 머신들의 집합입니다.">cluster</abbr>
* <abbr title="입력과 출력 계층 사이에 수많은 은닉 계층을 둔 인공 신경망을 사용하는 머신 러닝 방법으로, 이를 통해 포괄적인 내부 구조를 형성합니다">Deep Learning</abbr>
### abbr가 전체 문구와 설명을 제공 { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network - 모질라 개발자 네트워크: Firefox를 만드는 사람들이 작성한 개발자용 문서">MDN</abbr>
* <abbr title="Input/Output - 입력/출력: 디스크 읽기 또는 쓰기, 네트워크 통신.">I/O</abbr>.
////
//// tab | 정보
"abbr" 요소의 "title" 속성은 몇 가지 구체적인 지침에 따라 번역됩니다.
번역에서는(영어 단어를 설명하기 위해) 자체 "abbr" 요소를 추가할 수 있으며, LLM은 이를 제거하면 안 됩니다.
`scripts/translate.py`의 일반 프롬프트에서 `### HTML abbr elements` 섹션을 참고하세요.
////
## 제목 { #headings }
//// tab | 테스트
### 웹앱 개발하기 - 튜토리얼 { #develop-a-webapp-a-tutorial }
안녕하세요.
### 타입 힌트와 -애너테이션 { #type-hints-and-annotations }
다시 안녕하세요.
### super- 및 subclasses { #super-and-subclasses }
다시 안녕하세요.
////
//// tab | 정보
제목에 대한 유일한 강한 규칙은, LLM이 중괄호 안의 해시 부분을 변경하지 않아 링크가 깨지지 않게 하는 것입니다.
`scripts/translate.py`의 일반 프롬프트에서 `### Headings` 섹션을 참고하세요.
언어별 지침은 예를 들어 `docs/de/llm-prompt.md``### Headings` 섹션을 참고하세요.
////
## 문서에서 사용되는 용어 { #terms-used-in-the-docs }
//// tab | 테스트
* 당신
* 당신의
* 예: (e.g.)
* 등 (etc.)
* `int`로서의 `foo`
* `str`로서의 `bar`
* `list`로서의 `baz`
* 튜토리얼 - 사용자 가이드
* 고급 사용자 가이드
* SQLModel 문서
* API 문서
* 자동 문서
* Data Science
* Deep Learning
* Machine Learning
* Dependency Injection
* HTTP Basic authentication
* HTTP Digest
* ISO format
* JSON Schema 표준
* JSON schema
* schema definition
* Password Flow
* Mobile
* deprecated
* designed
* invalid
* on the fly
* standard
* default
* case-sensitive
* case-insensitive
* 애플리케이션을 서빙하다
* 페이지를 서빙하다
*
* 애플리케이션
* 요청
* 응답
* 오류 응답
* 경로 처리
* 경로 처리 데코레이터
* 경로 처리 함수
* body
* 요청 body
* 응답 body
* JSON body
* form body
* file body
* 함수 body
* parameter
* body parameter
* path parameter
* query parameter
* cookie parameter
* header parameter
* form parameter
* function parameter
* event
* startup event
* 서버 startup
* shutdown event
* lifespan event
* handler
* event handler
* exception handler
* 처리하다
* model
* Pydantic model
* data model
* database model
* form model
* model object
* class
* base class
* parent class
* subclass
* child class
* sibling class
* class method
* header
* headers
* authorization header
* `Authorization` header
* forwarded header
* dependency injection system
* dependency
* dependable
* dependant
* I/O bound
* CPU bound
* concurrency
* parallelism
* multiprocessing
* env var
* environment variable
* `PATH`
* `PATH` variable
* authentication
* authentication provider
* authorization
* authorization form
* authorization provider
* 사용자가 인증한다
* 시스템이 사용자를 인증한다
* CLI
* command line interface
* server
* client
* cloud provider
* cloud service
* development
* development stages
* dict
* dictionary
* enumeration
* enum
* enum member
* encoder
* decoder
* encode하다
* decode하다
* exception
* raise하다
* expression
* statement
* frontend
* backend
* GitHub discussion
* GitHub issue
* performance
* performance optimization
* return type
* return value
* security
* security scheme
* task
* background task
* task function
* template
* template engine
* type annotation
* type hint
* server worker
* Uvicorn worker
* Gunicorn Worker
* worker process
* worker class
* workload
* deployment
* deploy하다
* SDK
* software development kit
* `APIRouter`
* `requirements.txt`
* Bearer Token
* breaking change
* bug
* button
* callable
* code
* commit
* context manager
* coroutine
* database session
* disk
* domain
* engine
* fake X
* HTTP GET method
* item
* library
* lifespan
* lock
* middleware
* mobile application
* module
* mounting
* network
* origin
* override
* payload
* processor
* property
* proxy
* pull request
* query
* RAM
* remote machine
* status code
* string
* tag
* web framework
* wildcard
* return하다
* validate하다
////
//// tab | 정보
이것은 문서에서 보이는 (대부분) 기술 용어의 불완전하고 비규범적인 목록입니다. 프롬프트 설계자가 어떤 용어에 대해 LLM에 추가적인 도움이 필요한지 파악하는 데 유용할 수 있습니다. 예를 들어, 좋은 번역을 계속 덜 좋은 번역으로 되돌릴 때, 또는 언어에서 용어의 활용/변화를 처리하는 데 문제가 있을 때 도움이 됩니다.
예를 들어 `docs/de/llm-prompt.md``### List of English terms and their preferred German translations` 섹션을 참고하세요.
////

View File

@@ -1,247 +0,0 @@
# OpenAPI에서 추가 응답 { #additional-responses-in-openapi }
/// warning | 경고
이는 꽤 고급 주제입니다.
**FastAPI**를 막 시작했다면, 이 내용이 필요 없을 수도 있습니다.
///
추가 상태 코드, 미디어 타입, 설명 등을 포함한 추가 응답을 선언할 수 있습니다.
이러한 추가 응답은 OpenAPI 스키마에 포함되므로 API 문서에도 표시됩니다.
하지만 이러한 추가 응답의 경우, 상태 코드와 콘텐츠를 포함하여 `JSONResponse` 같은 `Response`를 직접 반환하도록 반드시 처리해야 합니다.
## `model`을 사용한 추가 응답 { #additional-response-with-model }
*경로 처리 데코레이터*에 `responses` 파라미터를 전달할 수 있습니다.
이는 `dict`를 받습니다. 키는 각 응답의 상태 코드(예: `200`)이고, 값은 각 응답에 대한 정보를 담은 다른 `dict`입니다.
각 응답 `dict`에는 `response_model`처럼 Pydantic 모델을 담는 `model` 키가 있을 수 있습니다.
**FastAPI**는 그 모델을 사용해 JSON Schema를 생성하고, OpenAPI의 올바른 위치에 포함합니다.
예를 들어, 상태 코드 `404`와 Pydantic 모델 `Message`를 사용하는 다른 응답을 선언하려면 다음과 같이 작성할 수 있습니다:
{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
/// note | 참고
`JSONResponse`를 직접 반환해야 한다는 점을 기억하세요.
///
/// info | 정보
`model` 키는 OpenAPI의 일부가 아닙니다.
**FastAPI**는 여기에서 Pydantic 모델을 가져와 JSON Schema를 생성하고 올바른 위치에 넣습니다.
올바른 위치는 다음과 같습니다:
* 값으로 또 다른 JSON 객체(`dict`)를 가지는 `content` 키 안에:
* 미디어 타입(예: `application/json`)을 키로 가지며, 값으로 또 다른 JSON 객체를 포함하고:
* `schema` 키가 있고, 그 값이 모델에서 생성된 JSON Schema입니다. 이것이 올바른 위치입니다.
* **FastAPI**는 이를 직접 포함하는 대신, OpenAPI의 다른 위치에 있는 전역 JSON Schemas를 참조하도록 여기에서 reference를 추가합니다. 이렇게 하면 다른 애플리케이션과 클라이언트가 그 JSON Schema를 직접 사용할 수 있고, 더 나은 코드 생성 도구 등을 제공할 수 있습니다.
///
이 *경로 처리*에 대해 OpenAPI에 생성되는 응답은 다음과 같습니다:
```JSON hl_lines="3-12"
{
"responses": {
"404": {
"description": "Additional Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
```
스키마는 OpenAPI 스키마 내부의 다른 위치를 참조합니다:
```JSON hl_lines="4-16"
{
"components": {
"schemas": {
"Message": {
"title": "Message",
"required": [
"message"
],
"type": "object",
"properties": {
"message": {
"title": "Message",
"type": "string"
}
}
},
"Item": {
"title": "Item",
"required": [
"id",
"value"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
}
},
"ValidationError": {
"title": "ValidationError",
"required": [
"loc",
"msg",
"type"
],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"type": "string"
}
},
"msg": {
"title": "Message",
"type": "string"
},
"type": {
"title": "Error Type",
"type": "string"
}
}
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
}
}
}
}
```
## 주요 응답에 대한 추가 미디어 타입 { #additional-media-types-for-the-main-response }
같은 `responses` 파라미터를 사용해 동일한 주요 응답에 대해 다른 미디어 타입을 추가할 수도 있습니다.
예를 들어, *경로 처리*가 JSON 객체(미디어 타입 `application/json`) 또는 PNG 이미지(미디어 타입 `image/png`)를 반환할 수 있다고 선언하기 위해 `image/png`라는 추가 미디어 타입을 추가할 수 있습니다:
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
/// note | 참고
이미지는 `FileResponse`를 사용해 직접 반환해야 한다는 점에 유의하세요.
///
/// info | 정보
`responses` 파라미터에서 다른 미디어 타입을 명시적으로 지정하지 않는 한, FastAPI는 응답이 주요 응답 클래스와 동일한 미디어 타입(기본값 `application/json`)을 가진다고 가정합니다.
하지만 커스텀 응답 클래스를 지정하면서 미디어 타입을 `None`으로 설정했다면, FastAPI는 연결된 모델이 있는 모든 추가 응답에 대해 `application/json`을 사용합니다.
///
## 정보 결합하기 { #combining-information }
`response_model`, `status_code`, `responses` 파라미터를 포함해 여러 위치의 응답 정보를 결합할 수도 있습니다.
기본 상태 코드 `200`(또는 필요하다면 커스텀 코드)을 사용하여 `response_model`을 선언하고, 그와 동일한 응답에 대한 추가 정보를 `responses`에서 OpenAPI 스키마에 직접 선언할 수 있습니다.
**FastAPI**는 `responses`의 추가 정보를 유지하고, 모델의 JSON Schema와 결합합니다.
예를 들어, Pydantic 모델을 사용하고 커스텀 `description`을 가진 상태 코드 `404` 응답을 선언할 수 있습니다.
또한 `response_model`을 사용하는 상태 코드 `200` 응답을 선언하되, 커스텀 `example`을 포함할 수도 있습니다:
{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
이 모든 내용은 OpenAPI에 결합되어 포함되고, API 문서에 표시됩니다:
<img src="/img/tutorial/additional-responses/image01.png">
## 미리 정의된 응답과 커스텀 응답 결합하기 { #combine-predefined-responses-and-custom-ones }
여러 *경로 처리*에 적용되는 미리 정의된 응답이 필요할 수도 있지만, 각 *경로 처리*마다 필요한 커스텀 응답과 결합하고 싶을 수도 있습니다.
그런 경우 Python의 `dict` “unpacking” 기법인 `**dict_to_unpack`을 사용할 수 있습니다:
```Python
old_dict = {
"old key": "old value",
"second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}
```
여기서 `new_dict`는 `old_dict`의 모든 키-값 쌍에 더해 새 키-값 쌍까지 포함합니다:
```Python
{
"old key": "old value",
"second old key": "second old value",
"new key": "new value",
}
```
이 기법을 사용해 *경로 처리*에서 일부 미리 정의된 응답을 재사용하고, 추가 커스텀 응답과 결합할 수 있습니다.
예를 들어:
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
## OpenAPI 응답에 대한 추가 정보 { #more-information-about-openapi-responses }
응답에 정확히 무엇을 포함할 수 있는지 보려면, OpenAPI 사양의 다음 섹션을 확인하세요:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI Responses Object</a>: `Response Object`를 포함합니다.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI Response Object</a>: `responses` 파라미터 안의 각 응답에 이것의 어떤 항목이든 직접 포함할 수 있습니다. `description`, `headers`, `content`(여기에서 서로 다른 미디어 타입과 JSON Schema를 선언합니다), `links` 등을 포함할 수 있습니다.

View File

@@ -1,466 +0,0 @@
# 프록시 뒤에서 실행하기 { #behind-a-proxy }
많은 경우 FastAPI 앱 앞단에 Traefik이나 Nginx 같은 **프록시(proxy)**를 두고 사용합니다.
이런 프록시는 HTTPS 인증서 처리 등 여러 작업을 담당할 수 있습니다.
## 프록시 전달 헤더 { #proxy-forwarded-headers }
애플리케이션 앞단의 **프록시**는 보통 **서버**로 요청을 보내기 전에, 해당 요청이 프록시에 의해 **전달(forwarded)**되었다는 것을 서버가 알 수 있도록 몇몇 헤더를 동적으로 설정합니다. 이를 통해 서버는 도메인을 포함한 원래의 (공개) URL, HTTPS 사용 여부 등 정보를 알 수 있습니다.
**서버** 프로그램(예: **FastAPI CLI**를 통해 실행되는 **Uvicorn**)은 이런 헤더를 해석할 수 있고, 그 정보를 애플리케이션으로 전달할 수 있습니다.
하지만 보안상, 서버는 자신이 신뢰할 수 있는 프록시 뒤에 있다는 것을 모르면 해당 헤더를 해석하지 않습니다.
/// note | 기술 세부사항
프록시 헤더는 다음과 같습니다:
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
///
### 프록시 전달 헤더 활성화하기 { #enable-proxy-forwarded-headers }
FastAPI CLI를 *CLI 옵션* `--forwarded-allow-ips`로 실행하고, 전달 헤더를 읽을 수 있도록 신뢰할 IP 주소들을 넘길 수 있습니다.
`--forwarded-allow-ips="*"`로 설정하면 들어오는 모든 IP를 신뢰합니다.
**서버**가 신뢰할 수 있는 **프록시** 뒤에 있고 프록시만 서버에 접근한다면, 이는 해당 **프록시**의 IP가 무엇이든 간에 받아들이게 됩니다.
<div class="termy">
```console
$ fastapi run --forwarded-allow-ips="*"
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### HTTPS에서 리디렉션 { #redirects-with-https }
예를 들어, *경로 처리* `/items/`를 정의했다고 해봅시다:
{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *}
클라이언트가 `/items`로 접근하면, 기본적으로 `/items/`로 리디렉션됩니다.
하지만 *CLI 옵션* `--forwarded-allow-ips`를 설정하기 전에는 `http://localhost:8000/items/`로 리디렉션될 수 있습니다.
그런데 애플리케이션이 `https://mysuperapp.com`에 호스팅되어 있고, 리디렉션도 `https://mysuperapp.com/items/`로 되어야 할 수 있습니다.
이때 `--proxy-headers`를 설정하면 FastAPI가 올바른 위치로 리디렉션할 수 있습니다. 😎
```
https://mysuperapp.com/items/
```
/// tip | 팁
HTTPS에 대해 더 알아보려면 가이드 [HTTPS에 대하여](../deployment/https.md){.internal-link target=_blank}를 확인하세요.
///
### 프록시 전달 헤더가 동작하는 방식 { #how-proxy-forwarded-headers-work }
다음은 **프록시**가 클라이언트와 **애플리케이션 서버** 사이에서 전달 헤더를 추가하는 과정을 시각적으로 나타낸 것입니다:
```mermaid
sequenceDiagram
participant Client
participant Proxy as Proxy/Load Balancer
participant Server as FastAPI Server
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
Note over Proxy: Proxy adds forwarded headers
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
Proxy->>Client: HTTPS Response
```
**프록시**는 원래의 클라이언트 요청을 가로채고, **애플리케이션 서버**로 요청을 전달하기 전에 특수한 *forwarded* 헤더(`X-Forwarded-*`)를 추가합니다.
이 헤더들은 그렇지 않으면 사라질 수 있는 원래 요청의 정보를 보존합니다:
* **X-Forwarded-For**: 원래 클라이언트의 IP 주소
* **X-Forwarded-Proto**: 원래 프로토콜(`https`)
* **X-Forwarded-Host**: 원래 호스트(`mysuperapp.com`)
**FastAPI CLI**를 `--forwarded-allow-ips`로 설정하면, 이 헤더를 신뢰하고 사용합니다. 예를 들어 리디렉션에서 올바른 URL을 생성하는 데 사용됩니다.
## 제거된 경로 접두사를 가진 프록시 { #proxy-with-a-stripped-path-prefix }
애플리케이션에 경로 접두사(prefix)를 추가하는 프록시를 둘 수도 있습니다.
이런 경우 `root_path`를 사용해 애플리케이션을 구성할 수 있습니다.
`root_path`는 (FastAPI가 Starlette를 통해 기반으로 하는) ASGI 사양에서 제공하는 메커니즘입니다.
`root_path`는 이러한 특정 사례를 처리하는 데 사용됩니다.
또한 서브 애플리케이션을 마운트할 때 내부적으로도 사용됩니다.
경로 접두사가 제거(stripped)되는 프록시가 있다는 것은, 코드에서는 `/app`에 경로를 선언하지만, 위에 한 겹(프록시)을 추가해 **FastAPI** 애플리케이션을 `/api/v1` 같은 경로 아래에 두는 것을 의미합니다.
이 경우 원래 경로 `/app`은 실제로 `/api/v1/app`에서 서비스됩니다.
코드는 모두 `/app`만 있다고 가정하고 작성되어 있는데도 말입니다.
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *}
그리고 프록시는 요청을 앱 서버(아마 FastAPI CLI를 통해 실행되는 Uvicorn)로 전달하기 전에, 동적으로 **경로 접두사**를 **"제거"**합니다. 그래서 애플리케이션은 여전히 `/app`에서 서비스된다고 믿게 되고, 코드 전체를 `/api/v1` 접두사를 포함하도록 수정할 필요가 없어집니다.
여기까지는 보통 정상적으로 동작합니다.
하지만 통합 문서 UI(프론트엔드)를 열면, OpenAPI 스키마를 `/api/v1/openapi.json`이 아니라 `/openapi.json`에서 가져오려고 합니다.
그래서 브라우저에서 실행되는 프론트엔드는 `/openapi.json`에 접근하려고 시도하지만 OpenAPI 스키마를 얻지 못합니다.
앱에 대해 `/api/v1` 경로 접두사를 가진 프록시가 있으므로, 프론트엔드는 `/api/v1/openapi.json`에서 OpenAPI 스키마를 가져와야 합니다.
```mermaid
graph LR
browser("Browser")
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"]
server["Server on http://127.0.0.1:8000/app"]
browser --> proxy
proxy --> server
```
/// tip | 팁
IP `0.0.0.0`은 보통 해당 머신/서버에서 사용 가능한 모든 IP에서 프로그램이 리슨한다는 의미로 사용됩니다.
///
문서 UI는 또한 OpenAPI 스키마에서 이 API `server``/api/v1`(프록시 뒤) 위치에 있다고 선언해야 합니다. 예:
```JSON hl_lines="4-8"
{
"openapi": "3.1.0",
// More stuff here
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
// More stuff here
}
}
```
이 예시에서 "Proxy"는 **Traefik** 같은 것이고, 서버는 **Uvicorn**으로 실행되는 FastAPI CLI처럼, FastAPI 애플리케이션을 실행하는 구성일 수 있습니다.
### `root_path` 제공하기 { #providing-the-root-path }
이를 달성하려면 다음처럼 커맨드 라인 옵션 `--root-path`를 사용할 수 있습니다:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Hypercorn을 사용한다면, Hypercorn에도 `--root-path` 옵션이 있습니다.
/// note | 기술 세부사항
ASGI 사양은 이 사용 사례를 위해 `root_path`를 정의합니다.
그리고 커맨드 라인 옵션 `--root-path`가 그 `root_path`를 제공합니다.
///
### 현재 `root_path` 확인하기 { #checking-the-current-root-path }
요청마다 애플리케이션에서 사용 중인 현재 `root_path`를 얻을 수 있는데, 이는 `scope` 딕셔너리(ASGI 사양의 일부)에 포함되어 있습니다.
여기서는 데모 목적을 위해 메시지에 포함하고 있습니다.
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *}
그 다음 Uvicorn을 다음과 같이 시작하면:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
응답은 다음과 비슷할 것입니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
### FastAPI 앱에서 `root_path` 설정하기 { #setting-the-root-path-in-the-fastapi-app }
또는 `--root-path` 같은 커맨드 라인 옵션(또는 동등한 방법)을 제공할 수 없는 경우, FastAPI 앱을 생성할 때 `root_path` 파라미터를 설정할 수 있습니다:
{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *}
`FastAPI`에 `root_path`를 전달하는 것은 Uvicorn이나 Hypercorn에 커맨드 라인 옵션 `--root-path`를 전달하는 것과 동일합니다.
### `root_path`에 대하여 { #about-root-path }
서버(Uvicorn)는 그 `root_path`를 앱에 전달하는 것 외에는 다른 용도로 사용하지 않는다는 점을 기억하세요.
하지만 브라우저로 <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>에 접속하면 정상 응답을 볼 수 있습니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
따라서 `http://127.0.0.1:8000/api/v1/app`로 접근될 것이라고 기대하지는 않습니다.
Uvicorn은 프록시가 `http://127.0.0.1:8000/app`에서 Uvicorn에 접근할 것을 기대하고, 그 위에 `/api/v1` 접두사를 추가하는 것은 프록시의 책임입니다.
## 제거된 경로 접두사를 가진 프록시에 대하여 { #about-proxies-with-a-stripped-path-prefix }
경로 접두사가 제거되는 프록시는 구성 방법 중 하나일 뿐이라는 점을 기억하세요.
많은 경우 기본값은 프록시가 경로 접두사를 제거하지 않는 방식일 것입니다.
그런 경우(경로 접두사를 제거하지 않는 경우) 프록시는 `https://myawesomeapp.com` 같은 곳에서 리슨하고, 브라우저가 `https://myawesomeapp.com/api/v1/app`로 접근하면, 서버(예: Uvicorn)가 `http://127.0.0.1:8000`에서 리슨하고 있을 때 프록시(경로 접두사를 제거하지 않는)는 동일한 경로로 Uvicorn에 접근합니다: `http://127.0.0.1:8000/api/v1/app`.
## Traefik으로 로컬 테스트하기 { #testing-locally-with-traefik }
<a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>을 사용하면, 경로 접두사가 제거되는 구성을 로컬에서 쉽게 실험할 수 있습니다.
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Traefik 다운로드</a>는 단일 바이너리이며, 압축 파일을 풀고 터미널에서 바로 실행할 수 있습니다.
그 다음 다음 내용을 가진 `traefik.toml` 파일을 생성하세요:
```TOML hl_lines="3"
[entryPoints]
[entryPoints.http]
address = ":9999"
[providers]
[providers.file]
filename = "routes.toml"
```
이는 Traefik이 9999 포트에서 리슨하고, 다른 파일 `routes.toml`을 사용하도록 지시합니다.
/// tip | 팁
표준 HTTP 포트 80 대신 9999 포트를 사용해서, 관리자(`sudo`) 권한으로 실행하지 않아도 되게 했습니다.
///
이제 다른 파일 `routes.toml`을 생성하세요:
```TOML hl_lines="5 12 20"
[http]
[http.middlewares]
[http.middlewares.api-stripprefix.stripPrefix]
prefixes = ["/api/v1"]
[http.routers]
[http.routers.app-http]
entryPoints = ["http"]
service = "app"
rule = "PathPrefix(`/api/v1`)"
middlewares = ["api-stripprefix"]
[http.services]
[http.services.app]
[http.services.app.loadBalancer]
[[http.services.app.loadBalancer.servers]]
url = "http://127.0.0.1:8000"
```
이 파일은 Traefik이 경로 접두사 `/api/v1`을 사용하도록 설정합니다.
그리고 Traefik은 요청을 `http://127.0.0.1:8000`에서 실행 중인 Uvicorn으로 전달합니다.
이제 Traefik을 시작하세요:
<div class="termy">
```console
$ ./traefik --configFile=traefik.toml
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
```
</div>
그리고 `--root-path` 옵션을 사용해 앱을 시작하세요:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### 응답 확인하기 { #check-the-responses }
이제 Uvicorn의 포트로 된 URL인 <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>로 접속하면 정상 응답을 볼 수 있습니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
/// tip | 팁
`http://127.0.0.1:8000/app`로 접근했는데도 `/api/v1`의 `root_path`가 표시되는 것에 주의하세요. 이는 옵션 `--root-path`에서 가져온 값입니다.
///
이제 Traefik의 포트가 포함되고 경로 접두사가 포함된 URL <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>을 여세요.
동일한 응답을 얻습니다:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
하지만 이번에는 프록시가 제공한 접두사 경로 `/api/v1`이 포함된 URL에서의 응답입니다.
물론 여기서의 아이디어는 모두가 프록시를 통해 앱에 접근한다는 것이므로, `/api/v1` 경로 접두사가 있는 버전이 "올바른" 접근입니다.
그리고 경로 접두사가 없는 버전(`http://127.0.0.1:8000/app`)은 Uvicorn이 직접 제공하는 것이며, 오직 _프록시_(Traefik)가 접근하기 위한 용도입니다.
이는 프록시(Traefik)가 경로 접두사를 어떻게 사용하는지, 그리고 서버(Uvicorn)가 옵션 `--root-path`로부터의 `root_path`를 어떻게 사용하는지를 보여줍니다.
### 문서 UI 확인하기 { #check-the-docs-ui }
하지만 재미있는 부분은 여기입니다. ✨
앱에 접근하는 "공식" 방법은 우리가 정의한 경로 접두사를 가진 프록시를 통해서입니다. 따라서 기대하는 대로, URL에 경로 접두사가 없는 상태에서 Uvicorn이 직접 제공하는 docs UI를 시도하면, 프록시를 통해 접근된다고 가정하고 있기 때문에 동작하지 않습니다.
<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>에서 확인할 수 있습니다:
<img src="/img/tutorial/behind-a-proxy/image01.png">
하지만 프록시(포트 `9999`)를 사용해 "공식" URL인 `/api/v1/docs`에서 docs UI에 접근하면, 올바르게 동작합니다! 🎉
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>에서 확인할 수 있습니다:
<img src="/img/tutorial/behind-a-proxy/image02.png">
원하던 그대로입니다. ✔️
이는 FastAPI가 이 `root_path`를 사용해, OpenAPI에서 기본 `server`를 `root_path`가 제공한 URL로 생성하기 때문입니다.
## 추가 서버 { #additional-servers }
/// warning | 경고
이는 더 고급 사용 사례입니다. 건너뛰어도 괜찮습니다.
///
기본적으로 **FastAPI**는 OpenAPI 스키마에서 `root_path`의 URL로 `server`를 생성합니다.
하지만 예를 들어 동일한 docs UI가 스테이징과 프로덕션 환경 모두와 상호작용하도록 하려면, 다른 대안 `servers`를 제공할 수도 있습니다.
사용자 정의 `servers` 리스트를 전달했고 `root_path`(API가 프록시 뒤에 있기 때문)가 있다면, **FastAPI**는 리스트의 맨 앞에 이 `root_path`를 가진 "server"를 삽입합니다.
예:
{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
다음과 같은 OpenAPI 스키마를 생성합니다:
```JSON hl_lines="5-7"
{
"openapi": "3.1.0",
// More stuff here
"servers": [
{
"url": "/api/v1"
},
{
"url": "https://stag.example.com",
"description": "Staging environment"
},
{
"url": "https://prod.example.com",
"description": "Production environment"
}
],
"paths": {
// More stuff here
}
}
```
/// tip | 팁
`root_path`에서 가져온 값인 `/api/v1`의 `url` 값을 가진, 자동 생성된 server에 주목하세요.
///
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>의 docs UI에서는 다음처럼 보입니다:
<img src="/img/tutorial/behind-a-proxy/image03.png">
/// tip | 팁
docs UI는 선택한 server와 상호작용합니다.
///
/// note | 기술 세부사항
OpenAPI 사양에서 `servers` 속성은 선택 사항입니다.
`servers` 파라미터를 지정하지 않고 `root_path`가 `/`와 같다면, 생성된 OpenAPI 스키마의 `servers` 속성은 기본적으로 완전히 생략되며, 이는 `url` 값이 `/`인 단일 server와 동등합니다.
///
### `root_path`에서 자동 server 비활성화하기 { #disable-automatic-server-from-root-path }
**FastAPI**가 `root_path`를 사용한 자동 server를 포함하지 않게 하려면, `root_path_in_servers=False` 파라미터를 사용할 수 있습니다:
{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
그러면 OpenAPI 스키마에 포함되지 않습니다.
## 서브 애플리케이션 마운트하기 { #mounting-a-sub-application }
프록시에서 `root_path`를 사용하면서도, [서브 애플리케이션 - 마운트](sub-applications.md){.internal-link target=_blank}에 설명된 것처럼 서브 애플리케이션을 마운트해야 한다면, 기대하는 대로 일반적으로 수행할 수 있습니다.
FastAPI가 내부적으로 `root_path`를 똑똑하게 사용하므로, 그냥 동작합니다. ✨

View File

@@ -1,95 +0,0 @@
# Dataclasses 사용하기 { #using-dataclasses }
FastAPI는 **Pydantic** 위에 구축되어 있으며, 지금까지는 Pydantic 모델을 사용해 요청과 응답을 선언하는 방법을 보여드렸습니다.
하지만 FastAPI는 <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>도 같은 방식으로 사용하는 것을 지원합니다:
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
이는 **Pydantic** 덕분에 여전히 지원되는데, Pydantic이 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">`dataclasses`에 대한 내부 지원</a>을 제공하기 때문입니다.
따라서 위 코드처럼 Pydantic을 명시적으로 사용하지 않더라도, FastAPI는 Pydantic을 사용해 표준 dataclasses를 Pydantic의 dataclasses 변형으로 변환합니다.
그리고 물론 다음과 같은 기능도 동일하게 지원합니다:
* 데이터 검증
* 데이터 직렬화
* 데이터 문서화 등
이는 Pydantic 모델을 사용할 때와 같은 방식으로 동작합니다. 그리고 실제로도 내부적으로는 Pydantic을 사용해 같은 방식으로 구현됩니다.
/// info | 정보
dataclasses는 Pydantic 모델이 할 수 있는 모든 것을 할 수는 없다는 점을 기억하세요.
그래서 여전히 Pydantic 모델을 사용해야 할 수도 있습니다.
하지만 이미 여러 dataclasses를 가지고 있다면, 이것은 FastAPI로 웹 API를 구동하는 데 그것들을 활용할 수 있는 좋은 방법입니다. 🤓
///
## `response_model`에서 Dataclasses 사용하기 { #dataclasses-in-response-model }
`response_model` 매개변수에서도 `dataclasses`를 사용할 수 있습니다:
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
dataclass는 자동으로 Pydantic dataclass로 변환됩니다.
이렇게 하면 해당 스키마가 API docs 사용자 인터페이스에 표시됩니다:
<img src="/img/tutorial/dataclasses/image01.png">
## 중첩 데이터 구조에서 Dataclasses 사용하기 { #dataclasses-in-nested-data-structures }
`dataclasses`를 다른 타입 애너테이션과 조합해 중첩 데이터 구조를 만들 수도 있습니다.
일부 경우에는 Pydantic 버전의 `dataclasses`를 사용해야 할 수도 있습니다. 예를 들어 자동 생성된 API 문서에서 오류가 발생하는 경우입니다.
그런 경우 표준 `dataclasses`를 드롭인 대체재인 `pydantic.dataclasses`로 간단히 바꾸면 됩니다:
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. 표준 `dataclasses`에서 `field`를 계속 임포트합니다.
2. `pydantic.dataclasses``dataclasses`의 드롭인 대체재입니다.
3. `Author` dataclass에는 `Item` dataclasses의 리스트가 포함됩니다.
4. `Author` dataclass가 `response_model` 매개변수로 사용됩니다.
5. 요청 본문으로 dataclasses와 함께 다른 표준 타입 애너테이션을 사용할 수 있습니다.
이 경우에는 `Item` dataclasses의 리스트입니다.
6. 여기서는 dataclasses 리스트인 `items`를 포함하는 딕셔너리를 반환합니다.
FastAPI는 여전히 데이터를 JSON으로 <abbr title="converting the data to a format that can be transmitted - 데이터를 전송 가능한 형식으로 변환하는 것">serializing</abbr>할 수 있습니다.
7. 여기서 `response_model``Author` dataclasses 리스트에 대한 타입 애너테이션을 사용합니다.
다시 말해, `dataclasses`를 표준 타입 애너테이션과 조합할 수 있습니다.
8. 이 *경로 처리 함수*는 `async def` 대신 일반 `def`를 사용하고 있다는 점에 주목하세요.
언제나처럼 FastAPI에서는 필요에 따라 `def``async def`를 조합해 사용할 수 있습니다.
어떤 것을 언제 사용해야 하는지 다시 확인하고 싶다면, [`async`와 `await`](../async.md#in-a-hurry){.internal-link target=_blank} 문서의 _"급하신가요?"_ 섹션을 확인하세요.
9. 이 *경로 처리 함수*는 dataclasses를(물론 반환할 수도 있지만) 반환하지 않고, 내부 데이터를 담은 딕셔너리들의 리스트를 반환합니다.
FastAPI는 `response_model` 매개변수(dataclasses 포함)를 사용해 응답을 변환합니다.
`dataclasses`는 다른 타입 애너테이션과 매우 다양한 조합으로 결합해 복잡한 데이터 구조를 구성할 수 있습니다.
더 구체적인 내용은 위 코드 내 애너테이션 팁을 확인하세요.
## 더 알아보기 { #learn-more }
`dataclasses`를 다른 Pydantic 모델과 조합하거나, 이를 상속하거나, 여러분의 모델에 포함하는 등의 작업도 할 수 있습니다.
자세한 내용은 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">dataclasses에 관한 Pydantic 문서</a>를 참고하세요.
## 버전 { #version }
이 기능은 FastAPI `0.67.0` 버전부터 사용할 수 있습니다. 🔖

View File

@@ -1,208 +0,0 @@
# SDK 생성하기 { #generating-sdks }
**FastAPI**는 **OpenAPI** 사양을 기반으로 하므로, FastAPI의 API는 많은 도구가 이해할 수 있는 표준 형식으로 설명할 수 있습니다.
덕분에 여러 언어용 클라이언트 라이브러리(<abbr title="Software Development Kits - 소프트웨어 개발 키트">**SDKs**</abbr>), 최신 **문서**, 그리고 코드와 동기화된 **테스트** 또는 **자동화 워크플로**를 쉽게 생성할 수 있습니다.
이 가이드에서는 FastAPI 백엔드용 **TypeScript SDK**를 생성하는 방법을 배웁니다.
## 오픈 소스 SDK 생성기 { #open-source-sdk-generators }
다양하게 활용할 수 있는 옵션으로 <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>가 있으며, **다양한 프로그래밍 언어**를 지원하고 OpenAPI 사양으로부터 SDK를 생성할 수 있습니다.
**TypeScript 클라이언트**의 경우 <a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a>는 TypeScript 생태계에 최적화된 경험을 제공하는 목적에 맞게 설계된 솔루션입니다.
더 많은 SDK 생성기는 <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a>에서 확인할 수 있습니다.
/// tip | 팁
FastAPI는 **OpenAPI 3.1** 사양을 자동으로 생성하므로, 사용하는 도구는 이 버전을 지원해야 합니다.
///
## FastAPI 스폰서의 SDK 생성기 { #sdk-generators-from-fastapi-sponsors }
이 섹션에서는 FastAPI를 후원하는 회사들이 제공하는 **벤처 투자 기반****기업 지원** 솔루션을 소개합니다. 이 제품들은 고품질로 생성된 SDK에 더해 **추가 기능**과 **통합**을 제공합니다.
✨ [**FastAPI 후원하기**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨를 통해, 이 회사들은 프레임워크와 그 **생태계**가 건강하고 **지속 가능**하게 유지되도록 돕습니다.
또한 이들의 후원은 FastAPI **커뮤니티**(여러분)에 대한 강한 헌신을 보여주며, **좋은 서비스**를 제공하는 것뿐 아니라, 견고하고 활발한 프레임워크인 FastAPI를 지원하는 데에도 관심이 있음을 나타냅니다. 🙇
예를 들어 다음을 사용해 볼 수 있습니다:
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
이 중 일부는 오픈 소스이거나 무료 티어를 제공하므로, 비용 부담 없이 사용해 볼 수 있습니다. 다른 상용 SDK 생성기도 있으며 온라인에서 찾을 수 있습니다. 🤓
## TypeScript SDK 만들기 { #create-a-typescript-sdk }
간단한 FastAPI 애플리케이션으로 시작해 보겠습니다:
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
*path operation*에서 요청 페이로드와 응답 페이로드에 사용하는 모델을 `Item`, `ResponseMessage` 모델로 정의하고 있다는 점에 주목하세요.
### API 문서 { #api-docs }
`/docs`로 이동하면, 요청으로 보낼 데이터와 응답으로 받을 데이터에 대한 **스키마(schemas)**가 있는 것을 볼 수 있습니다:
<img src="/img/tutorial/generate-clients/image01.png">
이 스키마는 앱에서 모델로 선언되었기 때문에 볼 수 있습니다.
그 정보는 앱의 **OpenAPI 스키마**에서 사용할 수 있고, 이후 API 문서에 표시됩니다.
OpenAPI에 포함된 모델의 동일한 정보가 **클라이언트 코드 생성**에 사용될 수 있습니다.
### Hey API { #hey-api }
모델이 포함된 FastAPI 앱이 준비되면, Hey API를 사용해 TypeScript 클라이언트를 생성할 수 있습니다. 가장 빠른 방법은 npx를 사용하는 것입니다.
```sh
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
```
이 명령은 `./src/client`에 TypeScript SDK를 생성합니다.
<a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">`@hey-api/openapi-ts` 설치 방법</a>과 <a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">생성된 결과물</a>은 해당 웹사이트에서 확인할 수 있습니다.
### SDK 사용하기 { #using-the-sdk }
이제 클라이언트 코드를 import해서 사용할 수 있습니다. 아래처럼 사용할 수 있으며, 메서드에 대한 자동 완성이 제공되는 것을 확인할 수 있습니다:
<img src="/img/tutorial/generate-clients/image02.png">
보낼 페이로드에 대해서도 자동 완성이 제공됩니다:
<img src="/img/tutorial/generate-clients/image03.png">
/// tip | 팁
`name``price`에 대한 자동 완성은 FastAPI 애플리케이션에서 `Item` 모델에 정의된 내용입니다.
///
전송하는 데이터에 대해 인라인 오류도 표시됩니다:
<img src="/img/tutorial/generate-clients/image04.png">
응답 객체도 자동 완성을 제공합니다:
<img src="/img/tutorial/generate-clients/image05.png">
## 태그가 있는 FastAPI 앱 { #fastapi-app-with-tags }
대부분의 경우 FastAPI 앱은 더 커지고, 서로 다른 *path operations* 그룹을 분리하기 위해 태그를 사용하게 될 가능성이 큽니다.
예를 들어 **items** 섹션과 **users** 섹션이 있고, 이를 태그로 분리할 수 있습니다:
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
### 태그로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-tags }
태그를 사용하는 FastAPI 앱에 대해 클라이언트를 생성하면, 일반적으로 생성된 클라이언트 코드도 태그를 기준으로 분리됩니다.
이렇게 하면 클라이언트 코드에서 항목들이 올바르게 정렬되고 그룹화됩니다:
<img src="/img/tutorial/generate-clients/image06.png">
이 경우 다음이 있습니다:
* `ItemsService`
* `UsersService`
### 클라이언트 메서드 이름 { #client-method-names }
현재 `createItemItemsPost` 같은 생성된 메서드 이름은 그다지 깔끔하지 않습니다:
```TypeScript
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
```
...이는 클라이언트 생성기가 각 *path operation*에 대해 OpenAPI 내부의 **operation ID**를 사용하기 때문입니다.
OpenAPI는 모든 *path operations* 전체에서 operation ID가 각각 유일해야 한다고 요구합니다. 그래서 FastAPI는 operation ID가 유일하도록 **함수 이름**, **경로**, **HTTP method/operation**을 조합해 operation ID를 생성합니다.
하지만 다음에서 이를 개선하는 방법을 보여드리겠습니다. 🤓
## 커스텀 Operation ID와 더 나은 메서드 이름 { #custom-operation-ids-and-better-method-names }
클라이언트에서 **더 단순한 메서드 이름**을 갖도록, operation ID가 **생성되는 방식**을 **수정**할 수 있습니다.
이 경우 operation ID가 다른 방식으로도 **유일**하도록 보장해야 합니다.
예를 들어 각 *path operation*이 태그를 갖도록 한 다음, **태그**와 *path operation* **이름**(함수 이름)을 기반으로 operation ID를 생성할 수 있습니다.
### 유일 ID 생성 함수 커스터마이징 { #custom-generate-unique-id-function }
FastAPI는 각 *path operation*에 대해 **유일 ID**를 사용하며, 이는 **operation ID** 및 요청/응답에 필요한 커스텀 모델 이름에도 사용됩니다.
이 함수를 커스터마이징할 수 있습니다. 이 함수는 `APIRoute`를 받아 문자열을 반환합니다.
예를 들어 아래에서는 첫 번째 태그(대부분 태그는 하나만 있을 것입니다)와 *path operation* 이름(함수 이름)을 사용합니다.
그 다음 이 커스텀 함수를 `generate_unique_id_function` 매개변수로 **FastAPI**에 전달할 수 있습니다:
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
### 커스텀 Operation ID로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-custom-operation-ids }
이제 클라이언트를 다시 생성하면, 개선된 메서드 이름을 확인할 수 있습니다:
<img src="/img/tutorial/generate-clients/image07.png">
보시다시피, 이제 메서드 이름은 태그 다음에 함수 이름이 오며, URL 경로와 HTTP operation의 정보는 포함하지 않습니다.
### 클라이언트 생성기를 위한 OpenAPI 사양 전처리 { #preprocess-the-openapi-specification-for-the-client-generator }
생성된 코드에는 여전히 일부 **중복 정보**가 있습니다.
`ItemsService`(태그에서 가져옴)에 이미 **items**가 포함되어 있어 이 메서드가 items와 관련되어 있음을 알 수 있지만, 메서드 이름에도 태그 이름이 접두사로 붙어 있습니다. 😕
OpenAPI 전반에서는 operation ID가 **유일**하다는 것을 보장하기 위해 이 방식을 유지하고 싶을 수 있습니다.
하지만 생성된 클라이언트에서는, 클라이언트를 생성하기 직전에 OpenAPI operation ID를 **수정**해서 메서드 이름을 더 보기 좋고 **깔끔하게** 만들 수 있습니다.
OpenAPI JSON을 `openapi.json` 파일로 다운로드한 뒤, 아래와 같은 스크립트로 **접두사 태그를 제거**할 수 있습니다:
{* ../../docs_src/generate_clients/tutorial004_py39.py *}
//// tab | Node.js
```Javascript
{!> ../../docs_src/generate_clients/tutorial004.js!}
```
////
이렇게 하면 operation ID가 `items-get_items` 같은 형태에서 `get_items`로 변경되어, 클라이언트 생성기가 더 단순한 메서드 이름을 생성할 수 있습니다.
### 전처리된 OpenAPI로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-the-preprocessed-openapi }
이제 최종 결과가 `openapi.json` 파일에 있으므로, 입력 위치를 업데이트해야 합니다:
```sh
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
```
새 클라이언트를 생성한 후에는 **깔끔한 메서드 이름**을 가지면서도, **자동 완성**, **인라인 오류** 등은 그대로 제공됩니다:
<img src="/img/tutorial/generate-clients/image08.png">
## 장점 { #benefits }
자동으로 생성된 클라이언트를 사용하면 다음에 대해 **자동 완성**을 받을 수 있습니다:
* 메서드
* 본문(body)의 요청 페이로드, 쿼리 파라미터 등
* 응답 페이로드
또한 모든 것에 대해 **인라인 오류**도 확인할 수 있습니다.
그리고 백엔드 코드를 업데이트한 뒤 프론트엔드를 **재생성(regenerate)**하면, 새 *path operations*가 메서드로 추가되고 기존 것은 제거되며, 그 밖의 변경 사항도 생성된 코드에 반영됩니다. 🤓
이는 무언가 변경되면 그 변경이 클라이언트 코드에도 자동으로 **반영**된다는 뜻입니다. 또한 클라이언트를 **빌드(build)**하면 사용된 데이터가 **불일치(mismatch)**할 경우 오류가 발생합니다.
따라서 운영 환경에서 최종 사용자에게 오류가 노출된 뒤 문제를 추적하는 대신, 개발 사이클 초기에 **많은 오류를 매우 빨리 감지**할 수 있습니다. ✨

View File

@@ -1,97 +0,0 @@
# 고급 Middleware { #advanced-middleware }
메인 튜토리얼에서 애플리케이션에 [커스텀 Middleware](../tutorial/middleware.md){.internal-link target=_blank}를 추가하는 방법을 읽었습니다.
그리고 [`CORSMiddleware`로 CORS 처리하기](../tutorial/cors.md){.internal-link target=_blank}도 읽었습니다.
이 섹션에서는 다른 middleware들을 사용하는 방법을 살펴보겠습니다.
## ASGI middleware 추가하기 { #adding-asgi-middlewares }
**FastAPI**는 Starlette를 기반으로 하고 <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr> 사양을 구현하므로, 어떤 ASGI middleware든 사용할 수 있습니다.
ASGI 사양을 따르기만 하면, FastAPI나 Starlette를 위해 만들어진 middleware가 아니어도 동작합니다.
일반적으로 ASGI middleware는 첫 번째 인자로 ASGI 앱을 받도록 기대하는 클래스입니다.
그래서 서드파티 ASGI middleware 문서에서는 아마 다음과 같이 하라고 안내할 것입니다:
```Python
from unicorn import UnicornMiddleware
app = SomeASGIApp()
new_app = UnicornMiddleware(app, some_config="rainbow")
```
하지만 FastAPI(정확히는 Starlette)는 더 간단한 방법을 제공하며, 이를 통해 내부 middleware가 서버 오류를 처리하고 커스텀 예외 핸들러가 올바르게 동작하도록 보장합니다.
이를 위해(그리고 CORS 예제에서처럼) `app.add_middleware()`를 사용합니다.
```Python
from fastapi import FastAPI
from unicorn import UnicornMiddleware
app = FastAPI()
app.add_middleware(UnicornMiddleware, some_config="rainbow")
```
`app.add_middleware()`는 첫 번째 인자로 middleware 클래스를 받고, 그 뒤에는 middleware에 전달할 추가 인자들을 받습니다.
## 통합 middleware { #integrated-middlewares }
**FastAPI**에는 일반적인 사용 사례를 위한 여러 middleware가 포함되어 있습니다. 다음에서 이를 사용하는 방법을 살펴보겠습니다.
/// note | 기술 세부사항
다음 예제에서는 `from starlette.middleware.something import SomethingMiddleware`를 사용해도 됩니다.
**FastAPI**는 개발자 편의를 위해 `fastapi.middleware`에 여러 middleware를 제공하지만, 사용 가능한 대부분의 middleware는 Starlette에서 직접 제공됩니다.
///
## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
들어오는 모든 요청이 `https` 또는 `wss`여야 하도록 강제합니다.
`http` 또는 `ws`로 들어오는 모든 요청은 대신 보안 스킴으로 리디렉션됩니다.
{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *}
## `TrustedHostMiddleware` { #trustedhostmiddleware }
HTTP Host Header 공격을 방어하기 위해, 들어오는 모든 요청에 올바르게 설정된 `Host` 헤더가 있어야 하도록 강제합니다.
{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
다음 인자들을 지원합니다:
* `allowed_hosts` - 호스트명으로 허용할 도메인 이름 목록입니다. `*.example.com` 같은 와일드카드 도메인으로 서브도메인을 매칭하는 것도 지원합니다. 어떤 호스트명이든 허용하려면 `allowed_hosts=["*"]`를 사용하거나 middleware를 생략하세요.
* `www_redirect` - True로 설정하면, 허용된 호스트의 non-www 버전으로 들어오는 요청을 www 버전으로 리디렉션합니다. 기본값은 `True`입니다.
들어오는 요청이 올바르게 검증되지 않으면 `400` 응답이 전송됩니다.
## `GZipMiddleware` { #gzipmiddleware }
`Accept-Encoding` 헤더에 `"gzip"`이 포함된 어떤 요청이든 GZip 응답을 처리합니다.
이 middleware는 일반 응답과 스트리밍 응답을 모두 처리합니다.
{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
다음 인자들을 지원합니다:
* `minimum_size` - 바이트 단위로 지정한 최소 크기보다 작은 응답은 GZip으로 압축하지 않습니다. 기본값은 `500`입니다.
* `compresslevel` - GZip 압축 중에 사용됩니다. 1부터 9까지의 정수입니다. 기본값은 `9`입니다. 값이 낮을수록 압축은 더 빠르지만 파일 크기는 더 커지고, 값이 높을수록 압축은 더 느리지만 파일 크기는 더 작아집니다.
## 다른 middleware { #other-middlewares }
다른 ASGI middleware도 많이 있습니다.
예를 들어:
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn의 `ProxyHeadersMiddleware`</a>
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
사용 가능한 다른 middleware를 보려면 <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette의 Middleware 문서</a>와 <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a>를 확인하세요.

View File

@@ -1,186 +0,0 @@
# OpenAPI 콜백 { #openapi-callbacks }
다른 사람이 만든 *external API*(아마도 당신의 API를 *사용*할 동일한 개발자)가 요청을 트리거하도록 만드는 *경로 처리*를 가진 API를 만들 수 있습니다.
당신의 API 앱이 *external API*를 호출할 때 일어나는 과정을 "callback"이라고 합니다. 외부 개발자가 작성한 소프트웨어가 당신의 API로 요청을 보낸 다음, 당신의 API가 다시 *external API*로 요청을 보내 *되돌려 호출*하기 때문입니다(아마도 같은 개발자가 만든 API일 것입니다).
이 경우, 그 *external API*가 어떤 형태여야 하는지 문서화하고 싶을 수 있습니다. 어떤 *경로 처리*를 가져야 하는지, 어떤 body를 기대하는지, 어떤 응답을 반환해야 하는지 등입니다.
## 콜백이 있는 앱 { #an-app-with-callbacks }
예시로 확인해 보겠습니다.
청구서를 생성할 수 있는 앱을 개발한다고 가정해 보세요.
이 청구서는 `id`, `title`(선택 사항), `customer`, `total`을 갖습니다.
당신의 API 사용자(외부 개발자)는 POST 요청으로 당신의 API에서 청구서를 생성합니다.
그 다음 당신의 API는(가정해 보면):
* 청구서를 외부 개발자의 고객에게 전송합니다.
* 돈을 수금합니다.
* API 사용자(외부 개발자)의 API로 다시 알림을 보냅니다.
* 이는 (당신의 API에서) 그 외부 개발자가 제공하는 어떤 *external API*로 POST 요청을 보내는 방식으로 수행됩니다(이것이 "callback"입니다).
## 일반적인 **FastAPI** 앱 { #the-normal-fastapi-app }
먼저 콜백을 추가하기 전, 일반적인 API 앱이 어떻게 생겼는지 보겠습니다.
`Invoice` body를 받는 *경로 처리*와, 콜백을 위한 URL을 담는 쿼리 파라미터 `callback_url`이 있을 것입니다.
이 부분은 꽤 일반적이며, 대부분의 코드는 이미 익숙할 것입니다:
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
/// tip | 팁
`callback_url` 쿼리 파라미터는 Pydantic의 <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> 타입을 사용합니다.
///
유일하게 새로운 것은 *경로 처리 데코레이터*의 인자로 `callbacks=invoices_callback_router.routes`가 들어간다는 점입니다. 이것이 무엇인지 다음에서 보겠습니다.
## 콜백 문서화하기 { #documenting-the-callback }
실제 콜백 코드는 당신의 API 앱에 크게 의존합니다.
그리고 앱마다 많이 달라질 수 있습니다.
다음처럼 한두 줄의 코드일 수도 있습니다:
```Python
callback_url = "https://example.com/api/v1/invoices/events/"
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
```
하지만 콜백에서 가장 중요한 부분은, 당신의 API 사용자(외부 개발자)가 콜백 요청 body로 *당신의 API*가 보낼 데이터 등에 맞춰 *external API*를 올바르게 구현하도록 보장하는 것입니다.
그래서 다음으로 할 일은, *당신의 API*에서 보내는 콜백을 받기 위해 그 *external API*가 어떤 형태여야 하는지 문서화하는 코드를 추가하는 것입니다.
그 문서는 당신의 API에서 `/docs`의 Swagger UI에 표시되며, 외부 개발자들이 *external API*를 어떻게 만들어야 하는지 알 수 있게 해줍니다.
이 예시는 콜백 자체(한 줄 코드로도 될 수 있음)를 구현하지 않고, 문서화 부분만 구현합니다.
/// tip | 팁
실제 콜백은 단지 HTTP 요청입니다.
콜백을 직접 구현할 때는 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>나 <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a> 같은 것을 사용할 수 있습니다.
///
## 콜백 문서화 코드 작성하기 { #write-the-callback-documentation-code }
이 코드는 앱에서 실행되지 않습니다. 그 *external API*가 어떤 형태여야 하는지 *문서화*하는 데만 필요합니다.
하지만 **FastAPI**로 API의 자동 문서를 쉽게 생성하는 방법은 이미 알고 있습니다.
따라서 그와 같은 지식을 사용해 *external API*가 어떻게 생겨야 하는지 문서화할 것입니다... 즉 외부 API가 구현해야 하는 *경로 처리(들)*(당신의 API가 호출할 것들)을 만들어서 말입니다.
/// tip | 팁
콜백을 문서화하는 코드를 작성할 때는, 자신이 그 *외부 개발자*라고 상상하는 것이 유용할 수 있습니다. 그리고 지금은 *당신의 API*가 아니라 *external API*를 구현하고 있다고 생각해 보세요.
이 관점(외부 개발자의 관점)을 잠시 채택하면, 그 *external API*를 위해 파라미터, body용 Pydantic 모델, 응답 등을 어디에 두어야 하는지가 더 명확하게 느껴질 수 있습니다.
///
### 콜백 `APIRouter` 생성하기 { #create-a-callback-apirouter }
먼저 하나 이상의 콜백을 담을 새 `APIRouter`를 만듭니다.
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
### 콜백 *경로 처리* 생성하기 { #create-the-callback-path-operation }
콜백 *경로 처리*를 만들려면 위에서 만든 동일한 `APIRouter`를 사용합니다.
일반적인 FastAPI *경로 처리*처럼 보일 것입니다:
* 아마도 받아야 할 body 선언이 있을 것입니다(예: `body: InvoiceEvent`).
* 그리고 반환해야 할 응답 선언도 있을 수 있습니다(예: `response_model=InvoiceEventReceived`).
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
일반적인 *경로 처리*와의 주요 차이점은 2가지입니다:
* 실제 코드를 가질 필요가 없습니다. 당신의 앱은 이 코드를 절대 호출하지 않기 때문입니다. 이는 *external API*를 문서화하는 데만 사용됩니다. 따라서 함수는 그냥 `pass`만 있어도 됩니다.
* *path*에는 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a>(자세한 내용은 아래 참고)이 포함될 수 있으며, 이를 통해 *당신의 API*로 보내진 원래 요청의 파라미터와 일부 값을 변수로 사용할 수 있습니다.
### 콜백 경로 표현식 { #the-callback-path-expression }
콜백 *path*는 *당신의 API*로 보내진 원래 요청의 일부를 포함할 수 있는 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a>을 가질 수 있습니다.
이 경우, 다음 `str`입니다:
```Python
"{$callback_url}/invoices/{$request.body.id}"
```
따라서 당신의 API 사용자(외부 개발자)가 *당신의 API*로 다음 요청을 보내고:
```
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
```
JSON body가 다음과 같다면:
```JSON
{
"id": "2expen51ve",
"customer": "Mr. Richie Rich",
"total": "9999"
}
```
그러면 *당신의 API*는 청구서를 처리하고, 나중에 어느 시점에서 `callback_url`(즉 *external API*)로 콜백 요청을 보냅니다:
```
https://www.external.org/events/invoices/2expen51ve
```
그리고 다음과 같은 JSON body를 포함할 것입니다:
```JSON
{
"description": "Payment celebration",
"paid": true
}
```
또한 그 *external API*로부터 다음과 같은 JSON body 응답을 기대합니다:
```JSON
{
"ok": true
}
```
/// tip | 팁
콜백 URL에는 `callback_url` 쿼리 파라미터로 받은 URL(`https://www.external.org/events`)뿐 아니라, JSON body 안의 청구서 `id`(`2expen51ve`)도 함께 사용된다는 점에 주목하세요.
///
### 콜백 라우터 추가하기 { #add-the-callback-router }
이 시점에서, 위에서 만든 콜백 라우터 안에 *콜백 경로 처리(들)*(즉 *external developer*가 *external API*에 구현해야 하는 것들)을 준비했습니다.
이제 *당신의 API 경로 처리 데코레이터*에서 `callbacks` 파라미터를 사용해, 그 콜백 라우터의 `.routes` 속성(실제로는 routes/*경로 처리*의 `list`)을 전달합니다:
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
/// tip | 팁
`callback=`에 라우터 자체(`invoices_callback_router`)를 넘기는 것이 아니라, `invoices_callback_router.routes`처럼 `.routes` 속성을 넘긴다는 점에 주목하세요.
///
### 문서 확인하기 { #check-the-docs }
이제 앱을 실행하고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>로 이동하세요.
*경로 처리*에 대해 "Callbacks" 섹션을 포함한 문서가 표시되며, *external API*가 어떤 형태여야 하는지 확인할 수 있습니다:
<img src="/img/tutorial/openapi-callbacks/image01.png">

View File

@@ -1,55 +0,0 @@
# OpenAPI Webhooks { #openapi-webhooks }
앱이 어떤 데이터와 함께 (요청을 보내서) *사용자의* 앱을 호출할 수 있고, 보통 어떤 **이벤트**를 **알리기** 위해 그렇게 할 수 있다는 것을 API **사용자**에게 알려야 하는 경우가 있습니다.
이는 사용자가 여러분의 API로 요청을 보내는 일반적인 과정 대신, **여러분의 API**(또는 앱)가 **사용자의 시스템**(사용자의 API, 사용자의 앱)으로 **요청을 보낼 수 있다**는 의미입니다.
이를 보통 **webhook**이라고 합니다.
## Webhooks 단계 { #webhooks-steps }
일반적인 과정은, 여러분이 코드에서 보낼 메시지, 즉 **요청 본문(body)**이 무엇인지 **정의**하는 것입니다.
또한 여러분의 앱이 어떤 **시점**에 그 요청(또는 이벤트)을 보낼지도 어떤 방식으로든 정의합니다.
그리고 **사용자**는 (예: 어딘가의 웹 대시보드에서) 여러분의 앱이 그 요청을 보내야 할 **URL**을 어떤 방식으로든 정의합니다.
webhook의 URL을 등록하는 방법과 실제로 그 요청을 보내는 코드에 대한 모든 **로직**은 여러분에게 달려 있습니다. **여러분의 코드**에서 원하는 방식으로 작성하면 됩니다.
## **FastAPI**와 OpenAPI로 webhooks 문서화하기 { #documenting-webhooks-with-fastapi-and-openapi }
**FastAPI**에서는 OpenAPI를 사용해, 이러한 webhook의 이름, 여러분의 앱이 보낼 수 있는 HTTP 작업 타입(예: `POST`, `PUT` 등), 그리고 여러분의 앱이 보낼 요청 **본문(body)**을 정의할 수 있습니다.
이렇게 하면 사용자가 여러분의 **webhook** 요청을 받기 위해 **자신들의 API를 구현**하기가 훨씬 쉬워지고, 경우에 따라서는 자신의 API 코드 일부를 자동 생성할 수도 있습니다.
/// info | 정보
Webhooks는 OpenAPI 3.1.0 이상에서 사용할 수 있으며, FastAPI `0.99.0` 이상에서 지원됩니다.
///
## webhooks가 있는 앱 { #an-app-with-webhooks }
**FastAPI** 애플리케이션을 만들면, *경로 처리*를 정의하는 것과 같은 방식으로(예: `@app.webhooks.post()`), *webhooks*를 정의하는 데 사용할 수 있는 `webhooks` 속성이 있습니다.
{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
여러분이 정의한 webhook은 **OpenAPI** 스키마와 자동 **docs UI**에 포함됩니다.
/// info | 정보
`app.webhooks` 객체는 실제로 `APIRouter`일 뿐이며, 여러 파일로 앱을 구조화할 때 사용하는 것과 동일한 타입입니다.
///
webhook에서는 실제로(`/items/` 같은) *경로(path)*를 선언하지 않는다는 점에 유의하세요. 그곳에 전달하는 텍스트는 webhook의 **식별자**(이벤트 이름)일 뿐입니다. 예를 들어 `@app.webhooks.post("new-subscription")`에서 webhook 이름은 `new-subscription`입니다.
이는 **사용자**가 webhook 요청을 받고 싶은 실제 **URL 경로**를 다른 방식(예: 웹 대시보드)으로 정의할 것이라고 기대하기 때문입니다.
### 문서 확인하기 { #check-the-docs }
이제 앱을 실행하고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>로 이동하세요.
문서에 일반적인 *경로 처리*가 보이고, 이제는 일부 **webhooks**도 함께 보일 것입니다:
<img src="/img/tutorial/openapi-webhooks/image01.png">

View File

@@ -1,172 +0,0 @@
# 경로 처리 고급 구성 { #path-operation-advanced-configuration }
## OpenAPI operationId { #openapi-operationid }
/// warning | 경고
OpenAPI “전문가”가 아니라면, 아마 이 내용은 필요하지 않을 것입니다.
///
매개변수 `operation_id`를 사용해 *경로 처리*에 사용할 OpenAPI `operationId`를 설정할 수 있습니다.
각 작업마다 고유하도록 보장해야 합니다.
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
### *경로 처리 함수* 이름을 operationId로 사용하기 { #using-the-path-operation-function-name-as-the-operationid }
API의 함수 이름을 `operationId`로 사용하고 싶다면, 모든 API를 순회하면서 `APIRoute.name`을 사용해 각 *경로 처리*의 `operation_id`를 덮어쓸 수 있습니다.
모든 *경로 처리*를 추가한 뒤에 수행해야 합니다.
{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
/// tip | 팁
`app.openapi()`를 수동으로 호출한다면, 그 전에 `operationId`들을 업데이트해야 합니다.
///
/// warning | 경고
이렇게 할 경우, 각 *경로 처리 함수*의 이름이 고유하도록 보장해야 합니다.
서로 다른 모듈(파이썬 파일)에 있어도 마찬가지입니다.
///
## OpenAPI에서 제외하기 { #exclude-from-openapi }
생성된 OpenAPI 스키마(따라서 자동 문서화 시스템)에서 특정 *경로 처리*를 제외하려면, `include_in_schema` 매개변수를 `False`로 설정하세요:
{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
## docstring에서 고급 설명 가져오기 { #advanced-description-from-docstring }
OpenAPI에 사용할 *경로 처리 함수*의 docstring 줄 수를 제한할 수 있습니다.
`\f`(이스케이프된 "form feed" 문자)를 추가하면 **FastAPI**는 이 지점에서 OpenAPI에 사용할 출력 내용을 잘라냅니다.
문서에는 표시되지 않지만, Sphinx 같은 다른 도구는 나머지 부분을 사용할 수 있습니다.
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
## 추가 응답 { #additional-responses }
*경로 처리*에 대해 `response_model``status_code`를 선언하는 방법을 이미 보셨을 것입니다.
이는 *경로 처리*의 기본 응답에 대한 메타데이터를 정의합니다.
모델, 상태 코드 등과 함께 추가 응답도 선언할 수 있습니다.
이에 대한 문서의 전체 장이 있으니, [OpenAPI의 추가 응답](additional-responses.md){.internal-link target=_blank}에서 읽어보세요.
## OpenAPI Extra { #openapi-extra }
애플리케이션에서 *경로 처리*를 선언하면, **FastAPI**는 OpenAPI 스키마에 포함될 해당 *경로 처리*의 관련 메타데이터를 자동으로 생성합니다.
/// note | 기술 세부사항
OpenAPI 명세에서는 이를 <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>라고 부릅니다.
///
여기에는 *경로 처리*에 대한 모든 정보가 있으며, 자동 문서를 생성하는 데 사용됩니다.
`tags`, `parameters`, `requestBody`, `responses` 등이 포함됩니다.
*경로 처리* 전용 OpenAPI 스키마는 보통 **FastAPI**가 자동으로 생성하지만, 확장할 수도 있습니다.
/// tip | 팁
이는 저수준 확장 지점입니다.
추가 응답만 선언하면 된다면, 더 편리한 방법은 [OpenAPI의 추가 응답](additional-responses.md){.internal-link target=_blank}을 사용하는 것입니다.
///
`openapi_extra` 매개변수를 사용해 *경로 처리*의 OpenAPI 스키마를 확장할 수 있습니다.
### OpenAPI 확장 { #openapi-extensions }
예를 들어 `openapi_extra`는 [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions)를 선언하는 데 도움이 될 수 있습니다:
{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
자동 API 문서를 열면, 해당 특정 *경로 처리*의 하단에 확장이 표시됩니다.
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
또한 API의 `/openapi.json`에서 결과 OpenAPI를 보면, 특정 *경로 처리*의 일부로 확장이 포함된 것도 확인할 수 있습니다:
```JSON hl_lines="22"
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"x-aperture-labs-portal": "blue"
}
}
}
}
```
### 사용자 정의 OpenAPI *경로 처리* 스키마 { #custom-openapi-path-operation-schema }
`openapi_extra`의 딕셔너리는 *경로 처리*에 대해 자동으로 생성된 OpenAPI 스키마와 깊게 병합됩니다.
따라서 자동 생성된 스키마에 추가 데이터를 더할 수 있습니다.
예를 들어 Pydantic과 함께 FastAPI의 자동 기능을 사용하지 않고, 자체 코드로 요청을 읽고 검증하기로 결정할 수도 있지만, OpenAPI 스키마에는 여전히 그 요청을 정의하고 싶을 수 있습니다.
그럴 때 `openapi_extra`를 사용할 수 있습니다:
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
이 예시에서는 어떤 Pydantic 모델도 선언하지 않았습니다. 사실 요청 바디는 JSON으로 <abbr title="converted from some plain format, like bytes, into Python objects - bytes 같은 일반 형식에서 Python 객체로 변환">parsed</abbr>되지도 않고, `bytes`로 직접 읽습니다. 그리고 함수 `magic_data_reader()`가 어떤 방식으로든 이를 파싱하는 역할을 담당합니다.
그럼에도 불구하고, 요청 바디에 대해 기대하는 스키마를 선언할 수 있습니다.
### 사용자 정의 OpenAPI 콘텐츠 타입 { #custom-openapi-content-type }
같은 트릭을 사용하면, Pydantic 모델을 이용해 JSON Schema를 정의하고 이를 *경로 처리*의 사용자 정의 OpenAPI 스키마 섹션에 포함시킬 수 있습니다.
요청의 데이터 타입이 JSON이 아니더라도 이렇게 할 수 있습니다.
예를 들어 이 애플리케이션에서는 Pydantic 모델에서 JSON Schema를 추출하는 FastAPI의 통합 기능도, JSON에 대한 자동 검증도 사용하지 않습니다. 실제로 요청 콘텐츠 타입을 JSON이 아니라 YAML로 선언합니다:
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
그럼에도 기본 통합 기능을 사용하지 않더라도, YAML로 받고자 하는 데이터에 대한 JSON Schema를 수동으로 생성하기 위해 Pydantic 모델을 여전히 사용합니다.
그 다음 요청을 직접 사용하고, 바디를 `bytes`로 추출합니다. 이는 FastAPI가 요청 페이로드를 JSON으로 파싱하려고 시도조차 하지 않는다는 뜻입니다.
그리고 코드에서 YAML 콘텐츠를 직접 파싱한 뒤, 다시 같은 Pydantic 모델을 사용해 YAML 콘텐츠를 검증합니다:
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
/// tip | 팁
여기서는 같은 Pydantic 모델을 재사용합니다.
하지만 마찬가지로, 다른 방식으로 검증할 수도 있습니다.
///

View File

@@ -1,107 +0,0 @@
# HTTP Basic Auth { #http-basic-auth }
가장 단순한 경우에는 HTTP Basic Auth를 사용할 수 있습니다.
HTTP Basic Auth에서는 애플리케이션이 사용자명과 비밀번호가 들어 있는 헤더를 기대합니다.
이를 받지 못하면 HTTP 401 "Unauthorized" 오류를 반환합니다.
그리고 값이 `Basic`이고 선택적으로 `realm` 파라미터를 포함하는 `WWW-Authenticate` 헤더를 반환합니다.
이는 브라우저가 사용자명과 비밀번호를 입력하는 통합 프롬프트를 표시하도록 알려줍니다.
그다음 사용자명과 비밀번호를 입력하면, 브라우저가 자동으로 해당 값을 헤더에 담아 전송합니다.
## 간단한 HTTP Basic Auth { #simple-http-basic-auth }
* `HTTPBasic``HTTPBasicCredentials`를 임포트합니다.
* `HTTPBasic`을 사용해 "`security` scheme"을 생성합니다.
* *경로 처리*에서 dependency로 해당 `security`를 사용합니다.
* `HTTPBasicCredentials` 타입의 객체를 반환합니다:
* 전송된 `username``password`를 포함합니다.
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *}
처음으로 URL을 열어보면(또는 문서에서 "Execute" 버튼을 클릭하면) 브라우저가 사용자명과 비밀번호를 물어봅니다:
<img src="/img/tutorial/security/image12.png">
## 사용자명 확인하기 { #check-the-username }
더 완전한 예시입니다.
dependency를 사용해 사용자명과 비밀번호가 올바른지 확인하세요.
이를 위해 Python 표준 모듈 <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a>를 사용해 사용자명과 비밀번호를 확인합니다.
`secrets.compare_digest()``bytes` 또는 ASCII 문자(영어에서 사용하는 문자)만 포함한 `str`을 받아야 합니다. 즉, `Sebastián``á` 같은 문자가 있으면 동작하지 않습니다.
이를 처리하기 위해 먼저 `username``password`를 UTF-8로 인코딩해서 `bytes`로 변환합니다.
그런 다음 `secrets.compare_digest()`를 사용해 `credentials.username``"stanleyjobson"`이고 `credentials.password``"swordfish"`인지 확실히 확인할 수 있습니다.
{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *}
이는 다음과 비슷합니다:
```Python
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
# Return some error
...
```
하지만 `secrets.compare_digest()`를 사용하면 "timing attacks"라고 불리는 한 유형의 공격에 대해 안전해집니다.
### Timing Attacks { #timing-attacks }
그렇다면 "timing attack"이란 무엇일까요?
공격자들이 사용자명과 비밀번호를 추측하려고 한다고 가정해봅시다.
그리고 사용자명 `johndoe`, 비밀번호 `love123`으로 요청을 보냅니다.
그러면 애플리케이션의 Python 코드는 대략 다음과 같을 것입니다:
```Python
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
```
하지만 Python이 `johndoe`의 첫 글자 `j``stanleyjobson`의 첫 글자 `s`와 비교하는 순간, 두 문자열이 같지 않다는 것을 이미 알게 되어 `False`를 반환합니다. 이는 “나머지 글자들을 비교하느라 계산을 더 낭비할 필요가 없다”고 판단하기 때문입니다. 그리고 애플리케이션은 "Incorrect username or password"라고 말합니다.
그런데 공격자들이 사용자명을 `stanleyjobsox`, 비밀번호를 `love123`으로 다시 시도합니다.
그러면 애플리케이션 코드는 다음과 비슷하게 동작합니다:
```Python
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
```
Python은 두 문자열이 같지 않다는 것을 알아차리기 전까지 `stanleyjobsox``stanleyjobson` 양쪽의 `stanleyjobso` 전체를 비교해야 합니다. 그래서 "Incorrect username or password"라고 응답하기까지 추가로 몇 마이크로초가 더 걸릴 것입니다.
#### 응답 시간은 공격자에게 도움이 됩니다 { #the-time-to-answer-helps-the-attackers }
이 시점에서 서버가 "Incorrect username or password" 응답을 보내는 데 몇 마이크로초 더 걸렸다는 것을 알아채면, 공격자들은 _무언가_ 맞았다는 것(초기 몇 글자가 맞았다는 것)을 알게 됩니다.
그리고 `johndoe`보다는 `stanleyjobsox`에 더 가까운 값을 시도해야 한다는 것을 알고 다시 시도할 수 있습니다.
#### "전문적인" 공격 { #a-professional-attack }
물론 공격자들은 이런 작업을 손으로 하지 않습니다. 보통 초당 수천~수백만 번 테스트할 수 있는 프로그램을 작성할 것이고, 한 번에 정답 글자 하나씩 추가로 얻어낼 수 있습니다.
그렇게 하면 몇 분 또는 몇 시간 만에, 응답에 걸린 시간만을 이용해(우리 애플리케이션의 “도움”을 받아) 올바른 사용자명과 비밀번호를 추측할 수 있게 됩니다.
#### `secrets.compare_digest()`로 해결하기 { #fix-it-with-secrets-compare-digest }
하지만 우리 코드는 실제로 `secrets.compare_digest()`를 사용하고 있습니다.
요약하면, `stanleyjobsox``stanleyjobson`을 비교하는 데 걸리는 시간은 `johndoe``stanleyjobson`을 비교하는 데 걸리는 시간과 같아집니다. 비밀번호도 마찬가지입니다.
이렇게 애플리케이션 코드에서 `secrets.compare_digest()`를 사용하면, 이러한 범위의 보안 공격 전반에 대해 안전해집니다.
### 오류 반환하기 { #return-the-error }
자격 증명이 올바르지 않다고 판단되면, 상태 코드 401(자격 증명이 제공되지 않았을 때와 동일)을 사용하는 `HTTPException`을 반환하고 브라우저가 로그인 프롬프트를 다시 표시하도록 `WWW-Authenticate` 헤더를 추가하세요:
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *}

View File

@@ -1,19 +0,0 @@
# 고급 보안 { #advanced-security }
## 추가 기능 { #additional-features }
[튜토리얼 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank}에서 다룬 내용 외에도, 보안을 처리하기 위한 몇 가지 추가 기능이 있습니다.
/// tip | 팁
다음 섹션들은 **반드시 "고급"이라고 할 수는 없습니다**.
그리고 사용 사례에 따라, 그중 하나에 해결책이 있을 수도 있습니다.
///
## 먼저 튜토리얼 읽기 { #read-the-tutorial-first }
다음 섹션은 주요 [튜토리얼 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank}을 이미 읽었다고 가정합니다.
모두 동일한 개념을 기반으로 하지만, 몇 가지 추가 기능을 사용할 수 있게 해줍니다.

View File

@@ -1,274 +0,0 @@
# OAuth2 스코프 { #oauth2-scopes }
**FastAPI**에서 OAuth2 스코프를 직접 사용할 수 있으며, 자연스럽게 동작하도록 통합되어 있습니다.
이를 통해 OAuth2 표준을 따르는 더 세밀한 권한 시스템을 OpenAPI 애플리케이션(및 API 문서)에 통합할 수 있습니다.
스코프를 사용하는 OAuth2는 Facebook, Google, GitHub, Microsoft, X(Twitter) 등 많은 대형 인증 제공자가 사용하는 메커니즘입니다. 이들은 이를 통해 사용자와 애플리케이션에 특정 권한을 제공합니다.
Facebook, Google, GitHub, Microsoft, X(Twitter)로 “로그인”할 때마다, 해당 애플리케이션은 스코프가 있는 OAuth2를 사용하고 있습니다.
이 섹션에서는 **FastAPI** 애플리케이션에서 동일한 “스코프가 있는 OAuth2”로 인증(Authentication)과 인가(Authorization)를 관리하는 방법을 확인합니다.
/// warning | 경고
이 섹션은 다소 고급 내용입니다. 이제 막 시작했다면 건너뛰어도 됩니다.
OAuth2 스코프가 반드시 필요한 것은 아니며, 인증과 인가는 원하는 방식으로 처리할 수 있습니다.
하지만 스코프가 있는 OAuth2는 (OpenAPI와 함께) API 및 API 문서에 깔끔하게 통합될 수 있습니다.
그럼에도 불구하고, 해당 스코프(또는 그 밖의 어떤 보안/인가 요구사항이든)는 코드에서 필요에 맞게 직접 강제해야 합니다.
많은 경우 스코프가 있는 OAuth2는 과한 선택일 수 있습니다.
하지만 필요하다고 알고 있거나 궁금하다면 계속 읽어보세요.
///
## OAuth2 스코프와 OpenAPI { #oauth2-scopes-and-openapi }
OAuth2 사양은 “스코프(scopes)”를 공백으로 구분된 문자열 목록으로 정의합니다.
각 문자열의 내용은 어떤 형식이든 될 수 있지만, 공백을 포함하면 안 됩니다.
이 스코프들은 “권한”을 나타냅니다.
OpenAPI(예: API 문서)에서는 “security schemes”를 정의할 수 있습니다.
이 security scheme 중 하나가 OAuth2를 사용한다면, 스코프도 선언하고 사용할 수 있습니다.
각 “스코프”는 (공백 없는) 문자열일 뿐입니다.
보통 다음과 같이 특정 보안 권한을 선언하는 데 사용합니다:
* `users:read` 또는 `users:write` 는 흔한 예시입니다.
* `instagram_basic` 는 Facebook/Instagram에서 사용합니다.
* `https://www.googleapis.com/auth/drive` 는 Google에서 사용합니다.
/// info | 정보
OAuth2에서 “스코프”는 필요한 특정 권한을 선언하는 문자열일 뿐입니다.
`:` 같은 다른 문자가 있거나 URL이어도 상관없습니다.
그런 세부사항은 구현에 따라 달라집니다.
OAuth2 입장에서는 그저 문자열입니다.
///
## 전체 개요 { #global-view }
먼저, 메인 **튜토리얼 - 사용자 가이드**의 [비밀번호(및 해싱)를 사용하는 OAuth2, JWT 토큰을 사용하는 Bearer](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 예제에서 어떤 부분이 바뀌는지 빠르게 살펴보겠습니다. 이제 OAuth2 스코프를 사용합니다:
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
이제 변경 사항을 단계별로 살펴보겠습니다.
## OAuth2 보안 스킴 { #oauth2-security-scheme }
첫 번째 변경 사항은 이제 사용 가능한 스코프 2개(`me`, `items`)로 OAuth2 보안 스킴을 선언한다는 점입니다.
`scopes` 매개변수는 각 스코프를 키로 하고, 설명을 값으로 하는 `dict`를 받습니다:
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
이제 스코프를 선언했기 때문에, 로그인/인가할 때 API 문서에 스코프가 표시됩니다.
그리고 접근을 허용할 스코프를 선택할 수 있게 됩니다: `me``items`.
이는 Facebook, Google, GitHub 등으로 로그인하면서 권한을 부여할 때 사용되는 것과 동일한 메커니즘입니다:
<img src="/img/tutorial/security/image11.png">
## 스코프를 포함한 JWT 토큰 { #jwt-token-with-scopes }
이제 토큰 *경로 처리*를 수정해, 요청된 스코프를 반환하도록 합니다.
여전히 동일한 `OAuth2PasswordRequestForm`을 사용합니다. 여기에는 요청에서 받은 각 스코프를 담는 `scopes` 속성이 있으며, 타입은 `str``list`입니다.
그리고 JWT 토큰의 일부로 스코프를 반환합니다.
/// danger | 위험
단순화를 위해, 여기서는 요청으로 받은 스코프를 그대로 토큰에 추가하고 있습니다.
하지만 실제 애플리케이션에서는 보안을 위해, 사용자가 실제로 가질 수 있는 스코프만(또는 미리 정의한 것만) 추가하도록 반드시 확인해야 합니다.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
## *경로 처리*와 의존성에서 스코프 선언하기 { #declare-scopes-in-path-operations-and-dependencies }
이제 `/users/me/items/`에 대한 *경로 처리*가 스코프 `items`를 요구한다고 선언합니다.
이를 위해 `fastapi`에서 `Security`를 import하여 사용합니다.
`Security`는 (`Depends`처럼) 의존성을 선언하는 데 사용할 수 있지만, `Security`는 스코프(문자열) 목록을 받는 `scopes` 매개변수도 받습니다.
이 경우, 의존성 함수 `get_current_active_user``Security`에 전달합니다(`Depends`로 할 때와 같은 방식).
하지만 스코프 `list`도 함께 전달합니다. 여기서는 스코프 하나만: `items`(더 많을 수도 있습니다).
또한 의존성 함수 `get_current_active_user``Depends`뿐 아니라 `Security`로도 하위 의존성을 선언할 수 있습니다. 자체 하위 의존성 함수(`get_current_user`)와 추가 스코프 요구사항을 선언합니다.
이 경우에는 스코프 `me`를 요구합니다(여러 스코프를 요구할 수도 있습니다).
/// note | 참고
반드시 서로 다른 곳에 서로 다른 스코프를 추가해야 하는 것은 아닙니다.
여기서는 **FastAPI**가 서로 다른 레벨에서 선언된 스코프를 어떻게 처리하는지 보여주기 위해 이렇게 합니다.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
/// info | 기술 세부사항
`Security`는 실제로 `Depends`의 서브클래스이며, 나중에 보게 될 추가 매개변수 하나만 더 있습니다.
하지만 `Depends` 대신 `Security`를 사용하면, **FastAPI**는 보안 스코프를 선언할 수 있음을 알고 내부적으로 이를 사용하며, OpenAPI로 API를 문서화할 수 있습니다.
하지만 `fastapi`에서 `Query`, `Path`, `Depends`, `Security` 등을 import할 때, 이것들은 실제로 특수한 클래스를 반환하는 함수입니다.
///
## `SecurityScopes` 사용하기 { #use-securityscopes }
이제 의존성 `get_current_user`를 업데이트합니다.
이는 위의 의존성들이 사용하는 것입니다.
여기에서 앞서 만든 동일한 OAuth2 스킴을 의존성으로 선언하여 사용합니다: `oauth2_scheme`.
이 의존성 함수 자체에는 스코프 요구사항이 없기 때문에, `oauth2_scheme`와 함께 `Depends`를 사용할 수 있습니다. 보안 스코프를 지정할 필요가 없을 때는 `Security`를 쓸 필요가 없습니다.
또한 `fastapi.security`에서 import한 `SecurityScopes` 타입의 특별한 매개변수를 선언합니다.
`SecurityScopes` 클래스는 `Request`와 비슷합니다(`Request`는 요청 객체를 직접 얻기 위해 사용했습니다).
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
## `scopes` 사용하기 { #use-the-scopes }
매개변수 `security_scopes`의 타입은 `SecurityScopes`입니다.
여기에는 `scopes` 속성이 있으며, 자기 자신과 이 함수를 하위 의존성으로 사용하는 모든 의존성이 요구하는 스코프 전체를 담은 `list`를 가집니다. 즉, 모든 “dependants”... 다소 헷갈릴 수 있는데, 아래에서 다시 설명합니다.
`security_scopes` 객체(`SecurityScopes` 클래스)에는 또한 `scope_str` 속성이 있는데, 공백으로 구분된 단일 문자열로 스코프들을 담고 있습니다(이를 사용할 것입니다).
나중에 여러 지점에서 재사용(`raise`)할 수 있는 `HTTPException`을 생성합니다.
이 예외에는 필요한 스코프(있다면)를 공백으로 구분된 문자열(`scope_str`)로 포함합니다. 그리고 그 스코프 문자열을 `WWW-Authenticate` 헤더에 넣습니다(이는 사양의 일부입니다).
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
## `username`과 데이터 형태 검증하기 { #verify-the-username-and-data-shape }
`username`을 얻었는지 확인하고, 스코프를 추출합니다.
그런 다음 Pydantic 모델로 데이터를 검증합니다(`ValidationError` 예외를 잡습니다). JWT 토큰을 읽거나 Pydantic으로 데이터를 검증하는 과정에서 오류가 나면, 앞에서 만든 `HTTPException`을 raise합니다.
이를 위해 Pydantic 모델 `TokenData`에 새 속성 `scopes`를 추가합니다.
Pydantic으로 데이터를 검증하면, 예를 들어 스코프가 정확히 `str``list`이고 `username``str`인지 등을 보장할 수 있습니다.
예를 들어 `dict`나 다른 형태라면, 나중에 애플리케이션이 어느 시점에 깨지면서 보안 위험이 될 수 있습니다.
또한 해당 username을 가진 사용자가 있는지 확인하고, 없다면 앞에서 만든 동일한 예외를 raise합니다.
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
## `scopes` 검증하기 { #verify-the-scopes }
이제 이 의존성과 모든 dependant( *경로 처리* 포함)가 요구하는 모든 스코프가, 받은 토큰의 스코프에 포함되어 있는지 확인합니다. 그렇지 않으면 `HTTPException`을 raise합니다.
이를 위해, 모든 스코프를 `str`로 담고 있는 `security_scopes.scopes`를 사용합니다.
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
## 의존성 트리와 스코프 { #dependency-tree-and-scopes }
이 의존성 트리와 스코프를 다시 살펴보겠습니다.
`get_current_active_user` 의존성은 `get_current_user`를 하위 의존성으로 가지므로, `get_current_active_user`에서 선언된 스코프 `"me"``get_current_user`에 전달되는 `security_scopes.scopes`의 요구 스코프 목록에 포함됩니다.
*경로 처리* 자체도 스코프 `"items"`를 선언하므로, 이것 또한 `get_current_user`에 전달되는 `security_scopes.scopes` 목록에 포함됩니다.
의존성과 스코프의 계층 구조는 다음과 같습니다:
* *경로 처리* `read_own_items`는:
* 의존성과 함께 요구 스코프 `["items"]`를 가집니다:
* `get_current_active_user`:
* 의존성 함수 `get_current_active_user`는:
* 의존성과 함께 요구 스코프 `["me"]`를 가집니다:
* `get_current_user`:
* 의존성 함수 `get_current_user`는:
* 자체적으로는 요구 스코프가 없습니다.
* `oauth2_scheme`를 사용하는 의존성이 있습니다.
* `SecurityScopes` 타입의 `security_scopes` 매개변수가 있습니다:
*`security_scopes` 매개변수는 위에서 선언된 모든 스코프를 담은 `list``scopes` 속성을 가지므로:
* *경로 처리* `read_own_items`의 경우 `security_scopes.scopes`에는 `["me", "items"]`가 들어갑니다.
* *경로 처리* `read_users_me`의 경우 `security_scopes.scopes`에는 `["me"]`가 들어갑니다. 이는 의존성 `get_current_active_user`에서 선언되기 때문입니다.
* *경로 처리* `read_system_status`의 경우 `security_scopes.scopes`에는 `[]`(없음)가 들어갑니다. `scopes`가 있는 `Security`를 선언하지 않았고, 그 의존성인 `get_current_user``scopes`를 선언하지 않았기 때문입니다.
/// tip | 팁
여기서 중요한 “마법 같은” 점은 `get_current_user`가 각 *경로 처리*마다 검사해야 하는 `scopes` 목록이 달라진다는 것입니다.
이는 특정 *경로 처리*에 대한 의존성 트리에서, 각 *경로 처리*와 각 의존성에 선언된 `scopes`에 따라 달라집니다.
///
## `SecurityScopes`에 대한 추가 설명 { #more-details-about-securityscopes }
`SecurityScopes`는 어느 지점에서든, 그리고 여러 곳에서 사용할 수 있으며, “루트” 의존성에만 있어야 하는 것은 아닙니다.
`SecurityScopes`**해당 특정** *경로 처리*와 **해당 특정** 의존성 트리에 대해, 현재 `Security` 의존성과 모든 dependant에 선언된 보안 스코프를 항상 갖고 있습니다.
`SecurityScopes`에는 dependant가 선언한 모든 스코프가 포함되므로, 중앙의 의존성 함수에서 토큰이 필요한 스코프를 가지고 있는지 검증한 다음, 서로 다른 *경로 처리*에서 서로 다른 스코프 요구사항을 선언할 수 있습니다.
이들은 각 *경로 처리*마다 독립적으로 검사됩니다.
## 확인하기 { #check-it }
API 문서를 열면, 인증하고 인가할 스코프를 지정할 수 있습니다.
<img src="/img/tutorial/security/image11.png">
어떤 스코프도 선택하지 않으면 “인증”은 되지만, `/users/me/` 또는 `/users/me/items/`에 접근하려고 하면 권한이 충분하지 않다는 오류가 발생합니다. `/status/`에는 여전히 접근할 수 있습니다.
그리고 스코프 `me`는 선택했지만 스코프 `items`는 선택하지 않았다면, `/users/me/`에는 접근할 수 있지만 `/users/me/items/`에는 접근할 수 없습니다.
이는 사용자가 애플리케이션에 얼마나 많은 권한을 부여했는지에 따라, 제3자 애플리케이션이 사용자로부터 제공받은 토큰으로 이 *경로 처리*들 중 하나에 접근하려고 할 때 발생하는 상황과 같습니다.
## 제3자 통합에 대해 { #about-third-party-integrations }
이 예제에서는 OAuth2 “password” 플로우를 사용하고 있습니다.
이는 보통 자체 프론트엔드가 있는 우리 애플리케이션에 로그인할 때 적합합니다.
우리가 이를 통제하므로 `username``password`를 받는 것을 신뢰할 수 있기 때문입니다.
하지만 다른 사람들이 연결할 OAuth2 애플리케이션(즉, Facebook, Google, GitHub 등과 동등한 인증 제공자를 만들고 있다면)을 구축한다면, 다른 플로우 중 하나를 사용해야 합니다.
가장 흔한 것은 implicit 플로우입니다.
가장 안전한 것은 code 플로우이지만, 더 많은 단계가 필요해 구현이 더 복잡합니다. 복잡하기 때문에 많은 제공자는 implicit 플로우를 권장하게 됩니다.
/// note | 참고
인증 제공자마다 자신들의 브랜드의 일부로 만들기 위해, 각 플로우를 서로 다른 방식으로 이름 붙이는 경우가 흔합니다.
하지만 결국, 동일한 OAuth2 표준을 구현하고 있는 것입니다.
///
**FastAPI**는 `fastapi.security.oauth2`에 이러한 모든 OAuth2 인증 플로우를 위한 유틸리티를 포함합니다.
## 데코레이터 `dependencies`에서의 `Security` { #security-in-decorator-dependencies }
[경로 처리 데코레이터의 의존성](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}에서 설명한 것처럼 데코레이터의 `dependencies` 매개변수에 `Depends``list`를 정의할 수 있는 것과 같은 방식으로, 거기에서 `scopes`와 함께 `Security`를 사용할 수도 있습니다.

View File

@@ -1,302 +0,0 @@
# 설정과 환경 변수 { #settings-and-environment-variables }
많은 경우 애플리케이션에는 외부 설정이나 구성(예: secret key, 데이터베이스 자격 증명, 이메일 서비스 자격 증명 등)이 필요할 수 있습니다.
이러한 설정 대부분은 데이터베이스 URL처럼 변동 가능(변경될 수 있음)합니다. 그리고 많은 설정은 secret처럼 민감할 수 있습니다.
이 때문에 보통 애플리케이션이 읽어들이는 환경 변수로 이를 제공하는 것이 일반적입니다.
/// tip | 팁
환경 변수를 이해하려면 [환경 변수](../environment-variables.md){.internal-link target=_blank}를 읽어보세요.
///
## 타입과 검증 { #types-and-validation }
이 환경 변수들은 Python 외부에 있으며 다른 프로그램 및 시스템의 나머지 부분(그리고 Linux, Windows, macOS 같은 서로 다른 운영체제와도)과 호환되어야 하므로, 텍스트 문자열만 다룰 수 있습니다.
즉, Python에서 환경 변수로부터 읽어온 어떤 값이든 `str`이 되며, 다른 타입으로의 변환이나 검증은 코드에서 수행해야 합니다.
## Pydantic `Settings` { #pydantic-settings }
다행히 Pydantic은 <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>를 통해 환경 변수에서 오는 이러한 설정을 처리할 수 있는 훌륭한 유틸리티를 제공합니다.
### `pydantic-settings` 설치하기 { #install-pydantic-settings }
먼저 [가상 환경](../virtual-environments.md){.internal-link target=_blank}을 만들고 활성화한 다음, `pydantic-settings` 패키지를 설치하세요:
<div class="termy">
```console
$ pip install pydantic-settings
---> 100%
```
</div>
또는 다음처럼 `all` extras를 설치하면 함께 포함됩니다:
<div class="termy">
```console
$ pip install "fastapi[all]"
---> 100%
```
</div>
### `Settings` 객체 만들기 { #create-the-settings-object }
Pydantic에서 `BaseSettings`를 import하고, Pydantic 모델과 매우 비슷하게 서브클래스를 만드세요.
Pydantic 모델과 같은 방식으로, 타입 어노테이션(그리고 필요하다면 기본값)과 함께 클래스 속성을 선언합니다.
다양한 데이터 타입, `Field()`로 추가 검증 등 Pydantic 모델에서 사용하는 동일한 검증 기능과 도구를 모두 사용할 수 있습니다.
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
/// tip | 팁
빠르게 복사/붙여넣기할 예시가 필요하다면, 이 예시는 사용하지 말고 아래의 마지막 예시를 사용하세요.
///
그 다음, 해당 `Settings` 클래스의 인스턴스(여기서는 `settings` 객체)를 생성하면 Pydantic이 대소문자를 구분하지 않고 환경 변수를 읽습니다. 따라서 대문자 변수 `APP_NAME``app_name` 속성에 대해 읽힙니다.
이후 데이터를 변환하고 검증합니다. 그래서 그 `settings` 객체를 사용할 때는 선언한 타입의 데이터를 갖게 됩니다(예: `items_per_user``int`가 됩니다).
### `settings` 사용하기 { #use-the-settings }
이제 애플리케이션에서 새 `settings` 객체를 사용할 수 있습니다:
{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *}
### 서버 실행하기 { #run-the-server }
다음으로 환경 변수를 통해 구성을 전달하면서 서버를 실행합니다. 예를 들어 다음처럼 `ADMIN_EMAIL``APP_NAME`을 설정할 수 있습니다:
<div class="termy">
```console
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
/// tip | 팁
하나의 명령에 여러 env var를 설정하려면 공백으로 구분하고, 모두 명령 앞에 두세요.
///
그러면 `admin_email` 설정은 `"deadpool@example.com"`으로 설정됩니다.
`app_name``"ChimichangApp"`이 됩니다.
그리고 `items_per_user`는 기본값 `50`을 유지합니다.
## 다른 모듈의 설정 { #settings-in-another-module }
[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}에서 본 것처럼, 설정을 다른 모듈 파일에 넣을 수도 있습니다.
예를 들어 `config.py` 파일을 다음처럼 만들 수 있습니다:
{* ../../docs_src/settings/app01_py39/config.py *}
그리고 `main.py` 파일에서 이를 사용합니다:
{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *}
/// tip | 팁
[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}에서 본 것처럼 `__init__.py` 파일도 필요합니다.
///
## 의존성에서 설정 사용하기 { #settings-in-a-dependency }
어떤 경우에는 어디서나 사용되는 전역 `settings` 객체를 두는 대신, 의존성에서 설정을 제공하는 것이 유용할 수 있습니다.
이는 특히 테스트 중에 유용할 수 있는데, 사용자 정의 설정으로 의존성을 override하기가 매우 쉽기 때문입니다.
### config 파일 { #the-config-file }
이전 예시에서 이어서, `config.py` 파일은 다음과 같을 수 있습니다:
{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *}
이제는 기본 인스턴스 `settings = Settings()`를 생성하지 않는다는 점에 유의하세요.
### 메인 앱 파일 { #the-main-app-file }
이제 새로운 `config.Settings()`를 반환하는 의존성을 생성합니다.
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// tip | 팁
`@lru_cache`는 조금 뒤에 다룹니다.
지금은 `get_settings()`가 일반 함수라고 가정해도 됩니다.
///
그 다음 *경로 처리 함수*에서 이를 의존성으로 요구하고, 필요한 어디서든 사용할 수 있습니다.
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *}
### 설정과 테스트 { #settings-and-testing }
그 다음, `get_settings`에 대한 의존성 override를 만들어 테스트 중에 다른 설정 객체를 제공하기가 매우 쉬워집니다:
{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *}
의존성 override에서는 새 `Settings` 객체를 생성할 때 `admin_email`의 새 값을 설정하고, 그 새 객체를 반환합니다.
그 다음 그것이 사용되는지 테스트할 수 있습니다.
## `.env` 파일 읽기 { #reading-a-env-file }
많이 바뀔 수 있는 설정이 많고, 서로 다른 환경에서 사용한다면, 이를 파일에 넣어 환경 변수인 것처럼 읽는 것이 유용할 수 있습니다.
이 관행은 충분히 흔해서 이름도 있는데, 이러한 환경 변수들은 보통 `.env` 파일에 두며, 그 파일을 "dotenv"라고 부릅니다.
/// tip | 팁
점(`.`)으로 시작하는 파일은 Linux, macOS 같은 Unix 계열 시스템에서 숨김 파일입니다.
하지만 dotenv 파일이 꼭 그 정확한 파일명을 가져야 하는 것은 아닙니다.
///
Pydantic은 외부 라이브러리를 사용해 이런 유형의 파일에서 읽는 기능을 지원합니다. 자세한 내용은 <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>를 참고하세요.
/// tip | 팁
이를 사용하려면 `pip install python-dotenv`가 필요합니다.
///
### `.env` 파일 { #the-env-file }
다음과 같은 `.env` 파일을 둘 수 있습니다:
```bash
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
```
### `.env`에서 설정 읽기 { #read-settings-from-env }
그리고 `config.py`를 다음처럼 업데이트합니다:
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
/// tip | 팁
`model_config` 속성은 Pydantic 설정을 위한 것입니다. 자세한 내용은 <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a>을 참고하세요.
///
여기서는 Pydantic `Settings` 클래스 안에 config `env_file`을 정의하고, 사용하려는 dotenv 파일의 파일명을 값으로 설정합니다.
### `lru_cache`로 `Settings`를 한 번만 생성하기 { #creating-the-settings-only-once-with-lru-cache }
디스크에서 파일을 읽는 것은 보통 비용이 큰(느린) 작업이므로, 각 요청마다 읽기보다는 한 번만 수행하고 동일한 settings 객체를 재사용하는 것이 좋습니다.
하지만 매번 다음을 수행하면:
```Python
Settings()
```
`Settings` 객체가 생성되고, 생성 시점에 `.env` 파일을 다시 읽게 됩니다.
의존성 함수가 단순히 다음과 같다면:
```Python
def get_settings():
return Settings()
```
요청마다 객체를 생성하게 되고, 요청마다 `.env` 파일을 읽게 됩니다. ⚠️
하지만 위에 `@lru_cache` 데코레이터를 사용하고 있으므로, `Settings` 객체는 최초 호출 시 딱 한 번만 생성됩니다. ✔️
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *}
그 다음 요청들에서 의존성으로 `get_settings()`가 다시 호출될 때마다, `get_settings()`의 내부 코드를 실행해서 새 `Settings` 객체를 만드는 대신, 첫 호출에서 반환된 동일한 객체를 계속 반환합니다.
#### `lru_cache` Technical Details { #lru-cache-technical-details }
`@lru_cache`는 데코레이션한 함수가 매번 다시 계산하는 대신, 첫 번째에 반환된 동일한 값을 반환하도록 함수를 수정합니다(즉, 매번 함수 코드를 실행하지 않습니다).
따라서 아래의 함수는 인자 조합마다 한 번씩 실행됩니다. 그리고 각각의 인자 조합에 대해 반환된 값은, 함수가 정확히 같은 인자 조합으로 호출될 때마다 반복해서 사용됩니다.
예를 들어 다음 함수가 있다면:
```Python
@lru_cache
def say_hi(name: str, salutation: str = "Ms."):
return f"Hello {salutation} {name}"
```
프로그램은 다음과 같이 실행될 수 있습니다:
```mermaid
sequenceDiagram
participant code as Code
participant function as say_hi()
participant execute as Execute function
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Camila")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick", salutation="Mr.")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Rick")
function ->> code: return stored result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
```
우리의 의존성 `get_settings()`의 경우, 함수가 어떤 인자도 받지 않으므로 항상 같은 값을 반환합니다.
이렇게 하면 거의 전역 변수처럼 동작합니다. 하지만 의존성 함수를 사용하므로 테스트를 위해 쉽게 override할 수 있습니다.
`@lru_cache`는 Python 표준 라이브러리의 `functools`에 포함되어 있으며, 자세한 내용은 <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">`@lru_cache`에 대한 Python 문서</a>에서 확인할 수 있습니다.
## 정리 { #recap }
Pydantic Settings를 사용하면 Pydantic 모델의 모든 강력한 기능을 활용해 애플리케이션의 설정 또는 구성을 처리할 수 있습니다.
* 의존성을 사용하면 테스트를 단순화할 수 있습니다.
* `.env` 파일을 사용할 수 있습니다.
* `@lru_cache`를 사용하면 각 요청마다 dotenv 파일을 반복해서 읽는 것을 피하면서도, 테스트 중에는 이를 override할 수 있습니다.

View File

@@ -1,485 +0,0 @@
# 대안, 영감, 비교 { #alternatives-inspiration-and-comparisons }
**FastAPI**에 영감을 준 것들, 대안과의 비교, 그리고 그로부터 무엇을 배웠는지에 대한 내용입니다.
## 소개 { #intro }
다른 사람들의 이전 작업이 없었다면 **FastAPI**는 존재하지 않았을 것입니다.
그 전에 만들어진 많은 도구들이 **FastAPI**의 탄생에 영감을 주었습니다.
저는 여러 해 동안 새로운 framework를 만드는 것을 피하고 있었습니다. 먼저 **FastAPI**가 다루는 모든 기능을 여러 서로 다른 framework, plug-in, 도구를 사용해 해결해 보려고 했습니다.
하지만 어느 시점에는, 이전 도구들의 가장 좋은 아이디어를 가져와 가능한 최선의 방식으로 조합하고, 이전에는 존재하지 않았던 언어 기능(Python 3.6+ type hints)을 활용해 이 모든 기능을 제공하는 무언가를 만드는 것 외에는 다른 선택지가 없었습니다.
## 이전 도구들 { #previous-tools }
### <a href="https://www.djangoproject.com/" class="external-link" target="_blank">Django</a> { #django }
가장 인기 있는 Python framework이며 널리 신뢰받고 있습니다. Instagram 같은 시스템을 만드는 데 사용됩니다.
상대적으로 관계형 데이터베이스(예: MySQL 또는 PostgreSQL)와 강하게 결합되어 있어서, NoSQL 데이터베이스(예: Couchbase, MongoDB, Cassandra 등)를 주 저장 엔진으로 사용하는 것은 그리 쉽지 않습니다.
백엔드에서 HTML을 생성하기 위해 만들어졌지, 현대적인 프런트엔드(예: React, Vue.js, Angular)나 다른 시스템(예: <abbr title="Internet of Things - 사물 인터넷">IoT</abbr> 기기)에서 사용되는 API를 만들기 위해 설계된 것은 아닙니다.
### <a href="https://www.django-rest-framework.org/" class="external-link" target="_blank">Django REST Framework</a> { #django-rest-framework }
Django REST framework는 Django를 기반으로 Web API를 구축하기 위한 유연한 toolkit으로 만들어졌고, Django의 API 기능을 개선하기 위한 목적이었습니다.
Mozilla, Red Hat, Eventbrite를 포함해 많은 회사에서 사용합니다.
**자동 API 문서화**의 초기 사례 중 하나였고, 이것이 특히 **FastAPI**를 "찾게 된" 첫 아이디어 중 하나였습니다.
/// note | 참고
Django REST Framework는 Tom Christie가 만들었습니다. **FastAPI**의 기반이 되는 Starlette와 Uvicorn을 만든 사람과 동일합니다.
///
/// check | **FastAPI**에 영감을 준 것
자동 API 문서화 웹 사용자 인터페이스를 제공하기.
///
### <a href="https://flask.palletsprojects.com" class="external-link" target="_blank">Flask</a> { #flask }
Flask는 "microframework"로, Django에 기본으로 포함된 데이터베이스 통합이나 여러 기능들을 포함하지 않습니다.
이 단순함과 유연성 덕분에 NoSQL 데이터베이스를 주 데이터 저장 시스템으로 사용하는 같은 작업이 가능합니다.
매우 단순하기 때문에 비교적 직관적으로 배울 수 있지만, 문서가 어떤 지점에서는 다소 기술적으로 깊어지기도 합니다.
또한 데이터베이스, 사용자 관리, 혹은 Django에 미리 구축되어 있는 다양한 기능들이 꼭 필요하지 않은 다른 애플리케이션에도 흔히 사용됩니다. 물론 이런 기능들 중 다수는 plug-in으로 추가할 수 있습니다.
이런 구성요소의 분리와, 필요한 것만 정확히 덧붙여 확장할 수 있는 "microframework"라는 점은 제가 유지하고 싶었던 핵심 특성이었습니다.
Flask의 단순함을 고려하면 API를 구축하는 데 잘 맞는 것처럼 보였습니다. 다음으로 찾고자 했던 것은 Flask용 "Django REST Framework"였습니다.
/// check | **FastAPI**에 영감을 준 것
micro-framework가 되기. 필요한 도구와 구성요소를 쉽게 조합할 수 있도록 하기.
단순하고 사용하기 쉬운 routing 시스템을 갖기.
///
### <a href="https://requests.readthedocs.io" class="external-link" target="_blank">Requests</a> { #requests }
**FastAPI**는 실제로 **Requests**의 대안이 아닙니다. 둘의 범위는 매우 다릅니다.
실제로 FastAPI 애플리케이션 *내부에서* Requests를 사용하는 경우도 흔합니다.
그럼에도 FastAPI는 Requests로부터 꽤 많은 영감을 얻었습니다.
**Requests**는 (클라이언트로서) API와 *상호작용*하기 위한 라이브러리이고, **FastAPI**는 (서버로서) API를 *구축*하기 위한 라이브러리입니다.
대략 말하면 서로 반대편에 있으며, 서로를 보완합니다.
Requests는 매우 단순하고 직관적인 설계를 가졌고, 합리적인 기본값을 바탕으로 사용하기가 아주 쉽습니다. 동시에 매우 강력하고 커스터마이징도 가능합니다.
그래서 공식 웹사이트에서 말하듯이:
> Requests is one of the most downloaded Python packages of all time
사용 방법은 매우 간단합니다. 예를 들어 `GET` 요청을 하려면 다음처럼 작성합니다:
```Python
response = requests.get("http://example.com/some/url")
```
이에 대응하는 FastAPI의 API *경로 처리*는 다음과 같이 보일 수 있습니다:
```Python hl_lines="1"
@app.get("/some/url")
def read_url():
return {"message": "Hello World"}
```
`requests.get(...)`와 `@app.get(...)`의 유사성을 확인해 보세요.
/// check | **FastAPI**에 영감을 준 것
* 단순하고 직관적인 API를 갖기.
* HTTP method 이름(operations)을 직접, 직관적이고 명확한 방식으로 사용하기.
* 합리적인 기본값을 제공하되, 강력한 커스터마이징을 가능하게 하기.
///
### <a href="https://swagger.io/" class="external-link" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" class="external-link" target="_blank">OpenAPI</a> { #swagger-openapi }
제가 Django REST Framework에서 가장 원했던 주요 기능은 자동 API 문서화였습니다.
그 후 JSON(또는 JSON의 확장인 YAML)을 사용해 API를 문서화하는 표준인 Swagger가 있다는 것을 알게 되었습니다.
그리고 Swagger API를 위한 웹 사용자 인터페이스도 이미 만들어져 있었습니다. 그래서 API에 대한 Swagger 문서를 생성할 수 있다면, 이 웹 사용자 인터페이스를 자동으로 사용할 수 있게 됩니다.
어느 시점에 Swagger는 Linux Foundation으로 넘어가 OpenAPI로 이름이 바뀌었습니다.
그래서 2.0 버전을 이야기할 때는 "Swagger"라고 말하는 것이 일반적이고, 3+ 버전은 "OpenAPI"라고 말하는 것이 일반적입니다.
/// check | **FastAPI**에 영감을 준 것
커스텀 schema 대신, API 사양을 위한 열린 표준을 채택하고 사용하기.
또한 표준 기반의 사용자 인터페이스 도구를 통합하기:
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>
* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>
이 두 가지는 꽤 대중적이고 안정적이기 때문에 선택되었습니다. 하지만 간단히 검색해보면 OpenAPI를 위한 대안 UI가 수십 가지나 있다는 것을 알 수 있습니다(**FastAPI**와 함께 사용할 수 있습니다).
///
### Flask REST framework들 { #flask-rest-frameworks }
Flask REST framework는 여러 개가 있지만, 시간을 들여 조사해 본 결과, 상당수가 중단되었거나 방치되어 있었고, 해결되지 않은 여러 이슈 때문에 적합하지 않은 경우가 많았습니다.
### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow }
API 시스템에 필요한 주요 기능 중 하나는 데이터 "<abbr title="also called marshalling, conversion - 마샬링, 변환이라고도 합니다">serialization</abbr>"입니다. 이는 코드(Python)에서 데이터를 가져와 네트워크로 전송할 수 있는 형태로 변환하는 것을 의미합니다. 예를 들어 데이터베이스의 데이터를 담은 객체를 JSON 객체로 변환하거나, `datetime` 객체를 문자열로 변환하는 등의 작업입니다.
API에 또 하나 크게 필요한 기능은 데이터 검증입니다. 특정 파라미터를 기준으로 데이터가 유효한지 확인하는 것입니다. 예를 들어 어떤 필드가 `int`인지, 임의의 문자열이 아닌지 확인하는 식입니다. 이는 특히 들어오는 데이터에 유용합니다.
데이터 검증 시스템이 없다면, 모든 검사를 코드에서 수동으로 해야 합니다.
이런 기능들을 제공하기 위해 Marshmallow가 만들어졌습니다. 훌륭한 라이브러리이며, 저도 이전에 많이 사용했습니다.
하지만 Python type hints가 존재하기 전에 만들어졌습니다. 그래서 각 <abbr title="the definition of how data should be formed - 데이터가 어떻게 구성되어야 하는지에 대한 정의">schema</abbr>를 정의하려면 Marshmallow가 제공하는 특정 유틸리티와 클래스를 사용해야 합니다.
/// check | **FastAPI**에 영감을 준 것
데이터 타입과 검증을 제공하는 "schema"를 코드로 정의하고, 이를 자동으로 활용하기.
///
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs }
API에 필요한 또 다른 큰 기능은 들어오는 요청에서 데이터를 <abbr title="reading and converting to Python data - 읽어서 Python 데이터로 변환하기">parsing</abbr>하는 것입니다.
Webargs는 Flask를 포함한 여러 framework 위에서 이를 제공하기 위해 만들어진 도구입니다.
내부적으로 Marshmallow를 사용해 데이터 검증을 수행합니다. 그리고 같은 개발자들이 만들었습니다.
아주 훌륭한 도구이며, 저도 **FastAPI**를 만들기 전에 많이 사용했습니다.
/// info | 정보
Webargs는 Marshmallow와 같은 개발자들이 만들었습니다.
///
/// check | **FastAPI**에 영감을 준 것
들어오는 요청 데이터의 자동 검증을 갖기.
///
### <a href="https://apispec.readthedocs.io/en/stable/" class="external-link" target="_blank">APISpec</a> { #apispec }
Marshmallow와 Webargs는 plug-in 형태로 검증, parsing, serialization을 제공합니다.
하지만 문서화는 여전히 부족했습니다. 그래서 APISpec이 만들어졌습니다.
이는 여러 framework를 위한 plug-in이며(Starlette용 plug-in도 있습니다).
작동 방식은, 각 route를 처리하는 함수의 docstring 안에 YAML 형식으로 schema 정의를 작성하고,
그로부터 OpenAPI schema를 생성합니다.
Flask, Starlette, Responder 등에서 이런 방식으로 동작합니다.
하지만 다시, Python 문자열 내부(큰 YAML)에서 micro-syntax를 다루어야 한다는 문제가 있습니다.
에디터가 이를 크게 도와주지 못합니다. 또한 파라미터나 Marshmallow schema를 수정해놓고 YAML docstring도 같이 수정하는 것을 잊어버리면, 생성된 schema는 오래된 상태가 됩니다.
/// info | 정보
APISpec은 Marshmallow와 같은 개발자들이 만들었습니다.
///
/// check | **FastAPI**에 영감을 준 것
API를 위한 열린 표준인 OpenAPI를 지원하기.
///
### <a href="https://flask-apispec.readthedocs.io/en/latest/" class="external-link" target="_blank">Flask-apispec</a> { #flask-apispec }
Flask plug-in으로, Webargs, Marshmallow, APISpec을 묶어줍니다.
Webargs와 Marshmallow의 정보를 사용해 APISpec으로 OpenAPI schema를 자동 생성합니다.
훌륭한 도구인데도 과소평가되어 있습니다. 다른 많은 Flask plug-in보다 훨씬 더 유명해져야 합니다. 문서가 너무 간결하고 추상적이라서 그럴 수도 있습니다.
이 도구는 Python docstring 내부에 YAML(또 다른 문법)을 작성해야 하는 문제를 해결했습니다.
Flask + Flask-apispec + Marshmallow + Webargs 조합은 **FastAPI**를 만들기 전까지 제가 가장 좋아하던 백엔드 stack이었습니다.
이를 사용하면서 여러 Flask full-stack generator가 만들어졌습니다. 이것들이 지금까지 저(그리고 여러 외부 팀)가 사용해 온 주요 stack입니다:
* <a href="https://github.com/tiangolo/full-stack" class="external-link" target="_blank">https://github.com/tiangolo/full-stack</a>
* <a href="https://github.com/tiangolo/full-stack-flask-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchbase</a>
* <a href="https://github.com/tiangolo/full-stack-flask-couchdb" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchdb</a>
그리고 이 동일한 full-stack generator들이 [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}의 기반이 되었습니다.
/// info | 정보
Flask-apispec은 Marshmallow와 같은 개발자들이 만들었습니다.
///
/// check | **FastAPI**에 영감을 준 것
serialization과 validation을 정의하는 동일한 코드로부터 OpenAPI schema를 자동 생성하기.
///
### <a href="https://nestjs.com/" class="external-link" target="_blank">NestJS</a> (그리고 <a href="https://angular.io/" class="external-link" target="_blank">Angular</a>) { #nestjs-and-angular }
이건 Python도 아닙니다. NestJS는 Angular에서 영감을 받은 JavaScript(TypeScript) NodeJS framework입니다.
Flask-apispec으로 할 수 있는 것과 어느 정도 비슷한 것을 달성합니다.
Angular 2에서 영감을 받은 의존성 주입 시스템이 통합되어 있습니다. 제가 아는 다른 의존성 주입 시스템들처럼 "injectable"을 사전에 등록해야 하므로, 장황함과 코드 반복이 늘어납니다.
파라미터가 TypeScript 타입(Python type hints와 유사함)으로 설명되기 때문에 에디터 지원은 꽤 좋습니다.
하지만 TypeScript 데이터는 JavaScript로 컴파일된 뒤에는 보존되지 않기 때문에, 타입에 의존해 검증, serialization, 문서화를 동시에 정의할 수 없습니다. 이 점과 일부 설계 결정 때문에, 검증/serialization/자동 schema 생성을 하려면 여러 곳에 decorator를 추가해야 하며, 결과적으로 매우 장황해집니다.
중첩 모델을 잘 처리하지 못합니다. 즉, 요청의 JSON body가 내부 필드를 가진 JSON 객체이고 그 내부 필드들이 다시 중첩된 JSON 객체인 경우, 제대로 문서화하고 검증할 수 없습니다.
/// check | **FastAPI**에 영감을 준 것
Python 타입을 사용해 뛰어난 에디터 지원을 제공하기.
강력한 의존성 주입 시스템을 갖추기. 코드 반복을 최소화하는 방법을 찾기.
///
### <a href="https://sanic.readthedocs.io/en/latest/" class="external-link" target="_blank">Sanic</a> { #sanic }
`asyncio` 기반의 매우 빠른 Python framework 중 초기 사례였습니다. Flask와 매우 유사하게 만들어졌습니다.
/// note | 기술 세부사항
기본 Python `asyncio` 루프 대신 <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a>를 사용했습니다. 이것이 매우 빠르게 만든 요인입니다.
이는 Uvicorn과 Starlette에 명확히 영감을 주었고, 현재 공개 benchmark에서는 이 둘이 Sanic보다 더 빠릅니다.
///
/// check | **FastAPI**에 영감을 준 것
미친 성능을 낼 수 있는 방법을 찾기.
그래서 **FastAPI**는 Starlette를 기반으로 합니다. Starlette는 사용 가능한 framework 중 가장 빠르기 때문입니다(서드파티 benchmark로 테스트됨).
///
### <a href="https://falconframework.org/" class="external-link" target="_blank">Falcon</a> { #falcon }
Falcon은 또 다른 고성능 Python framework로, 최소한으로 설계되었고 Hug 같은 다른 framework의 기반으로 동작하도록 만들어졌습니다.
함수가 두 개의 파라미터(하나는 "request", 하나는 "response")를 받도록 설계되어 있습니다. 그런 다음 request에서 일부를 "읽고", response에 일부를 "작성"합니다. 이 설계 때문에, 표준 Python type hints를 함수 파라미터로 사용해 요청 파라미터와 body를 선언하는 것이 불가능합니다.
따라서 데이터 검증, serialization, 문서화는 자동으로 되지 않고 코드로 해야 합니다. 또는 Hug처럼 Falcon 위에 framework를 얹어 구현해야 합니다. request 객체 하나와 response 객체 하나를 파라미터로 받는 Falcon의 설계에서 영감을 받은 다른 framework에서도 같은 구분이 나타납니다.
/// check | **FastAPI**에 영감을 준 것
훌륭한 성능을 얻는 방법을 찾기.
Hug(= Falcon 기반)과 함께, 함수에서 `response` 파라미터를 선언하도록 **FastAPI**에 영감을 주었습니다.
다만 FastAPI에서는 선택 사항이며, 주로 헤더, 쿠키, 그리고 대체 status code를 설정하는 데 사용됩니다.
///
### <a href="https://moltenframework.com/" class="external-link" target="_blank">Molten</a> { #molten }
**FastAPI**를 만들기 시작한 초기 단계에서 Molten을 알게 되었고, 꽤 비슷한 아이디어를 갖고 있었습니다:
* Python type hints 기반
* 이 타입으로부터 검증과 문서화 생성
* 의존성 주입 시스템
Pydantic 같은 서드파티 라이브러리를 사용해 데이터 검증/serialization/문서화를 하지 않고 자체 구현을 사용합니다. 그래서 이런 데이터 타입 정의를 쉽게 재사용하기는 어렵습니다.
조금 더 장황한 설정이 필요합니다. 또한 WSGI(ASGI가 아니라) 기반이므로, Uvicorn, Starlette, Sanic 같은 도구가 제공하는 고성능을 활용하도록 설계되지 않았습니다.
의존성 주입 시스템은 의존성을 사전에 등록해야 하고, 선언된 타입을 기반으로 의존성을 해결합니다. 따라서 특정 타입을 제공하는 "component"를 두 개 이상 선언할 수 없습니다.
Route는 한 곳에서 선언하고, 다른 곳에 선언된 함수를 사용합니다(엔드포인트를 처리하는 함수 바로 위에 둘 수 있는 decorator를 사용하는 대신). 이는 Flask(및 Starlette)보다는 Django 방식에 가깝습니다. 코드에서 상대적으로 강하게 결합된 것들을 분리해 놓습니다.
/// check | **FastAPI**에 영감을 준 것
모델 속성의 "default" 값으로 데이터 타입에 대한 추가 검증을 정의하기. 이는 에디터 지원을 개선하며, 이전에는 Pydantic에 없었습니다.
이것은 실제로 Pydantic의 일부를 업데이트하여 같은 검증 선언 스타일을 지원하도록 하는 데 영감을 주었습니다(이 기능은 이제 Pydantic에 이미 포함되어 있습니다).
///
### <a href="https://github.com/hugapi/hug" class="external-link" target="_blank">Hug</a> { #hug }
Hug는 Python type hints를 사용해 API 파라미터 타입을 선언하는 기능을 구현한 초기 framework 중 하나였습니다. 이는 다른 도구들도 같은 방식을 하도록 영감을 준 훌륭한 아이디어였습니다.
표준 Python 타입 대신 커스텀 타입을 선언에 사용했지만, 여전히 큰 진전이었습니다.
또한 전체 API를 JSON으로 선언하는 커스텀 schema를 생성한 초기 framework 중 하나였습니다.
OpenAPI나 JSON Schema 같은 표준을 기반으로 하지 않았기 때문에 Swagger UI 같은 다른 도구와 통합하는 것은 직관적이지 않았습니다. 하지만 역시 매우 혁신적인 아이디어였습니다.
흥미롭고 흔치 않은 기능이 하나 있습니다. 같은 framework로 API뿐 아니라 CLI도 만들 수 있습니다.
동기식 Python 웹 framework의 이전 표준(WSGI) 기반이어서 Websockets와 다른 것들을 처리할 수는 없지만, 성능은 여전히 높습니다.
/// info | 정보
Hug는 Timothy Crosley가 만들었습니다. Python 파일에서 import를 자동으로 정렬하는 훌륭한 도구인 <a href="https://github.com/timothycrosley/isort" class="external-link" target="_blank">`isort`</a>의 제작자이기도 합니다.
///
/// check | **FastAPI**에 영감을 준 아이디어들
Hug는 APIStar의 일부에 영감을 주었고, 저는 APIStar와 함께 Hug를 가장 유망한 도구 중 하나로 보았습니다.
Hug는 Python type hints로 파라미터를 선언하고, API를 정의하는 schema를 자동으로 생성하도록 **FastAPI**에 영감을 주었습니다.
Hug는 헤더와 쿠키를 설정하기 위해 함수에 `response` 파라미터를 선언하도록 **FastAPI**에 영감을 주었습니다.
///
### <a href="https://github.com/encode/apistar" class="external-link" target="_blank">APIStar</a> (<= 0.5) { #apistar-0-5 }
**FastAPI**를 만들기로 결정하기 직전에 **APIStar** 서버를 발견했습니다. 찾고 있던 거의 모든 것을 갖추고 있었고 설계도 훌륭했습니다.
NestJS와 Molten보다 앞서, Python type hints를 사용해 파라미터와 요청을 선언하는 framework 구현을 제가 처음 본 사례들 중 하나였습니다. Hug와 거의 같은 시기에 발견했습니다. 하지만 APIStar는 OpenAPI 표준을 사용했습니다.
여러 위치에서 동일한 type hints를 기반으로 자동 데이터 검증, 데이터 serialization, OpenAPI schema 생성을 제공했습니다.
Body schema 정의는 Pydantic처럼 동일한 Python type hints를 사용하지는 않았고 Marshmallow와 조금 더 비슷해서 에디터 지원은 그만큼 좋지 않았지만, 그래도 APIStar는 당시 사용할 수 있는 최선의 선택지였습니다.
당시 최고의 성능 benchmark를 가졌습니다(Starlette에 의해서만 추월됨).
처음에는 자동 API 문서화 웹 UI가 없었지만, Swagger UI를 추가할 수 있다는 것을 알고 있었습니다.
의존성 주입 시스템도 있었습니다. 위에서 언급한 다른 도구들처럼 component의 사전 등록이 필요했지만, 여전히 훌륭한 기능이었습니다.
보안 통합이 없어서 전체 프로젝트에서 사용해 볼 수는 없었습니다. 그래서 Flask-apispec 기반 full-stack generator로 갖추고 있던 모든 기능을 대체할 수 없었습니다. 그 기능을 추가하는 pull request를 만드는 것이 제 백로그에 있었습니다.
하지만 이후 프로젝트의 초점이 바뀌었습니다.
더 이상 API web framework가 아니게 되었는데, 제작자가 Starlette에 집중해야 했기 때문입니다.
이제 APIStar는 web framework가 아니라 OpenAPI 사양을 검증하기 위한 도구 모음입니다.
/// info | 정보
APIStar는 Tom Christie가 만들었습니다. 다음을 만든 사람과 동일합니다:
* Django REST Framework
* Starlette(**FastAPI**의 기반)
* Uvicorn(Starlette와 **FastAPI**에서 사용)
///
/// check | **FastAPI**에 영감을 준 것
존재하게 만들기.
동일한 Python 타입으로 여러 가지(데이터 검증, serialization, 문서화)를 선언하면서 동시에 뛰어난 에디터 지원을 제공한다는 아이디어는 제가 매우 훌륭하다고 생각했습니다.
그리고 오랫동안 비슷한 framework를 찾아 여러 대안을 테스트한 끝에, APIStar가 그때 이용 가능한 최선의 선택지였습니다.
그 후 APIStar 서버가 더는 존재하지 않게 되고 Starlette가 만들어졌는데, 이는 그런 시스템을 위한 더 새롭고 더 나은 기반이었습니다. 이것이 **FastAPI**를 만들게 된 최종 영감이었습니다.
저는 **FastAPI**를 APIStar의 "정신적 후계자"로 생각합니다. 동시에, 이 모든 이전 도구들에서 배운 것들을 바탕으로 기능, typing 시스템, 그리고 다른 부분들을 개선하고 확장했습니다.
///
## **FastAPI**가 사용하는 것 { #used-by-fastapi }
### <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> { #pydantic }
Pydantic은 Python type hints를 기반으로 데이터 검증, serialization, 문서화(JSON Schema 사용)를 정의하는 라이브러리입니다.
그 덕분에 매우 직관적입니다.
Marshmallow와 비교할 수 있습니다. 다만 benchmark에서 Marshmallow보다 빠릅니다. 그리고 동일한 Python type hints를 기반으로 하므로 에디터 지원도 훌륭합니다.
/// check | **FastAPI**가 이를 사용하는 목적
모든 데이터 검증, 데이터 serialization, 자동 모델 문서화(JSON Schema 기반)를 처리하기.
그 다음 **FastAPI**는 그 JSON Schema 데이터를 가져와 OpenAPI에 포함시키며, 그 외에도 여러 작업을 수행합니다.
///
### <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> { #starlette }
Starlette는 경량 <abbr title="The new standard for building asynchronous Python web applications - 비동기 Python 웹 애플리케이션을 구축하기 위한 새로운 표준">ASGI</abbr> framework/toolkit으로, 고성능 asyncio 서비스를 만들기에 이상적입니다.
매우 단순하고 직관적입니다. 쉽게 확장할 수 있도록 설계되었고, 모듈식 component를 갖습니다.
다음이 포함됩니다:
* 정말 인상적인 성능.
* WebSocket 지원.
* 프로세스 내 백그라운드 작업.
* 시작 및 종료 이벤트.
* HTTPX 기반의 테스트 클라이언트.
* CORS, GZip, Static Files, Streaming responses.
* 세션 및 쿠키 지원.
* 100% 테스트 커버리지.
* 100% 타입 주석이 달린 코드베이스.
* 소수의 필수 의존성.
Starlette는 현재 테스트된 Python framework 중 가장 빠릅니다. 단, framework가 아니라 서버인 Uvicorn이 더 빠릅니다.
Starlette는 웹 microframework의 기본 기능을 모두 제공합니다.
하지만 자동 데이터 검증, serialization, 문서화는 제공하지 않습니다.
그것이 **FastAPI**가 위에 추가하는 핵심 중 하나이며, 모두 Python type hints(Pydantic 사용)를 기반으로 합니다. 여기에 더해 의존성 주입 시스템, 보안 유틸리티, OpenAPI schema 생성 등도 포함됩니다.
/// note | 기술 세부사항
ASGI는 Django 코어 팀 멤버들이 개발 중인 새로운 "표준"입니다. 아직 "Python 표준"(PEP)은 아니지만, 그 방향으로 진행 중입니다.
그럼에도 이미 여러 도구에서 "표준"으로 사용되고 있습니다. 이는 상호운용성을 크게 개선합니다. 예를 들어 Uvicorn을 다른 ASGI 서버(예: Daphne 또는 Hypercorn)로 교체할 수도 있고, `python-socketio` 같은 ASGI 호환 도구를 추가할 수도 있습니다.
///
/// check | **FastAPI**가 이를 사용하는 목적
핵심 웹 부분을 모두 처리하기. 그 위에 기능을 추가하기.
`FastAPI` 클래스 자체는 `Starlette` 클래스를 직접 상속합니다.
따라서 Starlette로 할 수 있는 모든 것은 기본적으로 **FastAPI**로도 직접 할 수 있습니다. 즉, **FastAPI**는 사실상 Starlette에 강력한 기능을 더한 것입니다.
///
### <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a> { #uvicorn }
Uvicorn은 uvloop과 httptools로 구축된 초고속 ASGI 서버입니다.
web framework가 아니라 서버입니다. 예를 들어 경로 기반 routing을 위한 도구는 제공하지 않습니다. 그런 것은 Starlette(또는 **FastAPI**) 같은 framework가 위에서 제공합니다.
Starlette와 **FastAPI**에서 권장하는 서버입니다.
/// check | **FastAPI**가 이를 권장하는 방식
**FastAPI** 애플리케이션을 실행하기 위한 주요 웹 서버.
또한 `--workers` 커맨드라인 옵션을 사용하면 비동기 멀티프로세스 서버로 실행할 수도 있습니다.
자세한 내용은 [배포](deployment/index.md){.internal-link target=_blank} 섹션을 확인하세요.
///
## 벤치마크와 속도 { #benchmarks-and-speed }
Uvicorn, Starlette, FastAPI 사이의 차이를 이해하고 비교하려면 [벤치마크](benchmarks.md){.internal-link target=_blank} 섹션을 확인하세요.

View File

@@ -1,321 +0,0 @@
# 배포 개념 { #deployments-concepts }
**FastAPI** 애플리케이션(사실 어떤 종류의 웹 API든)을 배포할 때는, 여러분이 신경 써야 할 여러 개념이 있습니다. 그리고 이 개념들을 활용하면 **애플리케이션을 배포하기 위한 가장 적절한 방법**을 찾을 수 있습니다.
중요한 개념 몇 가지는 다음과 같습니다:
* 보안 - HTTPS
* 시작 시 실행
* 재시작
* 복제(실행 중인 프로세스 수)
* 메모리
* 시작 전 사전 단계
이것들이 **배포**에 어떤 영향을 주는지 살펴보겠습니다.
결국 최종 목표는 **API 클라이언트에 서비스를 제공**할 때 **보안**을 보장하고, **중단을 피하며**, **컴퓨팅 리소스**(예: 원격 서버/가상 머신)를 가능한 한 효율적으로 사용하는 것입니다. 🚀
여기서 이 **개념들**을 조금 더 설명하겠습니다. 그러면 서로 매우 다른 환경, 심지어 아직 존재하지 않는 **미래**의 환경에서도 API를 어떻게 배포할지 결정하는 데 필요한 **직관**을 얻을 수 있을 것입니다.
이 개념들을 고려하면, 여러분은 **자신의 API**를 배포하기 위한 최선의 방법을 **평가하고 설계**할 수 있습니다.
다음 장들에서는 FastAPI 애플리케이션을 배포하기 위한 더 **구체적인 레시피**를 제공하겠습니다.
하지만 지금은, 이 중요한 **개념적 아이디어**들을 확인해 봅시다. 이 개념들은 다른 어떤 종류의 웹 API에도 동일하게 적용됩니다. 💡
## 보안 - HTTPS { #security-https }
[이전 HTTPS 장](https.md){.internal-link target=_blank}에서 HTTPS가 API에 암호화를 제공하는 방식에 대해 배웠습니다.
또한 HTTPS는 일반적으로 애플리케이션 서버 바깥의 **외부** 컴포넌트인 **TLS Termination Proxy**가 제공한다는 것도 확인했습니다.
그리고 **HTTPS 인증서 갱신**을 담당하는 무언가가 필요합니다. 같은 컴포넌트가 그 역할을 할 수도 있고, 다른 무언가가 담당할 수도 있습니다.
### HTTPS를 위한 도구 예시 { #example-tools-for-https }
TLS Termination Proxy로 사용할 수 있는 도구는 예를 들어 다음과 같습니다:
* Traefik
* 인증서 갱신을 자동으로 처리 ✨
* Caddy
* 인증서 갱신을 자동으로 처리 ✨
* Nginx
* 인증서 갱신을 위해 Certbot 같은 외부 컴포넌트 사용
* HAProxy
* 인증서 갱신을 위해 Certbot 같은 외부 컴포넌트 사용
* Nginx 같은 Ingress Controller를 사용하는 Kubernetes
* 인증서 갱신을 위해 cert-manager 같은 외부 컴포넌트 사용
* 클라우드 제공자가 서비스 일부로 내부적으로 처리(아래를 읽어보세요 👇)
또 다른 선택지는 HTTPS 설정을 포함해 더 많은 일을 대신해주는 **클라우드 서비스**를 사용하는 것입니다. 제약이 있거나 비용이 더 들 수도 있습니다. 하지만 그 경우에는 TLS Termination Proxy를 직접 설정할 필요가 없습니다.
다음 장에서 구체적인 예시를 보여드리겠습니다.
---
다음으로 고려할 개념들은 실제로 여러분의 API를 실행하는 프로그램(예: Uvicorn)과 관련된 내용입니다.
## 프로그램과 프로세스 { #program-and-process }
실행 중인 "**프로세스**"에 대해 많이 이야기하게 될 텐데, 이 말이 무엇을 의미하는지, 그리고 "**프로그램**"이라는 단어와 무엇이 다른지 명확히 해두는 것이 유용합니다.
### 프로그램이란 { #what-is-a-program }
**프로그램**이라는 단어는 보통 여러 가지를 가리키는 데 사용됩니다:
* 여러분이 작성하는 **코드**, 즉 **Python 파일**들
* 운영체제에서 **실행**할 수 있는 **파일**, 예: `python`, `python.exe`, `uvicorn`
* 운영체제에서 **실행 중**인 특정 프로그램으로, CPU를 사용하고 메모리에 내용을 저장합니다. 이것을 **프로세스**라고도 합니다.
### 프로세스란 { #what-is-a-process }
**프로세스**라는 단어는 보통 더 구체적으로, 운영체제에서 실행 중인 것(위 마지막 항목처럼)만을 가리키는 데 사용됩니다:
* 운영체제에서 **실행 중**인 특정 프로그램
* 파일이나 코드를 의미하는 것이 아니라, 운영체제가 **실제로 실행**하고 관리하는 대상을 **구체적으로** 의미합니다.
* 어떤 프로그램이든 어떤 코드든, **실행**될 때만 무언가를 **할 수 있습니다**. 즉, **프로세스가 실행 중**일 때입니다.
* 프로세스는 여러분이, 혹은 운영체제가 **종료**(또는 “kill”)할 수 있습니다. 그러면 실행이 멈추고, 더 이상 **아무것도 할 수 없습니다**.
* 컴퓨터에서 실행 중인 각 애플리케이션 뒤에는 프로세스가 있습니다. 실행 중인 프로그램, 각 창 등도 마찬가지입니다. 그리고 컴퓨터가 켜져 있는 동안 보통 많은 프로세스가 **동시에** 실행됩니다.
* **같은 프로그램**의 **여러 프로세스**가 동시에 실행될 수도 있습니다.
운영체제의 “작업 관리자(task manager)”나 “시스템 모니터(system monitor)”(또는 비슷한 도구)를 확인해 보면, 이런 프로세스가 많이 실행 중인 것을 볼 수 있습니다.
또 예를 들어, 같은 브라우저 프로그램(Firefox, Chrome, Edge 등)을 실행하는 프로세스가 여러 개 있는 것도 보일 가능성이 큽니다. 보통 탭마다 하나의 프로세스를 실행하고, 그 외에도 추가 프로세스 몇 개가 더 있습니다.
<img class="shadow" src="/img/deployment/concepts/image01.png">
---
이제 **프로세스**와 **프로그램**의 차이를 알았으니, 배포에 대한 이야기를 계속해 보겠습니다.
## 시작 시 실행 { #running-on-startup }
대부분의 경우 웹 API를 만들면, 클라이언트가 언제나 접근할 수 있도록 **항상 실행**되고 중단되지 않기를 원합니다. 물론 특정 상황에서만 실행하고 싶은 특별한 이유가 있을 수는 있지만, 대부분은 지속적으로 실행되며 **사용 가능**한 상태이기를 원합니다.
### 원격 서버에서 { #in-a-remote-server }
원격 서버(클라우드 서버, 가상 머신 등)를 설정할 때, 가장 단순한 방법은 로컬 개발 때처럼 수동으로 `fastapi run`(Uvicorn을 사용합니다)이나 비슷한 명령을 실행하는 것입니다.
이 방식은 동작하고, **개발 중에는** 유용합니다.
하지만 서버에 대한 연결이 끊기면, 실행 중인 **프로세스**도 아마 종료될 것입니다.
또 서버가 재시작되면(예: 업데이트 이후, 혹은 클라우드 제공자의 마이그레이션 이후) 여러분은 아마 **알아차리지 못할** 겁니다. 그 결과, 프로세스를 수동으로 다시 시작해야 한다는 사실도 모르게 됩니다. 그러면 API는 그냥 죽은 상태로 남습니다. 😱
### 시작 시 자동 실행 { #run-automatically-on-startup }
일반적으로 서버 프로그램(예: Uvicorn)은 서버가 시작될 때 자동으로 시작되고, **사람의 개입** 없이도 FastAPI 앱을 실행하는 프로세스가 항상 실행 중이도록(예: FastAPI 앱을 실행하는 Uvicorn) 구성하고 싶을 것입니다.
### 별도의 프로그램 { #separate-program }
이를 위해 보통 애플리케이션이 시작 시 실행되도록 보장하는 **별도의 프로그램**을 둡니다. 그리고 많은 경우, 데이터베이스 같은 다른 컴포넌트나 애플리케이션도 함께 실행되도록 보장합니다.
### 시작 시 실행을 위한 도구 예시 { #example-tools-to-run-at-startup }
이 역할을 할 수 있는 도구 예시는 다음과 같습니다:
* Docker
* Kubernetes
* Docker Compose
* Swarm Mode의 Docker
* Systemd
* Supervisor
* 클라우드 제공자가 서비스 일부로 내부적으로 처리
* 기타...
다음 장에서 더 구체적인 예시를 제공하겠습니다.
## 재시작 { #restarts }
애플리케이션이 시작 시 실행되도록 보장하는 것과 비슷하게, 장애가 발생했을 때 **재시작**되도록 보장하고 싶을 것입니다.
### 우리는 실수합니다 { #we-make-mistakes }
사람은 언제나 **실수**합니다. 소프트웨어에는 거의 *항상* 여기저기에 숨은 **버그**가 있습니다. 🐛
그리고 개발자는 버그를 발견하고 새로운 기능을 구현하면서 코드를 계속 개선합니다(새로운 버그도 추가할 수 있겠죠 😅).
### 작은 오류는 자동으로 처리됨 { #small-errors-automatically-handled }
FastAPI로 웹 API를 만들 때 코드에 오류가 있으면, FastAPI는 보통 그 오류를 발생시킨 단일 요청 안에만 문제를 가둡니다. 🛡
클라이언트는 해당 요청에 대해 **500 Internal Server Error**를 받지만, 애플리케이션은 완전히 크래시하지 않고 다음 요청부터는 계속 동작합니다.
### 더 큰 오류 - 크래시 { #bigger-errors-crashes }
그럼에도 불구하고, 우리가 작성한 코드가 **전체 애플리케이션을 크래시**시켜 Uvicorn과 Python 자체가 종료되는 경우가 있을 수 있습니다. 💥
그래도 한 군데 오류 때문에 애플리케이션이 죽은 채로 남아 있기를 바라지는 않을 것입니다. 망가진 경로 처리를 제외한 나머지 *경로 처리*라도 **계속 실행**되기를 원할 가능성이 큽니다.
### 크래시 후 재시작 { #restart-after-crash }
하지만 실행 중인 **프로세스**가 크래시하는 정말 심각한 오류의 경우에는, 적어도 몇 번은 프로세스를 **재시작**하도록 담당하는 외부 컴포넌트가 필요합니다...
/// tip | 팁
...다만 애플리케이션 전체가 **즉시 계속 크래시**한다면, 무한히 재시작하는 것은 아마 의미가 없을 것입니다. 그런 경우에는 개발 중에, 또는 최소한 배포 직후에 알아차릴 가능성이 큽니다.
그러니 여기서는, 특정한 경우에만 전체가 크래시할 수 있고 **미래**에도 그럴 수 있으며, 그래도 재시작하는 것이 의미 있는 주요 사례에 집중해 봅시다.
///
애플리케이션을 재시작하는 역할은 **외부 컴포넌트**가 맡는 편이 보통 좋습니다. 그 시점에는 Uvicorn과 Python을 포함한 애플리케이션이 이미 크래시했기 때문에, 같은 앱의 같은 코드 안에서 이를 해결할 방법이 없기 때문입니다.
### 자동 재시작을 위한 도구 예시 { #example-tools-to-restart-automatically }
대부분의 경우 **시작 시 실행**에 사용한 도구가 자동 **재시작**도 함께 처리합니다.
예를 들어 다음이 가능합니다:
* Docker
* Kubernetes
* Docker Compose
* Swarm Mode의 Docker
* Systemd
* Supervisor
* 클라우드 제공자가 서비스 일부로 내부적으로 처리
* 기타...
## 복제 - 프로세스와 메모리 { #replication-processes-and-memory }
FastAPI 애플리케이션은 Uvicorn을 실행하는 `fastapi` 명령 같은 서버 프로그램을 사용하면, **하나의 프로세스**로 실행하더라도 여러 클라이언트를 동시에 처리할 수 있습니다.
하지만 많은 경우, 여러 워커 프로세스를 동시에 실행하고 싶을 것입니다.
### 여러 프로세스 - 워커 { #multiple-processes-workers }
단일 프로세스가 처리할 수 있는 것보다 클라이언트가 더 많고(예: 가상 머신이 그리 크지 않을 때), 서버 CPU에 **여러 코어**가 있다면, 같은 애플리케이션을 실행하는 **여러 프로세스**를 동시에 띄우고 요청을 분산시킬 수 있습니다.
같은 API 프로그램을 **여러 프로세스**로 실행할 때, 이 프로세스들을 보통 **workers**라고 부릅니다.
### 워커 프로세스와 포트 { #worker-processes-and-ports }
[HTTPS에 대한 문서](https.md){.internal-link target=_blank}에서, 서버에서 하나의 포트와 IP 주소 조합에는 하나의 프로세스만 리스닝할 수 있다는 것을 기억하시나요?
이것은 여전히 사실입니다.
따라서 **여러 프로세스**를 동시에 실행하려면, 먼저 **포트에서 리스닝하는 단일 프로세스**가 있어야 하고, 그 프로세스가 어떤 방식으로든 각 워커 프로세스로 통신을 전달해야 합니다.
### 프로세스당 메모리 { #memory-per-process }
이제 프로그램이 메모리에 무언가를 로드한다고 해봅시다. 예를 들어 머신러닝 모델을 변수에 올리거나 큰 파일 내용을 변수에 올리는 경우입니다. 이런 것들은 서버의 **메모리(RAM)**를 어느 정도 사용합니다.
그리고 여러 프로세스는 보통 **메모리를 공유하지 않습니다**. 즉, 각 실행 중인 프로세스는 자체 변수와 메모리를 갖습니다. 코드에서 메모리를 많이 사용한다면, **각 프로세스**가 그만큼의 메모리를 사용하게 됩니다.
### 서버 메모리 { #server-memory }
예를 들어 코드가 크기 **1 GB**의 머신러닝 모델을 로드한다고 해봅시다. API를 프로세스 하나로 실행하면 RAM을 최소 1GB 사용합니다. 그리고 **4개 프로세스**(워커 4개)를 시작하면 각각 1GB RAM을 사용합니다. 즉 총 **4 GB RAM**을 사용합니다.
그런데 원격 서버나 가상 머신의 RAM이 3GB뿐이라면, 4GB를 넘게 로드하려고 할 때 문제가 생깁니다. 🚨
### 여러 프로세스 - 예시 { #multiple-processes-an-example }
이 예시에서는 **Manager Process**가 두 개의 **Worker Processes**를 시작하고 제어합니다.
이 Manager Process는 아마 IP의 **포트**에서 리스닝하는 역할을 합니다. 그리고 모든 통신을 워커 프로세스로 전달합니다.
워커 프로세스들이 실제로 애플리케이션을 실행하며, **요청**을 받아 **응답**을 반환하는 주요 연산을 수행하고, RAM에 변수로 로드한 모든 내용을 담습니다.
<img src="/img/deployment/concepts/process-ram.drawio.svg">
그리고 물론 같은 머신에는 애플리케이션 외에도 **다른 프로세스**들이 실행 중일 가능성이 큽니다.
흥미로운 점은 각 프로세스의 **CPU 사용률**은 시간에 따라 크게 **변동**할 수 있지만, **메모리(RAM)**는 보통 대체로 **안정적**으로 유지된다는 것입니다.
매번 비슷한 양의 연산을 수행하는 API이고 클라이언트가 많다면, **CPU 사용률**도 (급격히 오르내리기보다는) *안정적일* 가능성이 큽니다.
### 복제 도구와 전략 예시 { #examples-of-replication-tools-and-strategies }
이를 달성하는 접근 방식은 여러 가지가 있을 수 있으며, 다음 장들에서 Docker와 컨테이너를 설명할 때 구체적인 전략을 더 알려드리겠습니다.
고려해야 할 주요 제약은 **공개 IP**의 **포트**를 처리하는 **단일** 컴포넌트가 있어야 한다는 점입니다. 그리고 그 컴포넌트는 복제된 **프로세스/워커**로 통신을 **전달**할 방법이 있어야 합니다.
가능한 조합과 전략 몇 가지는 다음과 같습니다:
* `--workers` 옵션을 사용한 **Uvicorn**
* 하나의 Uvicorn **프로세스 매니저**가 **IP**와 **포트**에서 리스닝하고, **여러 Uvicorn 워커 프로세스**를 시작합니다.
* **Kubernetes** 및 기타 분산 **컨테이너 시스템**
* **Kubernetes** 레이어의 무언가가 **IP**와 **포트**에서 리스닝합니다. 그리고 **여러 컨테이너**를 두어 복제하며, 각 컨테이너에는 **하나의 Uvicorn 프로세스**가 실행됩니다.
* 이를 대신 처리해주는 **클라우드 서비스**
* 클라우드 서비스가 **복제를 대신 처리**해줄 가능성이 큽니다. 실행할 **프로세스**나 사용할 **컨테이너 이미지**를 정의하게 해줄 수도 있지만, 어떤 경우든 대개 **단일 Uvicorn 프로세스**를 기준으로 하고, 클라우드 서비스가 이를 복제하는 역할을 맡습니다.
/// tip | 팁
**컨테이너**, Docker, Kubernetes에 대한 일부 내용이 아직은 잘 이해되지 않아도 괜찮습니다.
다음 장에서 컨테이너 이미지, Docker, Kubernetes 등을 더 설명하겠습니다: [컨테이너에서 FastAPI - Docker](docker.md){.internal-link target=_blank}.
///
## 시작 전 사전 단계 { #previous-steps-before-starting }
애플리케이션을 **시작하기 전에** 어떤 단계를 수행하고 싶은 경우가 많습니다.
예를 들어 **데이터베이스 마이그레이션**을 실행하고 싶을 수 있습니다.
하지만 대부분의 경우, 이런 단계는 **한 번만** 수행하고 싶을 것입니다.
그래서 애플리케이션을 시작하기 전에 그 **사전 단계**를 수행할 **단일 프로세스**를 두고 싶을 것입니다.
또한 이후에 애플리케이션 자체를 **여러 프로세스**(여러 워커)로 시작하더라도, 사전 단계를 수행하는 프로세스는 *반드시* 하나만 실행되도록 해야 합니다. 만약 사전 단계를 **여러 프로세스**가 수행하면, **병렬로** 실행하면서 작업이 **중복**될 수 있습니다. 그리고 데이터베이스 마이그레이션처럼 민감한 작업이라면 서로 충돌을 일으킬 수 있습니다.
물론 사전 단계를 여러 번 실행해도 문제가 없는 경우도 있습니다. 그런 경우에는 처리하기가 훨씬 쉽습니다.
/// tip | 팁
또한 설정에 따라, 어떤 경우에는 애플리케이션을 시작하기 전에 **사전 단계가 전혀 필요 없을** 수도 있다는 점을 기억하세요.
그런 경우에는 이런 것들을 전혀 걱정할 필요가 없습니다. 🤷
///
### 사전 단계 전략 예시 { #examples-of-previous-steps-strategies }
이는 여러분이 **시스템을 배포하는 방식**에 크게 좌우되며, 프로그램을 시작하는 방식, 재시작 처리 방식 등과도 연결되어 있을 가능성이 큽니다.
가능한 아이디어는 다음과 같습니다:
* 앱 컨테이너보다 먼저 실행되는 Kubernetes의 “Init Container”
* 사전 단계를 실행한 다음 애플리케이션을 시작하는 bash 스크립트
* 이 bash 스크립트를 시작/재시작하고, 오류를 감지하는 등의 방법도 여전히 필요합니다.
/// tip | 팁
컨테이너로 이를 처리하는 더 구체적인 예시는 다음 장에서 제공하겠습니다: [컨테이너에서 FastAPI - Docker](docker.md){.internal-link target=_blank}.
///
## 리소스 활용 { #resource-utilization }
서버는 여러분이 프로그램으로 소비하거나 **활용(utilize)**할 수 있는 **리소스**입니다. CPU의 계산 시간과 사용 가능한 RAM 메모리가 대표적입니다.
시스템 리소스를 얼마나 소비/활용하고 싶으신가요? “많지 않게”라고 생각하기 쉽지만, 실제로는 **크래시하지 않는 선에서 가능한 한 많이** 사용하고 싶을 가능성이 큽니다.
서버 3대를 비용을 내고 쓰고 있는데 RAM과 CPU를 조금만 사용한다면, 아마 **돈을 낭비**하고 💸, **서버 전력도 낭비**하고 🌎, 기타 등등이 될 수 있습니다.
그 경우에는 서버를 2대만 두고, 각 서버의 리소스(CPU, 메모리, 디스크, 네트워크 대역폭 등)를 더 높은 비율로 사용하는 것이 더 나을 수 있습니다.
반대로 서버 2대를 두고 CPU와 RAM을 **100%** 사용하고 있다면, 어느 시점에 프로세스 하나가 더 많은 메모리를 요청하게 되고, 서버는 디스크를 “메모리”처럼 사용해야 할 수도 있습니다(수천 배 느릴 수 있습니다). 또는 심지어 **크래시**할 수도 있습니다. 혹은 어떤 프로세스가 계산을 해야 하는데 CPU가 다시 비워질 때까지 기다려야 할 수도 있습니다.
이 경우에는 **서버 한 대를 추가**로 확보하고 일부 프로세스를 그쪽에서 실행해, 모두가 **충분한 RAM과 CPU 시간**을 갖도록 하는 편이 더 낫습니다.
또 어떤 이유로 API 사용량이 **급증(spike)**할 가능성도 있습니다. 바이럴이 되었거나, 다른 서비스나 봇이 사용하기 시작했을 수도 있습니다. 그런 경우를 대비해 추가 리소스를 확보해두고 싶을 수 있습니다.
리소스 활용률 목표로 **임의의 수치**를 정할 수 있습니다. 예를 들어 **50%에서 90% 사이**처럼요. 요점은, 이런 것들이 배포를 조정할 때 측정하고 튜닝하는 주요 지표가 될 가능성이 크다는 것입니다.
`htop` 같은 간단한 도구로 서버의 CPU와 RAM 사용량, 또는 각 프로세스별 사용량을 볼 수 있습니다. 혹은 서버 여러 대에 분산될 수도 있는 더 복잡한 모니터링 도구를 사용할 수도 있습니다.
## 요약 { #recap }
여기까지 애플리케이션 배포 방식을 결정할 때 염두에 두어야 할 주요 개념들을 읽었습니다:
* 보안 - HTTPS
* 시작 시 실행
* 재시작
* 복제(실행 중인 프로세스 수)
* 메모리
* 시작 전 사전 단계
이 아이디어들을 이해하고 적용하는 방법을 알면, 배포를 구성하고 조정할 때 필요한 직관을 얻는 데 도움이 될 것입니다. 🤓
다음 섹션에서는 따라 할 수 있는 가능한 전략의 더 구체적인 예시를 제공하겠습니다. 🚀

View File

@@ -1,65 +0,0 @@
# FastAPI Cloud { #fastapi-cloud }
**한 번의 명령**으로 FastAPI 앱을 <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>에 배포할 수 있습니다. 아직이라면 대기자 명단에 등록해 보세요. 🚀
## 로그인하기 { #login }
먼저 **FastAPI Cloud** 계정이 이미 있는지 확인하세요(대기자 명단에서 초대해 드렸을 거예요 😉).
그다음 로그인합니다:
<div class="termy">
```console
$ fastapi login
You are logged in to FastAPI Cloud 🚀
```
</div>
## 배포하기 { #deploy }
이제 **한 번의 명령**으로 앱을 배포합니다:
<div class="termy">
```console
$ fastapi deploy
Deploying to FastAPI Cloud...
✅ Deployment successful!
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
```
</div>
이게 전부입니다! 이제 해당 URL에서 앱에 접근할 수 있습니다. ✨
## FastAPI Cloud 소개 { #about-fastapi-cloud }
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**는 **FastAPI**를 만든 동일한 저자와 팀이 구축했습니다.
최소한의 노력으로 API를 **구축**, **배포**, **접근**하는 과정을 간소화합니다.
FastAPI로 앱을 만들 때의 동일한 **개발자 경험**을, 클라우드에 **배포**할 때도 제공합니다. 🎉
또한 앱을 배포할 때 보통 필요한 대부분의 것들도 처리해 줍니다. 예를 들면:
* HTTPS
* 요청을 기반으로 자동 스케일링하는 복제(Replication)
*
FastAPI Cloud는 *FastAPI and friends* 오픈 소스 프로젝트의 주요 스폰서이자 자금 지원 제공자입니다. ✨
## 다른 클라우드 제공업체에 배포하기 { #deploy-to-other-cloud-providers }
FastAPI는 오픈 소스이며 표준을 기반으로 합니다. 원하는 어떤 클라우드 제공업체에도 FastAPI 앱을 배포할 수 있습니다.
해당 클라우드 제공업체의 가이드를 따라 FastAPI 앱을 배포하세요. 🤓
## 자체 서버에 배포하기 { #deploy-your-own-server }
또한 이 **Deployment** 가이드에서 이후에 모든 세부사항을 알려드릴 거예요. 그래서 무슨 일이 일어나고 있는지, 무엇이 필요하며, 본인의 서버를 포함해 직접 FastAPI 앱을 어떻게 배포하는지까지 이해할 수 있게 될 것입니다. 🤓

View File

@@ -1,231 +0,0 @@
# HTTPS 알아보기 { #about-https }
HTTPS는 그냥 “켜져 있거나” 아니면 “꺼져 있는” 것이라고 생각하기 쉽습니다.
하지만 실제로는 훨씬 더 복잡합니다.
/// tip | 팁
바쁘거나 별로 신경 쓰고 싶지 않다면, 다음 섹션에서 다양한 기법으로 모든 것을 설정하는 단계별 안내를 계속 보세요.
///
소비자 관점에서 **HTTPS의 기본을 배우려면** <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>를 확인하세요.
이제 **개발자 관점**에서 HTTPS를 생각할 때 염두에 두어야 할 여러 가지가 있습니다:
* HTTPS를 사용하려면, **서버**가 **제3자**가 발급한 **"인증서(certificates)"**를 **보유**해야 합니다.
* 이 인증서는 실제로 제3자가 “생성”해 주는 것이고, 서버가 만드는 것이 아니라 제3자로부터 **발급/획득**하는 것입니다.
* 인증서에는 **유효 기간**이 있습니다.
* 즉, **만료**됩니다.
* 그리고 나면 제3자로부터 다시 **갱신**해서 **재발급/재획득**해야 합니다.
* 연결의 암호화는 **TCP 레벨**에서 일어납니다.
* 이는 **HTTP보다 한 계층 아래**입니다.
* 따라서 **인증서와 암호화** 처리는 **HTTP 이전**에 수행됩니다.
* **TCP는 "도메인"을 모릅니다.** IP 주소만 압니다.
* 어떤 **특정 도메인**을 요청했는지에 대한 정보는 **HTTP 데이터**에 들어 있습니다.
* **HTTPS 인증서**는 특정 **도메인**을 “인증”하지만, 프로토콜과 암호화는 TCP 레벨에서 일어나며, 어떤 도메인을 다루는지 **알기 전에** 처리됩니다.
* **기본적으로** 이는 IP 주소 하나당 **HTTPS 인증서 하나만** 둘 수 있다는 뜻입니다.
* 서버가 아무리 크든, 그 위에 올린 각 애플리케이션이 아무리 작든 상관없습니다.
* 하지만 이에 대한 **해결책**이 있습니다.
* **TLS** 프로토콜(HTTP 이전, TCP 레벨에서 암호화를 처리하는 것)에 대한 **확장** 중에 **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Server Name Indication - 서버 이름 표시">SNI</abbr></a>**라는 것이 있습니다.
* 이 SNI 확장을 사용하면, 단일 서버(**단일 IP 주소**)에서 **여러 HTTPS 인증서**를 사용하고 **여러 HTTPS 도메인/애플리케이션**을 제공할 수 있습니다.
* 이를 위해서는 서버에서 **공개 IP 주소**로 리스닝하는 **하나의** 컴포넌트(프로그램)가 서버에 있는 **모든 HTTPS 인증서**에 접근할 수 있어야 합니다.
* 보안 연결을 얻은 **이후에도**, 통신 프로토콜 자체는 **여전히 HTTP**입니다.
* **HTTP 프로토콜**로 전송되더라도, 내용은 **암호화**되어 있습니다.
일반적으로 서버(머신, 호스트 등)에는 **프로그램/HTTP 서버 하나**를 실행해 **HTTPS 관련 부분 전체**를 관리하게 합니다: **암호화된 HTTPS 요청**을 받고, 복호화된 **HTTP 요청**을 같은 서버에서 실행 중인 실제 HTTP 애플리케이션(이 경우 **FastAPI** 애플리케이션)으로 전달하고, 애플리케이션의 **HTTP 응답**을 받아 적절한 **HTTPS 인증서**로 **암호화**한 뒤 **HTTPS**로 클라이언트에 다시 보내는 역할입니다. 이런 서버를 흔히 **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">TLS Termination Proxy</a>**라고 부릅니다.
TLS Termination Proxy로 사용할 수 있는 옵션은 다음과 같습니다:
* Traefik (인증서 갱신도 처리 가능)
* Caddy (인증서 갱신도 처리 가능)
* Nginx
* HAProxy
## Let's Encrypt { #lets-encrypt }
Let's Encrypt 이전에는 이러한 **HTTPS 인증서**가 신뢰할 수 있는 제3자에 의해 판매되었습니다.
인증서를 획득하는 과정은 번거롭고, 꽤 많은 서류 작업이 필요했으며, 인증서도 상당히 비쌌습니다.
하지만 그 후 **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>**가 만들어졌습니다.
이는 Linux Foundation의 프로젝트입니다. 표준 암호학적 보안을 모두 사용하는 **HTTPS 인증서**를 **무료로**, 자동화된 방식으로 제공합니다. 이 인증서들은 수명이 짧고(약 3개월) 그래서 유효 기간이 짧은 만큼 **실제로 보안이 더 좋아지기도** 합니다.
도메인은 안전하게 검증되며 인증서는 자동으로 생성됩니다. 또한 이로 인해 인증서 갱신도 자동화할 수 있습니다.
목표는 인증서의 발급과 갱신을 자동화하여 **무료로, 영구히, 안전한 HTTPS**를 사용할 수 있게 하는 것입니다.
## 개발자를 위한 HTTPS { #https-for-developers }
개발자에게 중요한 개념들을 중심으로, HTTPS API가 단계별로 어떻게 보일 수 있는지 예시를 들어 보겠습니다.
### 도메인 이름 { #domain-name }
아마도 시작은 **도메인 이름**을 **획득**하는 것일 겁니다. 그 다음 DNS 서버(아마 같은 클라우드 제공업체)에서 이를 설정합니다.
대개 클라우드 서버(가상 머신) 같은 것을 사용하게 되고, 거기에는 <abbr title="That doesn't change - 변하지 않음">fixed</abbr> **공개 IP 주소**가 있습니다.
DNS 서버(들)에서 **도메인**이 서버의 **공개 IP 주소**를 가리키도록 레코드(“`A record`”)를 설정합니다.
보통은 처음 한 번, 모든 것을 설정할 때만 이 작업을 합니다.
/// tip | 팁
도메인 이름 부분은 HTTPS보다 훨씬 이전 단계지만, 모든 것이 도메인과 IP 주소에 의존하므로 여기서 언급할 가치가 있습니다.
///
### DNS { #dns }
이제 실제 HTTPS 부분에 집중해 보겠습니다.
먼저 브라우저는 **DNS 서버**에 질의하여, 여기서는 `someapp.example.com`이라는 **도메인에 대한 IP**가 무엇인지 확인합니다.
DNS 서버는 브라우저에게 특정 **IP 주소**를 사용하라고 알려줍니다. 이는 DNS 서버에 설정해 둔, 서버가 사용하는 공개 IP 주소입니다.
<img src="/img/deployment/https/https01.drawio.svg">
### TLS 핸드셰이크 시작 { #tls-handshake-start }
그 다음 브라우저는 **포트 443**(HTTPS 포트)에서 해당 IP 주소와 통신합니다.
통신의 첫 부분은 클라이언트와 서버 사이의 연결을 설정하고, 사용할 암호화 키 등을 결정하는 과정입니다.
<img src="/img/deployment/https/https02.drawio.svg">
클라이언트와 서버가 TLS 연결을 설정하기 위해 상호작용하는 이 과정을 **TLS 핸드셰이크**라고 합니다.
### SNI 확장을 사용하는 TLS { #tls-with-sni-extension }
서버에서는 특정 **IP 주소**의 특정 **포트**에서 **하나의 프로세스만** 리스닝할 수 있습니다. 같은 IP 주소에서 다른 포트로 리스닝하는 프로세스는 있을 수 있지만, IP 주소와 포트 조합마다 하나만 가능합니다.
TLS(HTTPS)는 기본적으로 특정 포트 `443`을 사용합니다. 따라서 우리가 필요한 포트는 이것입니다.
이 포트에서 하나의 프로세스만 리스닝할 수 있으므로, 그 역할을 하는 프로세스는 **TLS Termination Proxy**가 됩니다.
TLS Termination Proxy는 하나 이상의 **TLS 인증서**(HTTPS 인증서)에 접근할 수 있습니다.
앞에서 설명한 **SNI 확장**을 사용해, TLS Termination Proxy는 이 연결에 사용할 수 있는 TLS(HTTPS) 인증서들 중에서 클라이언트가 기대하는 도메인과 일치하는 것을 확인해 선택합니다.
이 경우에는 `someapp.example.com`에 대한 인증서를 사용합니다.
<img src="/img/deployment/https/https03.drawio.svg">
클라이언트는 이미 해당 TLS 인증서를 생성한 주체(여기서는 Let's Encrypt이지만, 이는 뒤에서 다시 보겠습니다)를 **신뢰**하므로, 인증서가 유효한지 **검증**할 수 있습니다.
그 다음 인증서를 사용해 클라이언트와 TLS Termination Proxy는 나머지 **TCP 통신**을 어떻게 **암호화할지 결정**합니다. 이로써 **TLS 핸드셰이크** 단계가 완료됩니다.
이후 클라이언트와 서버는 TLS가 제공하는 **암호화된 TCP 연결**을 갖게 됩니다. 그리고 그 연결을 사용해 실제 **HTTP 통신**을 시작할 수 있습니다.
이것이 바로 **HTTPS**입니다. 순수(암호화되지 않은) TCP 연결 대신 **안전한 TLS 연결** 안에서 **HTTP**를 그대로 사용하는 것입니다.
/// tip | 팁
통신의 암호화는 HTTP 레벨이 아니라 **TCP 레벨**에서 일어난다는 점에 주의하세요.
///
### HTTPS 요청 { #https-request }
이제 클라이언트와 서버(구체적으로는 브라우저와 TLS Termination Proxy)가 **암호화된 TCP 연결**을 갖게 되었으니 **HTTP 통신**을 시작할 수 있습니다.
따라서 클라이언트는 **HTTPS 요청**을 보냅니다. 이는 암호화된 TLS 연결을 통해 전달되는 HTTP 요청일 뿐입니다.
<img src="/img/deployment/https/https04.drawio.svg">
### 요청 복호화 { #decrypt-the-request }
TLS Termination Proxy는 합의된 암호화를 사용해 **요청을 복호화**하고, 애플리케이션을 실행 중인 프로세스(예: FastAPI 애플리케이션을 실행하는 Uvicorn 프로세스)에 **일반(복호화된) HTTP 요청**을 전달합니다.
<img src="/img/deployment/https/https05.drawio.svg">
### HTTP 응답 { #http-response }
애플리케이션은 요청을 처리하고 **일반(암호화되지 않은) HTTP 응답**을 TLS Termination Proxy로 보냅니다.
<img src="/img/deployment/https/https06.drawio.svg">
### HTTPS 응답 { #https-response }
그 다음 TLS Termination Proxy는 이전에 합의한 암호화( `someapp.example.com` 인증서로 시작된 것)를 사용해 **응답을 암호화**하고, 브라우저로 다시 보냅니다.
이후 브라우저는 응답이 유효한지, 올바른 암호화 키로 암호화되었는지 등을 확인합니다. 그런 다음 **응답을 복호화**하고 처리합니다.
<img src="/img/deployment/https/https07.drawio.svg">
클라이언트(브라우저)는 앞서 **HTTPS 인증서**로 합의한 암호화를 사용하고 있으므로, 해당 응답이 올바른 서버에서 왔다는 것을 알 수 있습니다.
### 여러 애플리케이션 { #multiple-applications }
같은 서버(또는 여러 서버)에는 예를 들어 다른 API 프로그램이나 데이터베이스처럼 **여러 애플리케이션**이 있을 수 있습니다.
특정 IP와 포트 조합은 하나의 프로세스만 처리할 수 있지만(예시에서는 TLS Termination Proxy), 다른 애플리케이션/프로세스도 **공개 IP와 포트 조합**을 동일하게 쓰려고만 하지 않는다면 서버에서 함께 실행될 수 있습니다.
<img src="/img/deployment/https/https08.drawio.svg">
이렇게 하면 TLS Termination Proxy가 **여러 도메인**에 대한 HTTPS와 인증서를 **여러 애플리케이션**에 대해 처리하고, 각 경우에 맞는 애플리케이션으로 요청을 전달할 수 있습니다.
### 인증서 갱신 { #certificate-renewal }
미래의 어느 시점에는 각 인증서가 **만료**됩니다(획득 후 약 3개월).
그 다음에는 또 다른 프로그램(경우에 따라 별도 프로그램일 수도 있고, 경우에 따라 같은 TLS Termination Proxy일 수도 있습니다)이 Let's Encrypt와 통신하여 인증서를 갱신합니다.
<img src="/img/deployment/https/https.drawio.svg">
**TLS 인증서**는 IP 주소가 아니라 **도메인 이름**과 **연결**되어 있습니다.
따라서 인증서를 갱신하려면, 갱신 프로그램이 권한 기관(Let's Encrypt)에게 해당 도메인을 실제로 **“소유”하고 제어하고 있음**을 **증명**해야 합니다.
이를 위해, 그리고 다양한 애플리케이션 요구를 수용하기 위해 여러 방법이 있습니다. 널리 쓰이는 방법은 다음과 같습니다:
* **일부 DNS 레코드 수정**.
* 이를 위해서는 갱신 프로그램이 DNS 제공업체의 API를 지원해야 하므로, 사용하는 DNS 제공업체에 따라 가능할 수도, 아닐 수도 있습니다.
* 도메인과 연결된 공개 IP 주소에서 **서버로 실행**(적어도 인증서 발급 과정 동안).
* 앞에서 말했듯 특정 IP와 포트에서는 하나의 프로세스만 리스닝할 수 있습니다.
* 이것이 동일한 TLS Termination Proxy가 인증서 갱신 과정까지 처리할 때 매우 유용한 이유 중 하나입니다.
* 그렇지 않으면 TLS Termination Proxy를 잠시 중지하고, 갱신 프로그램을 시작해 인증서를 획득한 다음, TLS Termination Proxy에 인증서를 설정하고, 다시 TLS Termination Proxy를 재시작해야 할 수도 있습니다. 이는 TLS Termination Proxy가 꺼져 있는 동안 앱(들)을 사용할 수 없으므로 이상적이지 않습니다.
앱을 계속 제공하면서 이 갱신 과정을 처리할 수 있는 것은, 애플리케이션 서버(예: Uvicorn)에서 TLS 인증서를 직접 쓰는 대신 TLS Termination Proxy로 HTTPS를 처리하는 **별도의 시스템**을 두고 싶어지는 주요 이유 중 하나입니다.
## 프록시 전달 헤더 { #proxy-forwarded-headers }
프록시를 사용해 HTTPS를 처리할 때, **애플리케이션 서버**(예: FastAPI CLI를 통한 Uvicorn)는 HTTPS 과정에 대해 아무것도 알지 못하고 **TLS Termination Proxy**와는 일반 HTTP로 통신합니다.
이 **프록시**는 보통 요청을 **애플리케이션 서버**에 전달하기 전에, 요청이 프록시에 의해 **전달(forwarded)**되고 있음을 애플리케이션 서버가 알 수 있도록 일부 HTTP 헤더를 즉석에서 설정합니다.
/// note | 기술 세부사항
프록시 헤더는 다음과 같습니다:
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
///
그럼에도 불구하고 **애플리케이션 서버**는 자신이 신뢰할 수 있는 **프록시** 뒤에 있다는 것을 모르므로, 기본적으로는 그 헤더들을 신뢰하지 않습니다.
하지만 **애플리케이션 서버**가 **프록시**가 보낸 *forwarded* 헤더를 신뢰하도록 설정할 수 있습니다. FastAPI CLI를 사용하고 있다면, *CLI Option* `--forwarded-allow-ips`를 사용해 어떤 IP에서 온 *forwarded* 헤더를 신뢰할지 지정할 수 있습니다.
예를 들어 **애플리케이션 서버**가 신뢰하는 **프록시**로부터만 통신을 받는다면, `--forwarded-allow-ips="*"`로 설정해 들어오는 모든 IP를 신뢰하게 할 수 있습니다. 어차피 **프록시**가 사용하는 IP에서만 요청을 받게 될 것이기 때문입니다.
이렇게 하면 애플리케이션은 자신이 사용하는 공개 URL이 무엇인지, HTTPS를 사용하는지, 도메인이 무엇인지 등을 알 수 있습니다.
예를 들어 리다이렉트를 올바르게 처리하는 데 유용합니다.
/// tip | 팁
이에 대해서는 [프록시 뒤에서 실행하기 - 프록시 전달 헤더 활성화](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} 문서에서 더 알아볼 수 있습니다.
///
## 요약 { #recap }
**HTTPS**는 매우 중요하며, 대부분의 경우 상당히 **핵심적**입니다. 개발자가 HTTPS와 관련해 해야 하는 노력의 대부분은 결국 **이 개념들을 이해**하고 그것들이 어떻게 동작하는지 파악하는 것입니다.
하지만 **개발자를 위한 HTTPS**의 기본 정보를 알고 나면, 여러 도구를 쉽게 조합하고 설정하여 모든 것을 간단하게 관리할 수 있습니다.
다음 장들에서는 **FastAPI** 애플리케이션을 위한 **HTTPS** 설정 방법을 여러 구체적인 예시로 보여드리겠습니다. 🔒

View File

@@ -1,157 +0,0 @@
# 서버를 수동으로 실행하기 { #run-a-server-manually }
## `fastapi run` 명령 사용하기 { #use-the-fastapi-run-command }
요약하면, `fastapi run`을 사용해 FastAPI 애플리케이션을 서비스하세요:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
```
</div>
대부분의 경우에는 이것으로 동작합니다. 😎
예를 들어 이 명령은 컨테이너나 서버 등에서 **FastAPI** 앱을 시작할 때 사용할 수 있습니다.
## ASGI 서버 { #asgi-servers }
이제 조금 더 자세히 살펴보겠습니다.
FastAPI는 <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr>라고 불리는, Python 웹 프레임워크와 서버를 만들기 위한 표준을 사용합니다. FastAPI는 ASGI 웹 프레임워크입니다.
원격 서버 머신에서 **FastAPI** 애플리케이션(또는 다른 ASGI 애플리케이션)을 실행하기 위해 필요한 핵심 요소는 **Uvicorn** 같은 ASGI 서버 프로그램입니다. `fastapi` 명령에는 기본으로 이것이 포함되어 있습니다.
다음을 포함해 여러 대안이 있습니다:
* <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a>: 고성능 ASGI 서버.
* <a href="https://hypercorn.readthedocs.io/" class="external-link" target="_blank">Hypercorn</a>: HTTP/2 및 Trio 등 여러 기능과 호환되는 ASGI 서버.
* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>: Django Channels를 위해 만들어진 ASGI 서버.
* <a href="https://github.com/emmett-framework/granian" class="external-link" target="_blank">Granian</a>: Python 애플리케이션을 위한 Rust HTTP 서버.
* <a href="https://unit.nginx.org/howto/fastapi/" class="external-link" target="_blank">NGINX Unit</a>: NGINX Unit은 가볍고 다용도로 사용할 수 있는 웹 애플리케이션 런타임입니다.
## 서버 머신과 서버 프로그램 { #server-machine-and-server-program }
이름에 관해 기억해 둘 작은 디테일이 있습니다. 💡
"**server**"라는 단어는 보통 원격/클라우드 컴퓨터(물리 또는 가상 머신)와, 그 머신에서 실행 중인 프로그램(예: Uvicorn) 둘 다를 가리키는 데 사용됩니다.
일반적으로 "server"를 읽을 때, 이 두 가지 중 하나를 의미할 수 있다는 점을 기억하세요.
원격 머신을 가리킬 때는 **server**라고 부르는 것이 일반적이지만, **machine**, **VM**(virtual machine), **node**라고 부르기도 합니다. 이것들은 보통 Linux를 실행하는 원격 머신의 한 형태를 뜻하며, 그곳에서 프로그램을 실행합니다.
## 서버 프로그램 설치하기 { #install-the-server-program }
FastAPI를 설치하면 프로덕션 서버인 Uvicorn이 함께 설치되며, `fastapi run` 명령으로 시작할 수 있습니다.
하지만 ASGI 서버를 수동으로 설치할 수도 있습니다.
[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 만들고 활성화한 다음, 서버 애플리케이션을 설치하세요.
예를 들어 Uvicorn을 설치하려면:
<div class="termy">
```console
$ pip install "uvicorn[standard]"
---> 100%
```
</div>
다른 어떤 ASGI 서버 프로그램도 비슷한 과정이 적용됩니다.
/// tip | 팁
`standard`를 추가하면 Uvicorn이 권장되는 추가 의존성 몇 가지를 설치하고 사용합니다.
여기에는 `asyncio`를 고성능으로 대체할 수 있는 드롭인 대체재인 `uvloop`가 포함되며, 큰 동시성 성능 향상을 제공합니다.
`pip install "fastapi[standard]"` 같은 방식으로 FastAPI를 설치하면 `uvicorn[standard]`도 함께 설치됩니다.
///
## 서버 프로그램 실행하기 { #run-the-server-program }
ASGI 서버를 수동으로 설치했다면, 보통 FastAPI 애플리케이션을 임포트하기 위해 특별한 형식의 import string을 전달해야 합니다:
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 80
<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
```
</div>
/// note | 참고
`uvicorn main:app` 명령은 다음을 가리킵니다:
* `main`: 파일 `main.py`(Python "module").
* `app`: `main.py` 안에서 `app = FastAPI()` 라인으로 생성된 객체.
이는 다음과 동일합니다:
```Python
from main import app
```
///
각 ASGI 서버 프로그램의 대안도 비슷한 명령을 갖고 있으며, 자세한 내용은 각자의 문서를 참고하세요.
/// warning | 경고
Uvicorn과 다른 서버는 개발 중에 유용한 `--reload` 옵션을 지원합니다.
`--reload` 옵션은 훨씬 더 많은 리소스를 소비하고, 더 불안정합니다.
**개발** 중에는 큰 도움이 되지만, **프로덕션**에서는 사용하지 **말아야** 합니다.
///
## 배포 개념 { #deployment-concepts }
이 예제들은 서버 프로그램(예: Uvicorn)을 실행하여 **단일 프로세스**를 시작하고, 사전에 정한 포트(예: `80`)에서 모든 IP(`0.0.0.0`)로 들어오는 요청을 받도록 합니다.
이것이 기본 아이디어입니다. 하지만 보통은 다음과 같은 추가 사항들도 처리해야 합니다:
* 보안 - HTTPS
* 시작 시 자동 실행
* 재시작
* 복제(실행 중인 프로세스 수)
* 메모리
* 시작 전 선행 단계
다음 장들에서 이 각각의 개념을 어떻게 생각해야 하는지, 그리고 이를 다루기 위한 전략의 구체적인 예시를 더 알려드리겠습니다. 🚀

View File

@@ -1,17 +0,0 @@
# 이전 403 인증 오류 상태 코드 사용하기 { #use-old-403-authentication-error-status-codes }
FastAPI 버전 `0.122.0` 이전에는, 통합 보안 유틸리티가 인증 실패 후 클라이언트에 오류를 반환할 때 HTTP 상태 코드 `403 Forbidden`을 사용했습니다.
FastAPI 버전 `0.122.0`부터는 더 적절한 HTTP 상태 코드 `401 Unauthorized`를 사용하며, HTTP 명세인 <a href="https://datatracker.ietf.org/doc/html/rfc7235#section-3.1" class="external-link" target="_blank">RFC 7235</a>, <a href="https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized" class="external-link" target="_blank">RFC 9110</a>를 따라 응답에 합리적인 `WWW-Authenticate` 헤더를 반환합니다.
하지만 어떤 이유로든 클라이언트가 이전 동작에 의존하고 있다면, 보안 클래스에서 `make_not_authenticated_error` 메서드를 오버라이드하여 이전 동작으로 되돌릴 수 있습니다.
예를 들어, 기본값인 `401 Unauthorized` 오류 대신 `403 Forbidden` 오류를 반환하는 `HTTPBearer`의 서브클래스를 만들 수 있습니다:
{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *}
/// tip | 팁
함수는 예외를 `raise`하는 것이 아니라 예외 인스턴스를 `return`한다는 점에 유의하세요. 예외를 발생시키는(`raise`) 작업은 내부 코드의 나머지 부분에서 수행됩니다.
///

View File

@@ -1,185 +0,0 @@
# 커스텀 Docs UI 정적 에셋(자체 호스팅) { #custom-docs-ui-static-assets-self-hosting }
API 문서는 **Swagger UI**와 **ReDoc**을 사용하며, 각각 JavaScript와 CSS 파일이 필요합니다.
기본적으로 이러한 파일은 <abbr title="Content Delivery Network - 콘텐츠 전송 네트워크: 일반적으로 여러 서버로 구성되어 JavaScript와 CSS 같은 정적 파일을 제공하는 서비스입니다. 보통 클라이언트에 더 가까운 서버에서 파일을 제공해 성능을 향상시키는 데 사용됩니다.">CDN</abbr>에서 제공됩니다.
하지만 이를 커스터마이징할 수 있으며, 특정 CDN을 지정하거나 파일을 직접 제공할 수도 있습니다.
## JavaScript와 CSS용 커스텀 CDN { #custom-cdn-for-javascript-and-css }
예를 들어 다른 <abbr title="Content Delivery Network">CDN</abbr>을 사용하고 싶다고 해봅시다. 예를 들면 `https://unpkg.com/`을 사용하려는 경우입니다.
이는 예를 들어 특정 국가에서 일부 URL을 제한하는 경우에 유용할 수 있습니다.
### 자동 문서 비활성화하기 { #disable-the-automatic-docs }
첫 번째 단계는 자동 문서를 비활성화하는 것입니다. 기본적으로 자동 문서는 기본 CDN을 사용하기 때문입니다.
비활성화하려면 `FastAPI` 앱을 생성할 때 해당 URL을 `None`으로 설정하세요:
{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *}
### 커스텀 문서 포함하기 { #include-the-custom-docs }
이제 커스텀 문서를 위한 *경로 처리*를 만들 수 있습니다.
FastAPI 내부 함수를 재사용해 문서용 HTML 페이지를 생성하고, 필요한 인자를 전달할 수 있습니다:
* `openapi_url`: 문서 HTML 페이지가 API의 OpenAPI 스키마를 가져올 수 있는 URL입니다. 여기서는 `app.openapi_url` 속성을 사용할 수 있습니다.
* `title`: API의 제목입니다.
* `oauth2_redirect_url`: 기본값을 사용하려면 여기서 `app.swagger_ui_oauth2_redirect_url`을 사용할 수 있습니다.
* `swagger_js_url`: Swagger UI 문서의 HTML이 **JavaScript** 파일을 가져올 수 있는 URL입니다. 커스텀 CDN URL입니다.
* `swagger_css_url`: Swagger UI 문서의 HTML이 **CSS** 파일을 가져올 수 있는 URL입니다. 커스텀 CDN URL입니다.
ReDoc도 마찬가지입니다...
{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *}
/// tip | 팁
`swagger_ui_redirect`에 대한 *경로 처리*는 OAuth2를 사용할 때 도움이 되는 헬퍼입니다.
API를 OAuth2 provider와 통합하면 인증을 수행한 뒤 획득한 자격 증명으로 API 문서로 다시 돌아올 수 있습니다. 그리고 실제 OAuth2 인증을 사용해 API와 상호작용할 수 있습니다.
Swagger UI가 이 과정을 백그라운드에서 처리해 주지만, 이를 위해 이 "redirect" 헬퍼가 필요합니다.
///
### 테스트용 *경로 처리* 만들기 { #create-a-path-operation-to-test-it }
이제 모든 것이 제대로 동작하는지 테스트할 수 있도록 *경로 처리*를 하나 만드세요:
{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *}
### 테스트하기 { #test-it }
이제 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>에서 문서에 접속한 뒤 페이지를 새로고침하면, 새 CDN에서 에셋을 불러오는 것을 확인할 수 있습니다.
## 문서용 JavaScript와 CSS 자체 호스팅하기 { #self-hosting-javascript-and-css-for-docs }
JavaScript와 CSS를 자체 호스팅하는 것은 예를 들어, 오프라인 상태이거나 외부 인터넷에 접근할 수 없는 환경, 또는 로컬 네트워크에서도 앱이 계속 동작해야 할 때 유용할 수 있습니다.
여기서는 동일한 FastAPI 앱에서 해당 파일을 직접 제공하고, 문서가 이를 사용하도록 설정하는 방법을 살펴봅니다.
### 프로젝트 파일 구조 { #project-file-structure }
프로젝트 파일 구조가 다음과 같다고 해봅시다:
```
.
├── app
│ ├── __init__.py
│ ├── main.py
```
이제 해당 정적 파일을 저장할 디렉터리를 만드세요.
새 파일 구조는 다음과 같을 수 있습니다:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
└── static/
```
### 파일 다운로드하기 { #download-the-files }
문서에 필요한 정적 파일을 다운로드해서 `static/` 디렉터리에 넣으세요.
각 링크를 우클릭한 뒤 "링크를 다른 이름으로 저장..."과 비슷한 옵션을 선택하면 될 것입니다.
**Swagger UI**는 다음 파일을 사용합니다:
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a>
* <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a>
**ReDoc**은 다음 파일을 사용합니다:
* <a href="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a>
이후 파일 구조는 다음과 같을 수 있습니다:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
└── static
├── redoc.standalone.js
├── swagger-ui-bundle.js
└── swagger-ui.css
```
### 정적 파일 제공하기 { #serve-the-static-files }
* `StaticFiles`를 import합니다.
* 특정 경로에 `StaticFiles()` 인스턴스를 "마운트(mount)"합니다.
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *}
### 정적 파일 테스트하기 { #test-the-static-files }
애플리케이션을 시작하고 <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>로 이동하세요.
**ReDoc**용 매우 긴 JavaScript 파일이 보일 것입니다.
예를 들어 다음과 같이 시작할 수 있습니다:
```JavaScript
/*! For license information please see redoc.standalone.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
...
```
이는 앱에서 정적 파일을 제공할 수 있고, 문서용 정적 파일을 올바른 위치에 배치했다는 것을 확인해 줍니다.
이제 문서가 이 정적 파일을 사용하도록 앱을 설정할 수 있습니다.
### 정적 파일을 위한 자동 문서 비활성화하기 { #disable-the-automatic-docs-for-static-files }
커스텀 CDN을 사용할 때와 마찬가지로, 첫 단계는 자동 문서를 비활성화하는 것입니다. 자동 문서는 기본적으로 CDN을 사용합니다.
비활성화하려면 `FastAPI` 앱을 생성할 때 해당 URL을 `None`으로 설정하세요:
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *}
### 정적 파일을 위한 커스텀 문서 포함하기 { #include-the-custom-docs-for-static-files }
그리고 커스텀 CDN을 사용할 때와 동일한 방식으로, 이제 커스텀 문서를 위한 *경로 처리*를 만들 수 있습니다.
다시 한 번, FastAPI 내부 함수를 재사용해 문서용 HTML 페이지를 생성하고, 필요한 인자를 전달할 수 있습니다:
* `openapi_url`: 문서 HTML 페이지가 API의 OpenAPI 스키마를 가져올 수 있는 URL입니다. 여기서는 `app.openapi_url` 속성을 사용할 수 있습니다.
* `title`: API의 제목입니다.
* `oauth2_redirect_url`: 기본값을 사용하려면 여기서 `app.swagger_ui_oauth2_redirect_url`을 사용할 수 있습니다.
* `swagger_js_url`: Swagger UI 문서의 HTML이 **JavaScript** 파일을 가져올 수 있는 URL입니다. **이제는 여러분의 앱이 직접 제공하는 파일입니다**.
* `swagger_css_url`: Swagger UI 문서의 HTML이 **CSS** 파일을 가져올 수 있는 URL입니다. **이제는 여러분의 앱이 직접 제공하는 파일입니다**.
ReDoc도 마찬가지입니다...
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *}
/// tip | 팁
`swagger_ui_redirect`에 대한 *경로 처리*는 OAuth2를 사용할 때 도움이 되는 헬퍼입니다.
API를 OAuth2 provider와 통합하면 인증을 수행한 뒤 획득한 자격 증명으로 API 문서로 다시 돌아올 수 있습니다. 그리고 실제 OAuth2 인증을 사용해 API와 상호작용할 수 있습니다.
Swagger UI가 이 과정을 백그라운드에서 처리해 주지만, 이를 위해 이 "redirect" 헬퍼가 필요합니다.
///
### 정적 파일 테스트용 *경로 처리* 만들기 { #create-a-path-operation-to-test-static-files }
이제 모든 것이 제대로 동작하는지 테스트할 수 있도록 *경로 처리*를 하나 만드세요:
{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *}
### 정적 파일 UI 테스트하기 { #test-static-files-ui }
이제 WiFi 연결을 끊고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>에서 문서에 접속한 뒤 페이지를 새로고침해 보세요.
인터넷이 없어도 API 문서를 보고, API와 상호작용할 수 있을 것입니다.

View File

@@ -1,109 +0,0 @@
# 커스텀 Request 및 APIRoute 클래스 { #custom-request-and-apiroute-class }
일부 경우에는 `Request``APIRoute` 클래스에서 사용되는 로직을 오버라이드하고 싶을 수 있습니다.
특히, 이는 middleware에 있는 로직의 좋은 대안이 될 수 있습니다.
예를 들어, 애플리케이션에서 처리되기 전에 요청 바디를 읽거나 조작하고 싶을 때가 그렇습니다.
/// danger | 위험
이 기능은 "고급" 기능입니다.
**FastAPI**를 이제 막 시작했다면 이 섹션은 건너뛰는 것이 좋습니다.
///
## 사용 사례 { #use-cases }
사용 사례에는 다음이 포함됩니다:
* JSON이 아닌 요청 바디를 JSON으로 변환하기(예: <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>).
* gzip으로 압축된 요청 바디 압축 해제하기.
* 모든 요청 바디를 자동으로 로깅하기.
## 커스텀 요청 바디 인코딩 처리하기 { #handling-custom-request-body-encodings }
커스텀 `Request` 서브클래스를 사용해 gzip 요청의 압축을 해제하는 방법을 살펴보겠습니다.
그리고 그 커스텀 요청 클래스를 사용하기 위한 `APIRoute` 서브클래스도 함께 보겠습니다.
### 커스텀 `GzipRequest` 클래스 만들기 { #create-a-custom-gziprequest-class }
/// tip | 팁
이 예시는 동작 방식 시연을 위한 장난감 예제입니다. Gzip 지원이 필요하다면 제공되는 [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}를 사용할 수 있습니다.
///
먼저, `GzipRequest` 클래스를 만듭니다. 이 클래스는 `Request.body()` 메서드를 덮어써서, 적절한 헤더가 있는 경우 바디를 압축 해제합니다.
헤더에 `gzip`이 없으면 바디를 압축 해제하려고 시도하지 않습니다.
이렇게 하면 동일한 route 클래스가 gzip으로 압축된 요청과 압축되지 않은 요청을 모두 처리할 수 있습니다.
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
### 커스텀 `GzipRoute` 클래스 만들기 { #create-a-custom-gziproute-class }
다음으로, `GzipRequest`를 활용하는 `fastapi.routing.APIRoute`의 커스텀 서브클래스를 만듭니다.
이번에는 `APIRoute.get_route_handler()` 메서드를 오버라이드합니다.
이 메서드는 함수를 반환합니다. 그리고 그 함수가 요청을 받아 응답을 반환합니다.
여기서는 원본 요청으로부터 `GzipRequest`를 만들기 위해 이를 사용합니다.
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
/// note | 기술 세부사항
`Request`에는 `request.scope` 속성이 있는데, 이는 요청과 관련된 메타데이터를 담고 있는 Python `dict`입니다.
`Request`에는 또한 `request.receive`가 있는데, 이는 요청의 바디를 "받기(receive)" 위한 함수입니다.
`scope` `dict``receive` 함수는 모두 ASGI 명세의 일부입니다.
그리고 이 두 가지, `scope``receive`가 새로운 `Request` 인스턴스를 만드는 데 필요한 것들입니다.
`Request`에 대해 더 알아보려면 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">Starlette의 Requests 문서</a>를 확인하세요.
///
`GzipRequest.get_route_handler`가 반환하는 함수가 다르게 하는 유일한 것은 `Request``GzipRequest`로 변환하는 것입니다.
이렇게 하면, 우리의 `GzipRequest`가 *경로 처리*로 전달하기 전에(필요하다면) 데이터의 압축 해제를 담당하게 됩니다.
그 이후의 모든 처리 로직은 동일합니다.
하지만 `GzipRequest.body`에서 변경을 했기 때문에, 필요할 때 **FastAPI**가 로드하는 시점에 요청 바디는 자동으로 압축 해제됩니다.
## 예외 핸들러에서 요청 바디 접근하기 { #accessing-the-request-body-in-an-exception-handler }
/// tip | 팁
같은 문제를 해결하려면 `RequestValidationError`에 대한 커스텀 핸들러에서 `body`를 사용하는 편이 아마 훨씬 더 쉽습니다([오류 처리하기](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}).
하지만 이 예시도 여전히 유효하며, 내부 컴포넌트와 상호작용하는 방법을 보여줍니다.
///
같은 접근 방식을 사용해 예외 핸들러에서 요청 바디에 접근할 수도 있습니다.
필요한 것은 `try`/`except` 블록 안에서 요청을 처리하는 것뿐입니다:
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
예외가 발생하더라도 `Request` 인스턴스는 여전히 스코프 안에 남아 있으므로, 오류를 처리할 때 요청 바디를 읽고 활용할 수 있습니다:
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
## 라우터에서의 커스텀 `APIRoute` 클래스 { #custom-apiroute-class-in-a-router }
`APIRouter``route_class` 파라미터를 설정할 수도 있습니다:
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
이 예시에서는 `router` 아래의 *경로 처리*들이 커스텀 `TimedRoute` 클래스를 사용하며, 응답을 생성하는 데 걸린 시간을 담은 추가 `X-Response-Time` 헤더가 응답에 포함됩니다:
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}

View File

@@ -1,80 +0,0 @@
# OpenAPI 확장하기 { #extending-openapi }
생성된 OpenAPI 스키마를 수정해야 하는 경우가 있습니다.
이 섹션에서 그 방법을 살펴보겠습니다.
## 일반적인 과정 { #the-normal-process }
일반적인(기본) 과정은 다음과 같습니다.
`FastAPI` 애플리케이션(인스턴스)에는 OpenAPI 스키마를 반환해야 하는 `.openapi()` 메서드가 있습니다.
애플리케이션 객체를 생성하는 과정에서 `/openapi.json`(또는 `openapi_url`에 설정한 경로)용 *경로 처리*가 등록됩니다.
이 경로 처리는 애플리케이션의 `.openapi()` 메서드 결과를 JSON 응답으로 반환할 뿐입니다.
기본적으로 `.openapi()` 메서드는 프로퍼티 `.openapi_schema`에 내용이 있는지 확인하고, 있으면 그 내용을 반환합니다.
없으면 `fastapi.openapi.utils.get_openapi`에 있는 유틸리티 함수를 사용해 생성합니다.
그리고 `get_openapi()` 함수는 다음을 파라미터로 받습니다:
* `title`: 문서에 표시되는 OpenAPI 제목.
* `version`: API 버전. 예: `2.5.0`.
* `openapi_version`: 사용되는 OpenAPI 스펙 버전. 기본값은 최신인 `3.1.0`.
* `summary`: API에 대한 짧은 요약.
* `description`: API 설명. markdown을 포함할 수 있으며 문서에 표시됩니다.
* `routes`: 라우트 목록. 각각 등록된 *경로 처리*입니다. `app.routes`에서 가져옵니다.
/// info | 정보
`summary` 파라미터는 OpenAPI 3.1.0 이상에서 사용할 수 있으며, FastAPI 0.99.0 이상에서 지원됩니다.
///
## 기본값 덮어쓰기 { #overriding-the-defaults }
위 정보를 바탕으로, 동일한 유틸리티 함수를 사용해 OpenAPI 스키마를 생성하고 필요한 각 부분을 덮어쓸 수 있습니다.
예를 들어, <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">커스텀 로고를 포함하기 위한 ReDoc의 OpenAPI 확장</a>을 추가해 보겠습니다.
### 일반적인 **FastAPI** { #normal-fastapi }
먼저, 평소처럼 **FastAPI** 애플리케이션을 모두 작성합니다:
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *}
### OpenAPI 스키마 생성하기 { #generate-the-openapi-schema }
그다음 `custom_openapi()` 함수 안에서, 동일한 유틸리티 함수를 사용해 OpenAPI 스키마를 생성합니다:
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *}
### OpenAPI 스키마 수정하기 { #modify-the-openapi-schema }
이제 OpenAPI 스키마의 `info` "object"에 커스텀 `x-logo`를 추가하여 ReDoc 확장을 더할 수 있습니다:
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *}
### OpenAPI 스키마 캐시하기 { #cache-the-openapi-schema }
생성한 스키마를 저장하기 위한 "cache"로 `.openapi_schema` 프로퍼티를 사용할 수 있습니다.
이렇게 하면 사용자가 API 문서를 열 때마다 애플리케이션이 스키마를 매번 생성하지 않아도 됩니다.
스키마는 한 번만 생성되고, 이후 요청에서는 같은 캐시된 스키마가 사용됩니다.
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *}
### 메서드 오버라이드하기 { #override-the-method }
이제 `.openapi()` 메서드를 새 함수로 교체할 수 있습니다.
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *}
### 확인하기 { #check-it }
<a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>로 이동하면 커스텀 로고(이 예시에서는 **FastAPI** 로고)를 사용하는 것을 확인할 수 있습니다:
<img src="/img/tutorial/extending-openapi/image01.png">

View File

@@ -1,39 +0,0 @@
# 일반 - 사용 방법 - 레시피 { #general-how-to-recipes }
일반적이거나 자주 나오는 질문에 대해, 문서의 다른 위치로 안내하는 몇 가지 포인터를 소개합니다.
## 데이터 필터링 - 보안 { #filter-data-security }
반환하면 안 되는 데이터를 과도하게 반환하지 않도록 하려면, [튜토리얼 - 응답 모델 - 반환 타입](../tutorial/response-model.md){.internal-link target=_blank} 문서를 읽어보세요.
## 문서화 태그 - OpenAPI { #documentation-tags-openapi }
*경로 처리*에 태그를 추가하고, 문서 UI에서 이를 그룹화하려면 [튜토리얼 - 경로 처리 구성 - 태그](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank} 문서를 읽어보세요.
## 문서화 요약 및 설명 - OpenAPI { #documentation-summary-and-description-openapi }
*경로 처리*에 요약과 설명을 추가하고, 문서 UI에 표시하려면 [튜토리얼 - 경로 처리 구성 - 요약 및 설명](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank} 문서를 읽어보세요.
## 문서화 응답 설명 - OpenAPI { #documentation-response-description-openapi }
문서 UI에 표시되는 응답의 설명을 정의하려면 [튜토리얼 - 경로 처리 구성 - 응답 설명](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} 문서를 읽어보세요.
## 문서화 *경로 처리* 지원 중단하기 - OpenAPI { #documentation-deprecate-a-path-operation-openapi }
*경로 처리*를 지원 중단(deprecate)으로 표시하고, 문서 UI에 보여주려면 [튜토리얼 - 경로 처리 구성 - 지원 중단](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank} 문서를 읽어보세요.
## 어떤 데이터든 JSON 호환으로 변환하기 { #convert-any-data-to-json-compatible }
어떤 데이터든 JSON 호환 형식으로 변환하려면 [튜토리얼 - JSON 호환 인코더](../tutorial/encoder.md){.internal-link target=_blank} 문서를 읽어보세요.
## OpenAPI 메타데이터 - 문서 { #openapi-metadata-docs }
라이선스, 버전, 연락처 등의 정보를 포함해 OpenAPI 스키마에 메타데이터를 추가하려면 [튜토리얼 - 메타데이터와 문서 URL](../tutorial/metadata.md){.internal-link target=_blank} 문서를 읽어보세요.
## OpenAPI 사용자 정의 URL { #openapi-custom-url }
OpenAPI URL을 커스터마이즈(또는 제거)하려면 [튜토리얼 - 메타데이터와 문서 URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank} 문서를 읽어보세요.
## OpenAPI 문서 URL { #openapi-docs-urls }
자동으로 생성되는 문서 사용자 인터페이스에서 사용하는 URL을 업데이트하려면 [튜토리얼 - 메타데이터와 문서 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank} 문서를 읽어보세요.

View File

@@ -1,60 +0,0 @@
# GraphQL { #graphql }
**FastAPI**는 **ASGI** 표준을 기반으로 하므로, ASGI와도 호환되는 어떤 **GraphQL** 라이브러리든 매우 쉽게 통합할 수 있습니다.
같은 애플리케이션에서 일반 FastAPI **경로 처리**와 GraphQL을 함께 조합할 수 있습니다.
/// tip | 팁
**GraphQL**은 몇 가지 매우 특정한 사용 사례를 해결합니다.
일반적인 **web API**와 비교했을 때 **장점**과 **단점**이 있습니다.
여러분의 사용 사례에서 **이점**이 **단점**을 상쇄하는지 꼭 평가해 보세요. 🤓
///
## GraphQL 라이브러리 { #graphql-libraries }
다음은 **ASGI** 지원이 있는 **GraphQL** 라이브러리들입니다. **FastAPI**와 함께 사용할 수 있습니다:
* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> 🍓
* <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">FastAPI용 문서</a> 제공
* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a>
* <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">FastAPI용 문서</a> 제공
* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a>
* ASGI 통합을 제공하기 위해 <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> 사용
* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>
* <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a> 사용
## Strawberry로 GraphQL 사용하기 { #graphql-with-strawberry }
**GraphQL**로 작업해야 하거나 작업하고 싶다면, <a href="https://strawberry.rocks/" class="external-link" target="_blank">**Strawberry**</a>를 **권장**합니다. **FastAPI**의 설계와 가장 가깝고, 모든 것이 **type annotations**에 기반해 있기 때문입니다.
사용 사례에 따라 다른 라이브러리를 선호할 수도 있지만, 제게 묻는다면 아마 **Strawberry**를 먼저 시도해 보라고 제안할 것입니다.
다음은 Strawberry를 FastAPI와 통합하는 방법에 대한 간단한 미리보기입니다:
{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *}
<a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry 문서</a>에서 Strawberry에 대해 더 알아볼 수 있습니다.
또한 <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">FastAPI에서 Strawberry 사용</a>에 대한 문서도 확인해 보세요.
## Starlette의 예전 `GraphQLApp` { #older-graphqlapp-from-starlette }
이전 버전의 Starlette에는 <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>과 통합하기 위한 `GraphQLApp` 클래스가 포함되어 있었습니다.
이것은 Starlette에서 deprecated 되었지만, 이를 사용하던 코드가 있다면 같은 사용 사례를 다루고 **거의 동일한 인터페이스**를 가진 <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>로 쉽게 **마이그레이션**할 수 있습니다.
/// tip | 팁
GraphQL이 필요하다면, 커스텀 클래스와 타입 대신 type annotations에 기반한 <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a>를 여전히 확인해 보시길 권장합니다.
///
## 더 알아보기 { #learn-more }
<a href="https://graphql.org/" class="external-link" target="_blank">공식 GraphQL 문서</a>에서 **GraphQL**에 대해 더 알아볼 수 있습니다.
또한 위에서 설명한 각 라이브러리에 대해서도 해당 링크에서 더 자세히 읽어볼 수 있습니다.

View File

@@ -1,13 +0,0 @@
# How To - 레시피 { #how-to-recipes }
여기에서는 **여러 주제**에 대한 다양한 레시피(“how to” 가이드)를 볼 수 있습니다.
대부분의 아이디어는 어느 정도 **서로 독립적**이며, 대부분의 경우 **여러분의 프로젝트**에 직접 적용되는 경우에만 학습하면 됩니다.
프로젝트에 흥미롭고 유용해 보이는 것이 있다면 확인해 보세요. 그렇지 않다면 아마 건너뛰어도 됩니다.
/// tip | 팁
**FastAPI를 구조적으로 학습**하고 싶다면(권장), 대신 [튜토리얼 - 사용자 가이드](../tutorial/index.md){.internal-link target=_blank}를 장별로 읽어보세요.
///

View File

@@ -1,135 +0,0 @@
# Pydantic v1에서 Pydantic v2로 마이그레이션하기 { #migrate-from-pydantic-v1-to-pydantic-v2 }
오래된 FastAPI 앱이 있다면 Pydantic 버전 1을 사용하고 있을 수 있습니다.
FastAPI 0.100.0 버전은 Pydantic v1 또는 v2 중 하나를 지원했습니다. 설치되어 있는 쪽을 사용했습니다.
FastAPI 0.119.0 버전에서는 v2로의 마이그레이션을 쉽게 하기 위해, Pydantic v2 내부에서 Pydantic v1을(`pydantic.v1`로) 부분적으로 지원하기 시작했습니다.
FastAPI 0.126.0 버전에서는 Pydantic v1 지원을 중단했지만, `pydantic.v1`은 잠시 동안 계속 지원했습니다.
/// warning | 경고
Pydantic 팀은 **Python 3.14**부터 최신 Python 버전에서 Pydantic v1 지원을 중단했습니다.
여기에는 `pydantic.v1`도 포함되며, Python 3.14 이상에서는 더 이상 지원되지 않습니다.
Python의 최신 기능을 사용하려면 Pydantic v2를 사용하고 있는지 확인해야 합니다.
///
Pydantic v1을 사용하는 오래된 FastAPI 앱이 있다면, 여기서는 이를 Pydantic v2로 마이그레이션하는 방법과 점진적 마이그레이션을 돕는 **FastAPI 0.119.0의 기능**을 소개하겠습니다.
## 공식 가이드 { #official-guide }
Pydantic에는 v1에서 v2로의 공식 <a href="https://docs.pydantic.dev/latest/migration/" class="external-link" target="_blank">Migration Guide</a>가 있습니다.
여기에는 무엇이 바뀌었는지, 검증이 이제 어떻게 더 정확하고 엄격해졌는지, 가능한 주의사항 등도 포함되어 있습니다.
변경된 내용을 더 잘 이해하기 위해 읽어보면 좋습니다.
## 테스트 { #tests }
앱에 대한 [tests](../tutorial/testing.md){.internal-link target=_blank}가 있는지 확인하고, 지속적 통합(CI)에서 테스트를 실행하세요.
이렇게 하면 업그레이드를 진행하면서도 모든 것이 기대한 대로 계속 동작하는지 확인할 수 있습니다.
## `bump-pydantic` { #bump-pydantic }
많은 경우, 커스터마이징 없이 일반적인 Pydantic 모델을 사용하고 있다면 Pydantic v1에서 Pydantic v2로의 마이그레이션 과정 대부분을 자동화할 수 있습니다.
같은 Pydantic 팀이 제공하는 <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a>를 사용할 수 있습니다.
이 도구는 변경해야 하는 코드의 대부분을 자동으로 바꾸는 데 도움을 줍니다.
그 다음 테스트를 실행해서 모든 것이 동작하는지 확인하면 됩니다. 잘 된다면 끝입니다. 😎
## v2 안의 Pydantic v1 { #pydantic-v1-in-v2 }
Pydantic v2는 Pydantic v1의 모든 것을 서브모듈 `pydantic.v1`로 포함합니다. 하지만 이는 Python 3.13보다 높은 버전에서는 더 이상 지원되지 않습니다.
즉, Pydantic v2의 최신 버전을 설치한 뒤, 이 서브모듈에서 예전 Pydantic v1 구성 요소를 import하여 예전 Pydantic v1을 설치한 것처럼 사용할 수 있습니다.
{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
### v2 안의 Pydantic v1에 대한 FastAPI 지원 { #fastapi-support-for-pydantic-v1-in-v2 }
FastAPI 0.119.0부터는 v2로의 마이그레이션을 쉽게 하기 위해, Pydantic v2 내부의 Pydantic v1에 대해서도 부분적인 지원이 있습니다.
따라서 Pydantic을 최신 v2로 업그레이드하고, import를 `pydantic.v1` 서브모듈을 사용하도록 바꾸면, 많은 경우 그대로 동작합니다.
{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
/// warning | 경고
Pydantic 팀이 Python 3.14부터 최신 Python 버전에서 Pydantic v1을 더 이상 지원하지 않으므로, `pydantic.v1`을 사용하는 것 역시 Python 3.14 이상에서는 지원되지 않는다는 점을 염두에 두세요.
///
### 같은 앱에서 Pydantic v1과 v2 함께 사용하기 { #pydantic-v1-and-v2-on-the-same-app }
Pydantic에서는 Pydantic v2 모델의 필드를 Pydantic v1 모델로 정의하거나 그 반대로 하는 것을 **지원하지 않습니다**.
```mermaid
graph TB
subgraph "❌ Not Supported"
direction TB
subgraph V2["Pydantic v2 Model"]
V1Field["Pydantic v1 Model"]
end
subgraph V1["Pydantic v1 Model"]
V2Field["Pydantic v2 Model"]
end
end
style V2 fill:#f9fff3
style V1 fill:#fff6f0
style V1Field fill:#fff6f0
style V2Field fill:#f9fff3
```
...하지만 같은 앱에서 Pydantic v1과 v2를 사용하되, 모델을 분리해서 둘 수는 있습니다.
```mermaid
graph TB
subgraph "✅ Supported"
direction TB
subgraph V2["Pydantic v2 Model"]
V2Field["Pydantic v2 Model"]
end
subgraph V1["Pydantic v1 Model"]
V1Field["Pydantic v1 Model"]
end
end
style V2 fill:#f9fff3
style V1 fill:#fff6f0
style V1Field fill:#fff6f0
style V2Field fill:#f9fff3
```
어떤 경우에는 FastAPI 앱의 같은 **경로 처리**에서 Pydantic v1과 v2 모델을 함께 사용하는 것도 가능합니다:
{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
위 예제에서 입력 모델은 Pydantic v1 모델이고, 출력 모델(`response_model=ItemV2`로 정의됨)은 Pydantic v2 모델입니다.
### Pydantic v1 파라미터 { #pydantic-v1-parameters }
Pydantic v1 모델과 함께 `Body`, `Query`, `Form` 등 파라미터용 FastAPI 전용 도구 일부를 사용해야 한다면, Pydantic v2로의 마이그레이션을 마칠 때까지 `fastapi.temp_pydantic_v1_params`에서 import할 수 있습니다:
{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
### 단계적으로 마이그레이션하기 { #migrate-in-steps }
/// tip | 팁
먼저 `bump-pydantic`로 시도해 보세요. 테스트가 통과하고 잘 동작한다면, 한 번의 명령으로 끝입니다. ✨
///
`bump-pydantic`가 여러분의 사용 사례에 맞지 않는다면, 같은 앱에서 Pydantic v1과 v2 모델을 모두 지원하는 기능을 이용해 Pydantic v2로 점진적으로 마이그레이션할 수 있습니다.
먼저 Pydantic을 최신 v2로 업그레이드하고, 모든 모델의 import를 `pydantic.v1`을 사용하도록 바꿀 수 있습니다.
그 다음 Pydantic v1에서 v2로 모델을 그룹 단위로, 점진적인 단계로 마이그레이션을 시작하면 됩니다. 🚶

View File

@@ -1,102 +0,0 @@
# 입력과 출력에 대해 OpenAPI 스키마를 분리할지 여부 { #separate-openapi-schemas-for-input-and-output-or-not }
**Pydantic v2**가 릴리스된 이후, 생성되는 OpenAPI는 이전보다 조금 더 정확하고 **올바르게** 만들어집니다. 😎
실제로 어떤 경우에는, 같은 Pydantic 모델에 대해 OpenAPI 안에 **두 개의 JSON Schema**가 생기기도 합니다. **기본값(default value)**이 있는지 여부에 따라, 입력용과 출력용으로 나뉩니다.
이것이 어떻게 동작하는지, 그리고 필요하다면 어떻게 변경할 수 있는지 살펴보겠습니다.
## 입력과 출력을 위한 Pydantic 모델 { #pydantic-models-for-input-and-output }
예를 들어, 다음처럼 기본값이 있는 Pydantic 모델이 있다고 해보겠습니다:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
### 입력용 모델 { #model-for-input }
이 모델을 다음처럼 입력으로 사용하면:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *}
...`description` 필드는 **필수가 아닙니다**. `None`이라는 기본값이 있기 때문입니다.
### 문서에서의 입력 모델 { #input-model-in-docs }
문서에서 `description` 필드에 **빨간 별표**가 없고, 필수로 표시되지 않는 것을 확인할 수 있습니다:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image01.png">
</div>
### 출력용 모델 { #model-for-output }
하지만 같은 모델을 다음처럼 출력으로 사용하면:
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *}
...`description`에 기본값이 있기 때문에, 그 필드에 대해 **아무것도 반환하지 않더라도** 여전히 그 **기본값**이 들어가게 됩니다.
### 출력 응답 데이터용 모델 { #model-for-output-response-data }
문서에서 직접 동작시켜 응답을 확인해 보면, 코드가 `description` 필드 중 하나에 아무것도 추가하지 않았더라도 JSON 응답에는 기본값(`null`)이 포함되어 있습니다:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image02.png">
</div>
이는 해당 필드가 **항상 값을 가진다는 것**을 의미합니다. 다만 그 값이 때로는 `None`(JSON에서는 `null`)일 수 있습니다.
즉, API를 사용하는 클라이언트는 값이 존재하는지 여부를 확인할 필요가 없고, **필드가 항상 존재한다고 가정**할 수 있습니다. 다만 어떤 경우에는 기본값 `None`이 들어갑니다.
이를 OpenAPI에서 표현하는 방법은, 그 필드를 **required**로 표시하는 것입니다. 항상 존재하기 때문입니다.
이 때문에, 하나의 모델이라도 **입력용인지 출력용인지**에 따라 JSON Schema가 달라질 수 있습니다:
* **입력**에서는 `description`**필수가 아님**
* **출력**에서는 **필수임** (그리고 값은 `None`일 수도 있으며, JSON 용어로는 `null`)
### 문서에서의 출력용 모델 { #model-for-output-in-docs }
문서에서 출력 모델을 확인해 보면, `name``description` **둘 다** **빨간 별표**로 **필수**로 표시되어 있습니다:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image03.png">
</div>
### 문서에서의 입력과 출력 모델 { #model-for-input-and-output-in-docs }
또 OpenAPI에서 사용 가능한 모든 Schemas(JSON Schemas)를 확인해 보면, `Item-Input` 하나와 `Item-Output` 하나, 이렇게 두 개가 있는 것을 볼 수 있습니다.
`Item-Input`에서는 `description`**필수가 아니며**, 빨간 별표가 없습니다.
하지만 `Item-Output`에서는 `description`**필수이며**, 빨간 별표가 있습니다.
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image04.png">
</div>
**Pydantic v2**의 이 기능 덕분에 API 문서는 더 **정밀**해지고, 자동 생성된 클라이언트와 SDK가 있다면 그것들도 더 정밀해져서 더 나은 **developer experience**와 일관성을 제공할 수 있습니다. 🎉
## 스키마를 분리하지 않기 { #do-not-separate-schemas }
이제 어떤 경우에는 **입력과 출력에 대해 같은 스키마를 사용**하고 싶을 수도 있습니다.
가장 대표적인 경우는, 이미 자동 생성된 클라이언트 코드/SDK가 있고, 아직은 그 자동 생성된 클라이언트 코드/SDK들을 전부 업데이트하고 싶지 않은 경우입니다. 언젠가는 업데이트해야 할 가능성이 높지만, 지금 당장은 아닐 수도 있습니다.
그런 경우에는, **FastAPI**에서 `separate_input_output_schemas=False` 파라미터로 이 기능을 비활성화할 수 있습니다.
/// info | 정보
`separate_input_output_schemas` 지원은 FastAPI `0.102.0`에 추가되었습니다. 🤓
///
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *}
### 문서에서 입력과 출력 모델에 같은 스키마 사용 { #same-schema-for-input-and-output-models-in-docs }
이제 모델에 대해 입력과 출력 모두에 사용되는 단일 스키마(오직 `Item`만)가 생성되며, `description`은 **필수가 아닌 것**으로 표시됩니다:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
</div>

View File

@@ -1,7 +0,0 @@
# 데이터베이스 테스트하기 { #testing-a-database }
데이터베이스, SQL, SQLModel에 대해서는 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel 문서</a>에서 학습할 수 있습니다. 🤓
<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">FastAPI에서 SQLModel을 사용하는 방법에 대한 미니 튜토리얼</a>도 있습니다. ✨
해당 튜토리얼에는 <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">SQL 데이터베이스 테스트</a>에 대한 섹션도 포함되어 있습니다. 😎

View File

@@ -1,504 +0,0 @@
# 더 큰 애플리케이션 - 여러 파일 { #bigger-applications-multiple-files }
애플리케이션이나 웹 API를 만들 때, 모든 것을 하나의 파일에 담을 수 있는 경우는 드뭅니다.
**FastAPI**는 모든 유연성을 유지하면서도 애플리케이션을 구조화할 수 있게 해주는 편리한 도구를 제공합니다.
/// info | 정보
Flask를 사용해 보셨다면, 이는 Flask의 Blueprints에 해당하는 개념입니다.
///
## 예시 파일 구조 { #an-example-file-structure }
다음과 같은 파일 구조가 있다고 해봅시다:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │ ├── __init__.py
│   │ ├── items.py
│   │ └── users.py
│   └── internal
│   ├── __init__.py
│   └── admin.py
```
/// tip | 팁
`__init__.py` 파일이 여러 개 있습니다: 각 디렉터리 또는 하위 디렉터리에 하나씩 있습니다.
이 파일들이 한 파일의 코드를 다른 파일로 import할 수 있게 해줍니다.
예를 들어 `app/main.py`에는 다음과 같은 줄이 있을 수 있습니다:
```
from app.routers import items
```
///
* `app` 디렉터리에는 모든 것이 들어 있습니다. 그리고 비어 있는 파일 `app/__init__.py`가 있어 "Python package"(“Python modules”의 모음)인 `app`이 됩니다.
* `app/main.py` 파일이 있습니다. Python package(`__init__.py` 파일이 있는 디렉터리) 안에 있으므로, 이 package의 "module"입니다: `app.main`.
* `app/dependencies.py` 파일도 있습니다. `app/main.py`와 마찬가지로 "module"입니다: `app.dependencies`.
* `app/routers/` 하위 디렉터리가 있고, 여기에 또 `__init__.py` 파일이 있으므로 "Python subpackage"입니다: `app.routers`.
* `app/routers/items.py` 파일은 `app/routers/` package 안에 있으므로, submodule입니다: `app.routers.items`.
* `app/routers/users.py`도 동일하게 또 다른 submodule입니다: `app.routers.users`.
* `app/internal/` 하위 디렉터리도 있고 여기에 `__init__.py`가 있으므로 또 다른 "Python subpackage"입니다: `app.internal`.
* 그리고 `app/internal/admin.py` 파일은 또 다른 submodule입니다: `app.internal.admin`.
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
같은 파일 구조에 주석을 추가하면 다음과 같습니다:
```bash
.
├── app # "app" is a Python package
│   ├── __init__.py # this file makes "app" a "Python package"
│   ├── main.py # "main" module, e.g. import app.main
│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
│   └── routers # "routers" is a "Python subpackage"
│   │ ├── __init__.py # makes "routers" a "Python subpackage"
│   │ ├── items.py # "items" submodule, e.g. import app.routers.items
│   │ └── users.py # "users" submodule, e.g. import app.routers.users
│   └── internal # "internal" is a "Python subpackage"
│   ├── __init__.py # makes "internal" a "Python subpackage"
│   └── admin.py # "admin" submodule, e.g. import app.internal.admin
```
## `APIRouter` { #apirouter }
사용자만 처리하는 전용 파일이 `/app/routers/users.py`의 submodule이라고 해봅시다.
코드를 정리하기 위해 사용자와 관련된 *path operations*를 나머지 코드와 분리해 두고 싶을 것입니다.
하지만 이것은 여전히 같은 **FastAPI** 애플리케이션/웹 API의 일부입니다(같은 "Python Package"의 일부입니다).
`APIRouter`를 사용해 해당 모듈의 *path operations*를 만들 수 있습니다.
### `APIRouter` import하기 { #import-apirouter }
`FastAPI` 클래스와 동일한 방식으로 import하고 "instance"를 생성합니다:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
### `APIRouter`로 *path operations* 만들기 { #path-operations-with-apirouter }
그 다음 이를 사용해 *path operations*를 선언합니다.
`FastAPI` 클래스를 사용할 때와 동일한 방식으로 사용합니다:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
`APIRouter`는 "미니 `FastAPI`" 클래스라고 생각할 수 있습니다.
동일한 옵션들이 모두 지원됩니다.
동일한 `parameters`, `responses`, `dependencies`, `tags` 등등.
/// tip | 팁
이 예시에서는 변수 이름이 `router`이지만, 원하는 이름으로 지어도 됩니다.
///
이제 이 `APIRouter`를 메인 `FastAPI` 앱에 포함(include)할 것이지만, 먼저 dependencies와 다른 `APIRouter` 하나를 확인해 보겠습니다.
## Dependencies { #dependencies }
애플리케이션의 여러 위치에서 사용되는 dependencies가 일부 필요하다는 것을 알 수 있습니다.
그래서 이를 별도의 `dependencies` 모듈(`app/dependencies.py`)에 둡니다.
이제 간단한 dependency를 사용해 커스텀 `X-Token` 헤더를 읽어 보겠습니다:
{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | 팁
이 예시를 단순화하기 위해 임의로 만든 헤더를 사용하고 있습니다.
하지만 실제 상황에서는 통합된 [Security 유틸리티](security/index.md){.internal-link target=_blank}를 사용하는 것이 더 좋은 결과를 얻을 수 있습니다.
///
## `APIRouter`가 있는 또 다른 모듈 { #another-module-with-apirouter }
애플리케이션의 "items"를 처리하는 전용 endpoint들도 `app/routers/items.py` 모듈에 있다고 해봅시다.
여기에는 다음에 대한 *path operations*가 있습니다:
* `/items/`
* `/items/{item_id}`
구조는 `app/routers/users.py`와 완전히 동일합니다.
하지만 우리는 조금 더 똑똑하게, 코드를 약간 단순화하고 싶습니다.
이 모듈의 모든 *path operations*에는 다음이 동일하게 적용됩니다:
* 경로 `prefix`: `/items`.
* `tags`: (태그 하나: `items`).
* 추가 `responses`.
* `dependencies`: 모두 우리가 만든 `X-Token` dependency가 필요합니다.
따라서 각 *path operation*마다 매번 모두 추가하는 대신, `APIRouter`에 한 번에 추가할 수 있습니다.
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
각 *path operation*의 경로는 다음처럼 `/`로 시작해야 하므로:
```Python hl_lines="1"
@router.get("/{item_id}")
async def read_item(item_id: str):
...
```
...prefix에는 마지막 `/`가 포함되면 안 됩니다.
따라서 이 경우 prefix는 `/items`입니다.
또한 이 router에 포함된 모든 *path operations*에 적용될 `tags` 목록과 추가 `responses`도 넣을 수 있습니다.
그리고 router의 모든 *path operations*에 추가될 `dependencies` 목록도 추가할 수 있으며, 해당 경로들로 들어오는 각 요청마다 실행/해결됩니다.
/// tip | 팁
[*path operation decorator의 dependencies*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}와 마찬가지로, *path operation function*에 어떤 값도 전달되지 않습니다.
///
최종적으로 item 경로는 다음과 같습니다:
* `/items/`
* `/items/{item_id}`
...의도한 그대로입니다.
* 단일 문자열 `"items"`를 포함하는 태그 목록으로 표시됩니다.
* 이 "tags"는 자동 대화형 문서 시스템(OpenAPI 사용)에 특히 유용합니다.
* 모두 미리 정의된 `responses`를 포함합니다.
* 이 모든 *path operations*는 실행되기 전에 `dependencies` 목록이 평가/실행됩니다.
* 특정 *path operation*에 dependencies를 추가로 선언하면 **그것들도 실행됩니다**.
* router dependencies가 먼저 실행되고, 그 다음에 [decorator의 `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, 그리고 일반 파라미터 dependencies가 실행됩니다.
* [`scopes`가 있는 `Security` dependencies](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}도 추가할 수 있습니다.
/// tip | 팁
`APIRouter`에 `dependencies`를 두는 것은 예를 들어 전체 *path operations* 그룹에 인증을 요구할 때 사용할 수 있습니다. 각 경로 처리에 개별적으로 dependencies를 추가하지 않아도 됩니다.
///
/// check | 확인
`prefix`, `tags`, `responses`, `dependencies` 파라미터는 (다른 많은 경우와 마찬가지로) 코드 중복을 피하도록 도와주는 **FastAPI**의 기능입니다.
///
### dependencies import하기 { #import-the-dependencies }
이 코드는 모듈 `app.routers.items`, 파일 `app/routers/items.py`에 있습니다.
그리고 dependency 함수는 모듈 `app.dependencies`, 파일 `app/dependencies.py`에서 가져와야 합니다.
그래서 dependencies에 대해 `..`를 사용하는 상대 import를 사용합니다:
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
#### 상대 import가 동작하는 방식 { #how-relative-imports-work }
/// tip | 팁
import가 동작하는 방식을 완벽히 알고 있다면, 아래 다음 섹션으로 넘어가세요.
///
다음과 같이 점 하나 `.`를 쓰면:
```Python
from .dependencies import get_token_header
```
의미는 다음과 같습니다:
* 이 모듈(파일 `app/routers/items.py`)이 속한 같은 package(디렉터리 `app/routers/`)에서 시작해서...
* `dependencies` 모듈(가상의 파일 `app/routers/dependencies.py`)을 찾고...
* 그 안에서 함수 `get_token_header`를 import합니다.
하지만 그 파일은 존재하지 않습니다. dependencies는 `app/dependencies.py` 파일에 있습니다.
우리 앱/파일 구조를 다시 떠올려 보세요:
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
---
다음처럼 점 두 개 `..`를 쓰면:
```Python
from ..dependencies import get_token_header
```
의미는 다음과 같습니다:
* 이 모듈(파일 `app/routers/items.py`)이 속한 같은 package(디렉터리 `app/routers/`)에서 시작해서...
* 상위 package(디렉터리 `app/`)로 올라가고...
* 그 안에서 `dependencies` 모듈(파일 `app/dependencies.py`)을 찾고...
* 그 안에서 함수 `get_token_header`를 import합니다.
이렇게 하면 제대로 동작합니다! 🎉
---
같은 방식으로 점 세 개 `...`를 사용했다면:
```Python
from ...dependencies import get_token_header
```
의미는 다음과 같습니다:
* 이 모듈(파일 `app/routers/items.py`)이 속한 같은 package(디렉터리 `app/routers/`)에서 시작해서...
* 상위 package(디렉터리 `app/`)로 올라가고...
* 그 package의 상위로 또 올라가는데(상위 package가 없습니다, `app`이 최상위입니다 😱)...
* 그 안에서 `dependencies` 모듈(파일 `app/dependencies.py`)을 찾고...
* 그 안에서 함수 `get_token_header`를 import합니다.
이는 `app/` 위쪽의 어떤 package(자신의 `__init__.py` 파일 등을 가진)에 대한 참조가 됩니다. 하지만 우리는 그런 것이 없습니다. 그래서 이 예시에서는 에러가 발생합니다. 🚨
이제 어떻게 동작하는지 알았으니, 앱이 얼마나 복잡하든 상대 import를 사용할 수 있습니다. 🤓
### 커스텀 `tags`, `responses`, `dependencies` 추가하기 { #add-some-custom-tags-responses-and-dependencies }
`APIRouter`에 이미 prefix `/items`와 `tags=["items"]`를 추가했기 때문에 각 *path operation*에 이를 추가하지 않습니다.
하지만 특정 *path operation*에만 적용될 _추가_ `tags`를 더할 수도 있고, 그 *path operation* 전용의 추가 `responses`도 넣을 수 있습니다:
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | 팁
이 마지막 경로 처리는 `["items", "custom"]` 태그 조합을 갖게 됩니다.
그리고 문서에는 `404`용 응답과 `403`용 응답, 두 가지 모두가 표시됩니다.
///
## 메인 `FastAPI` { #the-main-fastapi }
이제 `app/main.py` 모듈을 봅시다.
여기에서 `FastAPI` 클래스를 import하고 사용합니다.
이 파일은 모든 것을 하나로 엮는 애플리케이션의 메인 파일이 될 것입니다.
그리고 대부분의 로직이 각자의 특정 모듈로 분리되어 있으므로, 메인 파일은 꽤 단순해집니다.
### `FastAPI` import하기 { #import-fastapi }
평소처럼 `FastAPI` 클래스를 import하고 생성합니다.
또한 각 `APIRouter`의 dependencies와 결합될 [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}도 선언할 수 있습니다:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
### `APIRouter` import하기 { #import-the-apirouter }
이제 `APIRouter`가 있는 다른 submodule들을 import합니다:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
`app/routers/users.py`와 `app/routers/items.py` 파일은 같은 Python package `app`에 속한 submodule들이므로, 점 하나 `.`를 사용해 "상대 import"로 가져올 수 있습니다.
### import가 동작하는 방식 { #how-the-importing-works }
다음 구문은:
```Python
from .routers import items, users
```
의미는 다음과 같습니다:
* 이 모듈(파일 `app/main.py`)이 속한 같은 package(디렉터리 `app/`)에서 시작해서...
* subpackage `routers`(디렉터리 `app/routers/`)를 찾고...
* 그 안에서 submodule `items`(파일 `app/routers/items.py`)와 `users`(파일 `app/routers/users.py`)를 import합니다...
`items` 모듈에는 `router` 변수(`items.router`)가 있습니다. 이는 `app/routers/items.py` 파일에서 만든 것과 동일하며 `APIRouter` 객체입니다.
그리고 `users` 모듈도 같은 방식입니다.
다음처럼 import할 수도 있습니다:
```Python
from app.routers import items, users
```
/// info | 정보
첫 번째 버전은 "상대 import"입니다:
```Python
from .routers import items, users
```
두 번째 버전은 "절대 import"입니다:
```Python
from app.routers import items, users
```
Python Packages와 Modules에 대해 더 알아보려면 <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">Modules에 대한 Python 공식 문서</a>를 읽어보세요.
///
### 이름 충돌 피하기 { #avoid-name-collisions }
submodule `items`를 직접 import하고, 그 안의 `router` 변수만 import하지는 않습니다.
이는 submodule `users`에도 `router`라는 이름의 변수가 있기 때문입니다.
만약 다음처럼 순서대로 import했다면:
```Python
from .routers.items import router
from .routers.users import router
```
`users`의 `router`가 `items`의 `router`를 덮어써서 동시에 사용할 수 없게 됩니다.
따라서 같은 파일에서 둘 다 사용할 수 있도록 submodule들을 직접 import합니다:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
### `users`와 `items`용 `APIRouter` 포함하기 { #include-the-apirouters-for-users-and-items }
이제 submodule `users`와 `items`의 `router`를 포함해 봅시다:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
/// info | 정보
`users.router`는 `app/routers/users.py` 파일 안의 `APIRouter`를 담고 있습니다.
`items.router`는 `app/routers/items.py` 파일 안의 `APIRouter`를 담고 있습니다.
///
`app.include_router()`로 각 `APIRouter`를 메인 `FastAPI` 애플리케이션에 추가할 수 있습니다.
그 router의 모든 route가 애플리케이션의 일부로 포함됩니다.
/// note Technical Details | 기술 세부사항
내부적으로는 `APIRouter`에 선언된 각 *path operation*마다 *path operation*을 실제로 생성합니다.
즉, 내부적으로는 모든 것이 동일한 하나의 앱인 것처럼 동작합니다.
///
/// check | 확인
router를 포함(include)할 때 성능을 걱정할 필요는 없습니다.
이 작업은 마이크로초 단위이며 시작 시에만 발생합니다.
따라서 성능에 영향을 주지 않습니다. ⚡
///
### 커스텀 `prefix`, `tags`, `responses`, `dependencies`로 `APIRouter` 포함하기 { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
이제 조직에서 `app/internal/admin.py` 파일을 받았다고 가정해 봅시다.
여기에는 조직에서 여러 프로젝트 간에 공유하는 관리자용 *path operations*가 있는 `APIRouter`가 들어 있습니다.
이 예시에서는 매우 단순하게 만들겠습니다. 하지만 조직 내 다른 프로젝트와 공유되기 때문에, 이를 수정할 수 없어 `prefix`, `dependencies`, `tags` 등을 `APIRouter`에 직접 추가할 수 없다고 해봅시다:
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
하지만 `APIRouter`를 포함할 때 커스텀 `prefix`를 지정해 모든 *path operations*가 `/admin`으로 시작하게 하고, 이 프로젝트에서 이미 가진 `dependencies`로 보호하고, `tags`와 `responses`도 포함하고 싶습니다.
원래 `APIRouter`를 수정하지 않고도 `app.include_router()`에 파라미터를 전달해서 이를 선언할 수 있습니다:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
이렇게 하면 원래 `APIRouter`는 수정되지 않으므로, 조직 내 다른 프로젝트에서도 동일한 `app/internal/admin.py` 파일을 계속 공유할 수 있습니다.
결과적으로 우리 앱에서 `admin` 모듈의 각 *path operations*는 다음을 갖게 됩니다:
* prefix `/admin`.
* tag `admin`.
* dependency `get_token_header`.
* 응답 `418`. 🍵
하지만 이는 우리 앱에서 그 `APIRouter`에만 영향을 주며, 이를 사용하는 다른 코드에는 영향을 주지 않습니다.
따라서 다른 프로젝트들은 같은 `APIRouter`를 다른 인증 방식으로 사용할 수도 있습니다.
### *path operation* 포함하기 { #include-a-path-operation }
*path operations*를 `FastAPI` 앱에 직접 추가할 수도 있습니다.
여기서는 가능하다는 것을 보여주기 위해... 그냥 해봅니다 🤷:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
그리고 `app.include_router()`로 추가한 다른 모든 *path operations*와 함께 올바르게 동작합니다.
/// info | 정보
**참고**: 이는 매우 기술적인 세부사항이라 아마 **그냥 건너뛰어도 됩니다**.
---
`APIRouter`는 "mount"되는 것이 아니며, 애플리케이션의 나머지 부분과 격리되어 있지 않습니다.
이는 OpenAPI 스키마와 사용자 인터페이스에 그들의 *path operations*를 포함시키고 싶기 때문입니다.
나머지와 독립적으로 격리해 "mount"할 수 없으므로, *path operations*는 직접 포함되는 것이 아니라 "clone"(재생성)됩니다.
///
## 자동 API 문서 확인하기 { #check-the-automatic-api-docs }
이제 앱을 실행하세요:
<div class="termy">
```console
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
그리고 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>에서 문서를 여세요.
올바른 경로(및 prefix)와 올바른 태그를 사용해, 모든 submodule의 경로를 포함한 자동 API 문서를 볼 수 있습니다:
<img src="/img/tutorial/bigger-applications/image01.png">
## 같은 router를 다른 `prefix`로 여러 번 포함하기 { #include-the-same-router-multiple-times-with-different-prefix }
`.include_router()`를 사용해 *같은* router를 서로 다른 prefix로 여러 번 포함할 수도 있습니다.
예를 들어 `/api/v1`과 `/api/latest`처럼 서로 다른 prefix로 동일한 API를 노출할 때 유용할 수 있습니다.
이는 고급 사용 방식이라 실제로 필요하지 않을 수도 있지만, 필요할 때를 위해 제공됩니다.
## `APIRouter`에 다른 `APIRouter` 포함하기 { #include-an-apirouter-in-another }
`APIRouter`를 `FastAPI` 애플리케이션에 포함할 수 있는 것과 같은 방식으로, 다음을 사용해 `APIRouter`를 다른 `APIRouter`에 포함할 수 있습니다:
```Python
router.include_router(other_router)
```
`FastAPI` 앱에 `router`를 포함하기 전에 수행해야 하며, 그래야 `other_router`의 *path operations*도 함께 포함됩니다.

View File

@@ -1,100 +0,0 @@
# Body - 업데이트 { #body-updates }
## `PUT`으로 교체 업데이트하기 { #update-replacing-with-put }
항목을 업데이트하려면 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> 작업을 사용할 수 있습니다.
`jsonable_encoder`를 사용해 입력 데이터를 JSON으로 저장할 수 있는 데이터로 변환할 수 있습니다(예: NoSQL 데이터베이스 사용 시). 예를 들어 `datetime``str`로 변환하는 경우입니다.
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
`PUT`은 기존 데이터를 **대체**해야 하는 데이터를 받는 데 사용합니다.
### 대체 시 주의사항 { #warning-about-replacing }
즉, `PUT`으로 항목 `bar`를 업데이트하면서 다음과 같은 body를 보낸다면:
```Python
{
"name": "Barz",
"price": 3,
"description": None,
}
```
이미 저장된 속성 `"tax": 20.2`가 포함되어 있지 않기 때문에, 입력 모델은 `"tax": 10.5`라는 기본값을 사용하게 됩니다.
그리고 데이터는 그 “새로운” `tax``10.5`로 저장됩니다.
## `PATCH`로 부분 업데이트하기 { #partial-updates-with-patch }
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> 작업을 사용해 데이터를 *부분적으로* 업데이트할 수도 있습니다.
이는 업데이트하려는 데이터만 보내고, 나머지는 그대로 두는 것을 의미합니다.
/// note | 참고
`PATCH``PUT`보다 덜 일반적으로 사용되고 덜 알려져 있습니다.
그리고 많은 팀이 부분 업데이트에도 `PUT`만 사용합니다.
여러분은 원하는 방식으로 **자유롭게** 사용할 수 있으며, **FastAPI**는 어떤 제한도 강제하지 않습니다.
다만 이 가이드는 의도된 사용 방식이 대략 어떻게 되는지를 보여줍니다.
///
### Pydantic의 `exclude_unset` 파라미터 사용하기 { #using-pydantics-exclude-unset-parameter }
부분 업데이트를 받으려면 Pydantic 모델의 `.model_dump()`에서 `exclude_unset` 파라미터를 사용하는 것이 매우 유용합니다.
예: `item.model_dump(exclude_unset=True)`.
이는 `item` 모델을 만들 때 실제로 설정된 데이터만 포함하는 `dict`를 생성하고, 기본값은 제외합니다.
그 다음 이를 사용해 (요청에서 전송되어) 설정된 데이터만 포함하고 기본값은 생략한 `dict`를 만들 수 있습니다:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
### Pydantic의 `update` 파라미터 사용하기 { #using-pydantics-update-parameter }
이제 `.model_copy()`를 사용해 기존 모델의 복사본을 만들고, 업데이트할 데이터가 들어있는 `dict``update` 파라미터로 전달할 수 있습니다.
예: `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
### 부분 업데이트 요약 { #partial-updates-recap }
정리하면, 부분 업데이트를 적용하려면 다음을 수행합니다:
* (선택 사항) `PUT` 대신 `PATCH`를 사용합니다.
* 저장된 데이터를 조회합니다.
* 그 데이터를 Pydantic 모델에 넣습니다.
* 입력 모델에서 기본값이 제외된 `dict`를 생성합니다(`exclude_unset` 사용).
* 이렇게 하면 모델의 기본값으로 이미 저장된 값을 덮어쓰지 않고, 사용자가 실제로 설정한 값만 업데이트할 수 있습니다.
* 저장된 모델의 복사본을 만들고, 받은 부분 업데이트로 해당 속성들을 갱신합니다(`update` 파라미터 사용).
* 복사한 모델을 DB에 저장할 수 있는 형태로 변환합니다(예: `jsonable_encoder` 사용).
* 이는 모델의 `.model_dump()` 메서드를 다시 사용하는 것과 비슷하지만, JSON으로 변환 가능한 데이터 타입으로 값이 확실히 변환되도록 보장합니다(예: `datetime``str`).
* 데이터를 DB에 저장합니다.
* 업데이트된 모델을 반환합니다.
{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *}
/// tip | 팁
동일한 기법을 HTTP `PUT` 작업에서도 실제로 사용할 수 있습니다.
하지만 여기의 예시는 이런 사용 사례를 위해 만들어진 `PATCH`를 사용합니다.
///
/// note | 참고
입력 모델은 여전히 검증된다는 점에 유의하세요.
따라서 모든 속성을 생략할 수 있는 부분 업데이트를 받으려면, 모든 속성이 optional로 표시된(기본값을 가지거나 `None`을 기본값으로 가지는) 모델이 필요합니다.
**업데이트**를 위한 “모든 값이 optional인” 모델과, **생성**을 위한 “필수 값이 있는” 모델을 구분하려면 [추가 모델](extra-models.md){.internal-link target=_blank}에 설명된 아이디어를 사용할 수 있습니다.
///

View File

@@ -1,105 +0,0 @@
# 하위 의존성 { #sub-dependencies }
**하위 의존성**을 가지는 의존성을 만들 수 있습니다.
필요한 만큼 **깊게** 중첩할 수도 있습니다.
이것을 해결하는 일은 **FastAPI**가 알아서 처리합니다.
## 첫 번째 의존성 "dependable" { #first-dependency-dependable }
다음과 같이 첫 번째 의존성("dependable")을 만들 수 있습니다:
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *}
이 의존성은 선택적 쿼리 파라미터 `q``str`로 선언하고, 그대로 반환합니다.
매우 단순한 예시(그다지 유용하진 않음)이지만, 하위 의존성이 어떻게 동작하는지에 집중하는 데 도움이 됩니다.
## 두 번째 의존성 "dependable"과 "dependant" { #second-dependency-dependable-and-dependant }
그다음, 또 다른 의존성 함수("dependable")를 만들 수 있는데, 이 함수는 동시에 자기 자신의 의존성도 선언합니다(그래서 "dependant"이기도 합니다):
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
선언된 파라미터를 살펴보겠습니다:
* 이 함수 자체가 의존성("dependable")이지만, 다른 의존성도 하나 선언합니다(즉, 다른 무언가에 "의존"합니다).
* `query_extractor`에 의존하며, 그 반환값을 파라미터 `q`에 할당합니다.
* 또한 선택적 `last_query` 쿠키를 `str`로 선언합니다.
* 사용자가 쿼리 `q`를 제공하지 않았다면, 이전에 쿠키에 저장해 둔 마지막 쿼리를 사용합니다.
## 의존성 사용하기 { #use-the-dependency }
그다음 다음과 같이 의존성을 사용할 수 있습니다:
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *}
/// info | 정보
*경로 처리 함수*에서는 `query_or_cookie_extractor`라는 의존성 하나만 선언하고 있다는 점에 주목하세요.
하지만 **FastAPI**는 `query_or_cookie_extractor`를 호출하는 동안 그 결과를 전달하기 위해, 먼저 `query_extractor`를 해결해야 한다는 것을 알고 있습니다.
///
```mermaid
graph TB
query_extractor(["query_extractor"])
query_or_cookie_extractor(["query_or_cookie_extractor"])
read_query["/items/"]
query_extractor --> query_or_cookie_extractor --> read_query
```
## 같은 의존성을 여러 번 사용하기 { #using-the-same-dependency-multiple-times }
같은 *경로 처리*에 대해 의존성 중 하나가 여러 번 선언되는 경우(예: 여러 의존성이 공통 하위 의존성을 갖는 경우), **FastAPI**는 그 하위 의존성을 요청당 한 번만 호출해야 한다는 것을 알고 있습니다.
그리고 같은 요청에 대해 동일한 의존성을 여러 번 호출하는 대신, 반환값을 <abbr title="계산/생성된 값을 저장해 두었다가, 다시 계산하지 않고 재사용하기 위한 유틸리티/시스템.">"cache"</abbr>에 저장하고, 그 요청에서 해당 값이 필요한 모든 "dependants"에 전달합니다.
고급 시나리오로, 같은 요청에서 "cached" 값을 쓰는 대신 매 단계마다(아마도 여러 번) 의존성이 호출되어야 한다는 것을 알고 있다면, `Depends`를 사용할 때 `use_cache=False` 파라미터를 설정할 수 있습니다:
//// tab | Python 3.9+
```Python hl_lines="1"
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
return {"fresh_value": fresh_value}
```
////
//// tab | Python 3.9+ 비 Annotated
/// tip | 팁
가능하다면 `Annotated` 버전을 사용하는 것을 권장합니다.
///
```Python hl_lines="1"
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
return {"fresh_value": fresh_value}
```
////
## 정리 { #recap }
여기서 사용한 그럴듯한 용어들을 제외하면, **Dependency Injection** 시스템은 꽤 단순합니다.
*경로 처리 함수*와 같은 형태의 함수들일 뿐입니다.
하지만 여전히 매우 강력하며, 임의로 깊게 중첩된 의존성 "그래프"(트리)를 선언할 수 있습니다.
/// tip | 팁
이 단순한 예시만 보면 그다지 유용해 보이지 않을 수도 있습니다.
하지만 **보안**에 관한 챕터에서 이것이 얼마나 유용한지 보게 될 것입니다.
또한 얼마나 많은 코드를 아껴주는지도 보게 될 것입니다.
///

View File

@@ -1,244 +0,0 @@
# 오류 처리 { #handling-errors }
API를 사용하는 클라이언트에 오류를 알려야 하는 상황은 많이 있습니다.
이 클라이언트는 프론트엔드가 있는 브라우저일 수도 있고, 다른 사람이 작성한 코드일 수도 있고, IoT 장치일 수도 있습니다.
클라이언트에 다음과 같은 내용을 알려야 할 수도 있습니다:
* 클라이언트가 해당 작업을 수행할 충분한 권한이 없습니다.
* 클라이언트가 해당 리소스에 접근할 수 없습니다.
* 클라이언트가 접근하려고 한 항목이 존재하지 않습니다.
* 등등.
이런 경우 보통 **400**번대(400에서 499) 범위의 **HTTP 상태 코드**를 반환합니다.
이는 200번대 HTTP 상태 코드(200에서 299)와 비슷합니다. "200" 상태 코드는 어떤 형태로든 요청이 "성공"했음을 의미합니다.
400번대 상태 코드는 클라이언트 측에서 오류가 발생했음을 의미합니다.
**"404 Not Found"** 오류(그리고 농담들)도 다들 기억하시죠?
## `HTTPException` 사용하기 { #use-httpexception }
클라이언트에 오류가 포함된 HTTP 응답을 반환하려면 `HTTPException`을 사용합니다.
### `HTTPException` 가져오기 { #import-httpexception }
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *}
### 코드에서 `HTTPException` 발생시키기 { #raise-an-httpexception-in-your-code }
`HTTPException`은 API와 관련된 추가 데이터를 가진 일반적인 Python 예외입니다.
Python 예외이므로 `return` 하는 것이 아니라 `raise` 합니다.
이는 또한, *경로 처리 함수* 내부에서 호출하는 유틸리티 함수 안에서 `HTTPException``raise`하면, *경로 처리 함수*의 나머지 코드는 실행되지 않고 즉시 해당 요청이 종료되며 `HTTPException`의 HTTP 오류가 클라이언트로 전송된다는 뜻입니다.
값을 반환하는 것보다 예외를 발생시키는 것의 이점은 의존성과 보안에 대한 섹션에서 더 분명해집니다.
이 예시에서는, 클라이언트가 존재하지 않는 ID로 항목을 요청하면 상태 코드 `404`로 예외를 발생시킵니다:
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *}
### 결과 응답 { #the-resulting-response }
클라이언트가 `http://example.com/items/foo`( `item_id` `"foo"`)를 요청하면, HTTP 상태 코드 200과 다음 JSON 응답을 받습니다:
```JSON
{
"item": "The Foo Wrestlers"
}
```
하지만 클라이언트가 `http://example.com/items/bar`(존재하지 않는 `item_id` `"bar"`)를 요청하면, HTTP 상태 코드 404("not found" 오류)와 다음 JSON 응답을 받습니다:
```JSON
{
"detail": "Item not found"
}
```
/// tip | 팁
`HTTPException`을 발생시킬 때 `detail` 파라미터로 `str`만 전달할 수 있는 것이 아니라, JSON으로 변환할 수 있는 어떤 값이든 전달할 수 있습니다.
`dict`, `list` 등을 전달할 수 있습니다.
이들은 **FastAPI**가 자동으로 처리해 JSON으로 변환합니다.
///
## 커스텀 헤더 추가하기 { #add-custom-headers }
HTTP 오류에 커스텀 헤더를 추가할 수 있으면 유용한 상황이 있습니다. 예를 들어 특정 보안 유형에서 그렇습니다.
아마 코드에서 직접 사용할 일은 거의 없을 것입니다.
하지만 고급 시나리오에서 필요하다면 커스텀 헤더를 추가할 수 있습니다:
{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
## 커스텀 예외 핸들러 설치하기 { #install-custom-exception-handlers }
<a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">Starlette의 동일한 예외 유틸리티</a>를 사용해 커스텀 예외 핸들러를 추가할 수 있습니다.
여러분(또는 사용하는 라이브러리)이 `raise`할 수 있는 커스텀 예외 `UnicornException`이 있다고 가정해 봅시다.
그리고 이 예외를 FastAPI에서 전역적으로 처리하고 싶다고 해봅시다.
`@app.exception_handler()`로 커스텀 예외 핸들러를 추가할 수 있습니다:
{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *}
여기서 `/unicorns/yolo`를 요청하면, *경로 처리*가 `UnicornException``raise`합니다.
하지만 `unicorn_exception_handler`가 이를 처리합니다.
따라서 HTTP 상태 코드 `418`과 다음 JSON 내용을 가진 깔끔한 오류를 받게 됩니다:
```JSON
{"message": "Oops! yolo did something. There goes a rainbow..."}
```
/// note | 기술 세부사항
`from starlette.requests import Request``from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
**FastAPI**는 개발자의 편의를 위해 `starlette.responses``fastapi.responses`로도 동일하게 제공합니다. 하지만 사용 가능한 대부분의 응답은 Starlette에서 직접 옵니다. `Request`도 마찬가지입니다.
///
## 기본 예외 핸들러 오버라이드하기 { #override-the-default-exception-handlers }
**FastAPI**에는 몇 가지 기본 예외 핸들러가 있습니다.
이 핸들러들은 `HTTPException``raise`했을 때, 그리고 요청에 유효하지 않은 데이터가 있을 때 기본 JSON 응답을 반환하는 역할을 합니다.
이 예외 핸들러들을 여러분의 것으로 오버라이드할 수 있습니다.
### 요청 검증 예외 오버라이드하기 { #override-request-validation-exceptions }
요청에 유효하지 않은 데이터가 포함되면, **FastAPI**는 내부적으로 `RequestValidationError``raise`합니다.
그리고 이에 대한 기본 예외 핸들러도 포함되어 있습니다.
이를 오버라이드하려면 `RequestValidationError`를 가져오고, `@app.exception_handler(RequestValidationError)`로 예외 핸들러를 데코레이트해 사용하세요.
예외 핸들러는 `Request`와 예외를 받습니다.
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *}
이제 `/items/foo`로 이동하면, 다음과 같은 기본 JSON 오류 대신:
```JSON
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
```
다음과 같은 텍스트 버전을 받게 됩니다:
```
Validation errors:
Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
```
### `HTTPException` 오류 핸들러 오버라이드하기 { #override-the-httpexception-error-handler }
같은 방식으로 `HTTPException` 핸들러도 오버라이드할 수 있습니다.
예를 들어, 이런 오류들에 대해 JSON 대신 일반 텍스트 응답을 반환하고 싶을 수 있습니다:
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
/// note | 기술 세부사항
`from starlette.responses import PlainTextResponse`를 사용할 수도 있습니다.
**FastAPI**는 개발자의 편의를 위해 `starlette.responses``fastapi.responses`로도 동일하게 제공합니다. 하지만 사용 가능한 대부분의 응답은 Starlette에서 직접 옵니다.
///
/// warning | 경고
`RequestValidationError`에는 검증 오류가 발생한 파일 이름과 줄 정보가 포함되어 있어, 원한다면 관련 정보와 함께 로그에 표시할 수 있다는 점을 유념하세요.
하지만 이는 단순히 문자열로 변환해 그 정보를 그대로 반환하면 시스템에 대한 일부 정보를 누설할 수 있다는 뜻이기도 합니다. 그래서 여기의 코드는 각 오류를 독립적으로 추출해 보여줍니다.
///
### `RequestValidationError`의 body 사용하기 { #use-the-requestvalidationerror-body }
`RequestValidationError`에는 유효하지 않은 데이터와 함께 받은 `body`가 포함됩니다.
앱을 개발하는 동안 body를 로그로 남기고 디버그하거나, 사용자에게 반환하는 등으로 사용할 수 있습니다.
{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
이제 다음처럼 유효하지 않은 item을 보내보세요:
```JSON
{
"title": "towel",
"size": "XL"
}
```
받은 body를 포함해 데이터가 유효하지 않다고 알려주는 응답을 받게 됩니다:
```JSON hl_lines="12-15"
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "towel",
"size": "XL"
}
}
```
#### FastAPI의 `HTTPException` vs Starlette의 `HTTPException` { #fastapis-httpexception-vs-starlettes-httpexception }
**FastAPI**에는 자체 `HTTPException`이 있습니다.
그리고 **FastAPI**의 `HTTPException` 오류 클래스는 Starlette의 `HTTPException` 오류 클래스를 상속합니다.
유일한 차이는 **FastAPI**의 `HTTPException`은 `detail` 필드에 JSON으로 변환 가능한 어떤 데이터든 받을 수 있는 반면, Starlette의 `HTTPException`은 문자열만 받을 수 있다는 점입니다.
따라서 코드에서는 평소처럼 **FastAPI**의 `HTTPException`을 계속 `raise`하면 됩니다.
하지만 예외 핸들러를 등록할 때는 Starlette의 `HTTPException`에 대해 등록해야 합니다.
이렇게 하면 Starlette 내부 코드의 어떤 부분, 또는 Starlette 확장/플러그인이 Starlette `HTTPException`을 `raise`하더라도, 여러분의 핸들러가 이를 잡아서 처리할 수 있습니다.
이 예시에서는 동일한 코드에서 두 `HTTPException`을 모두 사용할 수 있도록, Starlette의 예외를 `StarletteHTTPException`으로 이름을 바꿉니다:
```Python
from starlette.exceptions import HTTPException as StarletteHTTPException
```
### **FastAPI**의 예외 핸들러 재사용하기 { #reuse-fastapis-exception-handlers }
예외를 사용하면서 **FastAPI**의 동일한 기본 예외 핸들러도 함께 사용하고 싶다면, `fastapi.exception_handlers`에서 기본 예외 핸들러를 가져와 재사용할 수 있습니다:
{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *}
이 예시에서는 매우 표현력 있는 메시지로 오류를 출력만 하고 있지만, 요지는 이해하셨을 겁니다. 예외를 사용한 뒤 기본 예외 핸들러를 그대로 재사용할 수 있습니다.

View File

@@ -1,203 +0,0 @@
# 보안 - 첫 단계 { #security-first-steps }
어떤 도메인에 **backend** API가 있다고 가정해 보겠습니다.
그리고 다른 도메인에 **frontend**가 있거나, 같은 도메인의 다른 경로에 있거나(또는 모바일 애플리케이션에 있을 수도 있습니다).
그리고 frontend가 **username**과 **password**를 사용해 backend에 인증할 수 있는 방법이 필요하다고 해봅시다.
**FastAPI**와 함께 **OAuth2**를 사용해서 이를 구현할 수 있습니다.
하지만 필요한 작은 정보 조각들을 찾기 위해 길고 긴 전체 스펙을 읽느라 시간을 쓰지 않도록 하겠습니다.
보안을 처리하기 위해 **FastAPI**가 제공하는 도구들을 사용해 봅시다.
## 어떻게 보이는지 { #how-it-looks }
먼저 코드를 그냥 사용해서 어떻게 동작하는지 보고, 그다음에 무슨 일이 일어나는지 이해하러 다시 돌아오겠습니다.
## `main.py` 만들기 { #create-main-py }
예제를 파일 `main.py`에 복사하세요:
{* ../../docs_src/security/tutorial001_an_py39.py *}
## 실행하기 { #run-it }
/// info | 정보
<a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> 패키지는 `pip install "fastapi[standard]"` 명령을 실행하면 **FastAPI**와 함께 자동으로 설치됩니다.
하지만 `pip install fastapi` 명령을 사용하면 `python-multipart` 패키지가 기본으로 포함되지 않습니다.
수동으로 설치하려면, [가상 환경](../../virtual-environments.md){.internal-link target=_blank}을 만들고 활성화한 다음, 아래로 설치하세요:
```console
$ pip install python-multipart
```
이는 **OAuth2**가 `username``password`를 보내기 위해 "form data"를 사용하기 때문입니다.
///
다음으로 예제를 실행하세요:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
## 확인하기 { #check-it }
대화형 문서로 이동하세요: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
다음과 비슷한 화면이 보일 것입니다:
<img src="/img/tutorial/security/image01.png">
/// check | Authorize 버튼!
반짝이는 새 "Authorize" 버튼이 이미 있습니다.
그리고 *경로 처리*에는 오른쪽 상단에 클릭할 수 있는 작은 자물쇠가 있습니다.
///
그리고 이를 클릭하면 `username``password`(그리고 다른 선택적 필드들)를 입력할 수 있는 작은 인증 폼이 나타납니다:
<img src="/img/tutorial/security/image02.png">
/// note | 참고
폼에 무엇을 입력하든 아직은 동작하지 않습니다. 하지만 곧 여기까지 구현할 것입니다.
///
물론 이것은 최종 사용자를 위한 frontend는 아니지만, 모든 API를 대화형으로 문서화하는 훌륭한 자동 도구입니다.
frontend 팀(그게 본인일 수도 있습니다)이 사용할 수 있습니다.
서드파티 애플리케이션과 시스템에서도 사용할 수 있습니다.
그리고 동일한 애플리케이션을 디버그하고, 확인하고, 테스트하기 위해 본인이 사용할 수도 있습니다.
## `password` 플로우 { #the-password-flow }
이제 조금 돌아가서 이것들이 무엇인지 이해해 봅시다.
`password` "flow"는 보안과 인증을 처리하기 위해 OAuth2에서 정의한 여러 방식("flows") 중 하나입니다.
OAuth2는 backend 또는 API가 사용자를 인증하는 서버와 독립적일 수 있도록 설계되었습니다.
하지만 이 경우에는 같은 **FastAPI** 애플리케이션이 API와 인증을 모두 처리합니다.
따라서, 단순화된 관점에서 다시 정리해보면:
* 사용자가 frontend에서 `username``password`를 입력하고 `Enter`를 누릅니다.
* frontend(사용자의 브라우저에서 실행됨)는 해당 `username``password`를 우리 API의 특정 URL로 보냅니다(`tokenUrl="token"`로 선언됨).
* API는 `username``password`를 확인하고 "token"으로 응답합니다(아직 아무것도 구현하지 않았습니다).
* "token"은 나중에 이 사용자를 검증하는 데 사용할 수 있는 어떤 내용이 담긴 문자열일 뿐입니다.
* 보통 token은 일정 시간이 지나면 만료되도록 설정합니다.
* 그래서 사용자는 나중에 어느 시점엔 다시 로그인해야 합니다.
* 그리고 token이 도난당하더라도 위험이 더 낮습니다. 대부분의 경우 영구적으로 항상 동작하는 키와는 다릅니다.
* frontend는 그 token을 임시로 어딘가에 저장합니다.
* 사용자가 frontend에서 클릭해서 frontend 웹 앱의 다른 섹션으로 이동합니다.
* frontend는 API에서 더 많은 데이터를 가져와야 합니다.
* 하지만 그 특정 endpoint에는 인증이 필요합니다.
* 그래서 우리 API에 인증하기 위해 `Authorization` 헤더를, 값은 `Bearer `에 token을 더한 형태로 보냅니다.
* token에 `foobar`가 들어 있다면 `Authorization` 헤더의 내용은 `Bearer foobar`가 됩니다.
## **FastAPI**의 `OAuth2PasswordBearer` { #fastapis-oauth2passwordbearer }
**FastAPI**는 이런 보안 기능을 구현하기 위해, 서로 다른 추상화 수준에서 여러 도구를 제공합니다.
이 예제에서는 **OAuth2**의 **Password** 플로우와 **Bearer** token을 사용합니다. 이를 위해 `OAuth2PasswordBearer` 클래스를 사용합니다.
/// info | 정보
"bearer" token만이 유일한 선택지는 아닙니다.
하지만 이 사용 사례에는 가장 적합한 선택입니다.
또한 OAuth2 전문가로서 왜 다른 옵션이 더 적합한지 정확히 아는 경우가 아니라면, 대부분의 사용 사례에도 가장 적합할 가능성이 큽니다.
그런 경우를 위해서도 **FastAPI**는 이를 구성할 수 있는 도구를 제공합니다.
///
`OAuth2PasswordBearer` 클래스의 인스턴스를 만들 때 `tokenUrl` 파라미터를 전달합니다. 이 파라미터에는 클라이언트(사용자의 브라우저에서 실행되는 frontend)가 token을 받기 위해 `username``password`를 보낼 URL이 들어 있습니다.
{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
/// tip | 팁
여기서 `tokenUrl="token"`은 아직 만들지 않은 상대 URL `token`을 가리킵니다. 상대 URL이므로 `./token`과 동일합니다.
상대 URL을 사용하므로, 예를 들어 API가 `https://example.com/`에 있다면 `https://example.com/token`을 가리킵니다. 하지만 API가 `https://example.com/api/v1/`에 있다면 `https://example.com/api/v1/token`을 가리킵니다.
상대 URL을 사용하는 것은 [프록시 뒤에서](../../advanced/behind-a-proxy.md){.internal-link target=_blank} 같은 고급 사용 사례에서도 애플리케이션이 계속 동작하도록 보장하는 데 중요합니다.
///
이 파라미터는 그 endpoint / *경로 처리*를 만들지는 않지만, URL `/token`이 클라이언트가 token을 얻기 위해 사용해야 할 URL이라고 선언합니다. 이 정보는 OpenAPI에 사용되고, 이어서 대화형 API 문서 시스템에서도 사용됩니다.
곧 실제 경로 처리를 만들 것입니다.
/// info | 정보
엄격한 "Pythonista"라면 `token_url` 대신 `tokenUrl` 같은 파라미터 이름 스타일이 마음에 들지 않을 수도 있습니다.
이는 OpenAPI 스펙에서 사용하는 이름과 동일하게 맞춘 것이기 때문입니다. 그래서 이런 보안 스킴에 대해 더 조사해야 할 때, 그대로 복사해서 붙여 넣어 더 많은 정보를 찾을 수 있습니다.
///
`oauth2_scheme` 변수는 `OAuth2PasswordBearer`의 인스턴스이지만, "callable"이기도 합니다.
다음처럼 호출될 수 있습니다:
```Python
oauth2_scheme(some, parameters)
```
따라서 `Depends`와 함께 사용할 수 있습니다.
### 사용하기 { #use-it }
이제 `Depends``oauth2_scheme`를 의존성에 전달할 수 있습니다.
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
이 의존성은 `str`을 제공하고, 그 값은 *경로 처리 함수*의 파라미터 `token`에 할당됩니다.
**FastAPI**는 이 의존성을 사용해 OpenAPI 스키마(및 자동 API 문서)에 "security scheme"를 정의할 수 있다는 것을 알게 됩니다.
/// info | 기술 세부사항
**FastAPI**는 (의존성에 선언된) `OAuth2PasswordBearer` 클래스를 사용해 OpenAPI에서 보안 스킴을 정의할 수 있다는 것을 알고 있습니다. 이는 `OAuth2PasswordBearer``fastapi.security.oauth2.OAuth2`를 상속하고, 이것이 다시 `fastapi.security.base.SecurityBase`를 상속하기 때문입니다.
OpenAPI(및 자동 API 문서)와 통합되는 모든 보안 유틸리티는 `SecurityBase`를 상속하며, 그래서 **FastAPI**가 이를 OpenAPI에 어떻게 통합할지 알 수 있습니다.
///
## 무엇을 하는지 { #what-it-does }
요청에서 `Authorization` 헤더를 찾아, 값이 `Bearer `에 어떤 token이 붙은 형태인지 확인한 뒤, 그 token을 `str`로 반환합니다.
`Authorization` 헤더가 없거나, 값에 `Bearer ` token이 없다면, 곧바로 401 상태 코드 오류(`UNAUTHORIZED`)로 응답합니다.
오류를 반환하기 위해 token이 존재하는지 직접 확인할 필요조차 없습니다. 함수가 실행되었다면 그 token에는 `str`이 들어 있다고 확신할 수 있습니다.
대화형 문서에서 이미 시도해 볼 수 있습니다:
<img src="/img/tutorial/security/image03.png">
아직 token의 유효성을 검증하진 않지만, 이것만으로도 시작은 된 셈입니다.
## 요약 { #recap }
즉, 추가로 3~4줄만으로도 이미 원시적인 형태의 보안을 갖추게 됩니다.

View File

@@ -1,106 +0,0 @@
# 보안 { #security }
보안, 인증(authentication), 인가(authorization)를 처리하는 방법은 매우 다양합니다.
그리고 보통 복잡하고 "어려운" 주제이기도 합니다.
많은 프레임워크와 시스템에서 보안과 인증만 처리하는 데도 큰 노력과 코드가 필요합니다(많은 경우 작성된 전체 코드의 50% 이상이 될 수도 있습니다).
**FastAPI**는 모든 보안 명세를 전부 공부하고 배울 필요 없이, 표준적인 방식으로 쉽고 빠르게 **보안(Security)** 을 다룰 수 있도록 여러 도구를 제공합니다.
하지만 먼저, 몇 가지 작은 개념을 확인해 보겠습니다.
## 급하신가요? { #in-a-hurry }
이 용어들에 관심이 없고 사용자명과 비밀번호 기반 인증을 사용한 보안을 *지금 당장* 추가하기만 하면 된다면, 다음 장들로 넘어가세요.
## OAuth2 { #oauth2 }
OAuth2는 인증과 인가를 처리하는 여러 방법을 정의하는 명세입니다.
상당히 방대한 명세이며 여러 복잡한 사용 사례를 다룹니다.
"제3자"를 사용해 인증하는 방법도 포함합니다.
바로 `"Facebook, Google, X (Twitter), GitHub로 로그인"` 같은 시스템들이 내부적으로 사용하는 방식입니다.
### OAuth 1 { #oauth-1 }
OAuth 1도 있었는데, 이는 OAuth2와 매우 다르고 통신을 암호화하는 방법까지 직접 명세에 포함했기 때문에 더 복잡했습니다.
요즘에는 그다지 인기 있거나 사용되지는 않습니다.
OAuth2는 통신을 어떻게 암호화할지는 명세하지 않고, 애플리케이션이 HTTPS로 제공될 것을 기대합니다.
/// tip | 팁
**배포**에 대한 섹션에서 Traefik과 Let's Encrypt를 사용해 무료로 HTTPS를 설정하는 방법을 볼 수 있습니다.
///
## OpenID Connect { #openid-connect }
OpenID Connect는 **OAuth2**를 기반으로 한 또 다른 명세입니다.
OAuth2에서 비교적 모호한 부분을 일부 구체화하여 상호 운용성을 높이려는 확장입니다.
예를 들어, Google 로그인은 OpenID Connect를 사용합니다(내부적으로는 OAuth2를 사용).
하지만 Facebook 로그인은 OpenID Connect를 지원하지 않습니다. 자체적인 변형의 OAuth2를 사용합니다.
### OpenID("OpenID Connect"가 아님) { #openid-not-openid-connect }
"OpenID"라는 명세도 있었습니다. 이는 **OpenID Connect**와 같은 문제를 해결하려고 했지만, OAuth2를 기반으로 하지 않았습니다.
따라서 완전히 별도의 추가 시스템이었습니다.
요즘에는 그다지 인기 있거나 사용되지는 않습니다.
## OpenAPI { #openapi }
OpenAPI(이전에는 Swagger로 알려짐)는 API를 구축하기 위한 공개 명세입니다(현재 Linux Foundation의 일부).
**FastAPI**는 **OpenAPI**를 기반으로 합니다.
이 덕분에 여러 자동 대화형 문서 인터페이스, 코드 생성 등과 같은 기능을 사용할 수 있습니다.
OpenAPI에는 여러 보안 "scheme"을 정의하는 방법이 있습니다.
이를 사용하면 이러한 대화형 문서 시스템을 포함해, 표준 기반 도구들을 모두 활용할 수 있습니다.
OpenAPI는 다음 보안 scheme들을 정의합니다:
* `apiKey`: 다음에서 전달될 수 있는 애플리케이션 전용 키:
* 쿼리 파라미터
* 헤더
* 쿠키
* `http`: 표준 HTTP 인증 시스템, 예:
* `bearer`: `Authorization` 헤더에 `Bearer ` + 토큰 값을 넣는 방식. OAuth2에서 유래했습니다.
* HTTP Basic 인증
* HTTP Digest 등
* `oauth2`: 보안을 처리하는 모든 OAuth2 방식(이를 "flow"라고 부릅니다).
* 이 flow들 중 여러 개는 OAuth 2.0 인증 제공자(예: Google, Facebook, X (Twitter), GitHub 등)를 구축하는 데 적합합니다:
* `implicit`
* `clientCredentials`
* `authorizationCode`
* 하지만 같은 애플리케이션에서 직접 인증을 처리하는 데 완벽하게 사용할 수 있는 특정 "flow"도 하나 있습니다:
* `password`: 다음 장들에서 이에 대한 예시를 다룹니다.
* `openIdConnect`: OAuth2 인증 데이터를 자동으로 탐색(discover)하는 방법을 정의합니다.
* 이 자동 탐색은 OpenID Connect 명세에서 정의됩니다.
/// tip | 팁
Google, Facebook, X (Twitter), GitHub 등 다른 인증/인가 제공자를 통합하는 것도 가능하며 비교적 쉽습니다.
가장 복잡한 문제는 그런 인증/인가 제공자 자체를 구축하는 것이지만, **FastAPI**는 어려운 작업을 대신 처리해 주면서 이를 쉽게 할 수 있는 도구를 제공합니다.
///
## **FastAPI** 유틸리티 { #fastapi-utilities }
FastAPI는 `fastapi.security` 모듈에서 각 보안 scheme에 대한 여러 도구를 제공하며, 이러한 보안 메커니즘을 더 쉽게 사용할 수 있게 해줍니다.
다음 장들에서는 **FastAPI**가 제공하는 도구를 사용해 API에 보안을 추가하는 방법을 보게 될 것입니다.
또한 대화형 문서 시스템에 어떻게 자동으로 통합되는지도 확인하게 됩니다.

View File

@@ -8,7 +8,6 @@ Language code: ko.
- Use polite, instructional Korean (e.g. 합니다/하세요 style).
- Keep the tone consistent with the existing Korean FastAPI docs.
- Do not translate “You” literally as “당신”. Use “여러분” where appropriate, or omit the subject if it sounds more natural in Korean.
### Headings

View File

@@ -1,8 +1,8 @@
# Arquivo de teste de LLM { #llm-test-file }
Este documento testa se o <abbr title="Large Language Model - Modelo de Linguagem de Grande Porte">LLM</abbr>, que traduz a documentação, entende o `general_prompt` em `scripts/translate.py` e o prompt específico do idioma em `docs/{language code}/llm-prompt.md`. O prompt específico do idioma é anexado ao `general_prompt`.
Este documento testa se o <abbr title="Large Language Model Modelo de Linguagem de Grande Porte">LLM</abbr>, que traduz a documentação, entende o `general_prompt` em `scripts/translate.py` e o prompt específico do idioma em `docs/{language code}/llm-prompt.md`. O prompt específico do idioma é anexado ao `general_prompt`.
Os testes adicionados aqui serão vistos por todos os designers dos prompts específicos de idioma.
Os testes adicionados aqui serão vistos por todos os autores dos prompts específicos de idioma.
Use da seguinte forma:
@@ -23,7 +23,7 @@ Este é um trecho de código: `foo`. E este é outro trecho de código: `bar`. E
////
//// tab | Informação
//// tab | Informações
O conteúdo dos trechos de código deve ser deixado como está.
@@ -45,9 +45,9 @@ O LLM provavelmente vai traduzir isso errado. O interessante é apenas se ele ma
////
//// tab | Informação
//// tab | Informações
O designer do prompt pode escolher se quer converter aspas neutras em aspas tipográficas. Também é aceitável deixá-las como estão.
O autor do prompt pode escolher se deseja converter aspas neutras em aspas tipográficas. Também é aceitável deixá-las como estão.
Veja, por exemplo, a seção `### Quotes` em `docs/de/llm-prompt.md`.
@@ -67,7 +67,7 @@ Pesado: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you ha
////
//// tab | Informação
//// tab | Informações
... No entanto, as aspas dentro de trechos de código devem permanecer como estão.
@@ -95,24 +95,24 @@ $ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid
...e outro exemplo de código de console...
```console
// Criar um diretório "Code"
// Crie um diretório "Code"
$ mkdir code
// Mudar para esse diretório
// Entre nesse diretório
$ cd code
```
...e um exemplo de código Python...
```Python
wont_work() # This won't work 😱
works(foo="bar") # This works 🎉
wont_work() # Isto não vai funcionar 😱
works(foo="bar") # Isto funciona 🎉
```
...e é isso.
////
//// tab | Informação
//// tab | Informações
O código em blocos de código não deve ser modificado, com exceção dos comentários.
@@ -154,7 +154,7 @@ Algum texto
////
//// tab | Informação
//// tab | Informações
Abas e blocos `Info`/`Note`/`Warning`/etc. devem ter a tradução do seu título adicionada após uma barra vertical (`|`).
@@ -181,7 +181,7 @@ O texto do link deve ser traduzido, o endereço do link deve apontar para a trad
////
//// tab | Informação
//// tab | Informações
Os links devem ser traduzidos, mas seus endereços devem permanecer inalterados. Uma exceção são links absolutos para páginas da documentação do FastAPI. Nesse caso, devem apontar para a tradução.
@@ -197,10 +197,10 @@ Aqui estão algumas coisas envolvidas em elementos HTML "abbr" (algumas são inv
### O abbr fornece uma frase completa { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done">GTD</abbr>
* <abbr title="less than - menos que"><code>lt</code></abbr>
* <abbr title="XML Web Token">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - Interface de Gateway de Servidor Paralelo">PSGI</abbr>
* <abbr title="Getting Things Done Fazer as Coisas">GTD</abbr>
* <abbr title="menos que"><code>lt</code></abbr>
* <abbr title="XML Web Token Token Web XML">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface Interface de Gateway de Servidor Paralelo">PSGI</abbr>
### O abbr fornece uma explicação { #the-abbr-gives-an-explanation }
@@ -209,12 +209,12 @@ Aqui estão algumas coisas envolvidas em elementos HTML "abbr" (algumas são inv
### O abbr fornece uma frase completa e uma explicação { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network: documentação para desenvolvedores, escrita pelo pessoal do Firefox">MDN</abbr>
* <abbr title="Input/Output: leitura ou escrita em disco, comunicações de rede.">I/O</abbr>.
* <abbr title="Mozilla Developer Network Rede de Desenvolvedores da Mozilla: documentação para desenvolvedores, escrita pelo pessoal do Firefox">MDN</abbr>
* <abbr title="Input/Output Entrada/Saída: leitura ou escrita em disco, comunicações de rede.">I/O</abbr>.
////
//// tab | Informação
//// tab | Informações
Os atributos "title" dos elementos "abbr" são traduzidos seguindo algumas instruções específicas.
@@ -228,7 +228,7 @@ Veja a seção `### HTML abbr elements` no prompt geral em `scripts/translate.py
//// tab | Teste
### Desenvolver uma webapp - um tutorial { #develop-a-webapp-a-tutorial }
### Desenvolver uma aplicação web - um tutorial { #develop-a-webapp-a-tutorial }
Olá.
@@ -242,7 +242,7 @@ Olá novamente.
////
//// tab | Informação
//// tab | Informações
A única regra rígida para títulos é que o LLM deixe a parte do hash dentro de chaves inalterada, o que garante que os links não quebrem.
@@ -494,9 +494,9 @@ Para algumas instruções específicas do idioma, veja, por exemplo, a seção `
////
//// tab | Informação
//// tab | Informações
Esta é uma lista não completa e não normativa de termos (principalmente) técnicos vistos na documentação. Pode ser útil para o designer do prompt descobrir para quais termos o LLM precisa de uma ajudinha. Por exemplo, quando ele continua revertendo uma boa tradução para uma tradução subótima. Ou quando tem problemas para conjugar/declinar um termo no seu idioma.
Esta é uma lista não completa e não normativa de termos (principalmente) técnicos vistos na documentação. Pode ser útil para o autor do prompt descobrir para quais termos o LLM precisa de uma ajudinha. Por exemplo, quando ele continua revertendo uma boa tradução para uma tradução subótima. Ou quando tem problemas para conjugar/declinar um termo no seu idioma.
Veja, por exemplo, a seção `### List of English terms and their preferred German translations` em `docs/de/llm-prompt.md`.

View File

@@ -10,7 +10,7 @@ Se você não é um "especialista" no OpenAPI, você provavelmente não precisa
Você pode definir o `operationId` do OpenAPI que será utilizado na sua *operação de rota* com o parâmetro `operation_id`.
Você deveria ter certeza que ele é único para cada operação.
Você precisa ter certeza que ele é único para cada operação.
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
@@ -18,13 +18,13 @@ Você deveria ter certeza que ele é único para cada operação.
Se você quiser utilizar o nome das funções da sua API como `operationId`s, você pode iterar sobre todos esses nomes e sobrescrever o `operation_id` em cada *operação de rota* utilizando o `APIRoute.name` dela.
Você deveria fazer isso depois de adicionar todas as suas *operações de rota*.
Você deve fazer isso depois de adicionar todas as suas *operações de rota*.
{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
/// tip | Dica
Se você chamar `app.openapi()` manualmente, você deveria atualizar os `operationId`s antes dessa chamada.
Se você chamar `app.openapi()` manualmente, os `operationId`s devem ser atualizados antes dessa chamada.
///
@@ -44,11 +44,11 @@ Para excluir uma *operação de rota* do esquema OpenAPI gerado (e por consequê
## Descrição avançada a partir de docstring { #advanced-description-from-docstring }
Você pode limitar as linhas utilizadas a partir da docstring de uma *função de operação de rota* para o OpenAPI.
Você pode limitar as linhas utilizadas a partir de uma docstring de uma *função de operação de rota* para o OpenAPI.
Adicionar um `\f` (um caractere de escape para "form feed") faz com que o **FastAPI** trunque a saída usada para o OpenAPI até esse ponto.
Adicionar um `\f` (um caractere de escape para alimentação de formulário) faz com que o **FastAPI** restrinja a saída utilizada pelo OpenAPI até esse ponto.
Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto.
Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto do texto.
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
@@ -131,38 +131,70 @@ E se você olhar o esquema OpenAPI resultante (na rota `/openapi.json` da sua AP
### Esquema de *operação de rota* do OpenAPI personalizado { #custom-openapi-path-operation-schema }
O dicionário em `openapi_extra` vai ser mesclado profundamente com o esquema OpenAPI gerado automaticamente para a *operação de rota*.
O dicionário em `openapi_extra` vai ter todos os seus níveis mesclados dentro do esquema OpenAPI gerado automaticamente para a *operação de rota*.
Então, você pode adicionar dados extras ao esquema gerado automaticamente.
Então, você pode adicionar dados extras para o esquema gerado automaticamente.
Por exemplo, você poderia decidir ler e validar a requisição com seu próprio código, sem usar as funcionalidades automáticas do FastAPI com o Pydantic, mas ainda assim querer definir a requisição no esquema OpenAPI.
Por exemplo, você poderia optar por ler e validar a requisição com seu próprio código, sem utilizar funcionalidades automatizadas do FastAPI com o Pydantic, mas você ainda pode quere definir a requisição no esquema OpenAPI.
Você pode fazer isso com `openapi_extra`:
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo <abbr title="converted from some plain format, like bytes, into Python objects - convertido de algum formato simples, como bytes, em objetos Python">analisado</abbr> como JSON, ele é lido diretamente como `bytes`, e a função `magic_data_reader()` seria a responsável por analisar ele de alguma forma.
Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo <abbr title="convertido de um formato plano, como bytes, para objetos Python">analisado</abbr> como JSON, ele é lido diretamente como `bytes` e a função `magic_data_reader()` seria a responsável por analisar ele de alguma forma.
De toda forma, nós podemos declarar o esquema esperado para o corpo da requisição.
### Tipo de conteúdo do OpenAPI personalizado { #custom-openapi-content-type }
Utilizando esse mesmo truque, você pode usar um modelo Pydantic para definir o JSON Schema que é então incluído na seção do esquema personalizado do OpenAPI na *operação de rota*.
Utilizando esse mesmo truque, você pode utilizar um modelo Pydantic para definir o JSON Schema que é então incluído na seção do esquema personalizado do OpenAPI na *operação de rota*.
E você pode fazer isso até mesmo quando o tipo de dados na requisição não é JSON.
E você pode fazer isso até mesmo quando os dados da requisição não seguem o formato JSON.
Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o JSON Schema dos modelos Pydantic nem a validação automática para JSON. Na verdade, estamos declarando o tipo de conteúdo da requisição como YAML, em vez de JSON:
Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o JSON Schema dos modelos Pydantic nem a validação automática do JSON. Na verdade, estamos declarando o tipo do conteúdo da requisição como YAML, em vez de JSON:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um JSON Schema manualmente para os dados que queremos receber em YAML.
////
Então utilizamos a requisição diretamente e extraímos o corpo como `bytes`. Isso significa que o FastAPI não vai sequer tentar analisar o payload da requisição como JSON.
//// tab | Pydantic v1
E então no nosso código, nós analisamos o conteúdo YAML diretamente e, em seguida, estamos usando novamente o mesmo modelo Pydantic para validar o conteúdo YAML:
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
////
/// info | Informação
Na versão 1 do Pydantic, o método para obter o JSON Schema de um modelo é `Item.schema()`, na versão 2 do Pydantic, o método é `Item.model_json_schema()`.
///
Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um JSON Schema manualmente para os dados que queremos receber no formato YAML.
Então utilizamos a requisição diretamente, e extraímos o corpo como `bytes`. Isso significa que o FastAPI não vai sequer tentar analisar o corpo da requisição como JSON.
E então no nosso código, nós analisamos o conteúdo YAML diretamente, e estamos utilizando o mesmo modelo Pydantic para validar o conteúdo YAML:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
////
/// info | Informação
Na versão 1 do Pydantic, o método para analisar e validar um objeto era `Item.parse_obj()`, na versão 2 do Pydantic, o método é chamado de `Item.model_validate()`.
///
/// tip | Dica
Aqui reutilizamos o mesmo modelo do Pydantic.

View File

@@ -46,6 +46,12 @@ $ pip install "fastapi[all]"
</div>
/// info | Informação
No Pydantic v1 ele vinha incluído no pacote principal. Agora é distribuído como um pacote independente para que você possa optar por instalá-lo ou não, caso não precise dessa funcionalidade.
///
### Criar o objeto `Settings` { #create-the-settings-object }
Importe `BaseSettings` do Pydantic e crie uma subclasse, muito parecido com um modelo do Pydantic.
@@ -54,8 +60,24 @@ Da mesma forma que com modelos do Pydantic, você declara atributos de classe co
Você pode usar as mesmas funcionalidades e ferramentas de validação que usa em modelos do Pydantic, como diferentes tipos de dados e validações adicionais com `Field()`.
//// tab | Pydantic v2
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
////
//// tab | Pydantic v1
/// info | Informação
No Pydantic v1 você importaria `BaseSettings` diretamente de `pydantic` em vez de `pydantic_settings`.
///
{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *}
////
/// tip | Dica
Se você quer algo rápido para copiar e colar, não use este exemplo, use o último abaixo.
@@ -193,6 +215,8 @@ APP_NAME="ChimichangApp"
E então atualizar seu `config.py` com:
//// tab | Pydantic v2
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
/// tip | Dica
@@ -201,6 +225,26 @@ O atributo `model_config` é usado apenas para configuração do Pydantic. Você
///
////
//// tab | Pydantic v1
{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *}
/// tip | Dica
A classe `Config` é usada apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
///
////
/// info | Informação
Na versão 1 do Pydantic a configuração era feita em uma classe interna `Config`, na versão 2 do Pydantic é feita em um atributo `model_config`. Esse atributo recebe um `dict`, e para ter autocompletar e erros inline você pode importar e usar `SettingsConfigDict` para definir esse `dict`.
///
Aqui definimos a configuração `env_file` dentro da sua classe `Settings` do Pydantic e definimos o valor como o nome do arquivo dotenv que queremos usar.
### Criando o `Settings` apenas uma vez com `lru_cache` { #creating-the-settings-only-once-with-lru-cache }

View File

@@ -2,23 +2,21 @@
Se você tem uma aplicação FastAPI antiga, pode estar usando o Pydantic versão 1.
O FastAPI versão 0.100.0 tinha suporte ao Pydantic v1 ou v2. Ele usaria aquele que você tivesse instalado.
O FastAPI tem suporte ao Pydantic v1 ou v2 desde a versão 0.100.0.
O FastAPI versão 0.119.0 introduziu suporte parcial ao Pydantic v1 a partir de dentro do Pydantic v2 (como `pydantic.v1`), para facilitar a migração para o v2.
Se você tiver o Pydantic v2 instalado, ele será utilizado. Se, em vez disso, tiver o Pydantic v1, será ele que será utilizado.
O FastAPI 0.126.0 removeu o suporte ao Pydantic v1, enquanto ainda oferece suporte a `pydantic.v1` por mais algum tempo.
O Pydantic v1 está agora descontinuado e o suporte a ele será removido nas próximas versões do FastAPI, você deveria migrar para o Pydantic v2. Assim, você terá as funcionalidades, melhorias e correções mais recentes.
/// warning | Atenção
A equipe do Pydantic interrompeu o suporte ao Pydantic v1 para as versões mais recentes do Python, a partir do **Python 3.14**.
Isso inclui `pydantic.v1`, que não é mais suportado no Python 3.14 e superiores.
Além disso, a equipe do Pydantic interrompeu o suporte ao Pydantic v1 para as versões mais recentes do Python, a partir do **Python 3.14**.
Se quiser usar as funcionalidades mais recentes do Python, você precisará garantir que usa o Pydantic v2.
///
Se você tem uma aplicação FastAPI antiga com Pydantic v1, aqui vou mostrar como migrá-la para o Pydantic v2, e as **funcionalidades no FastAPI 0.119.0** para ajudar em uma migração gradual.
Se você tem uma aplicação FastAPI antiga com Pydantic v1, aqui vou mostrar como migrá-la para o Pydantic v2 e as **novas funcionalidades no FastAPI 0.119.0** para ajudar em uma migração gradual.
## Guia oficial { #official-guide }
@@ -46,7 +44,7 @@ Depois disso, você pode rodar os testes e verificar se tudo funciona. Se funcio
## Pydantic v1 no v2 { #pydantic-v1-in-v2 }
O Pydantic v2 inclui tudo do Pydantic v1 como um submódulo `pydantic.v1`. Mas isso não é mais suportado em versões acima do Python 3.13.
O Pydantic v2 inclui tudo do Pydantic v1 como um submódulo `pydantic.v1`.
Isso significa que você pode instalar a versão mais recente do Pydantic v2 e importar e usar os componentes antigos do Pydantic v1 a partir desse submódulo, como se tivesse o Pydantic v1 antigo instalado.
@@ -68,7 +66,7 @@ Tenha em mente que, como a equipe do Pydantic não oferece mais suporte ao Pydan
### Pydantic v1 e v2 na mesma aplicação { #pydantic-v1-and-v2-on-the-same-app }
Não é **suportado** pelo Pydantic ter um modelo do Pydantic v2 com campos próprios definidos como modelos do Pydantic v1, ou vice-versa.
Não é suportado pelo Pydantic ter um modelo do Pydantic v2 com campos próprios definidos como modelos do Pydantic v1, ou vice-versa.
```mermaid
graph TB
@@ -88,7 +86,7 @@ graph TB
style V2Field fill:#f9fff3
```
...mas, você pode ter modelos separados usando Pydantic v1 e v2 na mesma aplicação.
...but, you can have separated models using Pydantic v1 and v2 in the same app.
```mermaid
graph TB
@@ -108,7 +106,7 @@ graph TB
style V2Field fill:#f9fff3
```
Em alguns casos, é até possível ter modelos Pydantic v1 e v2 na mesma **operação de rota** na sua aplicação FastAPI:
Em alguns casos, é até possível ter modelos Pydantic v1 e v2 na mesma operação de rota na sua aplicação FastAPI:
{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
@@ -124,7 +122,7 @@ Se você precisar usar algumas das ferramentas específicas do FastAPI para par
/// tip | Dica
Primeiro tente com o `bump-pydantic`, se seus testes passarem e isso funcionar, então você concluiu tudo com um único comando. ✨
Primeiro tente com o `bump-pydantic`; se seus testes passarem e isso funcionar, então você concluiu tudo com um único comando. ✨
///

View File

@@ -1,8 +1,8 @@
# Esquemas OpenAPI Separados para Entrada e Saída ou Não { #separate-openapi-schemas-for-input-and-output-or-not }
Desde que o **Pydantic v2** foi lançado, o OpenAPI gerado é um pouco mais exato e **correto** do que antes. 😎
Ao usar **Pydantic v2**, o OpenAPI gerado é um pouco mais exato e **correto** do que antes. 😎
De fato, em alguns casos, ele terá até **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saída, dependendo se eles possuem **valores padrão**.
Inclusive, em alguns casos, ele terá até **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saída, dependendo se eles possuem **valores padrão**.
Vamos ver como isso funciona e como alterar se for necessário.
@@ -95,8 +95,10 @@ O suporte para `separate_input_output_schemas` foi adicionado no FastAPI `0.102.
### Mesmo Esquema para Modelos de Entrada e Saída na Documentação { #same-schema-for-input-and-output-models-in-docs }
E agora haverá um único esquema para entrada e saída para o modelo, apenas `Item`, e ele terá `description` como **não obrigatório**:
E agora haverá um único esquema para entrada e saída para o modelo, apenas `Item`, e `description` **não será obrigatório**:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
</div>
Esse é o mesmo comportamento do Pydantic v1. 🤓

View File

@@ -40,8 +40,8 @@ Os recursos chave são:
* **Rápido**: alta performance, equivalente a **NodeJS** e **Go** (graças ao Starlette e Pydantic). [Um dos frameworks mais rápidos disponíveis](#performance).
* **Rápido para codar**: Aumenta a velocidade para desenvolver recursos entre 200% a 300%. *
* **Poucos bugs**: Reduz cerca de 40% de erros induzidos por humanos (desenvolvedores). *
* **Intuitivo**: Grande suporte a editores. <abbr title="também conhecido como auto-complete, autocompletion, IntelliSense">Completação</abbr> em todos os lugares. Menos tempo debugando.
* **Fácil**: Projetado para ser fácil de aprender e usar. Menos tempo lendo docs.
* **Intuitivo**: Grande suporte a _IDEs_. <abbr title="também conhecido como autocompletar, preenchimento automático, IntelliSense">Preenchimento automático</abbr> em todos os lugares. Menos tempo debugando.
* **Fácil**: Projetado para ser fácil de aprender e usar. Menos tempo lendo documentação.
* **Enxuto**: Minimize duplicação de código. Múltiplas funcionalidades para cada declaração de parâmetro. Menos bugs.
* **Robusto**: Tenha código pronto para produção. E com documentação interativa automática.
* **Baseado em padrões**: Baseado em (e totalmente compatível com) os padrões abertos para APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (anteriormente conhecido como Swagger) e <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
@@ -73,7 +73,7 @@ Os recursos chave são:
## Opiniões { #opinions }
"_[...] Estou usando **FastAPI** muito esses dias. [...] Estou na verdade planejando utilizar ele em todos os times de **serviços ML na Microsoft**. Alguns deles estão sendo integrados no _core_ do produto **Windows** e alguns produtos **Office**._"
"*[...] Estou usando **FastAPI** muito esses dias. [...] Estou na verdade planejando utilizar ele em todos os times de **serviços _Machine Learning_ na Microsoft**. Alguns deles estão sendo integrados no _core_ do produto **Windows** e alguns produtos **Office**.*"
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
@@ -91,45 +91,39 @@ Os recursos chave são:
---
"_Estou muito entusiasmado com o **FastAPI**. É tão divertido!_"
"*Estou extremamente entusiasmado com o **FastAPI**. É tão divertido!*"
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> apresentador do podcast</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcaster</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
---
"_Honestamente, o que você construiu parece super sólido e refinado. De muitas formas, é o que eu queria que o **Hug** fosse - é realmente inspirador ver alguém construir isso._"
"*Honestamente, o que você construiu parece super sólido e rebuscado. De muitas formas, eu queria que o **Hug** fosse assim - é realmente inspirador ver alguém que construiu ele.*"
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong>criador do<a href="https://github.com/hugapi/hug" target="_blank">Hug</a></strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
"_Se você está procurando aprender um **framework moderno** para construir APIs REST, dê uma olhada no **FastAPI** [...] É rápido, fácil de usar e fácil de aprender [...]_"
"*Se você está procurando aprender um **_framework_ moderno** para construir aplicações _REST_, dê uma olhada no **FastAPI** [...] É rápido, fácil de usar e fácil de aprender [...]*"
"_Nós trocamos nossas **APIs** por **FastAPI** [...] Acredito que você gostará dele [...]_"
"*Nós trocamos nossas **APIs** por **FastAPI** [...] Acredito que vocês gostarão dele [...]*"
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong>fundadores da <a href="https://explosion.ai" target="_blank">Explosion AI</a> - criadores da <a href="https://spacy.io" target="_blank">spaCy</a></strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
---
"_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**, e se tornou um **componente chave** para a nossa estratégia de desenvolvimento API first, impulsionando diversas automações e serviços, como o nosso Virtual TAC Engineer._"
"_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._"
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
---
## Mini documentário do FastAPI { #fastapi-mini-documentary }
Há um <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">mini documentário do FastAPI</a> lançado no fim de 2025, você pode assisti-lo online:
<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a>
## **Typer**, o FastAPI das interfaces de linhas de comando { #typer-the-fastapi-of-clis }
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
Se você estiver construindo uma aplicação <abbr title="Command Line Interface - Interface de Linha de Comando">CLI</abbr> para ser utilizada no terminal ao invés de uma API web, dê uma olhada no <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
Se você estiver construindo uma aplicação <abbr title="Command Line Interface Interface de Linha de Comando">CLI</abbr> para ser utilizada em um terminal ao invés de uma aplicação web, dê uma olhada no <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** é o irmão menor do FastAPI. E seu propósito é ser o **FastAPI das CLIs**. ⌨️ 🚀
**Typer** é o irmão menor do FastAPI. E seu propósito é ser o **FastAPI das _CLIs_**. ⌨️ 🚀
## Requisitos { #requirements }
@@ -261,10 +255,10 @@ Você verá a resposta JSON como:
Você acabou de criar uma API que:
* Recebe requisições HTTP nos _paths_ `/` e `/items/{item_id}`.
* Ambos _paths_ fazem <em>operações</em> `GET` (também conhecido como _métodos_ HTTP).
* O _path_ `/items/{item_id}` tem um _parâmetro de path_ `item_id` que deve ser um `int`.
* O _path_ `/items/{item_id}` tem um _parâmetro query_ `q` `str` opcional.
* Recebe requisições HTTP nas _rotas_ `/` e `/items/{item_id}`.
* Ambas _rotas_ fazem <em>operações</em> `GET` (também conhecido como _métodos_ HTTP).
* A _rota_ `/items/{item_id}` tem um _parâmetro de rota_ `item_id` que deve ser um `int`.
* A _rota_ `/items/{item_id}` tem um _parâmetro query_ `q` `str` opcional.
### Documentação Interativa da API { #interactive-api-docs }
@@ -284,7 +278,7 @@ Você verá a documentação automática alternativa (fornecida por <a href="htt
## Evoluindo o Exemplo { #example-upgrade }
Agora modifique o arquivo `main.py` para receber um corpo de uma requisição `PUT`.
Agora modifique o arquivo `main.py` para receber um corpo para uma requisição `PUT`.
Declare o corpo utilizando tipos padrão Python, graças ao Pydantic.
@@ -340,7 +334,7 @@ Agora vá para <a href="http://127.0.0.1:8000/docs" class="external-link" target
E agora, vá para <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
* A documentação alternativa também irá refletir o novo parâmetro query e o corpo:
* A documentação alternativa também irá refletir o novo parâmetro da _query_ e o corpo:
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
@@ -374,15 +368,15 @@ item: Item
* Validação de dados:
* Erros automáticos e claros quando o dado é inválido.
* Validação até para objetos JSON profundamente aninhados.
* <abbr title="também conhecido como: serialization, parsing, marshalling">Conversão</abbr> de dados de entrada: vindo da rede para dados e tipos Python. Consegue ler:
* <abbr title="também conhecido como: serialização, parsing, marshalling">Conversão</abbr> de dados de entrada: vindo da rede para dados e tipos Python. Consegue ler:
* JSON.
* Parâmetros de path.
* Parâmetros query.
* Cookies.
* Parâmetros de rota.
* Parâmetros de _query_ .
* _Cookies_.
* Cabeçalhos.
* Formulários.
* Arquivos.
* <abbr title="também conhecido como: serialization, parsing, marshalling">Conversão</abbr> de dados de saída: convertendo de tipos e dados Python para dados de rede (como JSON):
* <abbr title="também conhecido como: serialização, parsing, marshalling">Conversão</abbr> de dados de saída de tipos e dados Python para dados de rede (como JSON):
* Converte tipos Python (`str`, `int`, `float`, `bool`, `list` etc).
* Objetos `datetime`.
* Objetos `UUID`.
@@ -396,17 +390,17 @@ item: Item
Voltando ao código do exemplo anterior, **FastAPI** irá:
* Validar que existe um `item_id` no path para requisições `GET` e `PUT`.
* Validar que existe um `item_id` na rota para requisições `GET` e `PUT`.
* Validar que `item_id` é do tipo `int` para requisições `GET` e `PUT`.
* Se não for, o cliente verá um erro útil e claro.
* Verificar se existe um parâmetro query opcional nomeado como `q` (como em `http://127.0.0.1:8000/items/foo?q=somequery`) para requisições `GET`.
* Se não é validado, o cliente verá um útil, claro erro.
* Verificar se existe um parâmetro de _query_ opcional nomeado como `q` (como em `http://127.0.0.1:8000/items/foo?q=somequery`) para requisições `GET`.
* Como o parâmetro `q` é declarado com `= None`, ele é opcional.
* Sem o `None` ele seria obrigatório (como o corpo no caso de `PUT`).
* Sem o `None` ele poderia ser obrigatório (como o corpo no caso de `PUT`).
* Para requisições `PUT` para `/items/{item_id}`, lerá o corpo como JSON:
* Verifica que tem um atributo obrigatório `name` que deve ser `str`.
* Verifica que tem um atributo obrigatório `price` que tem que ser um `float`.
* Verifica que tem um atributo opcional `is_offer`, que deve ser um `bool`, se presente.
* Tudo isso também funcionaria para objetos JSON profundamente aninhados.
* Verifica que tem um atributo obrigatório `price` que deve ser `float`.
* Verifica que tem an atributo opcional `is_offer`, que deve ser `bool`, se presente.
* Tudo isso também funciona para objetos JSON profundamente aninhados.
* Converter de e para JSON automaticamente.
* Documentar tudo com OpenAPI, que poderá ser usado por:
* Sistemas de documentação interativos.
@@ -415,7 +409,7 @@ Voltando ao código do exemplo anterior, **FastAPI** irá:
---
Nós apenas arranhamos a superfície, mas você já tem ideia de como tudo funciona.
Nós apenas arranhamos a superfície, mas você já tem idéia de como tudo funciona.
Experimente mudar a seguinte linha:
@@ -443,22 +437,22 @@ Para um exemplo mais completo incluindo mais recursos, veja <a href="https://fas
**Alerta de Spoiler**: o tutorial - guia do usuário inclui:
* Declaração de **parâmetros** de diferentes lugares como: **cabeçalhos**, **cookies**, **campos de formulários** e **arquivos**.
* Como configurar **limitações de validação** como `maximum_length` ou `regex`.
* Um poderoso e fácil de usar sistema de **<abbr title="também conhecido como components, resources, providers, services, injectables">Injeção de Dependência</abbr>**.
* Segurança e autenticação, incluindo suporte para **OAuth2** com autenticação com **JWT tokens** e **HTTP Basic**.
* Declaração de **parâmetetros** de diferentes lugares como: **cabeçalhos**, **cookies**, **campos de formulários** e **arquivos**.
* Como configurar **Limitações de Validação** como `maximum_length` ou `regex`.
* Um poderoso e fácil de usar sistema de **<abbr title="também conhecido como componentes, recursos, fornecedores, serviços, injetáveis">Injeção de Dependência</abbr>**.
* 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 <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> e outras bibliotecas.
* Muitos recursos extras (graças ao Starlette) como:
* **WebSockets**
* testes extremamente fáceis baseados em HTTPX e `pytest`
* testes extrememamente fáceis baseados em HTTPX e `pytest`
* **CORS**
* **Cookie Sessions**
* ...e mais.
### Implemente sua aplicação (opcional) { #deploy-your-app-optional }
Você pode opcionalmente implantar sua aplicação FastAPI na <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, vá e entre na lista de espera se ainda não o fez. 🚀
Você pode opcionalmente implantar sua aplicação FastAPI na <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, inscreva-se na lista de espera se ainda não o fez. 🚀
Se você já tem uma conta na **FastAPI Cloud** (nós convidamos você da lista de espera 😉), pode implantar sua aplicação com um único comando.
@@ -512,7 +506,7 @@ Siga os tutoriais do seu provedor de nuvem para implantar aplicações FastAPI c
Testes de performance da _Independent TechEmpower_ mostram aplicações **FastAPI** rodando sob Uvicorn como <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">um dos _frameworks_ Python mais rápidos disponíveis</a>, somente atrás de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*)
Para entender mais sobre isso, veja a seção <a href="https://fastapi.tiangolo.com/pt/benchmarks/" class="internal-link" target="_blank">Comparações</a>.
Para entender mais sobre performance, veja a seção <a href="https://fastapi.tiangolo.com/pt/benchmarks/" class="internal-link" target="_blank">Comparações</a>.
## Dependências { #dependencies }
@@ -520,7 +514,7 @@ O FastAPI depende do Pydantic e do Starlette.
### Dependências `standard` { #standard-dependencies }
Quando você instala o FastAPI com `pip install "fastapi[standard]"`, ele vem com o grupo `standard` de dependências opcionais:
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:
@@ -530,7 +524,7 @@ Utilizado pelo Starlette:
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Obrigatório caso você queira utilizar o `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Obrigatório se você quer utilizar a configuração padrão de templates.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Obrigatório se você deseja suporte a <abbr title="converting the string that comes from an HTTP request into Python data - convertendo a string que vem de uma requisição HTTP em dados Python">"parsing"</abbr> de formulário, com `request.form()`.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Obrigatório se você deseja suporte a <abbr title="convertendo a string que vem de uma requisição HTTP em dados Python">"parsing"</abbr> de formulário, com `request.form()`.
Utilizado pelo FastAPI:
@@ -553,7 +547,7 @@ Existem algumas dependências adicionais que você pode querer instalar.
Dependências opcionais adicionais do Pydantic:
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - para gerenciamento de configurações.
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - para tipos extras a serem utilizados com o Pydantic.
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - tipos extras para serem utilizados com o Pydantic.
Dependências opcionais adicionais do FastAPI:

View File

@@ -31,7 +31,7 @@ Digamos que você tenha uma estrutura de arquivos como esta:
/// tip | Dica
Existem vários arquivos `__init__.py`: um em cada diretório ou subdiretório.
Existem vários arquivos `__init__.py` presentes em cada diretório ou subdiretório.
Isso permite a importação de código de um arquivo para outro.
@@ -43,32 +43,32 @@ from app.routers import items
///
* O diretório `app` contém tudo. E possui um arquivo vazio `app/__init__.py`, então ele é um "pacote Python" (uma coleção de "módulos Python"): `app`.
* Ele contém um arquivo `app/main.py`. Como está dentro de um pacote Python (um diretório com um arquivo `__init__.py`), ele é um "módulo" desse pacote: `app.main`.
* Existe também um arquivo `app/dependencies.py`, assim como `app/main.py`, ele é um "módulo": `app.dependencies`.
* O diretório `app` contém todo o código da aplicação. Ele possui um arquivo `app/__init__.py` vazio, o que o torna um "pacote Python" (uma coleção de "módulos Python"): `app`.
* Dentro dele, o arquivo `app/main.py` está localizado em um pacote Python (diretório com `__init__.py`). Portanto, ele é um "módulo" desse pacote: `app.main`.
* Existem também um arquivo `app/dependencies.py`, assim como o `app/main.py`, ele é um "módulo": `app.dependencies`.
* Há um subdiretório `app/routers/` com outro arquivo `__init__.py`, então ele é um "subpacote Python": `app.routers`.
* O arquivo `app/routers/items.py` está dentro de um pacote, `app/routers/`, portanto é um submódulo: `app.routers.items`.
* O mesmo com `app/routers/users.py`, ele é outro submódulo: `app.routers.users`.
* Há também um subdiretório `app/internal/` com outro arquivo `__init__.py`, então ele é outro "subpacote Python": `app.internal`.
* O arquivo `app/routers/items.py` está dentro de um pacote, `app/routers/`, portanto, é um "submódulo": `app.routers.items`.
* O mesmo com `app/routers/users.py`, ele é outro submódulo: `app.routers.users`.
* Há também um subdiretório `app/internal/` com outro arquivo `__init__.py`, então ele é outro "subpacote Python":`app.internal`.
* E o arquivo `app/internal/admin.py` é outro submódulo: `app.internal.admin`.
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
A mesma estrutura de arquivos com comentários:
```bash
```
.
├── app # "app" is a Python package
│   ├── __init__.py # this file makes "app" a "Python package"
│   ├── main.py # "main" module, e.g. import app.main
│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
│   └── routers # "routers" is a "Python subpackage"
│   │ ├── __init__.py # makes "routers" a "Python subpackage"
│   │ ├── items.py # "items" submodule, e.g. import app.routers.items
│   │ └── users.py # "users" submodule, e.g. import app.routers.users
│   └── internal # "internal" is a "Python subpackage"
│   ├── __init__.py # makes "internal" a "Python subpackage"
│   └── admin.py # "admin" submodule, e.g. import app.internal.admin
├── app # "app" é um pacote Python
│   ├── __init__.py # este arquivo torna "app" um "pacote Python"
│   ├── main.py # "main" módulo, e.g. import app.main
│   ├── dependencies.py # "dependencies" módulo, e.g. import app.dependencies
│   └── routers # "routers" é um "subpacote Python"
│   │ ├── __init__.py # torna "routers" um "subpacote Python"
│   │ ├── items.py # "items" submódulo, e.g. import app.routers.items
│   │ └── users.py # "users" submódulo, e.g. import app.routers.users
│   └── internal # "internal" é um "subpacote Python"
│   ├── __init__.py # torna "internal" um "subpacote Python"
│   └── admin.py # "admin" submódulo, e.g. import app.internal.admin
```
## `APIRouter` { #apirouter }
@@ -79,11 +79,11 @@ Você quer manter as *operações de rota* relacionadas aos seus usuários separ
Mas ele ainda faz parte da mesma aplicação/web API **FastAPI** (faz parte do mesmo "pacote Python").
Você pode criar as *operações de rota* para esse módulo usando o `APIRouter`.
Você pode criar as *operações de rotas* para esse módulo usando o `APIRouter`.
### Importe `APIRouter` { #import-apirouter }
Você o importa e cria uma "instância" da mesma maneira que faria com a classe `FastAPI`:
você o importa e cria uma "instância" da mesma maneira que faria com a classe `FastAPI`:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
@@ -91,7 +91,7 @@ Você o importa e cria uma "instância" da mesma maneira que faria com a classe
E então você o utiliza para declarar suas *operações de rota*.
Utilize-o da mesma maneira que utilizaria a classe `FastAPI`:
Utilize-o da mesma maneira que utilizaria a classe `FastAPI`:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
@@ -151,7 +151,7 @@ Então, em vez de adicionar tudo isso a cada *operação de rota*, podemos adici
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Como o path de cada *operação de rota* tem que começar com `/`, como em:
Como o caminho de cada *operação de rota* deve começar com `/`, como em:
```Python hl_lines="1"
@router.get("/{item_id}")
@@ -163,9 +163,9 @@ async def read_item(item_id: str):
Então, o prefixo neste caso é `/items`.
Também podemos adicionar uma list de `tags` e `responses` extras que serão aplicadas a todas as *operações de rota* incluídas neste router.
Também podemos adicionar uma lista de `tags` e `responses` extras que serão aplicadas a todas as *operações de rota* incluídas neste roteador.
E podemos adicionar uma list de `dependencies` que serão adicionadas a todas as *operações de rota* no router e serão executadas/resolvidas para cada request feita a elas.
E podemos adicionar uma lista de `dependencies` que serão adicionadas a todas as *operações de rota* no roteador e serão executadas/resolvidas para cada request feita a elas.
/// tip | Dica
@@ -173,7 +173,7 @@ Observe que, assim como [dependências em *decoradores de operação de rota*](d
///
O resultado final é que os paths dos itens agora são:
O resultado final é que os caminhos dos itens agora são:
* `/items/`
* `/items/{item_id}`
@@ -183,9 +183,9 @@ O resultado final é que os paths dos itens agora são:
* Elas serão marcadas com uma lista de tags que contêm uma única string `"items"`.
* Essas "tags" são especialmente úteis para os sistemas de documentação interativa automática (usando OpenAPI).
* Todas elas incluirão as `responses` predefinidas.
* Todas essas *operações de rota* terão a list de `dependencies` avaliada/executada antes delas.
* Todas essas *operações de rota* terão a lista de `dependencies` avaliada/executada antes delas.
* Se você também declarar dependências em uma *operação de rota* específica, **elas também serão executadas**.
* As dependências do router são executadas primeiro, depois as [`dependencies` no decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} e, em seguida, as dependências de parâmetros normais.
* As dependências do roteador são executadas primeiro, depois as [`dependencies` no decorador](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} e, em seguida, as dependências de parâmetros normais.
* Você também pode adicionar [dependências de `Segurança` com `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
/// tip | Dica
@@ -246,7 +246,7 @@ from ..dependencies import get_token_header
significa:
* Começando no mesmo pacote em que este módulo (o arquivo `app/routers/items.py`) vive (o diretório `app/routers/`)...
* Começando no mesmo pacote em que este módulo (o arquivo `app/routers/items.py`) reside (o diretório `app/routers/`)...
* vá para o pacote pai (o diretório `app/`)...
* e lá, encontre o módulo `dependencies` (o arquivo em `app/dependencies.py`)...
* e dele, importe a função `get_token_header`.
@@ -283,9 +283,9 @@ Mas ainda podemos adicionar _mais_ `tags` que serão aplicadas a uma *operação
/// tip | Dica
Esta última operação de rota terá a combinação de tags: `["items", "custom"]`.
Esta última operação de caminho terá a combinação de tags: `["items", "custom"]`.
E também terá ambas as responses na documentação, uma para `404` e uma para `403`.
E também terá ambas as respostas na documentação, uma para `404` e uma para `403`.
///
@@ -325,7 +325,7 @@ from .routers import items, users
significa:
* Começando no mesmo pacote em que este módulo (o arquivo `app/main.py`) vive (o diretório `app/`)...
* Começando no mesmo pacote em que este módulo (o arquivo `app/main.py`) reside (o diretório `app/`)...
* procure o subpacote `routers` (o diretório em `app/routers/`)...
* e dele, importe o submódulo `items` (o arquivo em `app/routers/items.py`) e `users` (o arquivo em `app/routers/users.py`)...
@@ -376,7 +376,7 @@ Então, para poder usar ambos no mesmo arquivo, importamos os submódulos direta
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
### Inclua os `APIRouter`s para `users` e `items` { #include-the-apirouters-for-users-and-items }
### Inclua os `APIRouter`s para `usuários` e `itens` { #include-the-apirouters-for-users-and-items }
Agora, vamos incluir os `router`s dos submódulos `users` e `items`:
@@ -392,7 +392,7 @@ E `items.router` contém o `APIRouter` dentro do arquivo `app/routers/items.py`.
Com `app.include_router()` podemos adicionar cada `APIRouter` ao aplicativo principal `FastAPI`.
Ele incluirá todas as rotas daquele router como parte dele.
Ele incluirá todas as rotas daquele roteador como parte dele.
/// note | Detalhes Técnicos
@@ -404,7 +404,7 @@ Então, nos bastidores, ele realmente funcionará como se tudo fosse o mesmo apl
/// check | Verifique
Você não precisa se preocupar com desempenho ao incluir routers.
Você não precisa se preocupar com desempenho ao incluir roteadores.
Isso levará microssegundos e só acontecerá na inicialização.
@@ -453,7 +453,7 @@ e funcionará corretamente, junto com todas as outras *operações de rota* adic
/// note | Detalhes Técnicos Avançados
**Nota**: este é um detalhe muito técnico que você provavelmente pode **simplesmente pular**.
**Observação**: este é um detalhe muito técnico que você provavelmente pode **simplesmente pular**.
---
@@ -479,15 +479,15 @@ $ fastapi dev app/main.py
</div>
E abra a documentação em <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
E abra os documentos em <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Você verá a documentação automática da API, incluindo os paths de todos os submódulos, usando os paths (e prefixos) corretos e as tags corretas:
Você verá a documentação automática da API, incluindo os caminhos de todos os submódulos, usando os caminhos (e prefixos) corretos e as tags corretas:
<img src="/img/tutorial/bigger-applications/image01.png">
## Inclua o mesmo router várias vezes com `prefix` diferentes { #include-the-same-router-multiple-times-with-different-prefix }
## Inclua o mesmo roteador várias vezes com `prefix` diferentes { #include-the-same-router-multiple-times-with-different-prefix }
Você também pode usar `.include_router()` várias vezes com o *mesmo* router usando prefixos diferentes.
Você também pode usar `.include_router()` várias vezes com o *mesmo* roteador usando prefixos diferentes.
Isso pode ser útil, por exemplo, para expor a mesma API sob prefixos diferentes, por exemplo, `/api/v1` e `/api/latest`.
@@ -495,10 +495,10 @@ Esse é um uso avançado que você pode não precisar, mas está lá caso precis
## Inclua um `APIRouter` em outro { #include-an-apirouter-in-another }
Da mesma forma que você pode incluir um `APIRouter` em uma aplicação `FastAPI`, você pode incluir um `APIRouter` em outro `APIRouter` usando:
Da mesma forma que você pode incluir um `APIRouter` em um aplicativo `FastAPI`, você pode incluir um `APIRouter` em outro `APIRouter` usando:
```Python
router.include_router(other_router)
```
Certifique-se de fazer isso antes de incluir `router` na aplicação `FastAPI`, para que as *operações de rota* de `other_router` também sejam incluídas.
Certifique-se de fazer isso antes de incluir `router` no aplicativo `FastAPI`, para que as *operações de rota* de `other_router` também sejam incluídas.

View File

@@ -1,6 +1,6 @@
# Corpo - Atualizações { #body-updates }
## Atualização substituindo com `PUT` { #update-replacing-with-put }
## Atualização de dados existentes com `PUT` { #update-replacing-with-put }
Para atualizar um item, você pode usar a operação <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>.
@@ -22,13 +22,13 @@ Isso significa que, se você quiser atualizar o item `bar` usando `PUT` com um c
}
```
como ele não inclui o atributo já armazenado `"tax": 20.2`, o modelo de entrada assumiria o valor padrão de `"tax": 10.5`.
Como ele não inclui o atributo já armazenado `"tax": 20.2`, o modelo de entrada assumiria o valor padrão de `"tax": 10.5`.
E os dados seriam salvos com esse "novo" `tax` de `10.5`.
## Atualizações parciais com `PATCH` { #partial-updates-with-patch }
Você também pode usar a operação <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> para atualizar dados *parcialmente*.
Você também pode usar a operação <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> para atualizar parcialmente os dados.
Isso significa que você pode enviar apenas os dados que deseja atualizar, deixando o restante intacto.
@@ -40,17 +40,25 @@ E muitas equipes usam apenas `PUT`, mesmo para atualizações parciais.
Você é **livre** para usá-los como preferir, **FastAPI** não impõe restrições.
Mas este guia mostra, mais ou menos, como eles são destinados a serem usados.
Mas este guia te dá uma ideia de como eles são destinados a serem usados.
///
### Usando o parâmetro `exclude_unset` do Pydantic { #using-pydantics-exclude-unset-parameter }
Se você quiser receber atualizações parciais, é muito útil usar o parâmetro `exclude_unset` no `.model_dump()` do modelo do Pydantic.
Se você quiser receber atualizações parciais, é muito útil usar o parâmetro `exclude_unset` no método `.model_dump()` do modelo do Pydantic.
Como `item.model_dump(exclude_unset=True)`.
Isso geraria um `dict` com apenas os dados que foram definidos ao criar o modelo `item`, excluindo os valores padrão.
/// info | Informação
No Pydantic v1, o método que era chamado `.dict()` e foi descontinuado (mas ainda suportado) no Pydantic v2. Agora, deve-se usar o método `.model_dump()`.
Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` a partir do Pydantic v2.
///
Isso gera um `dict` com apenas os dados definidos ao criar o modelo `item`, excluindo os valores padrão.
Então, você pode usar isso para gerar um `dict` com apenas os dados definidos (enviados na solicitação), omitindo valores padrão:
@@ -60,23 +68,31 @@ Então, você pode usar isso para gerar um `dict` com apenas os dados definidos
Agora, você pode criar uma cópia do modelo existente usando `.model_copy()`, e passar o parâmetro `update` com um `dict` contendo os dados para atualizar.
/// info | Informação
No Pydantic v1, o método era chamado `.copy()`, ele foi descontinuado (mas ainda suportado) no Pydantic v2, e renomeado para `.model_copy()`.
Os exemplos aqui usam `.copy()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_copy()` com o Pydantic v2.
///
Como `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
### Recapitulando as atualizações parciais { #partial-updates-recap }
Resumindo, para aplicar atualizações parciais você deveria:
Resumindo, para aplicar atualizações parciais você pode:
* (Opcionalmente) usar `PATCH` em vez de `PUT`.
* Recuperar os dados armazenados.
* Colocar esses dados em um modelo do Pydantic.
* Gerar um `dict` sem valores padrão a partir do modelo de entrada (usando `exclude_unset`).
* Dessa forma, você pode atualizar apenas os valores realmente definidos pelo usuário, em vez de substituir valores já armazenados por valores padrão do modelo.
* Dessa forma, você pode atualizar apenas os valores definidos pelo usuário, em vez de substituir os valores já armazenados com valores padrão em seu modelo.
* Criar uma cópia do modelo armazenado, atualizando seus atributos com as atualizações parciais recebidas (usando o parâmetro `update`).
* Converter o modelo copiado em algo que possa ser armazenado no seu BD (por exemplo, usando o `jsonable_encoder`).
* Isso é comparável a usar o método `.model_dump()` do modelo novamente, mas garante (e converte) os valores para tipos de dados que possam ser convertidos em JSON, por exemplo, `datetime` para `str`.
* Salvar os dados no seu BD.
* Converter o modelo copiado em algo que possa ser armazenado no seu banco de dados (por exemplo, usando o `jsonable_encoder`).
* Isso é comparável ao uso do método `.model_dump()`, mas garante (e converte) os valores para tipos de dados que possam ser convertidos em JSON, por exemplo, `datetime` para `str`.
* Salvar os dados no seu banco de dados.
* Retornar o modelo atualizado.
{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *}
@@ -93,8 +109,8 @@ Mas o exemplo aqui usa `PATCH` porque foi criado para esses casos de uso.
Observe que o modelo de entrada ainda é validado.
Portanto, se você quiser receber atualizações parciais que possam omitir todos os atributos, você precisa ter um modelo com todos os atributos marcados como opcionais (com valores padrão ou `None`).
Portanto, se você quiser receber atualizações parciais que possam omitir todos os atributos, precisa ter um modelo com todos os atributos marcados como opcionais (com valores padrão ou `None`).
Para distinguir entre os modelos com todos os valores opcionais para **atualizações** e modelos com valores obrigatórios para **criação**, você pode usar as ideias descritas em [Modelos Adicionais](extra-models.md){.internal-link target=_blank}.
Para distinguir os modelos com todos os valores opcionais para **atualizações** e modelos com valores obrigatórios para **criação**, você pode usar as ideias descritas em [Modelos Adicionais](extra-models.md){.internal-link target=_blank}.
///

View File

@@ -10,11 +10,11 @@ Para declarar um corpo da **requisição**, você utiliza os modelos do <a href=
/// info | Informação
Para enviar dados, você deveria usar um dos: `POST` (o mais comum), `PUT`, `DELETE` ou `PATCH`.
Para enviar dados, você deve usar um dos: `POST` (o mais comum), `PUT`, `DELETE` ou `PATCH`.
Enviar um corpo em uma requisição `GET` não tem um comportamento definido nas especificações, porém é suportado pelo FastAPI, apenas para casos de uso bem complexos/extremos.
Como é desencorajado, a documentação interativa com Swagger UI não irá mostrar a documentação para o corpo da requisição ao usar `GET`, e proxies intermediários podem não suportá-lo.
Como é desencorajado, a documentação interativa com Swagger UI não irá mostrar a documentação para o corpo da requisição para um `GET`, e proxies que intermediarem podem não suportar o corpo da requisição.
///
@@ -32,8 +32,7 @@ Utilize os tipos Python padrão para todos os atributos:
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
Assim como quando declaramos parâmetros de consulta, quando um atributo do modelo possui um valor padrão, ele não é obrigatório. Caso contrário, é obrigatório. Use `None` para torná-lo apenas opcional.
Assim como quando declaramos parâmetros de consulta, quando um atributo do modelo possui um valor padrão, ele se torna opcional. Caso contrário, se torna obrigatório. Use `None` para torná-lo opcional.
Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) como esse:
@@ -67,7 +66,7 @@ Para adicioná-lo à sua *operação de rota*, declare-o da mesma maneira que vo
Apenas com essa declaração de tipos do Python, o **FastAPI** irá:
* Ler o corpo da requisição como JSON.
* Ler o corpo da requisição como um JSON.
* Converter os tipos correspondentes (se necessário).
* Validar os dados.
* Se algum dado for inválido, irá retornar um erro bem claro, indicando exatamente onde e o que estava incorreto.
@@ -128,6 +127,14 @@ Dentro da função, você pode acessar todos os atributos do objeto do modelo di
{* ../../docs_src/body/tutorial002_py310.py *}
/// info | Informação
No Pydantic v1 o método se chamava `.dict()`, ele foi descontinuado (mas ainda é suportado) no Pydantic v2, e renomeado para `.model_dump()`.
Os exemplos aqui usam `.dict()` para compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` se puder usar o Pydantic v2.
///
## Corpo da requisição + parâmetros de rota { #request-body-path-parameters }
Você pode declarar parâmetros de rota e corpo da requisição ao mesmo tempo.
@@ -136,7 +143,6 @@ O **FastAPI** irá reconhecer que os parâmetros da função que combinam com pa
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
## Corpo da requisição + parâmetros de rota + parâmetros de consulta { #request-body-path-query-parameters }
Você também pode declarar parâmetros de **corpo**, **rota** e **consulta**, ao mesmo tempo.

View File

@@ -22,13 +22,21 @@ Aqui está uma ideia geral de como os modelos poderiam parecer com seus campos d
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
### Sobre `**user_in.model_dump()` { #about-user-in-model-dump }
/// info | Informação
#### O `.model_dump()` do Pydantic { #pydantics-model-dump }
No Pydantic v1 o método se chamava `.dict()`, ele foi descontinuado (mas ainda é suportado) no Pydantic v2 e renomeado para `.model_dump()`.
Os exemplos aqui usam `.dict()` por compatibilidade com o Pydantic v1, mas você deve usar `.model_dump()` se puder usar o Pydantic v2.
///
### Sobre `**user_in.dict()` { #about-user-in-dict }
#### O `.dict()` do Pydantic { #pydantics-dict }
`user_in` é um modelo Pydantic da classe `UserIn`.
Os modelos Pydantic possuem um método `.model_dump()` que retorna um `dict` com os dados do modelo.
Os modelos Pydantic possuem um método `.dict()` que retorna um `dict` com os dados do modelo.
Então, se criarmos um objeto Pydantic `user_in` como:
@@ -39,7 +47,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
e depois chamarmos:
```Python
user_dict = user_in.model_dump()
user_dict = user_in.dict()
```
agora temos um `dict` com os dados na variável `user_dict` (é um `dict` em vez de um objeto de modelo Pydantic).
@@ -95,20 +103,20 @@ UserInDB(
#### Um modelo Pydantic a partir do conteúdo de outro { #a-pydantic-model-from-the-contents-of-another }
Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.model_dump()`, este código:
Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.dict()`, este código:
```Python
user_dict = user_in.model_dump()
user_dict = user_in.dict()
UserInDB(**user_dict)
```
seria equivalente a:
```Python
UserInDB(**user_in.model_dump())
UserInDB(**user_in.dict())
```
...porque `user_in.model_dump()` é um `dict`, e depois fazemos o Python "desembrulhá-lo" passando-o para `UserInDB` precedido por `**`.
...porque `user_in.dict()` é um `dict`, e depois fazemos o Python "desembrulhá-lo" passando-o para `UserInDB` precedido por `**`.
Então, obtemos um modelo Pydantic a partir dos dados em outro modelo Pydantic.
@@ -117,7 +125,7 @@ Então, obtemos um modelo Pydantic a partir dos dados em outro modelo Pydantic.
E, então, adicionando o argumento de palavra-chave extra `hashed_password=hashed_password`, como em:
```Python
UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
UserInDB(**user_in.dict(), hashed_password=hashed_password)
```
...acaba sendo como:

View File

@@ -33,7 +33,7 @@ Para isso, primeiro importe:
O FastAPI adicionou suporte a `Annotated` (e passou a recomendá-lo) na versão 0.95.0.
Se você tiver uma versão mais antiga, teria erros ao tentar usar `Annotated`.
Se você tiver uma versão mais antiga, terá erros ao tentar usar `Annotated`.
Certifique-se de [Atualizar a versão do FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} para pelo menos 0.95.1 antes de usar `Annotated`.
@@ -109,7 +109,7 @@ Agora o FastAPI vai:
## Alternativa (antiga): `Query` como valor padrão { #alternative-old-query-as-the-default-value }
Versões anteriores do FastAPI (antes de <abbr title="before 2023-03 - antes de 2023-03">0.95.0</abbr>) exigiam que você usasse `Query` como valor padrão do seu parâmetro, em vez de colocá-lo em `Annotated`, há uma grande chance de você ver código usando isso por aí, então vou explicar.
Versões anteriores do FastAPI (antes de <abbr title="antes de 2023-03">0.95.0</abbr>) exigiam que você usasse `Query` como valor padrão do seu parâmetro, em vez de colocá-lo em `Annotated`. É muito provável que você veja código assim por aí, então vou te explicar.
/// tip | Dica
@@ -192,7 +192,7 @@ Você também pode adicionar um parâmetro `min_length`:
## Adicione expressões regulares { #add-regular-expressions }
Você pode definir um `pattern` de <abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings. - Uma expressão regular, regex ou regexp é uma sequência de caracteres que define um padrão de busca para strings.">expressão regular</abbr> que o parâmetro deve corresponder:
Você pode definir um `pattern` de <abbr title="Uma expressão regular, regex ou regexp é uma sequência de caracteres que define um padrão de busca para strings.">expressão regular</abbr> que o parâmetro deve corresponder:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
@@ -206,6 +206,20 @@ Se você se sentir perdido com essas ideias de **"expressão regular"**, não se
Agora você sabe que, sempre que precisar delas, pode usá-las no **FastAPI**.
### Pydantic v1 `regex` em vez de `pattern` { #pydantic-v1-regex-instead-of-pattern }
Antes da versão 2 do Pydantic e antes do FastAPI 0.100.0, o parâmetro se chamava `regex` em vez de `pattern`, mas agora está descontinuado.
Você ainda pode ver algum código usando isso:
//// tab | Pydantic v1
{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
////
Mas saiba que isso está descontinuado e deve ser atualizado para usar o novo parâmetro `pattern`. 🤓
## Valores padrão { #default-values }
Você pode, claro, usar valores padrão diferentes de `None`.
@@ -266,7 +280,7 @@ Então, com uma URL como:
http://localhost:8000/items/?q=foo&q=bar
```
você receberia os múltiplos valores dos *parâmetros de consulta* `q` (`foo` e `bar`) em uma `list` Python dentro da sua *função de operação de rota*, no *parâmetro da função* `q`.
você receberá os múltiplos valores do *parâmetro de consulta* `q` (`foo` e `bar`) em uma `list` Python dentro da sua *função de operação de rota*, no *parâmetro da função* `q`.
Assim, a resposta para essa URL seria:
@@ -336,7 +350,7 @@ Essas informações serão incluídas no OpenAPI gerado e usadas pelas interface
Tenha em mente que ferramentas diferentes podem ter níveis diferentes de suporte ao OpenAPI.
Algumas delas podem ainda não mostrar todas as informações extras declaradas, embora na maioria dos casos a funcionalidade ausente já esteja planejada para desenvolvimento.
Algumas delas podem ainda não mostrar todas as informações extras declaradas, embora na maioria dos casos o recurso ausente já esteja planejado para desenvolvimento.
///
@@ -372,7 +386,7 @@ Então você pode declarar um `alias`, e esse alias será usado para encontrar o
Agora digamos que você não gosta mais desse parâmetro.
Você tem que deixá-lo por um tempo, pois há clientes usando-o, mas quer que a documentação mostre claramente que ele está <abbr title="obsolete, recommended not to use it - obsoleto, recomenda-se não usá-lo">deprecated</abbr>.
Você tem que deixá-lo por um tempo, pois há clientes usando-o, mas quer que a documentação mostre claramente que ele está <abbr title="obsoleto, recomenda-se não usá-lo">descontinuado</abbr>.
Então passe o parâmetro `deprecated=True` para `Query`:
@@ -402,7 +416,7 @@ O Pydantic também tem <a href="https://docs.pydantic.dev/latest/concepts/valida
///
Por exemplo, este validador personalizado verifica se o ID do item começa com `isbn-` para um número de livro <abbr title="ISBN means International Standard Book Number - ISBN significa Número Padrão Internacional de Livro">ISBN</abbr> ou com `imdb-` para um ID de URL de filme <abbr title="IMDB (Internet Movie Database) is a website with information about movies - IMDB (Internet Movie Database) é um site com informações sobre filmes">IMDB</abbr>:
Por exemplo, este validador personalizado verifica se o ID do item começa com `isbn-` para um número de livro <abbr title="ISBN significa Número Padrão Internacional de Livro">ISBN</abbr> ou com `imdb-` para um ID de URL de filme <abbr title="IMDB (Internet Movie Database) é um site com informações sobre filmes">IMDB</abbr>:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
@@ -414,7 +428,7 @@ Isso está disponível com a versão 2 do Pydantic ou superior. 😎
/// tip | Dica
Se você precisar fazer qualquer tipo de validação que exija comunicação com algum **componente externo**, como um banco de dados ou outra API, você deveria usar **Dependências do FastAPI** em vez disso; você aprenderá sobre elas mais adiante.
Se você precisar fazer qualquer tipo de validação que exija comunicação com algum **componente externo**, como um banco de dados ou outra API, você deve usar **Dependências do FastAPI** em vez disso; você aprenderá sobre elas mais adiante.
Esses validadores personalizados são para coisas que podem ser verificadas **apenas** com os **mesmos dados** fornecidos na requisição.
@@ -426,7 +440,7 @@ O ponto importante é apenas usar **`AfterValidator` com uma função dentro de
---
Mas se você estiver curioso sobre este exemplo de código específico e ainda entretido, aqui vão alguns detalhes extras.
Mas se você está curioso sobre este exemplo específico e ainda entretido, aqui vão alguns detalhes extras.
#### String com `value.startswith()` { #string-with-value-startswith }
@@ -436,7 +450,7 @@ Percebeu? Uma string usando `value.startswith()` pode receber uma tupla, e verif
#### Um item aleatório { #a-random-item }
Com `data.items()` obtemos um <abbr title="Something we can iterate on with a for loop, like a list, set, etc. - Algo que podemos iterar com um laço for, como uma list, set, etc.">objeto iterável</abbr> com tuplas contendo a chave e o valor de cada item do dicionário.
Com `data.items()` obtemos um <abbr title="Algo que podemos iterar com um laço for, como uma list, set, etc.">objeto iterável</abbr> com tuplas contendo a chave e o valor de cada item do dicionário.
Convertimos esse objeto iterável em uma `list` adequada com `list(data.items())`.

View File

@@ -252,6 +252,20 @@ Então, se você enviar uma solicitação para essa *operação de rota* para o
/// info | Informação
No Pydantic v1, o método era chamado `.dict()`, ele foi descontinuado (mas ainda suportado) no Pydantic v2 e renomeado para `.model_dump()`.
Os exemplos aqui usam `.dict()` para compatibilidade com Pydantic v1, mas você deve usar `.model_dump()` em vez disso se puder usar Pydantic v2.
///
/// info | Informação
O FastAPI usa `.dict()` do modelo Pydantic com <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">seu parâmetro `exclude_unset`</a> para chegar a isso.
///
/// info | Informação
Você também pode usar:
* `response_model_exclude_defaults=True`

View File

@@ -8,17 +8,39 @@ Aqui estão várias maneiras de fazer isso.
Você pode declarar `examples` para um modelo Pydantic que serão adicionados ao JSON Schema gerado.
//// tab | Pydantic v2
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
////
//// tab | Pydantic v1
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
////
Essas informações extras serão adicionadas como estão ao **JSON Schema** de saída para esse modelo e serão usadas na documentação da API.
Você pode usar o atributo `model_config`, que recebe um `dict`, conforme descrito na <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">documentação do Pydantic: Configuration</a>.
//// tab | Pydantic v2
Na versão 2 do Pydantic, você usaria o atributo `model_config`, que recebe um `dict`, conforme descrito na <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">documentação do Pydantic: Configuration</a>.
Você pode definir `"json_schema_extra"` com um `dict` contendo quaisquer dados adicionais que você queira que apareçam no JSON Schema gerado, incluindo `examples`.
////
//// tab | Pydantic v1
Na versão 1 do Pydantic, você usaria uma classe interna `Config` e `schema_extra`, conforme descrito na <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">documentação do Pydantic: Schema customization</a>.
Você pode definir `schema_extra` com um `dict` contendo quaisquer dados adicionais que você queira que apareçam no JSON Schema gerado, incluindo `examples`.
////
/// tip | Dica
Você poderia usar a mesma técnica para estender o JSON Schema e adicionar suas próprias informações extras personalizadas.
Você pode usar a mesma técnica para estender o JSON Schema e adicionar suas próprias informações extras personalizadas.
Por exemplo, você poderia usá-la para adicionar metadados para uma interface de usuário de front-end, etc.
@@ -28,7 +50,7 @@ Por exemplo, você poderia usá-la para adicionar metadados para uma interface d
O OpenAPI 3.1.0 (usado desde o FastAPI 0.99.0) adicionou suporte a `examples`, que faz parte do padrão **JSON Schema**.
Antes disso, ele suportava apenas a palavrachave `example` com um único exemplo. Isso ainda é suportado pelo OpenAPI 3.1.0, mas é descontinuado e não faz parte do padrão JSON Schema. Portanto, você é incentivado a migrar de `example` para `examples`. 🤓
Antes disso, ele suportava apenas a palavrachave `example` com um único exemplo. Isso ainda é suportado pelo OpenAPI 3.1.0, mas é descontinuado e não faz parte do padrão JSON Schema. Portanto, é recomendado migrar de `example` para `examples`. 🤓
Você pode ler mais no final desta página.
@@ -80,7 +102,7 @@ No entanto, <abbr title="2023-08-26">no momento em que isto foi escrito</abbr>,
Antes do **JSON Schema** suportar `examples`, o OpenAPI já tinha suporte para um campo diferente também chamado `examples`.
Esse `examples` **específico do OpenAPI** vai em outra seção da especificação OpenAPI. Ele fica nos **detalhes de cada *operação de rota***, não dentro de cada JSON Schema.
Esse `examples` específico do OpenAPI vai em outra seção da especificação. Ele fica nos **detalhes de cada função de operação de rota**, não dentro de cada JSON Schema.
E o Swagger UI tem suportado esse campo `examples` particular há algum tempo. Então, você pode usá-lo para **mostrar** diferentes **exemplos na UI da documentação**.
@@ -167,9 +189,9 @@ Depois, o JSON Schema adicionou um campo <a href="https://json-schema.org/draft/
E então o novo OpenAPI 3.1.0 passou a se basear na versão mais recente (JSON Schema 2020-12), que incluiu esse novo campo `examples`.
E agora esse novo campo `examples` tem precedência sobre o antigo campo único (e customizado) `example`, que agora está descontinuado.
Agora, esse novo campo `examples` tem precedência sobre o antigo (e customizado) campo único `example`, que agora está descontinuado.
Esse novo campo `examples` no JSON Schema é **apenas uma `list`** de exemplos, não um dict com metadados extras como nos outros lugares do OpenAPI (descritos acima).
Esse novo campo `examples` no JSON Schema é **apenas uma `list`** de exemplos, não um `dict` com metadados extras como nos outros lugares do OpenAPI (descritos acima).
/// info | Informação
@@ -191,7 +213,7 @@ Mas agora que o FastAPI 0.99.0 e superiores usam o OpenAPI 3.1.0, que usa o JSON
### Swagger UI e `examples` específicos do OpenAPI { #swagger-ui-and-openapi-specific-examples }
Agora, como o Swagger UI não suportava vários exemplos no JSON Schema (em 2023-08-26), os usuários não tinham uma forma de mostrar vários exemplos na documentação.
Como o Swagger UI não suportava vários exemplos no JSON Schema (em 2023-08-26), os usuários não tinham uma forma de mostrar vários exemplos na documentação.
Para resolver isso, o FastAPI `0.103.0` **adicionou suporte** para declarar o mesmo antigo campo **específico do OpenAPI** `examples` com o novo parâmetro `openapi_examples`. 🤓

View File

@@ -10,26 +10,6 @@ Keep existing translations as they are if the term is already translated.
When translating documentation into Portuguese, use neutral and widely understandable language. Although Portuguese originated in Portugal and has its largest number of speakers in Brazil, it is also an official language in several countries and regions, such as Equatorial Guinea, Mozambique, Angola, Cape Verde, and São Tomé and Príncipe. Avoid words or expressions that are specific to a single country or region.
Only keep parentheses if they exist in the source text. Do not add parentheses to terms that do not have them.
### Avoiding Repetition in Translation
When translating sentences, avoid unnecessary repetition of words or phrases that are implied in context.
- Merge repeated words naturally while keeping the meaning.
- Do **not** introduce extra words to replace repeated phrases unnecessarily.
- Keep translations fluent and concise, but maintain the original meaning.
**Example:**
Source:
Let's see how that works and how to change it if you need to do that.
Avoid translating literally as:
Vamos ver como isso funciona e como alterar isso se você precisar fazer isso.
Better translation:
Vamos ver como isso funciona e como alterar se você precisar.
---
For the next terms, use the following translations:
@@ -42,11 +22,10 @@ For the next terms, use the following translations:
* /// note: /// note | Nota
* /// tip: /// tip | Dica
* /// warning: /// warning | Atenção
* you should: você deveria
* (you should): (você deveria)
* async context manager: gerenciador de contexto assíncrono
* autocomplete: autocompletar
* autocompletion: preenchimento automático
* auto-completion: preenchimento automático
* bug: bug
* context manager: gerenciador de contexto
* cross domain: cross domain (do not translate to "domínio cruzado")

View File

@@ -1,8 +1,8 @@
# Тестовый файл LLM { #llm-test-file }
Этот документ проверяет, понимает ли <abbr title="Large Language Model - Большая языковая модель">LLM</abbr>, переводящая документацию, `general_prompt` в `scripts/translate.py` и языковой специфичный промпт в `docs/{language code}/llm-prompt.md`. Языковой специфичный промпт добавляется к `general_prompt`.
Этот документ проверяет, понимает ли <abbr title="Large Language Model Большая языковая модель">LLM</abbr>, переводящая документацию, `general_prompt` в `scripts/translate.py` и языковой специфичный промпт в `docs/{language code}/llm-prompt.md`. Языковой специфичный промпт добавляется к `general_prompt`.
Тесты, добавленные здесь, увидят все создатели языковых специфичных промптов.
Тесты, добавленные здесь, увидят все создатели языковых промптов.
Использование:
@@ -11,7 +11,7 @@
* Проверьте, всё ли в порядке в переводе.
* При необходимости улучшите ваш языковой специфичный промпт, общий промпт или английский документ.
* Затем вручную исправьте оставшиеся проблемы в переводе, чтобы он был хорошим.
* Переведите заново, имея хороший перевод на месте. Идеальным результатом будет ситуация, когда LLM больше не вносит изменений в перевод. Это означает, что общий промпт и ваш языковой специфичный промпт настолько хороши, насколько это возможно (иногда он будет делать несколько, казалось бы, случайных изменений, причина в том, что <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM — недетерминированные алгоритмы</a>).
* Переведите заново, имея хороший перевод на месте. Идеальным результатом будет ситуация, когда LLM больше не вносит изменений в перевод. Это означает, что общий промпт и ваш языковой специфичный промпт максимально хороши (иногда он будет делать несколько, казалось бы, случайных изменений, причина в том, что <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM — недетерминированные алгоритмы</a>).
Тесты:
@@ -197,10 +197,10 @@ works(foo="bar") # Это работает 🎉
### abbr даёт полную расшифровку { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done - Как привести дела в порядок">GTD</abbr>
* <abbr title="less than - меньше чем"><code>lt</code></abbr>
* <abbr title="XML Web Token - XML веб‑токен">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - Параллельный серверный интерфейс шлюза">PSGI</abbr>
* <abbr title="Getting Things Done Как привести дела в порядок">GTD</abbr>
* <abbr title="less than меньше чем"><code>lt</code></abbr>
* <abbr title="XML Web Token XML веб‑токен">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface Параллельный серверный интерфейс шлюза">PSGI</abbr>
### abbr даёт объяснение { #the-abbr-gives-an-explanation }
@@ -209,8 +209,8 @@ works(foo="bar") # Это работает 🎉
### abbr даёт полную расшифровку и объяснение { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network - Сеть разработчиков Mozilla: документация для разработчиков, созданная командой Firefox">MDN</abbr>
* <abbr title="Input/Output - Ввод/Вывод: чтение или запись на диск, сетевое взаимодействие.">I/O</abbr>.
* <abbr title="Mozilla Developer Network Сеть разработчиков Mozilla: документация для разработчиков, созданная командой Firefox">MDN</abbr>
* <abbr title="Input/Output Ввод/Вывод: чтение или запись на диск, сетевое взаимодействие.">I/O</abbr>.
////

View File

@@ -14,7 +14,7 @@
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
### Использование имени *функции-обработчика пути* как operationId { #using-the-path-operation-function-name-as-the-operationid }
### Использование имени функции-обработчика пути как operationId { #using-the-path-operation-function-name-as-the-operationid }
Если вы хотите использовать имена функций ваших API в качестве `operationId`, вы можете пройти по всем из них и переопределить `operation_id` каждой *операции пути* с помощью их `APIRoute.name`.
@@ -38,7 +38,7 @@
## Исключить из OpenAPI { #exclude-from-openapi }
Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (а значит, и из автоматических систем документации), используйте параметр `include_in_schema` и установите его в `False`:
Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (а значит, и из автоматической документации), используйте параметр `include_in_schema` и установите его в `False`:
{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
@@ -48,7 +48,7 @@
Добавление `\f` (экранированного символа «form feed») заставит **FastAPI** обрезать текст, используемый для OpenAPI, в этой точке.
Это не отобразится в документации, но другие инструменты (например, Sphinx) смогут использовать остальное.
Эта часть не попадёт в документацию, но другие инструменты (например, Sphinx) смогут использовать остальное.
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
@@ -56,7 +56,7 @@
Вы, вероятно, уже видели, как объявлять `response_model` и `status_code` для *операции пути*.
Это определяет метаданные об основном HTTP-ответе *операции пути*.
Это определяет метаданные об основном ответе *операции пути*.
Также можно объявлять дополнительные ответы с их моделями, статус-кодами и т.д.
@@ -76,7 +76,7 @@
Там есть `tags`, `parameters`, `requestBody`, `responses` и т.д.
Эта специфичная для *операции пути* схема OpenAPI обычно генерируется автоматически **FastAPI**, но вы также можете её расширить.
Эта спецификация OpenAPI, специфичная для *операции пути*, обычно генерируется автоматически **FastAPI**, но вы также можете её расширить.
/// tip | Совет
@@ -129,13 +129,13 @@
}
```
### Пользовательская схема OpenAPI для *операции пути* { #custom-openapi-path-operation-schema }
### Пользовательская схема OpenAPI для операции пути { #custom-openapi-path-operation-schema }
Словарь в `openapi_extra` будет глубоко объединён с автоматически сгенерированной схемой OpenAPI для *операции пути*.
Словарь в `openapi_extra` будет объединён с автоматически сгенерированной схемой OpenAPI для *операции пути*.
Таким образом, вы можете добавить дополнительные данные к автоматически сгенерированной схеме.
Например, вы можете решить читать и валидировать HTTP-запрос своим кодом, не используя автоматические возможности FastAPI и Pydantic, но при этом захотите описать HTTP-запрос в схеме OpenAPI.
Например, вы можете решить читать и валидировать запрос своим кодом, не используя автоматические возможности FastAPI и Pydantic, но при этом захотите описать запрос в схеме OpenAPI.
Это можно сделать с помощью `openapi_extra`:
@@ -149,20 +149,52 @@
Используя тот же приём, вы можете воспользоваться Pydantic-моделью, чтобы определить JSON Schema, которая затем будет включена в пользовательский раздел схемы OpenAPI для *операции пути*.
И вы можете сделать это, даже если тип данных в HTTP-запросе — не JSON.
И вы можете сделать это, даже если тип данных в запросе — не JSON.
Например, в этом приложении мы не используем встроенную функциональность FastAPI для извлечения JSON Schema из моделей Pydantic, равно как и автоматическую валидацию JSON. Мы объявляем тип содержимого HTTP-запроса как YAML, а не JSON:
Например, в этом приложении мы не используем встроенную функциональность FastAPI для извлечения JSON Schema из моделей Pydantic, равно как и автоматическую валидацию JSON. Мы объявляем тип содержимого запроса как YAML, а не JSON:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *}
////
/// info | Информация
В Pydantic версии 1 метод для получения JSON Schema модели назывался `Item.schema()`, в Pydantic версии 2 метод называется `Item.model_json_schema()`.
///
Тем не менее, хотя мы не используем встроенную функциональность по умолчанию, мы всё равно используем Pydantic-модель, чтобы вручную сгенерировать JSON Schema для данных, которые мы хотим получить в YAML.
Затем мы работаем с HTTP-запросом напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не попытается распарсить полезную нагрузку HTTP-запроса как JSON.
Затем мы работаем с запросом напрямую и извлекаем тело как `bytes`. Это означает, что FastAPI даже не попытается распарсить полезную нагрузку запроса как JSON.
А затем в нашем коде мы напрямую парсим это содержимое YAML и снова используем ту же Pydantic-модель, чтобы валидировать YAML-содержимое:
А затем в нашем коде мы напрямую парсим этот YAML и снова используем ту же Pydantic-модель для валидации YAML-содержимого:
//// tab | Pydantic v2
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
////
//// tab | Pydantic v1
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *}
////
/// info | Информация
В Pydantic версии 1 метод для парсинга и валидации объекта назывался `Item.parse_obj()`, в Pydantic версии 2 метод называется `Item.model_validate()`.
///
/// tip | Совет
Здесь мы переиспользуем ту же Pydantic-модель.

View File

@@ -46,6 +46,12 @@ $ pip install "fastapi[all]"
</div>
/// info | Информация
В Pydantic v1 он входил в основной пакет. Теперь он распространяется как отдельный пакет, чтобы вы могли установить его только при необходимости.
///
### Создание объекта `Settings` { #create-the-settings-object }
Импортируйте `BaseSettings` из Pydantic и создайте подкласс, очень похожий на Pydanticмодель.
@@ -54,8 +60,24 @@ $ pip install "fastapi[all]"
Вы можете использовать все те же возможности валидации и инструменты, что и для Pydanticмоделей, например разные типы данных и дополнительную валидацию через `Field()`.
//// tab | Pydantic v2
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
////
//// tab | Pydantic v1
/// info | Информация
В Pydantic v1 вы бы импортировали `BaseSettings` напрямую из `pydantic`, а не из `pydantic_settings`.
///
{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *}
////
/// tip | Совет
Если вам нужно что-то быстро скопировать и вставить, не используйте этот пример — воспользуйтесь последним ниже.
@@ -193,6 +215,8 @@ APP_NAME="ChimichangApp"
Затем обновите ваш `config.py` так:
//// tab | Pydantic v2
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
/// tip | Совет
@@ -201,6 +225,26 @@ APP_NAME="ChimichangApp"
///
////
//// tab | Pydantic v1
{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *}
/// tip | Совет
Класс `Config` используется только для конфигурации Pydantic. Подробнее см. <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
///
////
/// info | Информация
В Pydantic версии 1 конфигурация задавалась во внутреннем классе `Config`, в Pydantic версии 2 — в атрибуте `model_config`. Этот атрибут принимает `dict`, и чтобы получить автозавершение и ошибки «на лету», вы можете импортировать и использовать `SettingsConfigDict` для описания этого `dict`.
///
Здесь мы задаем параметр конфигурации `env_file` внутри вашего класса Pydantic `Settings` и устанавливаем значение равным имени файла dotenv, который хотим использовать.
### Создание `Settings` только один раз с помощью `lru_cache` { #creating-the-settings-only-once-with-lru-cache }

View File

@@ -2,23 +2,21 @@
Если у вас старое приложение FastAPI, возможно, вы используете Pydantic версии 1.
FastAPI версии 0.100.0 поддерживал либо Pydantic v1, либо v2. Он использовал ту версию, которая была установлена.
FastAPI поддерживает и Pydantic v1, и v2 начиная с версии 0.100.0.
FastAPI версии 0.119.0 добавил частичную поддержку Pydantic v1 изнутри Pydantic v2 (как `pydantic.v1`), чтобы упростить миграцию на v2.
Если у вас был установлен Pydantic v2, использовался он. Если вместо этого был установлен Pydantic v1 — использовался он.
FastAPI 0.126.0 убрал поддержку Pydantic v1, при этом ещё некоторое время продолжал поддерживать `pydantic.v1`.
Сейчас Pydantic v1 объявлен устаревшим, и поддержка его будет удалена в следующих версиях FastAPI, поэтому вам следует **перейти на Pydantic v2**. Так вы получите последние возможности, улучшения и исправления.
/// warning | Предупреждение
Команда Pydantic прекратила поддержку Pydantic v1 для последних версий Python, начиная с **Python 3.14**.
Это включает `pydantic.v1`, который больше не поддерживается в Python 3.14 и выше.
Кроме того, команда Pydantic прекратила поддержку Pydantic v1 для последних версий Python, начиная с **Python 3.14**.
Если вы хотите использовать последние возможности Python, вам нужно убедиться, что вы используете Pydantic v2.
///
Если у вас старое приложение FastAPI с Pydantic v1, здесь я покажу, как мигрировать на Pydantic v2, и **возможности FastAPI 0.119.0**, которые помогут выполнить постепенную миграцию.
Если у вас старое приложение FastAPI с Pydantic v1, здесь я покажу, как мигрировать на Pydantic v2, и **новые возможности в FastAPI 0.119.0**, которые помогут выполнить постепенную миграцию.
## Официальное руководство { #official-guide }
@@ -40,13 +38,13 @@ FastAPI 0.126.0 убрал поддержку Pydantic v1, при этом ещ
Вы можете использовать <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a> от той же команды Pydantic.
Этот инструмент поможет автоматически изменить большую часть кода, который нужно изменить.
Этот инструмент поможет автоматически внести большую часть необходимых изменений в код.
После этого вы можете запустить тесты и проверить, что всё работает. Если да — на этом всё. 😎
После этого запустите тесты и проверьте, что всё работает. Если да — на этом всё. 😎
## Pydantic v1 в v2 { #pydantic-v1-in-v2 }
Pydantic v2 включает всё из Pydantic v1 как подмодуль `pydantic.v1`. Но это больше не поддерживается в версиях Python выше 3.13.
Pydantic v2 включает всё из Pydantic v1 как подмодуль `pydantic.v1`.
Это означает, что вы можете установить последнюю версию Pydantic v2 и импортировать и использовать старые компоненты Pydantic v1 из этого подмодуля так, как если бы у вас был установлен старый Pydantic v1.
@@ -54,7 +52,7 @@ Pydantic v2 включает всё из Pydantic v1 как подмодуль `
### Поддержка FastAPI для Pydantic v1 внутри v2 { #fastapi-support-for-pydantic-v1-in-v2 }
Начиная с FastAPI 0.119.0, есть также частичная поддержка Pydantic v1 изнутри Pydantic v2, чтобы упростить миграцию на v2.
Начиная с FastAPI 0.119.0, есть также частичная поддержка Pydantic v1 в составе Pydantic v2, чтобы упростить миграцию на v2.
Таким образом, вы можете обновить Pydantic до последней версии 2 и сменить импорты на подмодуль `pydantic.v1` — во многих случаях всё просто заработает.
@@ -108,7 +106,7 @@ graph TB
style V2Field fill:#f9fff3
```
В некоторых случаях можно использовать и модели Pydantic v1, и v2 в одной и той же **операции пути** (обработчике пути) вашего приложения FastAPI:
В некоторых случаях можно использовать и модели Pydantic v1, и v2 в одной и той же операции пути (обработчике пути) вашего приложения FastAPI:
{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
@@ -124,12 +122,12 @@ graph TB
/// tip | Совет
Сначала попробуйте `bump-pydantic`: если тесты проходят и всё работает, вы справились одной командой. ✨
Сначала попробуйте `bump-pydantic`. Если тесты проходят и всё работает, вы справились одной командой. ✨
///
Если `bump-pydantic` не подходит для вашего случая, вы можете использовать поддержку одновременной работы моделей Pydantic v1 и v2 в одном приложении, чтобы мигрировать на Pydantic v2 постепенно.
Сначала вы можете обновить Pydantic до последней 2-й версии и изменить импорты так, чтобы все ваши модели использовали `pydantic.v1`.
Сначала обновите Pydantic до последней 2-й версии и измените импорты так, чтобы все ваши модели использовали `pydantic.v1`.
Затем вы можете начать мигрировать ваши модели с Pydantic v1 на v2 группами, поэтапно. 🚶
Затем начните мигрировать ваши модели с Pydantic v1 на v2 группами, поэтапно. 🚶

View File

@@ -2,7 +2,7 @@
При использовании **Pydantic v2** сгенерированный OpenAPI становится чуть более точным и **корректным**, чем раньше. 😎
На самом деле, в некоторых случаях в OpenAPI будет даже **две JSON-схемы** для одной и той же Pydanticмодели: для входа и для выхода — в зависимости от наличия **значений по умолчанию**.
На самом деле, в некоторых случаях в OpenAPI будет даже **две JSON схемы** для одной и той же Pydanticмодели: для входа и для выхода — в зависимости от наличия **значений по умолчанию**.
Посмотрим, как это работает, и как это изменить при необходимости.
@@ -34,7 +34,7 @@
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *}
…то, поскольку у `description` есть значение по умолчанию, если вы **ничего не вернёте** для этого поля, оно всё равно будет иметь это **значение по умолчанию**.
…то, поскольку у `description` есть значение по умолчанию, даже если вы **ничего не вернёте** для этого поля, оно всё равно будет иметь это **значение по умолчанию**.
### Модель для данных ответа { #model-for-output-response-data }
@@ -46,13 +46,13 @@
Это означает, что у него **всегда будет какое‑то значение**, просто иногда это значение может быть `None` (или `null` в JSON).
Это означает, что клиентам, использующим ваш API, не нужно проверять, существует ли это значение или нет: они могут **исходить из того, что поле всегда присутствует**, но в некоторых случаях оно будет иметь значение по умолчанию `None`.
Следовательно, клиентам, использующим ваш API, не нужно проверять наличие этого значения: они могут **исходить из того, что поле всегда присутствует**, а в некоторых случаях имеет значение по умолчанию `None`.
В OpenAPI это описывается тем, что поле помечается как **обязательное**, поскольку оно всегда присутствует.
Из‑за этого JSON Schema для модели может отличаться в зависимости от использования для **входа** или **выхода**:
* для **входа** `description` **не будет обязательным**
* для **входа** `description` не будет обязательным
* для **выхода** оно будет **обязательным** (и при этом может быть `None`, или, в терминах JSON, `null`)
### Выходная модель в документации { #model-for-output-in-docs }
@@ -81,9 +81,9 @@
Однако бывают случаи, когда вы хотите иметь **одну и ту же схему для входа и выхода**.
Главный сценарий — когда у вас уже есть сгенерированный клиентский код/SDK, и вы пока не хотите обновлять весь этот автогенерируемый клиентский код/SDK, вероятно, вы захотите сделать это в какой-то момент, но, возможно, не прямо сейчас.
Главный сценарий — когда у вас уже есть сгенерированный клиентский код/SDK, и вы пока не хотите обновлять весь этот автогенерируемый код/SDK (рано или поздно вы это сделаете, но не сейчас).
В таком случае вы можете отключить эту функциональность в **FastAPI** с помощью параметра `separate_input_output_schemas=False`.
В таком случае вы можете отключить эту функциональность в FastAPI с помощью параметра `separate_input_output_schemas=False`.
/// info | Информация
@@ -95,8 +95,10 @@
### Одна и та же схема для входной и выходной моделей в документации { #same-schema-for-input-and-output-models-in-docs }
И теперь для модели будет одна общая схема и для входа, и для выхода — только `Item`, и в ней `description` будет **не обязательным**:
Теперь для этой модели будет одна общая схема и для входа, и для выхода — только `Item`, и в ней `description` будет **не обязательным**:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
</div>
Это то же поведение, что и в Pydantic v1. 🤓

View File

@@ -5,10 +5,10 @@
</style>
<p align="center">
<a href="https://fastapi.tiangolo.com/ru"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>Фреймворк FastAPI: высокая производительность, прост в изучении, позволяет быстро писать код, готов к продакшн</em>
<em>Фреймворк FastAPI: высокая производительность, прост в изучении, быстрый в разработке, готов к продакшн</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
@@ -40,7 +40,7 @@ FastAPI — это современный, быстрый (высокопрои
* **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых доступных фреймворков Python](#performance).
* **Быстрота разработки**: Увеличьте скорость разработки фич примерно на 200300%. *
* **Меньше ошибок**: Сократите примерно на 40% количество ошибок, вызванных человеком (разработчиком). *
* **Интуитивность**: Отличная поддержка редактора кода. <abbr title="также известное как: автодополнение, автозавершение, IntelliSense">Автозавершение</abbr> везде. Меньше времени на отладку.
* **Интуитивность**: Отличная поддержка редактора кода. <abbr title="также известное как: автодополнение, IntelliSense">Автозавершение</abbr> везде. Меньше времени на отладку.
* **Простота**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации.
* **Краткость**: Минимизируйте дублирование кода. Несколько возможностей из каждого объявления параметров. Меньше ошибок.
* **Надежность**: Получите код, готовый к продакшн. С автоматической интерактивной документацией.
@@ -117,12 +117,6 @@ FastAPI — это современный, быстрый (высокопрои
---
## Мини-документальный фильм о FastAPI { #fastapi-mini-documentary }
В конце 2025 года вышел <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">мини-документальный фильм о FastAPI</a>, вы можете посмотреть его онлайн:
<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a>
## **Typer**, FastAPI для CLI { #typer-the-fastapi-of-clis }
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
@@ -263,7 +257,7 @@ INFO: Application startup complete.
* Получает HTTP-запросы по _путям_ `/` и `/items/{item_id}`.
* Оба _пути_ используют `GET` <em>операции</em> (также известные как HTTP _методы_).
* _Путь_ `/items/{item_id}` имеет _path-параметр_ `item_id`, который должен быть `int`.
* _Путь_ `/items/{item_id}` имеет _параметр пути_ `item_id`, который должен быть `int`.
* _Путь_ `/items/{item_id}` имеет необязательный `str` _параметр запроса_ `q`.
### Интерактивная документация API { #interactive-api-docs }
@@ -284,9 +278,9 @@ INFO: Application startup complete.
## Пример обновления { #example-upgrade }
Теперь измените файл `main.py`, чтобы принимать тело запроса из `PUT` HTTP-запроса.
Теперь измените файл `main.py`, чтобы принимать тело запроса из `PUT` запроса.
Объявите тело запроса, используя стандартные типы Python, спасибо Pydantic.
Объявите тело, используя стандартные типы Python, спасибо Pydantic.
```Python hl_lines="4 9-12 25-27"
from typing import Union
@@ -324,7 +318,7 @@ def update_item(item_id: int, item: Item):
Перейдите на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
* Интерактивная документация API будет автоматически обновлена, включая новое тело запроса:
* Интерактивная документация API будет автоматически обновлена, включая новое тело:
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
@@ -340,13 +334,13 @@ def update_item(item_id: int, item: Item):
Теперь откройте <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
* Альтернативная документация также отразит новый параметр запроса и тело запроса:
* Альтернативная документация также отразит новый параметр запроса и тело:
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### Подведём итоги { #recap }
Итак, вы объявляете **один раз** типы параметров, тело запроса и т.д. как параметры функции.
Итак, вы объявляете **один раз** типы параметров, тела запроса и т.д. как параметры функции.
Вы делаете это с помощью стандартных современных типов Python.
@@ -396,13 +390,13 @@ item: Item
Возвращаясь к предыдущему примеру кода, **FastAPI** будет:
* Валидировать наличие `item_id` в пути для `GET` и `PUT` HTTP-запросов.
* Валидировать, что `item_id` имеет тип `int` для `GET` и `PUT` HTTP-запросов.
* Валидировать наличие `item_id` в пути для `GET` и `PUT` запросов.
* Валидировать, что `item_id` имеет тип `int` для `GET` и `PUT` запросов.
* Если это не так, клиент увидит полезную понятную ошибку.
* Проверять, есть ли необязательный параметр запроса с именем `q` (например, `http://127.0.0.1:8000/items/foo?q=somequery`) для `GET` HTTP-запросов.
* Проверять, есть ли необязательный параметр запроса с именем `q` (например, `http://127.0.0.1:8000/items/foo?q=somequery`) для `GET` запросов.
* Поскольку параметр `q` объявлен с `= None`, он необязателен.
* Без `None` он был бы обязательным (как тело запроса в случае с `PUT`).
* Для `PUT` HTTP-запросов к `/items/{item_id}` читать тело запроса как JSON:
* Для `PUT` запросов к `/items/{item_id}` читать тело запроса как JSON:
* Проверять, что есть обязательный атрибут `name`, который должен быть `str`.
* Проверять, что есть обязательный атрибут `price`, который должен быть `float`.
* Проверять, что есть необязательный атрибут `is_offer`, который должен быть `bool`, если он присутствует.
@@ -441,11 +435,11 @@ item: Item
Более полный пример с дополнительными возможностями см. в <a href="https://fastapi.tiangolo.com/ru/tutorial/">Учебник - Руководство пользователя</a>.
**Осторожно, спойлер**: учебник - руководство пользователя включает:
**Осторожно, спойлер**: учебник - руководство включает:
* Объявление **параметров** из других источников: **HTTP-заголовки**, **cookies**, **поля формы** и **файлы**.
* Как задать **ограничения валидации** вроде `maximum_length` или `regex`.
* Очень мощную и простую в использовании систему **<abbr title="также известна как: компоненты, ресурсы, провайдеры, сервисы, инъекции">внедрения зависимостей</abbr>**.
* Очень мощную и простую в использовании систему **<abbr title="также известная как: компоненты, ресурсы, провайдеры, сервисы, инъекции">внедрения зависимостей</abbr>**.
* Безопасность и аутентификацию, включая поддержку **OAuth2** с **JWT токенами** и **HTTP Basic** аутентификацию.
* Более продвинутые (но столь же простые) приёмы объявления **глубоко вложенных JSON-моделей** (спасибо Pydantic).
* Интеграцию **GraphQL** с <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> и другими библиотеками.
@@ -530,11 +524,11 @@ FastAPI зависит от Pydantic и Starlette.
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> — обязателен, если вы хотите использовать `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> — обязателен, если вы хотите использовать конфигурацию шаблонов по умолчанию.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - обязателен, если вы хотите поддерживать <abbr title="преобразование строки, полученной из HTTP-запроса, в данные Python">«парсинг»</abbr> форм через `request.form()`.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> обязателен, если вы хотите поддерживать <abbr title="преобразование строки, полученной из HTTP-запроса, в данные Python">«парсинг»</abbr> форм через `request.form()`.
Используется FastAPI:
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> — сервер, который загружает и «отдаёт» ваше приложение. Включает `uvicorn[standard]`, содержащий некоторые зависимости (например, `uvloop`), нужные для высокой производительности.
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> — сервер, который загружает и обслуживает ваше приложение. Включает `uvicorn[standard]`, содержащий некоторые зависимости (например, `uvloop`), нужные для высокой производительности.
* `fastapi-cli[standard]` — чтобы предоставить команду `fastapi`.
* Включает `fastapi-cloud-cli`, который позволяет развернуть ваше приложение FastAPI в <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.

View File

@@ -1,4 +1,4 @@
# Большие приложения — несколько файлов { #bigger-applications-multiple-files }
# Большие приложения, в которых много файлов { #bigger-applications-multiple-files }
При построении приложения или веб-API нам редко удается поместить всё в один файл.
@@ -31,7 +31,7 @@
/// tip | Подсказка
Есть несколько файлов `__init__.py`: по одному в каждом каталоге или подкаталоге.
Обратите внимание, что в каждом каталоге и подкаталоге имеется файл `__init__.py`
Это как раз то, что позволяет импортировать код из одного файла в другой.
@@ -43,63 +43,61 @@ from app.routers import items
///
* Всё помещается в каталоге `app`. В нём также находится пустой файл `app/__init__.py`. Таким образом, `app` является "Python-пакетом" (коллекцией "Python-модулей"): `app`.
* Он содержит файл `app/main.py`. Данный файл является частью Python-пакета (т.е. находится внутри каталога, содержащего файл `__init__.py`), и, соответственно, он является модулем этого пакета: `app.main`.
* Всё помещается в каталоге `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`. Он является Python-подпакетом: `app.routers`.
* Файл `app/routers/items.py` находится внутри пакета `app/routers/`. Таким образом, он является подмодулем: `app.routers.items`.
* Точно так же `app/routers/users.py` является ещё одним подмодулем: `app.routers.users`.
* Подкаталог `app/internal/`, содержащий файл `__init__.py`, является ещё одним Python-подпакетом: `app.internal`.
* А файл `app/internal/admin.py` является ещё одним подмодулем: `app.internal.admin`.
* Здесь также находится подкаталог `app/routers/`, содержащий `__init__.py`. Он является суб-пакетом: `app.routers`.
* Файл `app/routers/items.py` находится внутри пакета `app/routers/`. Таким образом, он является суб-модулем: `app.routers.items`.
* Точно также `app/routers/users.py` является ещё одним суб-модулем: `app.routers.users`.
* Подкаталог `app/internal/`, содержащий файл `__init__.py`, является ещё одним суб-пакетом: `app.internal`.
* А файл `app/internal/admin.py` является ещё одним суб-модулем: `app.internal.admin`.
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
Та же самая файловая структура приложения, но с комментариями:
```bash
```
.
├── 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
│   └── 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` { #apirouter }
Давайте предположим, что для работы с пользователями используется отдельный файл (подмодуль) `/app/routers/users.py`.
Давайте предположим, что для работы с пользователями используется отдельный файл (суб-модуль) `/app/routers/users.py`.
Вы хотите отделить *операции пути*, связанные с пользователями, от остального кода, чтобы сохранить порядок.
Для лучшей организации приложения, вы хотите отделить операции пути, связанные с пользователями, от остального кода.
Но это всё равно часть того же приложения/веб-API на **FastAPI** (часть того же «Python-пакета»).
Но так, чтобы эти операции по-прежнему оставались частью **FastAPI** приложения/веб-API (частью одного пакета)
С помощью `APIRouter` вы можете создать *операции пути* для этого модуля.
С помощью `APIRouter` вы можете создать *операции пути* (*эндпоинты*) для данного модуля.
### Импорт `APIRouter` { #import-apirouter }
Точно так же, как и в случае с классом `FastAPI`, вам нужно импортировать и создать его «экземпляр»:
Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`.
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
### *Операции пути* с `APIRouter` { #path-operations-with-apirouter }
### Создание *эндпоинтов* с помощью `APIRouter` { #path-operations-with-apirouter }
И затем вы используете его, чтобы объявить ваши *операции пути*.
Используйте его так же, как вы использовали бы класс `FastAPI`:
В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
Вы можете думать об `APIRouter` как об «мини-классе `FastAPI`».
Вы можете думать об `APIRouter` как об "уменьшенной версии" класса FastAPI`.
Поддерживаются все те же опции.
`APIRouter` поддерживает все те же самые опции.
Все те же `parameters`, `responses`, `dependencies`, `tags` и т.д.
`APIRouter` поддерживает все те же самые параметры, такие как `parameters`, `responses`, `dependencies`, `tags`, и т. д.
/// tip | Подсказка
@@ -107,21 +105,21 @@ from app.routers import items
///
Мы собираемся подключить данный `APIRouter` к нашему основному приложению на `FastAPI`, но сначала давайте проверим зависимости и ещё один `APIRouter`.
Мы собираемся подключить данный `APIRouter` к нашему основному приложению на `FastAPI`, но сначала давайте проверим зависимости и создадим ещё один модуль с `APIRouter`.
## Зависимости { #dependencies }
Мы видим, что нам понадобятся некоторые зависимости, которые будут использоваться в нескольких местах приложения.
Нам понадобятся некоторые зависимости, которые мы будем использовать в разных местах нашего приложения.
Поэтому мы поместим их в отдельный модуль `dependencies` (`app/dependencies.py`).
Мы поместим их в отдельный модуль `dependencies` (`app/dependencies.py`).
Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомный HTTP-заголовок `X-Token`:
Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомизированный `X-Token` из заголовка:
{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | Подсказка
Для простоты мы воспользовались выдуманным заголовком.
Для простоты мы воспользовались неким воображаемым заголовоком.
В реальных случаях для получения наилучших результатов используйте интегрированные [утилиты безопасности](security/index.md){.internal-link target=_blank}.
@@ -129,29 +127,30 @@ from app.routers import items
## Ещё один модуль с `APIRouter` { #another-module-with-apirouter }
Давайте также предположим, что у вас есть эндпоинты, отвечающие за обработку «items» в вашем приложении, и они находятся в модуле `app/routers/items.py`.
Давайте также предположим, что у вас есть *эндпоинты*, отвечающие за обработку "items", и они находятся в модуле `app/routers/items.py`.
У вас определены *операции пути* для:
У вас определены следующие *операции пути* (*эндпоинты*):
* `/items/`
* `/items/{item_id}`
Тут всё та же структура, как и в случае с `app/routers/users.py`.
Тут всё точно также, как и в ситуации с `app/routers/users.py`.
Но мы хотим поступить умнее и слегка упростить код.
Но теперь мы хотим поступить немного умнее и слегка упростить код.
Мы знаем, что все *операции пути* этого модуля имеют одинаковые:
Мы знаем, что все *эндпоинты* данного модуля имеют некоторые общие свойства:
* `prefix` пути: `/items`.
* `tags`: (один единственный тег: `items`).
* Дополнительные `responses`.
* `dependencies`: всем им нужна та зависимость `X-Token`, которую мы создали.
* Префикс пути: `/items`.
* Теги: (один единственный тег: `items`).
* Дополнительные ответы (responses)
* Зависимости: использование созданной нами зависимости `X-token`
Таким образом, вместо того чтобы добавлять всё это в каждую *операцию пути*, мы можем добавить это в `APIRouter`.
Таким образом, вместо того чтобы добавлять все эти свойства в функцию каждого отдельного *эндпоинта*,
мы добавим их в `APIRouter`.
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Так как путь каждой *операции пути* должен начинаться с `/`, как здесь:
Так как каждый *эндпоинт* начинается с символа `/`:
```Python hl_lines="1"
@router.get("/{item_id}")
@@ -163,74 +162,73 @@ async def read_item(item_id: str):
В нашем случае префиксом является `/items`.
Мы также можем добавить список `tags` и дополнительные `responses`, которые будут применяться ко всем *операциям пути*, включённым в этот маршрутизатор.
Мы также можем добавить в наш маршрутизатор (router) список `тегов` (`tags`) и дополнительных `ответов` (`responses`), которые являются общими для каждого *эндпоинта*.
И ещё мы можем добавить список `dependencies`, которые будут добавлены ко всем *операциям пути* в маршрутизаторе и будут выполняться/разрешаться для каждого HTTP-запроса к ним.
И ещё мы можем добавить в наш маршрутизатор список `зависимостей`, которые должны вызываться при каждом обращении к *эндпоинтам*.
/// tip | Подсказка
Обратите внимание, что так же, как и в случае с [зависимостями в декораторах *операций пути*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, никакое значение не будет передано в вашу *функцию-обработчик пути*.
Обратите внимание, что также, как и в случае с зависимостями в декораторах *эндпоинтов* ([зависимости в декораторах операций пути](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), никакого значения в *функцию эндпоинта* передано не будет.
///
В результате пути для items теперь такие:
В результате мы получим следующие эндпоинты:
* `/items/`
* `/items/{item_id}`
...как мы и планировали.
* Они будут помечены списком тегов, содержащим одну строку `"items"`.
* Эти «теги» особенно полезны для систем автоматической интерактивной документации (с использованием OpenAPI).
* Все они будут включать предопределённые `responses`.
* Все эти *операции пути* будут иметь список `dependencies`, вычисляемых/выполняемых перед ними.
* Если вы также объявите зависимости в конкретной *операции пути*, **они тоже будут выполнены**.
* Сначала выполняются зависимости маршрутизатора, затем [`dependencies` в декораторе](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, и затем обычные параметрические зависимости.
* Вы также можете добавить [`Security`-зависимости с `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
* Они будут помечены тегами из заданного списка, в нашем случае это `"items"`.
* Эти теги особенно полезны для системы автоматической интерактивной документации (с использованием OpenAPI).
* Каждый из них будет включать предопределенные ответы `responses`.
* Каждый *эндпоинт* будет иметь список зависимостей (`dependencies`), исполняемых перед вызовом *эндпоинта*.
* Если вы определили зависимости в самой операции пути, **то она также будет выполнена**.
* Сначала выполняются зависимости маршрутизатора, затем вызываются [зависимости в декораторе](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, и, наконец, обычные параметрические зависимости.
* Вы также можете добавить [зависимости `Security` с `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
/// tip | Подсказка
Например, с помощью зависимостей в `APIRouter` мы можем потребовать аутентификации для доступа ко всей группе *операций пути*. Даже если зависимости не добавляются по отдельности к каждой из них.
Например, с помощью зависимостей в `APIRouter` мы можем потребовать аутентификации для доступа ко всей группе *эндпоинтов*, не указывая зависимости для каждой отдельной функции *эндпоинта*.
///
/// check | Заметка
Параметры `prefix`, `tags`, `responses` и `dependencies` — это (как и во многих других случаях) просто возможность **FastAPI**, помогающая избежать дублирования кода.
Параметры `prefix`, `tags`, `responses` и `dependencies` относятся к функционалу **FastAPI**, помогающему избежать дублирования кода.
///
### Импорт зависимостей { #import-the-dependencies }
Этот код находится в модуле `app.routers.items`, в файле `app/routers/items.py`.
Наш код находится в модуле `app.routers.items` (файл `app/routers/items.py`).
И нам нужно получить функцию зависимости из модуля `app.dependencies`, файла `app/dependencies.py`.
И нам нужно вызвать функцию зависимости из модуля `app.dependencies` (файл `app/dependencies.py`).
Поэтому мы используем относительный импорт с `..` для зависимостей:
Мы используем операцию относительного импорта `..` для импорта зависимости:
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
#### Как работает относительный импорт { #how-relative-imports-work }
#### Как работает относительный импорт? { #how-relative-imports-work }
/// tip | Подсказка
Если вы прекрасно знаете, как работает импорт, переходите к следующему разделу ниже.
Если вы прекрасно знаете, как работает импорт в Python, то переходите к следующему разделу.
///
Одна точка `.`, как здесь:
Одна точка `.`, как в данном примере:
```Python
from .dependencies import get_token_header
```
означает:
* Начать в том же пакете, в котором находится этот модуль (файл `app/routers/items.py`) (каталог `app/routers/`)...
* найти модуль `dependencies` (воображаемый файл `app/routers/dependencies.py`)...
* и импортировать из него функцию `get_token_header`.
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` расположен в каталоге `app/routers/`)...
* ... найдите модуль `dependencies` (файл `app/routers/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
Но такого файла не существует, наши зависимости находятся в файле `app/dependencies.py`.
К сожалению, такого файла не существует, и наши зависимости находятся в файле `app/dependencies.py`.
Вспомните, как выглядит файловая структура нашего приложения:
@@ -238,7 +236,7 @@ from .dependencies import get_token_header
---
Две точки `..`, как здесь:
Две точки `..`, как в данном примере:
```Python
from ..dependencies import get_token_header
@@ -246,12 +244,12 @@ from ..dependencies import get_token_header
означают:
* Начать в том же пакете, в котором находится этот модуль (файл `app/routers/items.py`) (каталог `app/routers/`)...
* перейти в родительский пакет (каталог `app/`)...
* и там найти модуль `dependencies` (файл `app/dependencies.py`)...
* и импортировать из него функцию `get_token_header`.
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` находится в каталоге `app/routers/`)...
* ... перейдите в родительский пакет (каталог `app/`)...
* ... найдите в нём модуль `dependencies` (файл `app/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
Это работает корректно! 🎉
Это работает верно! 🎉
---
@@ -263,29 +261,29 @@ from ...dependencies import get_token_header
то это бы означало:
* Начать в том же пакете, в котором находится этот модуль (файл `app/routers/items.py`) расположен в (каталоге `app/routers/`)...
* перейти в родительский пакет (каталог `app/`)...
* затем перейти в родительский пакет этого пакета (родительского пакета нет, `app` верхний уровень 😱)...
* и там найти модуль `dependencies` (файл `app/dependencies.py`)...
* и импортировать из него функцию `get_token_header`.
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` находится в каталоге `app/routers/`)...
* ... перейдите в родительский пакет (каталог `app/`)...
* ... затем перейдите в родительский пакет текущего пакета (такого пакета не существует, `app` находится на самом верхнем уровне 😱)...
* ... найдите в нём модуль `dependencies` (файл `app/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
Это ссылалось бы на какой-то пакет выше `app/`, со своим файлом `__init__.py` и т.п. Но у нас такого нет. Поэтому это вызвало бы ошибку в нашем примере. 🚨
Это будет относиться к некоторому пакету, находящемуся на один уровень выше чем `app/` и содержащему свой собственный файл `__init__.py`. Но ничего такого у нас нет. Поэтому это приведет к ошибке в нашем примере. 🚨
Но теперь вы знаете, как это работает, так что можете использовать относительные импорты в своих приложениях, независимо от того, насколько они сложные. 🤓
Теперь вы знаете, как работает импорт в Python, и сможете использовать относительное импортирование в своих собственных приложениях любого уровня сложности. 🤓
### Добавление пользовательских `tags`, `responses` и `dependencies` { #add-some-custom-tags-responses-and-dependencies }
### Добавление пользовательских тегов (`tags`), ответов (`responses`) и зависимостей (`dependencies`) { #add-some-custom-tags-responses-and-dependencies }
Мы не добавляем префикс `/items` и `tags=["items"]` к каждой *операции пути*, потому что мы добавили их в `APIRouter`.
Мы не будем добавлять префикс `/items` и список тегов `tags=["items"]` для каждого *эндпоинта*, т.к. мы уже их добавили с помощью `APIRouter`.
Но мы всё равно можем добавить _ещё_ `tags`, которые будут применяться к конкретной *операции пути*, а также дополнительные `responses`, специфичные для этой *операции пути*:
Но помимо этого мы можем добавить новые теги для каждого отдельного *эндпоинта*, а также некоторые дополнительные ответы (`responses`), характерные для данного *эндпоинта*:
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | Подсказка
Эта последняя операция пути будет иметь комбинацию тегов: `["items", "custom"]`.
Последний *эндпоинт* будет иметь следующую комбинацию тегов: `["items", "custom"]`.
И в документации у неё будут оба ответа: один для `404` и один для `403`.
А также в его документации будут содержаться оба ответа: один для `404` и другой для `403`.
///
@@ -295,29 +293,29 @@ from ...dependencies import get_token_header
Именно сюда вы импортируете и именно здесь вы используете класс `FastAPI`.
Это основной файл вашего приложения, который связывает всё воедино.
Это основной файл вашего приложения, который объединяет всё в одно целое.
И так как большая часть вашей логики теперь будет находиться в отдельных специфичных модулях, основной файл будет довольно простым.
И теперь, когда большая часть логики приложения разделена на отдельные модули, основной файл `app/main.py` будет достаточно простым.
### Импорт `FastAPI` { #import-fastapi }
Вы импортируете и создаёте класс `FastAPI` как обычно.
Вы импортируете и создаете класс `FastAPI` как обычно.
И мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого `APIRouter`:
Мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
### Импорт `APIRouter` { #import-the-apirouter }
Теперь мы импортируем другие подмодули, содержащие `APIRouter`:
Теперь мы импортируем другие суб-модули, содержащие `APIRouter`:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются подмодулями, входящими в один и тот же Python-пакет `app`, мы можем использовать одну точку `.` для импорта через «относительные импорты».
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`.
### Как работает импорт { #how-the-importing-works }
### Как работает импорт? { #how-the-importing-works }
Этот фрагмент:
Данная строка кода:
```Python
from .routers import items, users
@@ -325,15 +323,15 @@ from .routers import items, users
означает:
* Начать в том же пакете, в котором находится этот модуль (файл `app/main.py`) расположен в (каталоге `app/`)...
* найти подпакет `routers` (каталог `app/routers/`)...
* и импортировать из него подмодули `items` (файл `app/routers/items.py`) и `users` (файл `app/routers/users.py`)...
* Начните с пакета, в котором содержится данный модуль (файл `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`.
В модуле `items` содержится переменная `router` (`items.router`), та самая, которую мы создали в файле `app/routers/items.py`, она является объектом класса `APIRouter`.
И затем мы делаем то же самое для модуля `users`.
И затем мы сделаем то же самое для модуля `users`.
Мы также могли бы импортировать их так:
Мы также могли бы импортировать и другим методом:
```Python
from app.routers import items, users
@@ -341,44 +339,44 @@ from app.routers import items, users
/// info | Примечание
Первая версия — это «относительный импорт»:
Первая версия является примером относительного импорта:
```Python
from .routers import items, users
```
Вторая версия — это «абсолютный импорт»:
Вторая версия является примером абсолютного импорта:
```Python
from app.routers import items, users
```
Чтобы узнать больше о Python-пакетах и модулях, прочитайте <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">официальную документацию Python о модулях</a>.
Узнать больше о пакетах и модулях в Python вы можете из <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">официальной документации Python о модулях</a>
///
### Избегайте конфликтов имён { #avoid-name-collisions }
### Избегайте конфликтов имен { #avoid-name-collisions }
Мы импортируем подмодуль `items` напрямую, вместо того чтобы импортировать только его переменную `router`.
Вместо того чтобы импортировать только переменную `router`, мы импортируем непосредственно суб-модуль `items`.
Это потому, что у нас также есть другая переменная с именем `router` в подмодуле `users`.
Мы делаем это потому, что у нас есть ещё одна переменная `router` в суб-модуле `users`.
Если бы мы импортировали их одну за другой, как здесь:
Если бы мы импортировали их одну за другой, как показано в примере:
```Python
from .routers.items import router
from .routers.users import router
```
то `router` из `users` перезаписал бы `router` из `items`, и мы не смогли бы использовать их одновременно.
то переменная `router` из `users` переписал бы переменную `router` из `items`, и у нас не было бы возможности использовать их одновременно.
Поэтому, чтобы иметь возможность использовать обе в одном файле, мы импортируем подмодули напрямую:
Поэтому, для того чтобы использовать обе эти переменные в одном файле, мы импортировали соответствующие суб-модули:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
### Подключение `APIRouter` для `users` и `items` { #include-the-apirouters-for-users-and-items }
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items` { #include-the-apirouters-for-users-and-items }
Теперь давайте подключим `router` из подмодулей `users` и `items`:
Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
@@ -390,78 +388,79 @@ from .routers.users import router
///
С помощью `app.include_router()` мы можем добавить каждый `APIRouter` в основное приложение `FastAPI`.
С помощью `app.include_router()` мы можем добавить каждый из маршрутизаторов (`APIRouter`) в основное приложение `FastAPI`.
Он включит все маршруты этого маршрутизатора как часть приложения.
Он подключит все маршруты заданного маршрутизатора к нашему приложению.
/// note | Технические детали
Фактически, внутри он создаст *операцию пути* для каждой *операции пути*, объявленной в `APIRouter`.
Фактически, внутри он создаст все *операции пути* для каждой операции пути объявленной в `APIRouter`.
Так что под капотом всё будет работать так, как будто всё было одним приложением.
И под капотом всё будет работать так, как будто бы мы имеем дело с одним файлом приложения.
///
/// check | Заметка
При подключении маршрутизаторов не нужно беспокоиться о производительности.
При подключении маршрутизаторов не стоит беспокоиться о производительности.
Это займёт микросекунды и произойдёт только при старте.
Операция подключения займёт микросекунды и понадобится только при запуске приложения.
Так что это не повлияет на производительность. ⚡
Таким образом, это не повлияет на производительность. ⚡
///
### Подключение `APIRouter` с пользовательскими `prefix`, `tags`, `responses` и `dependencies` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
### Подключение `APIRouter` с пользовательскими префиксом (`prefix`), тегами (`tags`), ответами (`responses`), и зависимостями (`dependencies`) { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
Теперь давайте представим, что ваша организация передала вам файл `app/internal/admin.py`.
Он содержит `APIRouter` с некоторыми административными *операциями пути*, которые ваша организация использует в нескольких проектах.
Он содержит `APIRouter` с некоторыми *эндпоитами* администрирования, которые ваша организация использует для нескольких проектов.
Для этого примера всё будет очень просто. Но допустим, что поскольку он используется совместно с другими проектами в организации, мы не можем модифицировать его и добавить `prefix`, `dependencies`, `tags` и т.д. непосредственно в `APIRouter`:
В данном примере это сделать очень просто. Но давайте предположим, что поскольку файл используется для нескольких проектов,
то мы не можем модифицировать его, добавляя префиксы (`prefix`), зависимости (`dependencies`), теги (`tags`), и т.д. непосредственно в `APIRouter`:
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Но мы всё равно хотим задать пользовательский `prefix` при подключении `APIRouter`, чтобы все его *операции пути* начинались с `/admin`, хотим защитить его с помощью `dependencies`, которые у нас уже есть для этого проекта, и хотим включить `tags` и `responses`.
Но, несмотря на это, мы хотим использовать кастомный префикс (`prefix`) для подключенного маршрутизатора (`APIRouter`), в результате чего, каждая *операция пути* будет начинаться с `/admin`. Также мы хотим защитить наш маршрутизатор с помощью зависимостей, созданных для нашего проекта. И ещё мы хотим включить теги (`tags`) и ответы (`responses`).
Мы можем объявить всё это, не изменяя исходный `APIRouter`, передав эти параметры в `app.include_router()`:
Мы можем применить все вышеперечисленные настройки, не изменяя начальный `APIRouter`. Нам всего лишь нужно передать нужные параметры в `app.include_router()`.
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
Таким образом исходный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации.
Таким образом, оригинальный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации.
В результате в нашем приложении каждая из *операций пути* из модуля `admin` будет иметь:
В результате, в нашем приложении каждый *эндпоинт* модуля `admin` будет иметь:
* Префикс `/admin`.
* Тег `admin`.
* Зависимость `get_token_header`.
* Ответ `418`. 🍵
Но это повлияет только на этот `APIRouter` в нашем приложении, а не на любой другой код, который его использует.
Это будет иметь место исключительно для `APIRouter` в нашем приложении, и не затронет любой другой код, использующий его.
Так что, например, другие проекты могут использовать тот же `APIRouter` с другим методом аутентификации.
Например, другие проекты, могут использовать тот же самый `APIRouter` с другими методами аутентификации.
### Подключение *операции пути* { #include-a-path-operation }
### Подключение отдельного *эндпоинта* { #include-a-path-operation }
Мы также можем добавлять *операции пути* напрямую в приложение `FastAPI`.
Мы также можем добавить *эндпоинт* непосредственно в основное приложение `FastAPI`.
Здесь мы делаем это... просто чтобы показать, что можем 🤷:
Здесь мы это делаем ... просто, чтобы показать, что это возможно 🤷:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
и это будет работать корректно вместе со всеми другими *операциями пути*, добавленными через `app.include_router()`.
и это будет работать корректно вместе с другими *эндпоинтами*, добавленными с помощью `app.include_router()`.
/// info | Очень технические детали
/// info | Сложные технические детали
**Примечание**: это очень техническая деталь, которую, вероятно, можно **просто пропустить**.
**Примечание**: это сложная техническая деталь, которую, скорее всего, **вы можете пропустить**.
---
`APIRouter` не «монтируются», они не изолированы от остального приложения.
Маршрутизаторы (`APIRouter`) не "монтируются" по-отдельности и не изолируются от остального приложения.
Это потому, что мы хотим включить их *операции пути* в OpenAPI-схему и пользовательские интерфейсы.
Это происходит потому, что нужно включить их *эндпоинты* в OpenAPI схему и в интерфейс пользователя.
Так как мы не можем просто изолировать их и «смонтировать» независимо от остального, *операции пути* «клонируются» (пересоздаются), а не включаются напрямую.
В силу того, что мы не можем их изолировать и "примонтировать" независимо от остальных, *эндпоинты* клонируются (пересоздаются) и не подключаются напрямую.
///
@@ -481,24 +480,24 @@ $ fastapi dev app/main.py
Откройте документацию по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите автоматическую документацию API, включая пути из всех подмодулей, с использованием корректных путей (и префиксов) и корректных тегов:
Вы увидите автоматическую API документацию. Она включает в себя маршруты из суб-модулей, используя верные маршруты, префиксы и теги:
<img src="/img/tutorial/bigger-applications/image01.png">
## Подключение одного и того же маршрутизатора несколько раз с разными `prefix` { #include-the-same-router-multiple-times-with-different-prefix }
## Подключение существующего маршрута через новый префикс (`prefix`) { #include-the-same-router-multiple-times-with-different-prefix }
Вы можете использовать `.include_router()` несколько раз с *одним и тем же* маршрутизатором, используя разные префиксы.
Вы можете использовать `.include_router()` несколько раз с одним и тем же маршрутом, применив различные префиксы.
Это может быть полезно, например, чтобы предоставить доступ к одному и тому же API с разными префиксами, например `/api/v1` и `/api/latest`.
Это может быть полезным, если нужно предоставить доступ к одному и тому же API через различные префиксы, например, `/api/v1` и `/api/latest`.
Это продвинутое использование, которое вам может и не понадобиться, но оно есть на случай, если понадобится.
Это продвинутый способ, который вам может и не пригодится. Мы приводим его на случай, если вдруг вам это понадобится.
## Подключение `APIRouter` в другой `APIRouter` { #include-an-apirouter-in-another }
## Включение одного маршрутизатора (`APIRouter`) в другой { #include-an-apirouter-in-another }
Точно так же, как вы можете подключить `APIRouter` к приложению `FastAPI`, вы можете подключить `APIRouter` к другому `APIRouter`, используя:
Точно так же, как вы включаете `APIRouter` в приложение `FastAPI`, вы можете включить `APIRouter` в другой `APIRouter`:
```Python
router.include_router(other_router)
```
Убедитесь, что вы сделали это до подключения `router` к приложению `FastAPI`, чтобы *операции пути* из `other_router` также были подключены.
Удостоверьтесь, что вы сделали это до того, как подключить маршрутизатор (`router`) к вашему `FastAPI` приложению, и *эндпоинты* маршрутизатора `other_router` были также подключены.

View File

@@ -2,13 +2,13 @@
## Обновление с заменой при помощи `PUT` { #update-replacing-with-put }
Чтобы обновить элемент, вы можете использовать операцию <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>.
Для полного обновления элемента можно воспользоваться операцией <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a>.
Вы можете использовать `jsonable_encoder`, чтобы преобразовать входные данные в данные, которые можно сохранить как JSON (например, в NoSQL-базе данных). Например, преобразование `datetime` в `str`.
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
`PUT` используется для получения данных, которые должны заменить существующие данные.
`PUT` используется для получения данных, которые должны полностью заменить существующие данные.
### Предупреждение о замене { #warning-about-replacing }
@@ -24,11 +24,11 @@
поскольку оно не включает уже сохраненный атрибут `"tax": 20.2`, входная модель примет значение по умолчанию `"tax": 10.5`.
И данные будут сохранены с этим «новым» `tax`, равным `10.5`.
И данные будут сохранены с этим "новым" `tax`, равным `10,5`.
## Частичное обновление с помощью `PATCH` { #partial-updates-with-patch }
Также можно использовать операцию <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> для *частичного* обновления данных.
Также можно использовать <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> операцию для *частичного* обновления данных.
Это означает, что можно передавать только те данные, которые необходимо обновить, оставляя остальные нетронутыми.
@@ -46,13 +46,19 @@
### Использование параметра `exclude_unset` в Pydantic { #using-pydantics-exclude-unset-parameter }
Если вы хотите получать частичные обновления, очень полезно использовать параметр `exclude_unset` в `.model_dump()` модели Pydantic.
Если необходимо выполнить частичное обновление, то очень полезно использовать параметр `exclude_unset` в методе `.model_dump()` модели Pydantic.
Например, `item.model_dump(exclude_unset=True)`.
В результате будет сгенерирован `dict`, содержащий только те данные, которые были заданы при создании модели `item`, без учета значений по умолчанию.
/// info | Информация
Затем вы можете использовать это для создания `dict` только с теми данными, которые были установлены (отправлены в запросе), опуская значения по умолчанию:
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он помечен как устаревший (но все еще поддерживается) и переименован в `.model_dump()`.
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, лучше используйте `.model_dump()`.
///
В результате будет сгенерирован словарь, содержащий только те данные, которые были заданы при создании модели `item`, без учета значений по умолчанию. Затем вы можете использовать это для создания словаря только с теми данными, которые были установлены (отправлены в запросе), опуская значения по умолчанию:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
@@ -60,6 +66,14 @@
Теперь можно создать копию существующей модели, используя `.model_copy()`, и передать параметр `update` с `dict`, содержащим данные для обновления.
/// info | Информация
В Pydantic v1 метод назывался `.copy()`, в Pydantic v2 он помечен как устаревший (но все еще поддерживается) и переименован в `.model_copy()`.
Примеры здесь используют `.copy()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, лучше используйте `.model_copy()`.
///
Например, `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
@@ -70,9 +84,9 @@
* (Опционально) использовать `PATCH` вместо `PUT`.
* Извлечь сохранённые данные.
* Поместить эти данные в Pydantic-модель.
* Поместить эти данные в Pydantic модель.
* Сгенерировать `dict` без значений по умолчанию из входной модели (с использованием `exclude_unset`).
* Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять уже сохраненные значения значениями по умолчанию из вашей модели.
* Таким образом, можно обновлять только те значения, которые действительно установлены пользователем, вместо того чтобы переопределять значения, уже сохраненные в модели по умолчанию.
* Создать копию хранимой модели, обновив ее атрибуты полученными частичными обновлениями (с помощью параметра `update`).
* Преобразовать скопированную модель в то, что может быть сохранено в вашей БД (например, с помощью `jsonable_encoder`).
* Это сравнимо с повторным использованием метода модели `.model_dump()`, но при этом происходит проверка (и преобразование) значений в типы данных, которые могут быть преобразованы в JSON, например, `datetime` в `str`.
@@ -83,7 +97,7 @@
/// tip | Подсказка
На самом деле эту же технику можно использовать и для операции HTTP `PUT`.
Эту же технику можно использовать и для операции HTTP `PUT`.
Но в приведенном примере используется `PATCH`, поскольку он был создан именно для таких случаев использования.

View File

@@ -32,10 +32,9 @@
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
Так же, как при объявлении параметров запроса: когда атрибут модели имеет значение по умолчанию, он не обязателен. Иначе он обязателен. Используйте `None`, чтобы сделать его просто необязательным.
Например, модель выше описывает такой JSON "`object`" (или Python `dict`):
Например, модель выше описывает такой JSON "объект" (или Python `dict`):
```JSON
{
@@ -46,7 +45,7 @@
}
```
...так как `description` и `tax` являются необязательными (со значением по умолчанию `None`), такой JSON "`object`" тоже будет корректным:
...так как `description` и `tax` являются необязательными (со значением по умолчанию `None`), такой JSON "объект" тоже будет корректным:
```JSON
{
@@ -74,7 +73,7 @@
* Передаст полученные данные в параметр `item`.
* Поскольку внутри функции вы объявили его с типом `Item`, у вас будет поддержка со стороны редактора кода (автозавершение и т. п.) для всех атрибутов и их типов.
* Сгенерирует определения <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> для вашей модели; вы можете использовать их и в других местах, если это имеет смысл для вашего проекта.
* Эти схемы будут частью сгенерированной схемы OpenAPI и будут использоваться автоматической документацией <abbr title="User Interfaces - Пользовательские интерфейсы">UIs</abbr>.
* Эти схемы будут частью сгенерированной схемы OpenAPI и будут использоваться автоматической документацией <abbr title="User Interfaces Пользовательские интерфейсы">UIs</abbr>.
## Автоматическая документация { #automatic-docs }
@@ -128,6 +127,14 @@ JSON Schema ваших моделей будет частью сгенериро
{* ../../docs_src/body/tutorial002_py310.py *}
/// info | Информация
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы можете использовать Pydantic v2, используйте `.model_dump()`.
///
## Тело запроса + параметры пути { #request-body-path-parameters }
Вы можете одновременно объявить параметры пути и тело запроса.
@@ -136,7 +143,6 @@ JSON Schema ваших моделей будет частью сгенериро
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
## Тело запроса + параметры пути + параметры запроса { #request-body-path-query-parameters }
Вы также можете одновременно объявить параметры **тела**, **пути** и **запроса**.
@@ -147,7 +153,7 @@ JSON Schema ваших моделей будет частью сгенериро
Параметры функции будут распознаны следующим образом:
* Если параметр также объявлен в **пути**, он будет использоваться как path-параметр.
* Если параметр также объявлен в **пути**, он будет использоваться как параметр пути.
* Если параметр имеет **скалярный тип** (например, `int`, `float`, `str`, `bool` и т. п.), он будет интерпретирован как параметр **запроса**.
* Если параметр объявлен как тип **модели Pydantic**, он будет интерпретирован как **тело** запроса.
@@ -155,7 +161,7 @@ JSON Schema ваших моделей будет частью сгенериро
FastAPI понимает, что значение `q` не является обязательным из-за значения по умолчанию `= None`.
Аннотации типов `str | None` (Python 3.10+) или `Union` в `Union[str, None]` (Python 3.9+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`.
Аннотации типов `str | None` (Python 3.10+) или `Union[str, None]` (Python 3.9+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`.
Но добавление аннотаций типов позволит вашему редактору кода лучше вас поддерживать и обнаруживать ошибки.
@@ -163,4 +169,4 @@ FastAPI понимает, что значение `q` не является об
## Без Pydantic { #without-pydantic }
Если вы не хотите использовать модели Pydantic, вы также можете использовать параметры **Body**. См. раздел документации [Тело запроса - Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
Если вы не хотите использовать модели Pydantic, вы также можете использовать параметры **Body**. См. раздел документации [Тело Несколько параметров: Единичные значения в теле](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.

View File

@@ -22,13 +22,21 @@
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
### Про `**user_in.model_dump()` { #about-user-in-model-dump }
/// info | Информация
#### `.model_dump()` из Pydantic { #pydantics-model-dump }
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
`user_in` — это Pydantic-модель класса `UserIn`.
В примерах здесь используется `.dict()` для совместимости с Pydantic v1, но если вы используете Pydantic v2, следует использовать `.model_dump()`.
У Pydantic-моделей есть метод `.model_dump()`, который возвращает `dict` с данными модели.
///
### Про `**user_in.dict()` { #about-user-in-dict }
#### `.dict()` из Pydantic { #pydantics-dict }
`user_in` - это Pydantic-модель класса `UserIn`.
У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели.
Поэтому, если мы создадим Pydantic-объект `user_in` таким способом:
@@ -39,10 +47,10 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
и затем вызовем:
```Python
user_dict = user_in.model_dump()
user_dict = user_in.dict()
```
то теперь у нас есть `dict` с данными в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).
то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).
И если мы вызовем:
@@ -50,7 +58,7 @@ user_dict = user_in.model_dump()
print(user_dict)
```
мы получим Python `dict` с:
мы можем получить `dict` с такими данными:
```Python
{
@@ -63,7 +71,7 @@ print(user_dict)
#### Распаковка `dict` { #unpacking-a-dict }
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python его "распакует". Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода:
@@ -71,7 +79,7 @@ print(user_dict)
UserInDB(**user_dict)
```
будет эквивалентно:
Будет работать так же, как примерно такой код:
```Python
UserInDB(
@@ -82,7 +90,7 @@ UserInDB(
)
```
Или, более точно, если использовать `user_dict` напрямую, с любым содержимым, которое он может иметь в будущем:
Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так:
```Python
UserInDB(
@@ -93,22 +101,22 @@ UserInDB(
)
```
#### Pydantic-модель из содержимого другой { #a-pydantic-model-from-the-contents-of-another }
#### Pydantic-модель из содержимого другой модели { #a-pydantic-model-from-the-contents-of-another }
Как в примере выше мы получили `user_dict` из `user_in.model_dump()`, этот код:
Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код:
```Python
user_dict = user_in.model_dump()
user_dict = user_in.dict()
UserInDB(**user_dict)
```
будет равнозначен такому:
```Python
UserInDB(**user_in.model_dump())
UserInDB(**user_in.dict())
```
...потому что `user_in.model_dump()` это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` с префиксом `**`.
...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`.
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели.
@@ -117,10 +125,10 @@ UserInDB(**user_in.model_dump())
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:
```Python
UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
UserInDB(**user_in.dict(), hashed_password=hashed_password)
```
...то в итоге получится что-то подобное:
... то мы получим что-то подобное:
```Python
UserInDB(
@@ -134,13 +142,13 @@ UserInDB(
/// warning | Предупреждение
Вспомогательные дополнительные функции `fake_password_hasher` и `fake_save_user` используются только для демонстрации возможного потока данных и, конечно, не обеспечивают настоящую безопасность.
Вспомогательные функции `fake_password_hasher` и `fake_save_user` используются только для демонстрации возможного потока данных и, конечно, не обеспечивают настоящую безопасность.
///
## Сократите дублирование { #reduce-duplication }
Сокращение дублирования кода это одна из главных идей **FastAPI**.
Сокращение дублирования кода - это одна из главных идей **FastAPI**.
Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д.
@@ -158,7 +166,7 @@ UserInDB(
## `Union` или `anyOf` { #union-or-anyof }
Вы можете объявить HTTP-ответ как `Union` из двух или более типов. Это означает, что HTTP-ответ может быть любым из них.
Вы можете определить ответ как `Union` из двух или более типов. Это означает, что ответ должен соответствовать одному из них.
Он будет определён в OpenAPI как `anyOf`.
@@ -166,7 +174,7 @@ UserInDB(
/// note | Примечание
При объявлении <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a> сначала указывайте наиболее специфичный тип, затем менее специфичный. В примере ниже более специфичный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`.
При объявлении <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`.
///
@@ -184,19 +192,19 @@ UserInDB(
some_variable: PlaneItem | CarItem
```
Но если мы поместим это в присваивание `response_model=PlaneItem | CarItem`, мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
## Список моделей { #list-of-models }
Таким же образом вы можете объявлять HTTP-ответы, возвращающие списки объектов.
Таким же образом вы можете определять ответы как списки объектов.
Для этого используйте стандартный `typing.List` в Python (или просто `list` в Python 3.9 и выше):
Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше):
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
## Ответ с произвольным `dict` { #response-with-arbitrary-dict }
Вы также можете объявить HTTP-ответ, используя обычный произвольный `dict`, объявив только тип ключей и значений, без использования Pydantic-модели.
Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей.
Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели).
@@ -206,6 +214,6 @@ some_variable: PlaneItem | CarItem
## Резюме { #recap }
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждого случая.
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них.
Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояние, включающее `password`, `password_hash` и отсутствие пароля.
Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля.

View File

@@ -8,7 +8,7 @@
Query-параметр `q` имеет тип `str | None`, это означает, что он имеет тип `str`, но также может быть `None`. Значение по умолчанию действительно `None`, поэтому FastAPI будет знать, что он не обязателен.
/// note | Примечание
/// note | Технические детали
FastAPI поймёт, что значение `q` не обязательно, из‑за значения по умолчанию `= None`.
@@ -177,7 +177,7 @@ q: str = Query(default="rick")
**Значение по умолчанию** у **параметра функции** — это **настоящее значение по умолчанию**, что более интуитивно для Python. 😌
Вы можете **вызвать** эту же функцию в **других местах** без FastAPI, и она будет **работать как ожидается**. Если есть **обязательный** параметр (без значения по умолчанию), ваш **редактор** сообщит об ошибке, **Python** тоже пожалуется, если вы запустите её без передачи обязательного параметра.
Вы можете **вызвать** эту же функцию в **других местах** без FastAPI, и она будет **работать как ожидается**. Если есть **обязательный** параметр (без значения по умолчанию), ваш **редактор кода** сообщит об ошибке, **Python** тоже пожалуется, если вы запустите её без передачи обязательного параметра.
Если вы не используете `Annotated`, а применяете **(устаревший) стиль со значением по умолчанию**, то при вызове этой функции без FastAPI в **других местах** вам нужно **помнить** о том, что надо передать аргументы, чтобы всё работало корректно, иначе значения будут не такими, как вы ожидаете (например, вместо `str` будет `QueryInfo` или что-то подобное). И ни редактор, ни Python не будут ругаться при самом вызове функции — ошибка проявится лишь при операциях внутри.
@@ -191,7 +191,7 @@ q: str = Query(default="rick")
## Регулярные выражения { #add-regular-expressions }
Вы можете определить <abbr title="Регулярное выражение (regex, regexp) - это последовательность символов, задающая шаблон поиска для строк.">регулярное выражение</abbr> `pattern`, которому должен соответствовать параметр:
Вы можете определить <abbr title="Регулярное выражение (regex, regexp) это последовательность символов, задающая шаблон поиска для строк.">регулярное выражение</abbr> `pattern`, которому должен соответствовать параметр:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
@@ -205,6 +205,20 @@ q: str = Query(default="rick")
Теперь вы знаете, что когда они понадобятся, вы сможете использовать их в **FastAPI**.
### `regex` из Pydantic v1 вместо `pattern` { #pydantic-v1-regex-instead-of-pattern }
До Pydantic версии 2 и до FastAPI 0.100.0 этот параметр назывался `regex`, а не `pattern`, но сейчас он устарел.
Вы всё ещё можете встретить такой код:
//// tab | Pydantic v1
{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
////
Имейте в виду, что это устарело, и код следует обновить на использование нового параметра `pattern`. 🤓
## Значения по умолчанию { #default-values }
Конечно, можно использовать и другие значения по умолчанию, не только `None`.
@@ -265,7 +279,7 @@ q: Annotated[str | None, Query(min_length=3)] = None
http://localhost:8000/items/?q=foo&q=bar
```
вы получите множественные значения *query-параметров* `q` (`foo` и `bar`) в виде Python-`list` внутри вашей *функции-обработчика пути*, в *параметре функции* `q`.
вы получите множественные значения query-параметра `q` (`foo` и `bar`) в виде Python-`list` внутри вашей *функции обработки пути*, в *параметре функции* `q`.
Таким образом, ответ на этот URL будет:
@@ -317,7 +331,7 @@ http://localhost:8000/items/
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
/// note | Примечание
/// note | Технические детали
Имейте в виду, что в этом случае FastAPI не будет проверять содержимое списка.
@@ -331,7 +345,7 @@ http://localhost:8000/items/
Эта информация будет включена в сгенерированную OpenAPI-схему и использована интерфейсами документации и внешними инструментами.
/// note | Примечание
/// note | Технические детали
Помните, что разные инструменты могут иметь разный уровень поддержки OpenAPI.
@@ -401,7 +415,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
///
Например, эта кастомная проверка убеждается, что ID элемента начинается с `isbn-` для номера книги <abbr title="ISBN означает International Standard Book Number - Международный стандартный книжный номер">ISBN</abbr> или с `imdb-` для ID URL фильма на <abbr title="IMDB (Internet Movie Database) - веб‑сайт с информацией о фильмах">IMDB</abbr>:
Например, эта кастомная проверка убеждается, что ID элемента начинается с `isbn-` для номера книги <abbr title="ISBN означает International Standard Book Number Международный стандартный книжный номер">ISBN</abbr> или с `imdb-` для ID URL фильма на <abbr title="IMDB (Internet Movie Database) веб‑сайт с информацией о фильмах">IMDB</abbr>:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
@@ -441,7 +455,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
Затем с `random.choice()` можно получить **случайное значение** из списка — то есть кортеж вида `(id, name)`. Это будет что‑то вроде `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
После этого мы **присваиваем эти два значения** кортежа переменным `id` и `name`.
После этого мы **распаковываем** эти два значения кортежа в переменные `id` и `name`.
Так что, если пользователь не передал ID элемента, он всё равно получит случайную рекомендацию.

View File

@@ -6,11 +6,11 @@
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
FastAPI будет использовать этот возвращаемый тип, чтобы:
FastAPI будет использовать этот тип ответа для:
* **Валидировать** возвращаемые данные.
* Если данные невалидны (например, отсутствует поле), это означает, что код *вашего* приложения работает некорректно и возвращает не то, что должен. В таком случае будет возвращена ошибка сервера вместо неправильных данных. Так вы и ваши клиенты можете быть уверены, что получите ожидаемые данные и ожидаемую структуру данных.
* Добавить **JSON Schema** для ответа в OpenAPI *операции пути*.
* **Валидации** возвращаемых данных.
* Если данные невалидны (например, отсутствует поле), это означает, что код *вашего* приложения работает некорректно и возвращает не то, что должен. В таком случае будет возвращена ошибка сервера вместо неправильных данных. Так вы и ваши клиенты можете быть уверены, что получите ожидаемые данные и ожидаемую структуру.
* Добавления **JSON Schema** для ответа в OpenAPI *операции пути*.
* Это будет использовано **автоматической документацией**.
* Это также будет использовано инструментами автоматической генерации клиентского кода.
@@ -23,7 +23,7 @@ FastAPI будет использовать этот возвращаемый т
Бывают случаи, когда вам нужно или хочется возвращать данные, которые не в точности соответствуют объявленному типу.
Например, вы можете хотеть **возвращать словарь** или объект из базы данных, но **объявить его как Pydantic-модель**. Тогда Pydantic-модель выполнит документирование данных, валидацию и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).
Например, вы можете хотеть **возвращать словарь (dict)** или объект из базы данных, но **объявить его как Pydantic-модель**. Тогда Pydantic-модель выполнит документирование данных, валидацию и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).
Если вы добавите аннотацию возвращаемого типа, инструменты и редакторы кода начнут жаловаться (и будут правы), что функция возвращает тип (например, dict), отличный от объявленного (например, Pydantic-модель).
@@ -47,13 +47,13 @@ FastAPI будет использовать этот возвращаемый т
`response_model` принимает тот же тип, что вы бы объявили для поля Pydantic-модели, то есть это может быть одна Pydantic-модель, а может быть, например, `list` Pydantic-моделей, как `List[Item]`.
FastAPI будет использовать этот `response_model` для документирования, валидации данных и т.п., а также для **конвертации и фильтрации выходных данных** к объявленному типу.
FastAPI будет использовать `response_model` для документации, валидации и т. п., а также для **конвертации и фильтрации выходных данных** к объявленному типу.
/// tip | Совет
Если у вас в редакторе кода, mypy и т.п. включены строгие проверки типов, вы можете объявить возвращаемый тип функции как `Any`.
Если у вас в редакторе кода, mypy и т. п. включены строгие проверки типов, вы можете объявить возвращаемый тип функции как `Any`.
Так вы сообщите редактору, что намеренно возвращаете что угодно. Но FastAPI всё равно выполнит документирование, валидацию, фильтрацию данных и т.д. с помощью `response_model`.
Так вы сообщите редактору, что намеренно возвращаете что угодно. Но FastAPI всё равно выполнит документацию данных, валидацию, фильтрацию и т.д. с помощью `response_model`.
///
@@ -61,7 +61,7 @@ FastAPI будет использовать этот `response_model` для д
Если вы объявите и возвращаемый тип, и `response_model`, приоритет будет у `response_model`, именно его использует FastAPI.
Так вы можете добавить корректные аннотации типов к своим функциям, даже если фактически возвращаете тип, отличный от модели ответа, чтобы ими пользовались редактор кода и инструменты вроде mypy. И при этом FastAPI продолжит выполнять валидацию данных, документацию и т.д. с использованием `response_model`.
Так вы можете добавить корректные аннотации типов к своим функциям, даже если фактически возвращаете тип, отличный от модели ответа, чтобы ими пользовались редактор и инструменты вроде mypy. И при этом FastAPI продолжит выполнять валидацию данных, документацию и т.д. с использованием `response_model`.
Вы также можете указать `response_model=None`, чтобы отключить создание модели ответа для данной *операции пути*. Это может понадобиться, если вы добавляете аннотации типов для вещей, не являющихся валидными полями Pydantic. Пример вы увидите ниже.
@@ -75,7 +75,7 @@ FastAPI будет использовать этот `response_model` для д
Чтобы использовать `EmailStr`, сначала установите <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>.
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установите пакет, например:
Создайте [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активируйте его и затем установите пакет, например:
```console
$ pip install email-validator
@@ -105,7 +105,7 @@ $ pip install "pydantic[email]"
///
## Добавить выходную модель { #add-an-output-model }
## Добавить модель для ответа { #add-an-output-model }
Вместо этого мы можем создать входную модель с паролем в открытом виде и выходную модель без него:
@@ -123,7 +123,7 @@ $ pip install "pydantic[email]"
### `response_model` или возвращаемый тип { #response-model-or-return-type }
В этом случае, поскольку две модели различаются, если бы мы аннотировали возвращаемый тип функции как `UserOut`, редактор кода и инструменты пожаловались бы, что мы возвращаем неверный тип, так как это разные классы.
В этом случае, поскольку две модели различаются, если бы мы аннотировали возвращаемый тип функции как `UserOut`, редактор и инструменты пожаловались бы, что мы возвращаем неверный тип, так как это разные классы.
Поэтому в этом примере мы должны объявить тип ответа в параметре `response_model`.
@@ -135,33 +135,33 @@ $ pip install "pydantic[email]"
Мы хотим, чтобы FastAPI продолжал **фильтровать** данные с помощью модели ответа. Так что, даже если функция возвращает больше данных, в ответ будут включены только поля, объявленные в модели ответа.
В предыдущем примере, поскольку классы были разными, нам пришлось использовать параметр `response_model`. Но это также означает, что мы теряем поддержку от редактора кода и инструментов, проверяющих возвращаемый тип функции.
В предыдущем примере, поскольку классы были разными, нам пришлось использовать параметр `response_model`. Но это также означает, что мы теряем поддержку от редактора и инструментов, проверяющих возвращаемый тип функции.
Однако в большинстве таких случаев нам нужно лишь **отфильтровать/убрать** некоторые данные, как в этом примере.
И в этих случаях мы можем использовать классы и наследование, чтобы воспользоваться **аннотациями типов** функций для лучшей поддержки в редакторе кода и инструментах и при этом получить **фильтрацию данных** от FastAPI.
И в этих случаях мы можем использовать классы и наследование, чтобы воспользоваться **аннотациями типов** функций для лучшей поддержки в редакторе и инструментах и при этом получить **фильтрацию данных** от FastAPI.
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
Так мы получаем поддержку инструментов редакторов кода и mypy, так как этот код корректен с точки зрения типов — и одновременно получаем фильтрацию данных от FastAPI.
Так мы получаем поддержку инструментов (редакторы, mypy) — код корректен с точки зрения типов — и одновременно получаем фильтрацию данных от FastAPI.
Как это работает? Давайте разберёмся. 🤓
### Аннотации типов и инструменты { #type-annotations-and-tooling }
Сначала посмотрим, как это увидят редактор кода, mypy и другие инструменты.
Сначала посмотрим, как это увидят редакторы, mypy и другие инструменты.
`BaseUser` содержит базовые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет поле `password`, то есть он будет включать все поля обеих моделей.
`BaseUser` содержит базовые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет поле `password`, то есть он включает все поля обеих моделей.
Мы аннотируем возвращаемый тип функции как `BaseUser`, но фактически возвращаем экземпляр `UserIn`.
Редактор кода, mypy и другие инструменты не будут возражать, потому что с точки зрения типов `UserIn` — подкласс `BaseUser`, что означает, что это *валидный* тип везде, где ожидается что-то, являющееся `BaseUser`.
Редактор, mypy и другие инструменты не будут возражать, потому что с точки зрения типов `UserIn` — подкласс `BaseUser`, что означает, что это *валидный* тип везде, где ожидается что-то, являющееся `BaseUser`.
### Фильтрация данных FastAPI { #fastapi-data-filtering }
Теперь для FastAPI: он увидит возвращаемый тип и убедится, что то, что вы возвращаете, включает **только** поля, объявленные в этом типе.
Теперь, для FastAPI: он увидит возвращаемый тип и убедится, что то, что вы возвращаете, включает **только** поля, объявленные в этом типе.
FastAPI делает несколько вещей внутри вместе с Pydantic, чтобы гарантировать, что те же правила наследования классов не используются для фильтрации возвращаемых данных, иначе вы могли бы в итоге вернуть намного больше данных, чем ожидали.
FastAPI делает несколько вещей внутри вместе с Pydantic, чтобы гарантировать, что те же правила наследования классов не используются для фильтрации возвращаемых данных, иначе вы могли бы вернуть гораздо больше данных, чем ожидали.
Таким образом вы получаете лучшее из обоих миров: аннотации типов с **поддержкой инструментов** и **фильтрацию данных**.
@@ -171,17 +171,17 @@ FastAPI делает несколько вещей внутри вместе с
<img src="/img/tutorial/response-model/image01.png">
И обе модели будут использоваться в интерактивной документации API:
И обе модели используются в интерактивной документации API:
<img src="/img/tutorial/response-model/image02.png">
## Другие аннотации возвращаемых типов { #other-return-type-annotations }
Бывают случаи, когда вы возвращаете что-то, что не является валидным полем Pydantic, и аннотируете это в функции только ради поддержки инструментов (редактор кода, mypy и т.д.).
Бывают случаи, когда вы возвращаете что-то, что не является валидным полем Pydantic, и аннотируете это в функции только ради поддержки инструментов (редактор, mypy и т. д.).
### Возврат Response напрямую { #return-a-response-directly }
Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах документации для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}.
Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}.
{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
@@ -195,7 +195,7 @@ FastAPI делает несколько вещей внутри вместе с
{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
Это тоже сработает, так как `RedirectResponse` — подкласс `Response`, и FastAPI автоматически обработает этот простой случай.
Это тоже сработает, так как `RedirectResponse` — подкласс `Response`, и FastAPI автоматически обработает этот случай.
### Некорректные аннотации возвращаемых типов { #invalid-return-type-annotations }
@@ -209,15 +209,15 @@ FastAPI делает несколько вещей внутри вместе с
### Отключить модель ответа { #disable-response-model }
Продолжая пример выше, вы можете не хотеть использовать стандартные валидацию данных, документирование, фильтрацию и т.п., выполняемые FastAPI.
Продолжая пример выше, вы можете не хотеть использовать стандартную валидацию данных, документацию, фильтрацию и т.д., выполняемые FastAPI.
Но при этом вы можете хотеть сохранить аннотацию возвращаемого типа в функции, чтобы пользоваться поддержкой инструментов вроде редакторов кода и инструментов проверки типов (например, mypy).
Но при этом вы можете хотеть сохранить аннотацию возвращаемого типа в функции, чтобы пользоваться поддержкой инструментов (редакторы, проверки типов вроде mypy).
В этом случае вы можете отключить генерацию модели ответа, установив `response_model=None`:
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
Так FastAPI пропустит генерацию модели ответа, и вы сможете использовать любые аннотации возвращаемых типов, которые вам нужны, без влияния на ваше приложение FastAPI. 🤓
Так FastAPI пропустит генерацию модели ответа, и вы сможете использовать любые аннотации возвращаемых типов, не влияя на ваше приложение FastAPI. 🤓
## Параметры кодирования модели ответа { #response-model-encoding-parameters }
@@ -252,6 +252,20 @@ FastAPI делает несколько вещей внутри вместе с
/// info | Информация
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
Примеры здесь используют `.dict()` для совместимости с Pydantic v1, но если вы используете Pydantic v2, применяйте `.model_dump()`.
///
/// info | Информация
FastAPI использует метод `.dict()` у Pydantic-моделей с <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">параметром `exclude_unset`</a>, чтобы добиться такого поведения.
///
/// info | Информация
Вы также можете использовать:
* `response_model_exclude_defaults=True`
@@ -298,7 +312,7 @@ FastAPI достаточно умен (на самом деле, это Pydantic
Обратите внимание, что значения по умолчанию могут быть любыми, не только `None`.
Это может быть список (`[]`), число с плавающей точкой `10.5` и т.д.
Это может быть список (`[]`), число с плавающей точкой `10.5` и т. д.
///
@@ -332,7 +346,7 @@ FastAPI достаточно умен (на самом деле, это Pydantic
#### Использование `list` вместо `set` { #using-lists-instead-of-sets }
Если вы забыли использовать `set` и применили `list` или `tuple` вместо него, FastAPI всё равно преобразует это в `set`, и всё будет работать корректно:
Если вы забыли использовать `set` и применили `list` или `tuple`, FastAPI всё равно преобразует это в `set`, и всё будет работать корректно:
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}

View File

@@ -8,14 +8,36 @@
Вы можете объявить `examples` для модели Pydantic, которые будут добавлены в сгенерированную JSON Schema.
//// tab | Pydantic v2
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
////
//// tab | Pydantic v1
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
////
Эта дополнительная информация будет добавлена как есть в выходную **JSON Schema** этой модели и будет использоваться в документации API.
Вы можете использовать атрибут `model_config`, который принимает `dict`, как описано в <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Документации Pydantic: Конфигурация</a>.
//// tab | Pydantic v2
В Pydantic версии 2 вы будете использовать атрибут `model_config`, который принимает `dict`, как описано в <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Документации Pydantic: Конфигурация</a>.
Вы можете задать `"json_schema_extra"` с `dict`, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая `examples`.
////
//// tab | Pydantic v1
В Pydantic версии 1 вы будете использовать внутренний класс `Config` и `schema_extra`, как описано в <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">Документации Pydantic: Настройка схемы</a>.
Вы можете задать `schema_extra` со `dict`, содержащим любые дополнительные данные, которые вы хотите видеть в сгенерированной JSON Schema, включая `examples`.
////
/// tip | Подсказка
Вы можете использовать тот же приём, чтобы расширить JSON Schema и добавить свою собственную дополнительную информацию.
@@ -102,7 +124,7 @@ OpenAPI 3.1.0 (используется начиная с FastAPI 0.99.0) доб
Ключи `dict` идентифицируют каждый пример, а каждое значение — это ещё один `dict`.
Каждый конкретный пример `dict` в `examples` может содержать:
Каждый конкретный пример`dict` в `examples` может содержать:
* `summary`: Краткое описание примера.
* `description`: Подробное описание, которое может содержать текст в Markdown.
@@ -113,7 +135,7 @@ OpenAPI 3.1.0 (используется начиная с FastAPI 0.99.0) доб
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
### OpenAPI-примеры в UI документации { #openapi-examples-in-the-docs-ui }
### OpenAPI-примеры в UI документации { #openapi-examples-in-the-docs-ui }
С `openapi_examples`, добавленным в `Body()`, страница `/docs` будет выглядеть так:
@@ -191,7 +213,7 @@ OpenAPI также добавила поля `example` и `examples` в друг
### Swagger UI и специфичные для OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples }
Теперь, поскольку Swagger UI не поддерживал несколько примеров JSON Schema (по состоянию на 2023-08-26), у пользователей не было способа показать несколько примеров в документации.
Раньше, поскольку Swagger UI не поддерживал несколько примеров JSON Schema (по состоянию на 2023-08-26), у пользователей не было способа показать несколько примеров в документации.
Чтобы решить это, FastAPI `0.103.0` **добавил поддержку** объявления того же старого, **специфичного для OpenAPI**, поля `examples` с новым параметром `openapi_examples`. 🤓

View File

@@ -90,12 +90,5 @@ For the following technical terms, use these specific translations to ensure con
* serve (meaning providing access to something): «отдавать» (or `предоставлять доступ к`)
* recap (noun): резюме
* utility function: вспомогательная функция
* fast to code: позволяет быстро писать код
* Tutorial - User Guide: Учебник - Руководство пользователя
* submodule: подмодуль
* subpackage: подпакет
* router: роутер
* building, deploying, accessing (when describing features of FastAPI Cloud): созданиe образа, развертывание и доступ
* type checker tool: инструмент проверки типов
Do not add whitespace in `т.д.`, `т.п.`.

View File

@@ -4,16 +4,10 @@ Translate to Turkish (Türkçe).
Language code: tr.
### Core principle
Don't translate word-by-word. Rewrite naturally in Turkish as if writing the doc from scratch. Preserve meaning, but prioritize fluency over literal accuracy.
### Grammar and tone
- Use instructional Turkish, consistent with existing Turkish docs.
- Use imperative/guide language (e.g. "açalım", "gidin", "kopyalayalım", "bir bakalım").
- Avoid filler words and overly long sentences.
- Ensure sentences make sense in Turkish context — adjust structure, conjunctions, and verb forms as needed for natural flow (e.g. use "Ancak" instead of "Ve" when connecting contrasting sentences, use "-maktadır/-mektedir" for formal statements).
- Use imperative/guide language when appropriate (e.g. açalım, gidin, kopyalayalım).
### Headings
@@ -21,23 +15,13 @@ Don't translate word-by-word. Rewrite naturally in Turkish as if writing the doc
### Quotes
- Keep quote style consistent with existing Turkish docs (typically ASCII quotes in text).
- Never modify quotes inside inline code, code blocks, URLs, or file paths.
- Alıntı stili mevcut Türkçe dokümanlarla tutarlı tutun (genellikle metin içinde ASCII tırnak işaretleri kullanılır).
- Satır içi kod, kod blokları, URL'ler veya dosya yolları içindeki tırnak işaretlerini asla değiştirmeyin.
### Ellipsis
- Keep ellipsis style (`...`) consistent with existing Turkish docs.
- Never modify `...` in code, URLs, or CLI examples.
### Consistency
- Use the same translation for the same term throughout the document.
- If you translate a concept one way, keep it consistent across all occurrences.
### Links and references
- Never modify link syntax like `{.internal-link target=_blank}`.
- Keep markdown link structure intact: `[text](url){.internal-link}`.
- Üç nokta (...) stili mevcut Türkçe dokümanlarla tutarlı tutun.
- Kod, URL veya CLI örneklerindeki `...` ifadesini asla değiştirmeyin.
### Preferred translations / glossary

View File

@@ -1,44 +1,57 @@
# OPENAPI 中的其他响应
# OPENAPI 中的其他响应 { #additional-responses-in-openapi }
您可以声明附加响应,包括附加状态代码、媒体类型、描述等。
/// warning | 警告
些额外的响应将包含在OpenAPI模式中因此它们也将出现在API文档中
是一个相当高级的话题
但是对于那些额外的响应,你必须确保你直接返回一个像 `JSONResponse` 一样的 `Response` ,并包含你的状态代码和内容
## `model`附加响应
您可以向路径操作装饰器传递参数 `responses`
它接收一个 `dict`,键是每个响应的状态代码(如`200`),值是包含每个响应信息的其他 `dict`
每个响应字典都可以有一个关键模型,其中包含一个 `Pydantic` 模型,就像 `response_model` 一样。
**FastAPI**将采用该模型,生成其`JSON Schema`并将其包含在`OpenAPI`中的正确位置。
例如,要声明另一个具有状态码 `404``Pydantic`模型 `Message` 的响应,可以写:
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
/// note
请记住,您必须直接返回 `JSONResponse`
如果你刚开始使用 **FastAPI**,可能不需要这个
///
/// info
你可以声明附加响应,包括附加状态码、媒体类型、描述等。
`model` 密钥不是OpenAPI的一部分
**FastAPI**将从那里获取`Pydantic`模型,生成` JSON Schema` ,并将其放在正确的位置。
- 正确的位置是:
- 在键 `content` 中,其具有另一个`JSON`对象( `dict` )作为值,该`JSON`对象包含:
- 媒体类型的密钥,例如 `application/json` ,它包含另一个`JSON`对象作为值,该对象包含:
- 一个键` schema` ,它的值是来自模型的`JSON Schema`,正确的位置在这里。
- **FastAPI**在这里添加了对OpenAPI中另一个地方的全局JSON模式的引用而不是直接包含它。这样其他应用程序和客户端可以直接使用这些JSON模式提供更好的代码生成工具等
这些额外的响应将包含在 OpenAPI schema 中,因此它们也将出现在 API 文档中
但是对于这些额外的响应,你必须确保你直接返回一个像 `JSONResponse` 一样的 `Response`,并包含你的状态码和内容。
## 使用 `model` 的附加响应 { #additional-response-with-model }
你可以向你的*路径操作装饰器*传递参数 `responses`
它接收一个 `dict`:键是每个响应的状态码(如 `200`),值是包含每个响应信息的其他 `dict`
这些响应 `dict` 中的每一个都可以有一个键 `model`,包含一个 Pydantic 模型,就像 `response_model` 一样。
**FastAPI** 将采用该模型,生成其 JSON Schema 并将其包含在 OpenAPI 的正确位置。
例如,要声明另一个具有状态码 `404` 和 Pydantic 模型 `Message` 的响应,你可以这样写:
{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
/// note | 注意
请记住,你必须直接返回 `JSONResponse`
///
**在OpenAPI中为该路径操作生成的响应将是**
/// info | 信息
```json hl_lines="3-12"
`model` 键不是 OpenAPI 的一部分。
**FastAPI** 将从那里获取 Pydantic 模型,生成 JSON Schema并将其放在正确的位置。
正确的位置是:
* 在键 `content` 中,其值是另一个 JSON 对象(`dict`),它包含:
* 一个以媒体类型为键(例如 `application/json`)的条目,其值是另一个 JSON 对象,它包含:
* 一个键 `schema`,其值是来自模型的 JSON Schema这里就是正确的位置。
* **FastAPI** 在这里添加了对 OpenAPI 中另一个地方的全局 JSON Schemas 的引用,而不是直接包含它。这样,其他应用程序和客户端可以直接使用这些 JSON Schemas提供更好的代码生成工具等。
///
为该*路径操作*在 OpenAPI 中生成的响应将是:
```JSON hl_lines="3-12"
{
"responses": {
"404": {
@@ -73,10 +86,11 @@
}
}
}
```
**模式被引用到OpenAPI模式中的另一个位置**
```json hl_lines="4-16"
这些 schema 被引用到 OpenAPI schema 中的另一个位置:
```JSON hl_lines="4-16"
{
"components": {
"schemas": {
@@ -153,48 +167,54 @@
}
}
}
```
## 主响应的其他媒体类型
您可以使用相同的 `responses` 参数为相同的主响应添加不同的媒体类型
## 为主响应添加其他媒体类型 { #additional-media-types-for-the-main-response }
例如,您可以添加一个额外的媒体类型` image/png` 声明您的路径操作可以返回JSON对象媒体类型 `application/json` 或PNG图像
你可以使用同一个 `responses` 参数,为同一个主响应添加不同的媒体类型。
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
例如,你可以添加一个额外的媒体类型 `image/png`,声明你的*路径操作*可以返回一个 JSON 对象(媒体类型 `application/json`)或一张 PNG 图片:
/// note
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
- 请注意,您必须直接使用 `FileResponse` 返回图像。
/// note | 注意
请注意,你必须直接使用 `FileResponse` 返回图片。
///
/// info
/// info | 信息
- 除非在 `responses` 参数中明确指定不同的媒体类型,否则**FastAPI**将假定响应与主响应类具有相同的媒体类型(默认为` application/json` )。
- 但是如果您指定了一个自定义响应类,并将 `None `作为其媒体类型,**FastAPI**将使用 `application/json` 作为具有关联模型的任何其他响应。
除非在 `responses` 参数中显式指定不同的媒体类型,否则 FastAPI假定响应与主响应类具有相同的媒体类型(默认为 `application/json`)。
但是如果你指定了一个自定义响应类,并将 `None` 作为其媒体类型FastAPI 将对任何带有关联模型的附加响应使用 `application/json`。
///
## 组合信息
您还可以联合接收来自多个位置的响应信息,包括 `response_model `、 `status_code` 和 `responses `参数。
## 组合信息 { #combining-information }
您可以使用默认的状态码 `200` (或者您需要的自定义状态码)声明一个 `response_model `然后直接在OpenAPI模式中在 `responses` 中声明相同响应的其他信息
你也可以组合来自多个位置的响应信息,包括 `response_model`、`status_code` 和 `responses` 参数
**FastAPI**将保留来自 `responses` 的附加信息并将其与模型中的JSON Schema结合起来
你可以声明一个 `response_model`,使用默认状态码 `200`(或者如果你需要,也可以用自定义状态码),然后在 `responses` 中直接在 OpenAPI schema 里为同一个响应声明附加信息
例如,您可以使用状态码 `404` 声明响应,该响应使用`Pydantic`模型并具有自定义的` description`
**FastAPI** 将保留来自 `responses` 的附加信息,并将其与模型的 JSON Schema 结合起来
以及一个状态码 `200` 的响应,它使用您的 `response_model` ,但包含自定义的 `example`
例如,你可以声明一个状态码 `404` 的响应,它使用 Pydantic 模型并具有自定义的 `description`。
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
以及一个状态码为 `200` 的响应,它使用你的 `response_model`,但包含自定义的 `example`
所有这些都将被合并并包含在您的OpenAPI中并在API文档中显示
{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
## 联合预定义响应和自定义响应
这些将全部被合并并包含在你的 OpenAPI 中,并在 API 文档中显示:
<img src="/img/tutorial/additional-responses/image01.png">
## 组合预定义响应和自定义响应 { #combine-predefined-responses-and-custom-ones }
你可能希望有一些适用于许多*路径操作*的预定义响应,但你也想把它们与每个*路径操作*所需的自定义响应组合起来。
对于这些情况,你可以使用 Python 的“解包unpacking”技术通过 `**dict_to_unpack` 解包一个 `dict`
您可能希望有一些应用于许多路径操作的预定义响应,但是你想将不同的路径和自定义的相应组合在一块。
对于这些情况你可以使用Python的技术将 `dict` 与 `**dict_to_unpack` 解包:
```Python
old_dict = {
"old key": "old value",
@@ -203,19 +223,25 @@ old_dict = {
new_dict = {**old_dict, "new key": "new value"}
```
这里, new_dict 将包含来自 old_dict 的所有键值对加上新的键值对:
```python
这里,`new_dict` 将包含来自 `old_dict` 的所有键值对,以及新的键值对:
```Python
{
"old key": "old value",
"second old key": "second old value",
"new key": "new value",
}
```
您可以使用该技术在路径操作中重用一些预定义的响应,并将它们与其他自定义响应相结合。
**例如:**
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
## 有关OpenAPI响应的更多信息
要了解您可以在响应中包含哪些内容您可以查看OpenAPI规范中的以下部分
+ [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responsesObject),它包括 Response Object 。
+ [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responseObject),您可以直接在 `responses` 参数中的每个响应中包含任何内容。包括 `description` 、 `headers` 、 `content` 其中是声明不同的媒体类型和JSON Schemas和 `links` 。
你可以使用该技术在你的*路径操作*中复用一些预定义响应,并将它们与额外的自定义响应相结合。
例如:
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
## 关于 OpenAPI 响应的更多信息 { #more-information-about-openapi-responses }
要查看你到底可以在响应中包含哪些内容,你可以查看 OpenAPI 规范中的这些部分:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI Responses Object</a>,它包含 `Response Object`。
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI Response Object</a>,你可以直接把这里的任何内容包含到 `responses` 参数中每个响应里。包括 `description`、`headers`、`content`(在这里声明不同媒体类型和 JSON Schemas以及 `links`。

View File

@@ -1,41 +1,41 @@
# 额外的状态码
# 额外的状态码 { #additional-status-codes }
**FastAPI** 默认使用 `JSONResponse` 返回一个响应,将你的 *路径操作* 中的返回内容放到该 `JSONResponse`
默认情况下,**FastAPI** 会使用 `JSONResponse` 返回响应,把你从 *路径操作* 返回内容放到该 `JSONResponse`
**FastAPI** 会自动使用默认的状态码或者使用你在 *路径操作* 中设置的状态码。
它会使用默认的状态码或者使用你在 *路径操作* 中设置的状态码。
## 额外的状态码
## 额外的状态码 { #additional-status-codes_1 }
如果你想要返回主要状态码之外的状态码,你可以通过直接返回一个 `Response` 来实现,比如 `JSONResponse`,然后直接设置额外的状态码。
如果你想主要状态码之外返回额外的状态码,你可以通过直接返回一个 `Response` 来实现,比如 `JSONResponse`,然后直接设置额外的状态码。
例如,假设你想一个 *路径操作* 能够更新条目,并且更新成功时返回 200 「成功」 的 HTTP 状态码。
例如,假设你想一个允许更新条目的 *路径操作*,并且成功时返回 200 “OK” 的 HTTP 状态码。
你也希望它能够接受新条目。并且当这些条目不存在时,会自动创建并返回 201 「创建」的 HTTP 状态码。
但你也希望它能够接受新条目。当这些条目之前不存在时,它会创建它们,并返回 201 “Created” 的 HTTP 状态码。
要实现,导入 `JSONResponse`然后在其中直接返回你的内容,并将 `status_code` 设置为为你要的值。
要实现这一点,导入 `JSONResponse`并直接在其中返回你的内容,设置你想要的 `status_code`
{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *}
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
/// warning | 警告
当你直接返回一个像上面例子中的 `Response` 对象时,它会直接返回。
当你直接返回一个 `Response`(如上面的示例)时,它会直接返回。
FastAPI 不会用模型等对该响应进行序列化。
不会使用模型等进行序列化。
确保其中有你想要的数据,且返回的值为合法的 JSON如果你使用 `JSONResponse` 的话)。
确保它包含你希望包含的数据,并且这些值是合法的 JSON如果你使用 `JSONResponse` 的话)。
///
/// note | 技术细节
你也可以使用 `from starlette.responses import JSONResponse` 
你也可以使用 `from starlette.responses import JSONResponse`
出于方便,**FastAPI** 为开发者提供同 `starlette.responses` 一样`fastapi.responses`。但大多数可用的响应都直接来自 Starlette。`status` 也是一样。
**FastAPI**了方便你(开发者)使用,提供了与 `starlette.responses` 相同`fastapi.responses`。但大多数可用的响应都直接来自 Starlette。`status` 也是一样。
///
## OpenAPI 和 API 文档
## OpenAPI 和 API 文档 { #openapi-and-api-docs }
如果你直接返回额外的状态码和响应,它们不会包含在 OpenAPI 方案API 文档)中,因为 FastAPI 没办法预先知道你要返回什么。
如果你直接返回额外的状态码和响应,它们不会包含在 OpenAPI schemaAPI 文档)中,因为 FastAPI 没办法预先知道你要返回什么。
你可以使用 [额外的响应](additional-responses.md){.internal-link target=_blank} 在代码中记录这些内容。
但你可以在代码中使用[额外的响应](additional-responses.md){.internal-link target=_blank} 记录这些内容。

View File

@@ -1,65 +1,163 @@
# 高级依赖项
# 高级依赖项 { #advanced-dependencies }
## 参数化的依赖项
## 参数化的依赖项 { #parameterized-dependencies }
我们之前看到的所有依赖项都是写死的函数或类。
但也可以为依赖项设置参数,避免声明多个不同的函数或类。
但也可以在某些情况下为依赖项设置参数,而无需声明许多不同的函数或类。
假设要创建校验查询参数 `q` 是否包含固定内容的依赖项
假设要创建一个依赖项,用来检查查询参数 `q` 是否包含某些固定内容。
此处要把待检验的固定内容定义为参数。
我们希望能够对该固定内容进行参数
## **可调用**实例
## **可调用**实例 { #a-callable-instance }
Python 可以把类实例变为**可调用项**
Python 可以把类实例变为可调用项
这里说的不是类本身(类本就是可调用项),而是实例。
这里说的不是类本身(类本就是可调用项),而是该类的实例。
为此,需要声明 `__call__` 方法:
{* ../../docs_src/dependencies/tutorial011.py hl[10] *}
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
本例中,**FastAPI** 使用 `__call__` 检查附加参数子依赖项,稍后,还要调用它向*路径操作函数*传递值
本例中,这个 `__call__` 将被 **FastAPI** 用来检查额外参数子依赖项,并且稍后将被调用,把一个值传递给你的*路径操作函数*中的参数
## 参数化实例
## 参数化实例 { #parameterize-the-instance }
接下来,使用 `__init__` 声明用于**参数化**依赖项的实例参数
接下来,我们可以使用 `__init__` 声明实例的参数,用于“参数化依赖项:
{* ../../docs_src/dependencies/tutorial011.py hl[7] *}
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
本例中,**FastAPI** 不使用 `__init__`,我们要直接在代码中使用。
在这个例子中,**FastAPI** 不会触碰或关心 `__init__`,我们在代码中直接使用
## 创建实例
## 创建实例 { #create-an-instance }
使用以下代码创建类实例:
我们可以用下面的方式创建该类的实例:
{* ../../docs_src/dependencies/tutorial011.py hl[16] *}
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *}
这样就可以**参数化**依赖项,它包含 `checker.fixed_content` 的属性 - `"bar"`
这样我们就能“参数化依赖项,它现在包含 `"bar"`,作为属性 `checker.fixed_content`
## 把实例作为依赖项
## 把实例作为依赖项 { #use-the-instance-as-a-dependency }
然后,不要再`Depends(checker)` 中使用 `Depends(FixedContentQueryChecker)` 而是要使用 `checker`因为依赖项是实例 - `checker`,不是类。
然后,我们可以`Depends(checker)` 中使用这个 `checker`,而不是 `Depends(FixedContentQueryChecker)`,因为依赖项是实例 `checker`,不是类本身
处理依赖项时,**FastAPI** 以如下方式调用 `checker`
处理依赖项时,**FastAPI** 会像这样调用 `checker`
```Python
checker(q="somequery")
```
……并用*路径操作函数*参数 `fixed_content_included` 返回依赖项的值
...并将其返回值作为依赖项的值,传递给我们的*路径操作函数*,对应参数 `fixed_content_included`
{* ../../docs_src/dependencies/tutorial011.py hl[20] *}
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
/// tip | 提示
本章示例有些刻意,也看不出有什么用处。
本章示例可能看起来有些刻意,而且目前还不太清楚有什么用处。
个简例只是为了说明高级依赖项的运作机制
些示例刻意保持简单,但展示了它是如何工作的
在有关安全的章节中,工具函数将以这种方式实现。
在有关安全的章节中,有一些工具函数就是以同样的方式实现
只要能理解本章内容,就能理解安全工具背后的运行机制
如果你理解了这些内容,你就已经知道那些安全工具在底层是如何工作的
///
## 带有 `yield`、`HTTPException`、`except` 以及 Background Tasks 的依赖项 { #dependencies-with-yield-httpexception-except-and-background-tasks }
/// warning | 警告
你很可能不需要这些技术细节。
这些细节主要在以下场景有用:如果你有一个早于 0.121.0 的 FastAPI 应用,并且你正遇到与带有 `yield` 的依赖项相关的问题。
///
带有 `yield` 的依赖项随着时间推移不断演进,以覆盖不同用例并修复一些问题。下面是变更总结。
### 带有 `yield` 和 `scope` 的依赖项 { #dependencies-with-yield-and-scope }
在 0.121.0 版本中FastAPI 为带有 `yield` 的依赖项添加了对 `Depends(scope="function")` 的支持。
使用 `Depends(scope="function")` 时,`yield` 之后的退出代码会在*路径操作函数*结束后立刻执行,并且是在响应发送回客户端之前。
而当使用 `Depends(scope="request")`(默认值)时,`yield` 之后的退出代码会在响应发送之后执行。
你可以在文档中阅读更多:[带有 `yield` 的依赖项 - 提前退出与 `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope)。
### 带有 `yield` 和 `StreamingResponse` 的依赖项,技术细节 { #dependencies-with-yield-and-streamingresponse-technical-details }
在 FastAPI 0.118.0 之前,如果你使用了带有 `yield` 的依赖项,它会在*路径操作函数*返回之后、但在发送响应之前运行退出代码。
其目的是避免在等待响应通过网络传输时,资源被持有超过必要的时间。
但这个改动也意味着:如果你返回的是 `StreamingResponse`,那么带有 `yield` 的依赖项的退出代码会已经执行过了。
例如,如果你在一个带有 `yield` 的依赖项中创建了数据库 session那么在流式传输数据时`StreamingResponse` 将无法使用该 session因为该 session 已经在 `yield` 之后的退出代码中被关闭了。
在 0.118.0 中,这种行为被回滚,使得 `yield` 之后的退出代码在响应发送之后执行。
/// info | 信息
如你将在下文看到的,这与 0.106.0 之前的行为非常类似,但针对边界情况做了多项改进与 bug 修复。
///
#### 提前执行退出代码的用例 { #use-cases-with-early-exit-code }
对于某些特定条件下的用例,在发送响应之前运行带有 `yield` 的依赖项退出代码(旧行为)可能会有好处。
例如,设想你有一段代码,在带有 `yield` 的依赖项中使用数据库 session 仅用于验证用户,但在*路径操作函数*中不再使用该数据库 session只在依赖项中使用并且响应发送需要很长时间比如一个慢速发送数据的 `StreamingResponse`,但由于某些原因并不使用数据库。
在这种情况下,数据库 session 会一直被持有,直到响应发送完毕。但如果你不再使用它,那么其实没必要一直持有它。
它可能长这样:
{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
退出代码,也就是在这里自动关闭 `Session` 的部分:
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
...会在响应把慢速数据发送完之后才运行:
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
但由于 `generate_stream()` 并不使用数据库 session所以在发送响应期间保持 session 打开并不必要。
如果你在使用 SQLModel或 SQLAlchemy时遇到这种特定用例你可以在不再需要 session 后显式关闭它:
{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
这样 session 就会释放数据库连接,以便其他请求可以使用它。
如果你有其他用例需要从带有 `yield` 的依赖项中提前退出,请创建一个 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussion Question</a>,说明你的具体用例以及为何你会受益于对带有 `yield` 的依赖项进行提前关闭。
如果确实存在有说服力的用例需要在带有 `yield` 的依赖项中提前关闭,我会考虑添加一种新的方式来选择启用提前关闭。
### 带有 `yield` 和 `except` 的依赖项,技术细节 { #dependencies-with-yield-and-except-technical-details }
在 FastAPI 0.110.0 之前,如果你使用了带有 `yield` 的依赖项,并且在该依赖项中使用 `except` 捕获了异常,但没有再次抛出该异常,那么该异常会被自动抛出/转发到任何异常处理器或内部服务器错误处理器。
在 0.110.0 版本中,这一行为被更改,用于修复:在没有处理器(内部服务器错误)的情况下,转发异常会导致未处理的内存消耗;同时也使其与常规 Python 代码的行为保持一致。
### Background Tasks 与带有 `yield` 的依赖项,技术细节 { #background-tasks-and-dependencies-with-yield-technical-details }
在 FastAPI 0.106.0 之前,在 `yield` 之后抛出异常是不可能的,因为带有 `yield` 的依赖项的退出代码是在响应发送*之后*执行的,因此 [异常处理器](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} 早已运行完毕。
这样设计主要是为了允许在后台任务中使用依赖项“yield 出来”的同一个对象,因为退出代码会在后台任务完成之后才执行。
在 FastAPI 0.106.0 中,这一行为被更改,其意图是不在等待响应通过网络传输时持有资源。
/// tip | 提示
此外,后台任务通常是一套独立的逻辑,应该单独处理,并使用它自己的资源(例如它自己的数据库连接)。
因此,这样你通常会得到更干净的代码。
///
如果你过去依赖这种行为,那么现在你应该在后台任务内部创建资源,并且在内部只使用那些不依赖带有 `yield` 的依赖项资源的数据。
例如,不再使用同一个数据库 session而是在后台任务内部创建一个新的数据库 session并用这个新 session 从数据库中获取对象。然后,不再把数据库对象作为参数传递给后台任务函数,而是传递该对象的 ID并在后台任务函数内部再次获取该对象。

View File

@@ -1,26 +1,26 @@
# 异步测试
# 异步测试 { #async-tests }
已经了解了如何使用 `TestClient` 测试 **FastAPI** 应用程序。但是到目前为止,只了解了如何编写同步测试,而没有使用 `async` 异步函数。
已经了解了如何使用提供的 `TestClient` 测试你的 **FastAPI** 应用程序。到目前为止,只了解了如何编写同步测试,而没有使用 `async` 函数。
在测试中能够使用异步函数可能会很有用,比如当需要异步查询数据库的时候。想象一下,想要测试向 FastAPI 应用程序发送请求,然后验证的后端是否成功在数据库中写入了正确的数据,与此同时使用了异步数据库库。
在测试中能够使用异步函数可能会很有用,比如当需要异步查询数据库的时候。想象一下,想要测试向 FastAPI 应用程序发送请求,然后验证的后端是否成功在数据库中写入了正确的数据,同时使用了一个异步数据库库。
让我们看看如何才能实现这一点。
## pytest.mark.anyio
## pytest.mark.anyio { #pytest-mark-anyio }
如果我们想在测试中调用异步函数,那么我们的测试函数必须是异步的。 AnyIO 为此提供了一个简洁的插件,它允许我们指定些测试函数要异步调用。
如果我们想在测试中调用异步函数那么我们的测试函数必须是异步的。AnyIO 为此提供了一个简洁的插件,它允许我们指定些测试函数要异步方式调用。
## HTTPX
## HTTPX { #httpx }
即使**FastAPI** 应用程序使用普通的 `def` 函数而不是 `async def` ,它本质上仍是一个 `async` 异步应用程序。
即使**FastAPI** 应用程序使用普通的 `def` 函数而不是 `async def`,它在底层仍然是一个 `async` 应用程序。
`TestClient` 在内部通过一些“魔法”操作,使得可以在普通的 `def` 测试函数中调用异步的 FastAPI 应用程序,并使用标准的 pytest。但当我们在异步函数中使用它时这种“魔法”就不再生效了。由于测试以异步方式运行我们无法在测试函数中继续使用 `TestClient`
`TestClient` 在内部通过一些“魔法”操作,使得可以在普通的 `def` 测试函数中调用异步的 FastAPI 应用程序,并使用标准的 pytest。但当我们在异步函数中使用它时这种“魔法”就不再生效了。由于测试以异步方式运行我们无法在测试函数中继续使用 `TestClient`
`TestClient` 基于 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> 的。幸运的是我们可以直接使用它来测试API。
`TestClient` 基于 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>幸运的是,我们可以直接使用它来测试 API。
## 示例
## 示例 { #example }
举个简单的例子,让我们来看一个[更大的应用](../tutorial/bigger-applications.md){.internal-link target=_blank}[测试](../tutorial/testing.md){.internal-link target=_blank}中描述的类似文件结构:
举个简单的例子,让我们来看一个[更大的应用](../tutorial/bigger-applications.md){.internal-link target=_blank}[测试](../tutorial/testing.md){.internal-link target=_blank} 中描述的类似文件结构:
```
.
@@ -30,17 +30,17 @@
│   └── test_main.py
```
文件 `main.py` 将包含:
文件 `main.py` 将包含
{* ../../docs_src/async_tests/main.py *}
{* ../../docs_src/async_tests/app_a_py39/main.py *}
文件 `test_main.py` 将包含针对 `main.py` 的测试,现在它可能看起来如下:
{* ../../docs_src/async_tests/test_main.py *}
{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
## 运行测试
## 运行 { #run-it }
可以通过以下方式照常运行测试:
可以通过以下方式照常运行测试:
<div class="termy">
@@ -52,21 +52,21 @@ $ pytest
</div>
## 详细说明
## 详细说明 { #in-detail }
这个标记 `@pytest.mark.anyio` 会告诉 pytest 该测试函数应该被异步调用:
{* ../../docs_src/async_tests/test_main.py hl[7] *}
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
/// tip
/// tip | 提示
请注意,测试函数现在用的是 `async def`,而不是像以前使用 `TestClient` 时那样只是 `def`
请注意,测试函数现在用的是 `async def`,而不是像以前使用 `TestClient` 时那样只是 `def`
///
我们现在可以使用应用程序创建一个 `AsyncClient` ,并使用 `await` 向其发送异步请求。
然后我们可以使用应用程序创建一个 `AsyncClient`,并使用 `await` 向其发送异步请求。
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
这相当于:
@@ -74,26 +74,26 @@ $ pytest
response = client.get('/')
```
我们曾经通过它向 `TestClient` 发出请求。
...我们过去使用 `TestClient` 发出请求时用的方式
/// tip
/// tip | 提示
请注意,我们正在将 async/await 与新的 `AsyncClient` 一起使用——请求是异步的。
///
/// warning
/// warning | 警告
如果的应用程序依赖于生命周期事件, `AsyncClient` 将不会触发这些事件。为了确保它们被触发,请使用 <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a> 中的 `LifespanManager`
如果的应用程序依赖于 lifespan 事件,`AsyncClient` 将不会触发这些事件。为了确保它们被触发,请使用 <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a> 中的 `LifespanManager`
///
## 其他异步函数调用
## 其他异步函数调用 { #other-asynchronous-function-calls }
由于测试函数现在是异步的,因此除了在测试中向 FastAPI 应用程序发送请求之外,现在还可以调用(和使用 `await` 等待)其他 `async` 异步函数,就和在代码中的其他任何地方调用它们的方法一样。
由于测试函数现在是异步的,因此除了在测试中向 FastAPI 应用程序发送请求之外,现在还可以调用( `await`)其他 `async` 函数,就和在代码中的其他任何地方调用它们的方法一样。
/// tip
/// tip | 提示
如果在测试程序中集成异步函数调用的时候遇到一个 `RuntimeError: Task attached to a different loop` 的报错(例如,使用 <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB MotorClient</a> 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如通过 `'@app.on_event("startup")` 回调函数进行初始化。
如果在测试中集成异步函数调用的时候遇到 `RuntimeError: Task attached to a different loop`(例如,使用 <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB's MotorClient</a> 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如 `@app.on_event("startup")` 回调函数中实例化。
///

View File

@@ -1,30 +1,131 @@
# 使用代理
# 在代理之后 { #behind-a-proxy }
有些情况下,您可能要使用 Traefik 或 Nginx 等**代理**服务器,并添加应用不能识别的附加路径前缀配置
在很多情况下,你会在 FastAPI 应用前面使用 **proxy**(代理),例如 Traefik 或 Nginx
此时,要使用 `root_path` 配置应用
这些代理可以处理 HTTPS 证书以及其他事情
`root_path` 是 ASGI 规范提供的机制FastAPI 就是基于此规范开发的(通过 Starlette
## Proxy 转发头 { #proxy-forwarded-headers }
在你的应用前面的 **proxy** 通常会在将请求发送到你的 **server**(服务器)之前,动态设置一些 headers让服务器知道该请求是由代理 **forwarded**转发从而让它知道原始公网URL包含域名、它在使用 HTTPS 等信息。
**server** 程序(例如通过 **FastAPI CLI** 使用 **Uvicorn**)能够解释这些 headers然后将该信息传递给你的应用。
但出于安全原因,由于 server 不知道自己在一个受信任的代理之后,它不会解释这些 headers。
/// note | 技术细节
proxy headers 包括:
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
///
### 启用 Proxy 转发头 { #enable-proxy-forwarded-headers }
你可以使用 *CLI Option* `--forwarded-allow-ips` 启动 FastAPI CLI并传入应被信任来读取这些转发 headers 的 IP 地址。
如果你设置为 `--forwarded-allow-ips="*"`,它会信任所有传入的 IP。
如果你的 **server** 在一个受信任的 **proxy** 后面,并且只有代理会与它通信,这会让它接受该 **proxy** 的 IP。
<div class="termy">
```console
$ fastapi run --forwarded-allow-ips="*"
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### HTTPS 的重定向 { #redirects-with-https }
例如,假设你定义了一个 *路径操作* `/items/`
{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *}
如果客户端尝试访问 `/items`,默认情况下会被重定向到 `/items/`
但在设置 *CLI Option* `--forwarded-allow-ips` 之前,它可能会重定向到 `http://localhost:8000/items/`
但也许你的应用托管在 `https://mysuperapp.com`,重定向应该是 `https://mysuperapp.com/items/`
现在通过设置 `--proxy-headers`FastAPI 就能够重定向到正确的位置。 😎
```
https://mysuperapp.com/items/
```
/// tip | 提示
如果你想了解更多关于 HTTPS 的内容,请查看指南 [关于 HTTPS](../deployment/https.md){.internal-link target=_blank}。
///
### Proxy 转发头如何工作 { #how-proxy-forwarded-headers-work }
下面是一个可视化示例,展示 **proxy** 如何在客户端和 **application server**(应用服务器)之间添加转发头:
```mermaid
sequenceDiagram
participant Client
participant Proxy as Proxy/Load Balancer
participant Server as FastAPI Server
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
Note over Proxy: Proxy adds forwarded headers
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
Proxy->>Client: HTTPS Response
```
**proxy** 会拦截客户端的原始请求,并在将请求传递给 **application server** 之前添加特殊的 *forwarded* headers`X-Forwarded-*`)。
这些 headers 保留了原始请求的信息,否则这些信息会丢失:
* **X-Forwarded-For**:原始客户端的 IP 地址
* **X-Forwarded-Proto**:原始协议(`https`
* **X-Forwarded-Host**:原始主机(`mysuperapp.com`
**FastAPI CLI** 配置了 `--forwarded-allow-ips` 时,它会信任并使用这些 headers例如用于在重定向中生成正确的 URL。
## 带有剥离路径前缀的代理 { #proxy-with-a-stripped-path-prefix }
你可能有一个 proxy会为你的应用添加一个路径前缀。
在这些情况下,你可以使用 `root_path` 来配置你的应用。
`root_path` 是 ASGI 规范提供的机制FastAPI 通过 Starlette 构建在该规范之上)。
`root_path` 用于处理这些特定情况。
在挂载子应用时,也可以在内部使用。
并且它也会在挂载子应用时在内部使用。
## 移除路径前缀的代理
在这种情况下,使用带有剥离路径前缀的代理意味着:你可以在代码里声明路径 `/app`,但你在上层(代理)添加了一层,把你的 **FastAPI** 应用放在类似 `/api/v1` 这样的路径之下。
本例中,移除路径前缀的代理是指在代码中声明路径 `/app`,然后在应用顶层添加代理,把 **FastAPI** 应用放在 `/api/v1` 路径下
此时,原始路径 `/app` 实际上会在 `/api/v1/app` 提供服务
本例的原始路径 `/app` 实际上是在 `/api/v1/app` 提供服务
即便你的所有代码都假设只有 `/app`
哪怕所有代码都假设只有 `/app`
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *}
代理只在把请求传送给 Uvicorn 之前才会**移除路径前缀**,让应用以为它是在 `/app` 提供服务,因此不必在代码中加入前缀 `/api/v1`
并且代理会在将请求传递给应用服务器(可能是通过 FastAPI CLI 的 Uvicorn之前,动态 **“剥离”** 这个 **路径前缀**,让你的应用一直以为它是在 `/app` 提供服务,因此你不需要更新所有代码来包含前缀 `/api/v1`
但之后,在(前端)打开 API 文档时,代理会要求在 `/openapi.json`,而不是 `/api/v1/openapi.json` 中提取 OpenAPI 概图
到这里为止,一切都会像平常一样工作
因此, (运行在浏览器中的)前端会尝试访问 `/openapi.json`,但没有办法获取 OpenAPI 概图
但是,当你打开集成的 docs UI前端它会期望从 `/openapi.json` 获取 OpenAPI schema而不是从 `/api/v1/openapi.json` 获取
这是因为应用使用了以 `/api/v1` 为路径前缀的代理,前端要从 `/api/v1/openapi.json` 中提取 OpenAPI 概图
所以,(运行在浏览器中的)前端会尝试访问 `/openapi.json`,但它无法获取 OpenAPI schema
因为我们的应用是通过一个带有 `/api/v1` 路径前缀的代理提供服务的,前端需要从 `/api/v1/openapi.json` 获取 OpenAPI schema。
```mermaid
graph LR
@@ -39,15 +140,15 @@ proxy --> server
/// tip | 提示
IP `0.0.0.0` 常用于程序监听本机或服务器上的所有有效 IP。
IP `0.0.0.0` 常用于表示程序监听该机器/服务器上的所有可用 IP。
///
API 文档还需要 OpenAPI 概图声明 API `server` 位于 `/api/v1`使用代理时的 URL)。例如:
docs UI 还需要 OpenAPI schema 来声明这个 API `server` 位于 `/api/v1`在代理之后)。例如:
```JSON hl_lines="4-8"
{
"openapi": "3.0.2",
"openapi": "3.1.0",
// More stuff here
"servers": [
{
@@ -60,53 +161,53 @@ API 文档还需要 OpenAPI 概图声明 API `server` 位于 `/api/v1`(使用
}
```
本例中的 `Proxy` 是 **Traefik**`server` 是运行 FastAPI 应用的 **Uvicorn**
在这个示例中,“Proxy” 可能是 **Traefik** 之类的东西。而 server 则可能是带 **Uvicorn** 的 FastAPI CLI用来运行你的 FastAPI 应用
### 提供 `root_path`
### 提供 `root_path` { #providing-the-root-path }
此,要以如下方式使用命令行选项 `--root-path`
实现这一点,你可以使用命令行选项 `--root-path`,例如
<div class="termy">
```console
$ uvicorn main:app --root-path /api/v1
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Hypercorn 也支持 `--root-path `选项。
如果你使用 Hypercorn,它也有 `--root-path` 选项。
/// note | 技术细节
ASGI 规范定义 `root_path` 就是为了这种用例
ASGI 规范为这个用例定义 `root_path`。
并且 `--root-path` 命令行选项支持 `root_path`。
而命令行选项 `--root-path` 会提供这个 `root_path`。
///
### 查当前的 `root_path`
### 查当前的 `root_path` { #checking-the-current-root-path }
获取应用每个请求使用的当前 `root_path`是 `scope` 字典的内容(也是 ASGI 规范的内容)。
你可以获取你的应用每个请求使用的当前 `root_path`是 `scope` 字典的一部分(这也是 ASGI 规范的一部分)。
我们在这里的信息里包含 `roo_path` 只是为了演示。
这里我们把它包含在消息中只是为了演示。
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *}
然后,用以下命令启动 Uvicorn
然后,如果你用下面的方式启动 Uvicorn
<div class="termy">
```console
$ uvicorn main:app --root-path /api/v1
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
返回的响应如下
响应会类似于
```JSON
{
@@ -115,19 +216,19 @@ $ uvicorn main:app --root-path /api/v1
}
```
### 在 FastAPI 应用设置 `root_path`
### 在 FastAPI 应用设置 `root_path` { #setting-the-root-path-in-the-fastapi-app }
还有一种方,如果不能提供 `--root-path` 或等效的命令行选项,则在创建 FastAPI 应用时设置 `root_path` 参数
一种方式是,如果你没有办法提供 `--root-path` 这样的命令行选项或等效方式,你可以在创建 FastAPI 应用时设置 `root_path` 参数
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *}
{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *}
传递 `root_path` 给 `FastAPI` 与传递 `--root-path` 命令行选项给 Uvicorn 或 Hypercorn 一样
`root_path` 给 `FastAPI` 等同于将命令行选项 `--root-path` 给 Uvicorn 或 Hypercorn。
### 关于 `root_path`
### 关于 `root_path` { #about-root-path }
注意,服务器Uvicorn只是把 `root_path` 传递给应用。
请记住serverUvicorn除了把 `root_path` 传递给应用之外,不会把它用于其他任何事情
在浏览器中输入 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app 时能看到标准响应:</a>
但如果你在浏览器中打开 <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>,你会看到正常响应:
```JSON
{
@@ -136,25 +237,25 @@ $ uvicorn main:app --root-path /api/v1
}
```
它不要求访问 `http://127.0.0.1:800/api/v1/app`。
因此,它不会期望通过 `http://127.0.0.1:8000/api/v1/app` 被访问
Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn而在顶部添加 `/api/v1` 前缀是代理要做的事情
Uvicorn 会期望代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn然后由代理负责在其上额外添加 `/api/v1` 前缀。
## 关于移除路径前缀的代理
## 关于带有剥离路径前缀的代理 { #about-proxies-with-a-stripped-path-prefix }
注意,移除路径前缀的代理只是配置代理的方式之一
请记住,带有剥离路径前缀的代理只是其中一种配置方式
大部分情况下,代理默认都不会移除路径前缀。
在很多情况下,默认可能是代理不会剥离路径前缀。
(未移除路径前缀)代理监听 `https://myawesomeapp.com` 等对象,如果浏览器跳转到 `https://myawesomeapp.com/api/v1/app`且服务器(例如 Uvicorn监听 `http://127.0.0.1:8000` 代理(未移除路径前缀) 会在同样的路径`http://127.0.0.1:8000/api/v1/app` 访问 Uvicorn
在这种情况下(没有剥离路径前缀)代理监听类似 `https://myawesomeapp.com` 的地址;然后如果浏览器访问 `https://myawesomeapp.com/api/v1/app`并且你的 server(例如 Uvicorn监听 `http://127.0.0.1:8000`,那么代理(没有剥离路径前缀)会在相同路径访问 Uvicorn`http://127.0.0.1:8000/api/v1/app`。
## 本地测试 Traefik
## 使用 Traefik 本地测试 { #testing-locally-with-traefik }
可以轻易地在本地使用 <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a> 运行移除路径前缀的验。
可以使用 <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>,在本地很容易地运行带有剥离路径前缀的验。
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">下载 Traefik</a>是一个二进制文件,需要解压文件,并在 Terminal 中直接运行。
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">下载 Traefik</a>是一个单独的二进制文件,你可以解压压缩包并直接在终端中运行。
然后创建包含如下内容的 `traefik.toml` 文件
然后创建一个文件 `traefik.toml`,内容如下
```TOML hl_lines="3"
[entryPoints]
@@ -166,15 +267,15 @@ Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn而在顶
filename = "routes.toml"
```
个文件把 Traefik 监听端口设置为 `9999`,并设置要使用另一个文件 `routes.toml`。
会告诉 Traefik 监听 9999 端口,并使用另一个文件 `routes.toml`。
/// tip | 提示
使用端口 9999 代替标准的 HTTP 端口 80这样就不使用管理员权限运行`sudo`)。
我们使用 9999 端口而不是标准的 HTTP 端口 80这样就不需要使用管理员(`sudo`权限运行它
///
接下来,创建 `routes.toml`
现在创建另一个文件 `routes.toml`
```TOML hl_lines="5 12 20"
[http]
@@ -199,11 +300,11 @@ Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn而在顶
url = "http://127.0.0.1:8000"
```
这个文件配置 Traefik 使用路径前缀 `/api/v1`。
这个文件 Traefik 配置为使用路径前缀 `/api/v1`。
然后,它把请求重定到运行在 `http://127.0.0.1:8000` 的 Uvicorn。
然后 Traefik 会将它的请求重定到运行在 `http://127.0.0.1:8000` 的 Uvicorn。
现在启动 Traefik
现在启动 Traefik
<div class="termy">
@@ -215,21 +316,21 @@ INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
</div>
接下来,使用 Uvicorn 启动应用,使用 `--root-path` 选项:
然后启动你的应用,使用 `--root-path` 选项:
<div class="termy">
```console
$ uvicorn main:app --root-path /api/v1
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### 查看响应
### 检查响应 { #check-the-responses }
访问含 Uvicorn 端口的 URL<a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app,就能看到标准响应:</a>
现在,如果你打开带 Uvicorn 端口的 URL<a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>,你会看到正常响应:
```JSON
{
@@ -240,13 +341,13 @@ $ uvicorn main:app --root-path /api/v1
/// tip | 提示
注意,就算访问 `http://127.0.0.1:8000/app`也显示从选项 `--root-path` 中提取的 `/api/v1`,这是 `root_path` 的值
注意,即便你访问的是 `http://127.0.0.1:8000/app`它仍然会显示来自选项 `--root-path` 的 `root_path` `/api/v1`
///
打开 Traefik 端口的 URL包含路径前缀<a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>
然后打开 Traefik 端口的 URL包含路径前缀:<a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>
得到同样的响应:
你会得到相同的响应:
```JSON
{
@@ -255,57 +356,57 @@ $ uvicorn main:app --root-path /api/v1
}
```
但这一次 URL 包含代理提供的路径前缀:`/api/v1`。
但这一次是在包含代理提供的前缀路径 `/api/v1` 的 URL 上
当然,这通过代理访问应用的方式,因此,路径前缀 `/app/v1` 版本才是**正确**的。
当然,这里的想法是每个人都应该通过代理访问应用,所以带路径前缀 `/api/v1` 版本是“正确”的。
而不带路径前缀的版本(`http://127.0.0.1:8000/app`,则由 Uvicorn 直接提供,专供*代理*Traefik访问。
而不带路径前缀的版本(`http://127.0.0.1:8000/app`)由 Uvicorn 直接提供,只会专门用于让 _proxy_Traefik访问。
这演示了代理Traefik如何使用路径前缀以及服务器Uvicorn如何使用选项 `--root-path` 的 `root_path`。
这演示了 ProxyTraefik如何使用路径前缀以及 serverUvicorn如何使用来自选项 `--root-path` 的 `root_path`。
### 查看文档
### 检查 docs UI { #check-the-docs-ui }
这才是有趣的地方
有趣的部分来了。
访问应用的**官方**方式是通过路径前缀的代理。因此,不出所料,如果没有在 URL 中添加路径前缀,直接访问通过 Uvicorn 运行的 API 文档,不能正常访问,因为需要通过代理才能访问。
访问应用的“官方”方式是通过我们定义了路径前缀的代理来访问。因此,正如你所预期的那样,如果你尝试直接访问 Uvicorn 提供的 docs UI而 URL 中没有路径前缀,它将无法工作,因为它期望通过代理访问。
输入 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs 查看 API 文档:</a>
你可以在 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> 查看:
<img src="/img/tutorial/behind-a-proxy/image01.png">
输入**官方**链接 `/api/v1/docs`,并使用端口 `9999` 访问 API 文档,就能正常运行了!🎉
如果我们通过端口为 `9999` 的代理以“官方”URL `/api/v1/docs` 访问 docs UI它就能正常工作 🎉
输入 <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs 查看文档:</a>
你可以在 <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> 查看:
<img src="/img/tutorial/behind-a-proxy/image02.png">
一切正常。 ✔️
正如我们所希望的那样。 ✔️
这是因为 FastAPI 在 OpenAPI 里使用 `root_path` 提供的 URL 创建默认 `server`。
这是因为 FastAPI 会使用这个 `root_path`用 `root_path` 提供的 URL 在 OpenAPI 中创建默认 `server`。
## 附加的服务器
## 附加的服务器 { #additional-servers }
/// warning | 警告
此用例较难,可以跳过。
这是一个更高级的用例,可以跳过。
///
默认情况下,**FastAPI** 使用 `root_path` 的链接在 OpenAPI 概图中创建 `server`。
默认情况下,**FastAPI** 会在 OpenAPI schema 中使用 `root_path` 的 URL 创建一个 `server`。
但也可以使用其它备选 `servers`,例如,需要同一个 API 文档与 staging 和生产环境交互。
也可以提供其他备选 `servers`,例如,你希望 *同一个* docs UI 同时与 staging 环境和生产环境交互。
如果传递自定义 `servers` 列表,并 `root_path` 因为 API 使用了代理**FastAPI** 会在列表开头使用这个 `root_path` 插入**服务器**
如果你传入了自定义 `servers` 列表,并且存在 `root_path`(因为你的 API 在代理之后**FastAPI** 会在列表开头插入一个使用该 `root_path` 的“server”
例如:
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
这段代码生产如下 OpenAPI 概图
会生成类似下面的 OpenAPI schema
```JSON hl_lines="5-7"
{
"openapi": "3.0.2",
"openapi": "3.1.0",
// More stuff here
"servers": [
{
@@ -328,30 +429,38 @@ $ uvicorn main:app --root-path /api/v1
/// tip | 提示
注意自动生成服务器时,`url` 值 `/api/v1` 提取自 `roog_path`。
注意自动生成的 server`url` 值 `/api/v1`,来自 `root_path`。
///
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs 的 API 文档所示如下:</a>
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> 的 docs UI 中会是这样的:
<img src="/img/tutorial/behind-a-proxy/image03.png">
/// tip | 提示
API 文档与所选的服务器进行交互。
docs UI 会与你选择的 server 进行交互。
///
### 从 `root_path` 禁用自动服务器
/// note | 技术细节
如果不想让 **FastAPI** 包含使用 `root_path` 的自动服务器,则要使用参数 `root_path_in_servers=False`
OpenAPI 规范中的 `servers` 属性是可选的。
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *}
如果你不指定 `servers` 参数,并且 `root_path` 等于 `/`,则生成的 OpenAPI schema 会默认完全省略 `servers` 属性,这等同于只有一个 `url` 值为 `/` 的 server。
这样,就不会在 OpenAPI 概图中包含服务器了。
///
## 挂载子应用
### 从 `root_path` 禁用自动 server { #disable-automatic-server-from-root-path }
需挂载子应用(详见 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}),也要通过 `root_path` 使用代理,这与正常应用一样,别无二致。
果你不想让 **FastAPI** 包含使用 `root_path` 的自动 server可以使用参数 `root_path_in_servers=False`
FastAPI 在内部使用 `root_path`,因此子应用也可以正常运行。✨
{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
然后它就不会在 OpenAPI schema 中包含它。
## 挂载子应用 { #mounting-a-sub-application }
如果你需要挂载子应用(如 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank} 所述),同时也使用带 `root_path` 的代理,你可以像预期一样正常操作。
FastAPI 会在内部智能地使用 `root_path`,所以它会直接工作。 ✨

View File

@@ -1,162 +1,178 @@
# 自定义响应 - HTML文件和其他
# 自定义响应 - HTML文件和其他 { #custom-response-html-stream-file-others }
**FastAPI** 默认会使用 `JSONResponse` 返回响应。
默认情况下,**FastAPI** 会使用 `JSONResponse` 返回响应。
你可以通过直接返回 `Response` 来重载它,参见 [直接返回响应](response-directly.md){.internal-link target=_blank}。
但如果你直接返回 `Response`返回数据不会自动转换,也不会自动生成文档(例如,在 HTTP 头 `Content-Type` 中包含特定的「媒体类型」作为生成的 OpenAPI 的一部分)。
是,如果你直接返回 `Response`(或任何子类,比如 `JSONResponse`,数据不会自动转换(即使你声明了 `response_model`),并且也不会自动生成文档(例如,作为生成的 OpenAPI 的一部分,在 HTTP 头 `Content-Type` 中包含特定的「媒体类型」)。
你还可以在 *路径操作装饰器* 中声明你想用的 `Response`
不过,你也可以在 *路径操作装饰器*通过 `response_class` 参数声明你想要使用的 `Response`(例如任何 `Response` 子类)
你从 *路径操作函数* 中返回的内容将被放`Response` 中。
你从 *路径操作函数* 中返回的内容将被放`Response` 中。
并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或者 `UJSONResponse`时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic `response_model` 自动转换(和过滤)。
并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如 `JSONResponse` `UJSONResponse`情况,你返回的数据将使用你在 *路径操作装饰器* 中声明的任何 Pydantic `response_model` 自动转换(和过滤)。
/// note | 说明
/// note | 注意
如果你使用不带有任何媒体类型的响应类FastAPI 认为你的响应没有任何内容,所以不会在生成的OpenAPI文档中记录响应格式。
如果你使用不带有任何媒体类型的响应类FastAPI 认为你的响应没有任何内容,因此不会在生成的 OpenAPI 文档中记录响应格式。
///
## 使用 `ORJSONResponse`
## 使用 `ORJSONResponse` { #use-orjsonresponse }
例如,如果你需要压榨性能,你可以安装并使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 并将响应设置为 `ORJSONResponse`
例如,如果你压榨性能,你可以安装并使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 并将响应设置为 `ORJSONResponse`
导入你想要使用的 `Response` 类(子类)然后在 *路径操作装饰器* 中声明它。
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
对于大型响应,直接返回 `Response` 比返回字典要快得多。
/// info | 提示
这是因为默认情况下FastAPI 会检查其中的每一项,并使用教程中解释的同一个 [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} 确保它可以被序列化为 JSON。这也正是它允许你返回**任意对象**(例如数据库模型)的原因。
但是,如果你确定你返回的内容**可以用 JSON 序列化**,你可以直接把它传给响应类,避免 FastAPI 在将返回内容传给响应类之前,先通过 `jsonable_encoder` 处理带来的额外开销。
{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
/// info | 信息
参数 `response_class` 也会用来定义响应的「媒体类型」。
在这个例子中HTTP 头 `Content-Type` 会被设置成 `application/json`
在这个例子中HTTP 头 `Content-Type` 会被设置成 `application/json`
并且在 OpenAPI 文档中也会这样记录。
///
/// tip | 小贴士
/// tip | 提示
`ORJSONResponse` 目前只在 FastAPI 中可用,在 Starlette 中不可用。
`ORJSONResponse` 只在 FastAPI 中可用,在 Starlette 中不可用。
///
## HTML 响应
## HTML 响应 { #html-response }
使用 `HTMLResponse`**FastAPI** 直接返回一个 HTML 响应。
**FastAPI** 直接返回一个 HTML 响应,使用 `HTMLResponse`
* 导入 `HTMLResponse`
*`HTMLResponse` 作为你的 *路径操作*`response_class` 参数传入。
*`HTMLResponse` 作为你的 *路径操作装饰器*`response_class` 参数传入。
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *}
/// info | 提示
/// info | 信息
参数 `response_class` 也会用来定义响应的「媒体类型」。
在这个例子中HTTP 头 `Content-Type` 会被设置成 `text/html`
在这个例子中HTTP 头 `Content-Type` 会被设置成 `text/html`
并且在 OpenAPI 文档中也会这样记录。
///
### 返回一个 `Response`
### 返回一个 `Response` { #return-a-response }
正如你在 [直接返回响应](response-directly.md){.internal-link target=_blank} 中了解到的,你也可以通过直接返回响应*路径操作* 中直接重载响应。
正如你在 [直接返回响应](response-directly.md){.internal-link target=_blank} 中到的,你也可以在 *路径操作*通过返回响应来直接重载响应。
和上面一样的例子,返回一个 `HTMLResponse` 看起来可能是这样:
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
/// warning | 警告
*路径操作函数* 直接返回的 `Response` 不会 OpenAPI 的文档记录(比如,`Content-Type` 不会被文档记录),并且在自动交互文档中也不可见
你的 *路径操作函数* 直接返回的 `Response` 不会 OpenAPI 中被文档化(例如,`Content-Type` 不会被记录),并且在自动交互文档中也不可见。
///
/// info | 提示
/// info | 信息
当然,实际的 `Content-Type`状态码等等,将来自于你返回的 `Response` 对象。
当然,实际的 `Content-Type`状态码等将来自于你返回的 `Response` 对象。
///
### OpenAPI 中文档重载 `Response`
### OpenAPI 中文档化并重载 `Response` { #document-in-openapi-and-override-response }
如果你想在函数内重载响应,但是同时在 OpenAPI 中文档化「媒体类型」,你可以使用 `response_class` 参数并返回一个 `Response` 对象。
如果你想在函数内重载响应,同时又要在 OpenAPI 中文档化「媒体类型」,你可以使用 `response_class` 参数并返回一个 `Response` 对象。
接着 `response_class` 参数只会被用来文档化 OpenAPI 的 *路径操作*,你的 `Response`来返回响应
然后 `response_class` 只会用于文档化 OpenAPI 的 *路径操作*你的 `Response` 将按原样使用。
### 直接返回 `HTMLResponse`
#### 直接返回一个 `HTMLResponse` { #return-an-htmlresponse-directly }
比如像这样:
例如,它可能是这样
{* ../../docs_src/custom_response/tutorial004.py hl[7,23,21] *}
{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *}
在这个例子中,函数 `generate_html_response()` 已经生成并返回 `Response` 对象而不是在 `str` 中返回 HTML。
在这个例子中,函数 `generate_html_response()` 已经生成并返回一个 `Response`而不是在 `str` 中返回 HTML。
通过返回函数 `generate_html_response()`调用结果,你已经返回一个重载 **FastAPI** 默认行为的 `Response` 对象,
通过返回调用 `generate_html_response()` 的结果,你已经返回一个重载 **FastAPI** 默认行为的 `Response`
如果你`response_class`传入了 `HTMLResponse`**FastAPI** 会知道如何在 OpenAPI 和交互式文档中使用 `text/html` 将其文档化为 HTML
由于你也`response_class` 中传入了 `HTMLResponse`**FastAPI** 会知道如何在 OpenAPI 和交互式文档中,将其以 `text/html` 为 HTML 来文档化:
<img src="/img/tutorial/custom-response/image01.png">
## 可用响应
## 可用响应 { #available-responses }
这里有一些可用的响应。
要记得你可以使用 `Response` 来返回任何其他东西,甚至创建一个自定义的子类。
记住,你可以使用 `Response` 来返回任何其他东西,甚至创建一个自定义的子类。
/// note | 技术细节
你也可以使用 `from starlette.responses import HTMLResponse`
**FastAPI** 提供了同 `fastapi.responses` 相同的 `starlette.responses` 只是为了方便开发者。但大多数可用的响应都直接来自 Starlette。
**FastAPI** 提供 `fastapi.responses` 相同的 `starlette.responses` 只是为了方便你(开发者。但大多数可用的响应都直接来自 Starlette。
///
### `Response`
### `Response` { #response }
其他全部的响应都继承自主类 `Response`
`Response` 类,其他所有响应都继承自它
你可以直接返回它。
`Response`接受下参数:
接受下参数:
* `content` - 一个 `str` `bytes`
* `content` - 一个 `str``bytes`
* `status_code` - 一个 `int` 类型的 HTTP 状态码。
* `headers` - 一个由字符串组成的 `dict`
* `media_type` - 一个给出媒体类型的 `str`,比`"text/html"`
* `media_type` - 一个给出媒体类型的 `str`。例`"text/html"`
FastAPI实际上是 Starlette自动包含 Content-Length 头。它还将包含一个基于 media_type Content-Type 头,并为文本类型加一个字符集
FastAPI实际上是 Starlette自动包含一个 Content-Length 头。它还基于 `media_type` 包含一个 Content-Type 头,并为文本类型加一个 charset
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
### `HTMLResponse`
### `HTMLResponse` { #htmlresponse }
如上文所述,接受文本或字节并返回 HTML 响应。
### `PlainTextResponse`
### `PlainTextResponse` { #plaintextresponse }
接受文本或字节并返回纯文本响应。
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
### `JSONResponse`
### `JSONResponse` { #jsonresponse }
接受数据并返回一个 `application/json` 编码的响应。
如上文所述,这是 **FastAPI** 中使用的默认响应。
### `ORJSONResponse`
### `ORJSONResponse` { #orjsonresponse }
如上文所述,`ORJSONResponse`一个使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 的快速的可选 JSON 响应。
如上文所述,一个使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 的快速替代 JSON 响应。
/// info | 信息
### `UJSONResponse`
这需要安装 `orjson`,例如使用 `pip install orjson`
`UJSONResponse` 是一个使用 <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a> 的可选 JSON 响应。
///
### `UJSONResponse` { #ujsonresponse }
一个使用 <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a> 的可选 JSON 响应。
/// info | 信息
这需要安装 `ujson`,例如使用 `pip install ujson`
///
/// warning | 警告
@@ -164,55 +180,133 @@ FastAPI实际上是 Starlette将自动包含 Content-Length 的头。它
///
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
/// tip | 小贴士
/// tip | 提示
`ORJSONResponse` 可能是一个更快的选择
`ORJSONResponse` 可能是一个更快的替代方案
///
### `RedirectResponse`
### `RedirectResponse` { #redirectresponse }
返回 HTTP 重定向。默认情况下使用 307 状态码(临时重定向)。
返回 HTTP 重定向。默认使用 307 状态码(Temporary Redirect)。
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
你可以直接返回一个 `RedirectResponse`
### `StreamingResponse`
{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。
---
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
或者你也可以在 `response_class` 参数中使用它:
#### 对类似文件的对象使用 `StreamingResponse`
{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
如果您有类似文件的对象(例如,由 `open()` 返回的对象),则可以在 `StreamingResponse` 中将其返回
如果你这么做,那么你可以直接从你的 *路径操作函数* 返回 URL
包括许多与云存储,视频处理等交互的库
在这种情况下,使用的 `status_code` 会是 `RedirectResponse` 的默认值,即 `307`
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
---
/// tip | 小贴士
你也可以将 `status_code` 参数与 `response_class` 参数组合使用:
注意在这里,因为我们使用的是不支持 `async``await` 的标准 `open()`,我们使用普通的 `def` 声明了路径操作。
{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
### `StreamingResponse` { #streamingresponse }
接受异步生成器或普通生成器/迭代器,并流式传输响应体。
{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
#### 使用 `StreamingResponse` 处理类似文件的对象 { #using-streamingresponse-with-file-like-objects }
如果你有一个 <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">类似文件file-like</a> 的对象(例如,由 `open()` 返回的对象),你可以创建一个生成器函数来遍历那个类似文件的对象。
这样,你不必先把它全部读进内存;你可以把该生成器函数传给 `StreamingResponse` 并返回它。
这包括许多与云存储、视频处理等交互的库。
{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
1. 这是生成器函数。它之所以是「生成器函数」,是因为它内部包含 `yield` 语句。
2. 通过使用 `with` 块,我们可以确保在生成器函数结束后关闭类似文件的对象。因此,在它发送完响应之后会关闭。
3. 这个 `yield from` 告诉函数去遍历名为 `file_like` 的东西。然后,对于遍历到的每一部分,把那一部分作为来自该生成器函数(`iterfile`)的内容 `yield` 出去。
因此,它是一个生成器函数,把「生成」的工作在内部转交给别的东西来完成。
通过这样做,我们可以把它放在一个 `with` 块里,从而确保在完成后关闭类似文件的对象。
/// tip | 提示
注意这里,因为我们使用的是不支持 `async``await` 的标准 `open()`,我们用普通的 `def` 来声明路径操作。
///
### `FileResponse`
### `FileResponse` { #fileresponse }
异步传输文件作为响应。
异步方式将文件作为响应进行流式传输
与其他响应类型相比,接受不同的参数集进行实例化:
与其他响应类型相比,接受一组不同的参数实例化:
* `path` - 要流式传输的文件的文件路径。
* `headers` - 任何自定义响应头,传入字典类型。
* `media_type` - 给出媒体类型的字符串。如果未设置,则文件名或路径将用于推断媒体类型。
* `filename` - 如果给出,它将包含在响应的 `Content-Disposition` 中。
* `headers` - 要包含的任何自定义响应头,字典类型。
* `media_type` - 给出媒体类型的字符串。如果未设置,则会使用文件名或路径推断媒体类型。
* `filename` - 如果设置,它将包含在响应的 `Content-Disposition` 中。
文件响应包含适`Content-Length``Last-Modified``ETag` 的响应头。
文件响应包含适的 `Content-Length``Last-Modified``ETag` 头。
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
## 额外文档
你也可以使用 `response_class` 参数:
您还可以使用 `response` 在 OpenAPI 中声明媒体类型和许多其他详细信息:[OpenAPI 中的额外文档](additional-responses.md){.internal-link target=_blank}。
{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
在这种情况下,你可以直接从你的 *路径操作函数* 返回文件路径。
## 自定义响应类 { #custom-response-class }
你可以创建自己的自定义响应类,继承自 `Response` 并使用它。
例如,假设你想使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>,但想要使用一些在内置 `ORJSONResponse` 类中未使用的自定义设置。
比如你想让它返回带缩进并格式化的 JSON因此你想使用 orjson 选项 `orjson.OPT_INDENT_2`
你可以创建一个 `CustomORJSONResponse`。你需要做的主要事情是创建一个 `Response.render(content)` 方法,它返回 `bytes` 形式的内容:
{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
现在不再返回:
```json
{"message": "Hello World"}
```
...这个响应将会返回:
```json
{
"message": "Hello World"
}
```
当然,你可能会发现比格式化 JSON 更好的方式来利用这一点。 😉
## 默认响应类 { #default-response-class }
在创建一个 **FastAPI** 类实例或一个 `APIRouter` 时,你可以指定默认使用哪种响应类。
定义它的参数是 `default_response_class`
在下面的示例中,**FastAPI** 会在所有 *路径操作* 中默认使用 `ORJSONResponse`,而不是 `JSONResponse`
{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
/// tip | 提示
你仍然可以像之前一样,在 *路径操作* 中重载 `response_class`
///
## 额外文档 { #additional-documentation }
你还可以使用 `responses` 在 OpenAPI 中声明媒体类型和许多其他细节:[OpenAPI 中的额外响应](additional-responses.md){.internal-link target=_blank}。

View File

@@ -1,173 +1,165 @@
# 生命周期事件
# 生命周期事件 { #lifespan-events }
你可以定义在应用**启动**前执行的逻辑(代码)。这意味着在应用**开始接收请求**之前,这些代码只会被执行**一次**
你可以定义在应用**启动**前应该执行的逻辑(代码)。这意味着这些代码将会被执行**一次**,并且在应用**开始接收请求之前**执行
同样地,你可以定义在应用**关闭**时应执行的逻辑。在这种情况下,这段代码将在**处理可能的多请求后**执行**一次**
同样地,你可以定义在应用**关闭**时应执行的逻辑(代码)。在这种情况下,这段代码将会被执行**一次**在处理可能的**许多请求后**执行。
因为这段代码在应用开始接收请求**之前**执行,也会在处理可能的若干请求**之后**执行,它覆盖了整个应用程序的**生命周期**"生命周期"这个词很重要😉)。
因为这段代码在应用**开始**接收请求之前执行,并且在它**完成**处理请求后马上执行,它覆盖了整个应用程序的**生命周期**“lifespan”这个词马上会很重要😉)。
这对于设置你需要在整个应用中使用的**资源**非常有用,这些资源在请求之间**共享**,你可能需要在之后进行**释放**。例如数据库连接池,或加载一个共享的机器学习模型。
这对于设置你需要在整个应用中使用的、在请求之间**共享**的**资源**,以及/或者你之后需要**清理**的资源,非常有用。例如数据库连接池,或加载一个共享的机器学习模型。
## 用例
## 用例 { #use-case }
让我们从一个示例用例开始,看看如何解决
让我们从一个示例**用例**开始,然后看看如何用它来解决。
假设你有几个**机器学习模型**,你想用它们来处理请求。
假设你有一些**机器学习模型**,你想用它们来处理请求。🤖
相同的模型在请求之间是共享的,因此并非每个请求或每个用户各自拥有一个模型。
相同的模型在请求之间是共享的,所以并不是每个请求一个模型,或每个用户一个模型之类的
假设加载模型可能**需要相当长的时间**,因为它必须从**磁盘**读取大量数据。因此你不希望每个请求都加载它
假设加载模型可能**需要相当长的时间**,因为它必须从**磁盘读取大量数据**。所以你不希望每个请求都这么做
你可以在模块/文件的顶加载它,但也意味着即使你只是运行一个简单的自动化测试,它也会**加载模型**这样测试将**慢**,因为它必须在能够独立运行代码的其他部分之前等待模型加载完成。
你可以在模块/文件的顶加载它,但也意味着即使你只是运行一个简单的自动化测试,它也会**加载模型**然后测试会很**慢**,因为它必须等待模型加载完成,才能运行代码中独立的部分
这就是我们要解决的问题——在处理请求前加载模型,但只在应用开始接收请求,而不是代码执行时
这就是我们要解决的问题在处理请求前加载模型,但只在应用开始接收请求之前加载,而不是代码被加载时加载
## 生命周期 lifespan
## 生命周期 { #lifespan }
你可以使用`FastAPI()`应用的`lifespan`参数一个上下文管理器(稍后我将为你展示)来定义**启动**和**关闭**的逻辑。
你可以使用 `FastAPI` 应用的 `lifespan` 参数,以及一个上下文管理器”(我马上会给你展示它是什么)来定义这些 *startup**shutdown* 逻辑。
让我们从一个例子开始,然后详细介绍
让我们从一个例子开始,然后详细看看
我们使`yield`创建了一个异步函数`lifespan()`像这样:
我们用 `yield` 创建了一个异步函数 `lifespan()`像这样:
```Python hl_lines="16 19"
{!../../docs_src/events/tutorial003.py!}
```
{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
这里我们在 `yield` 之前将(虚拟的)模型函数放机器学习模型的字典中,以此模拟加载模型的耗时**启动**操作。这段代码在应用程序**开始处理请求之前**执行,即**启动**期间。
这里我们通过在 `yield` 之前把(假的)模型函数放机器学习模型的字典中,模拟加载模型这种开销较大的 *startup* 操作。这段代码在应用**开始接收请求之前**执行,也就是在 *startup* 期间。
然后,在 `yield` 之后,我们卸载模型。这段代码会在应用程序**完成处理请求后**执行,即在**关闭**之前。这可以释放诸如内存或 GPU 之类的资源。
然后,`yield` 之后,我们卸载模型。这段代码会在应用**完成处理请求后**执行,也就是在 *shutdown* 之前。比如,这可以释放内存或 GPU 资源。
/// tip | 提示
**关闭**事件只会在你停止应用时发。
`shutdown` 会在你**停止**应用时发
可能你需要启动一个新版本,或者你只是你厌倦了运行它。 🤷
也许你需要启动一个新版本,或者你只是你厌倦了运行它。 🤷
///
## 生命周期函数
### 生命周期函数 { #lifespan-function }
首先要注意的是,我们定义了一个带有 `yield` 的异步函数。这与带有 `yield` 的依赖项非常相似。
```Python hl_lines="14-19"
{!../../docs_src/events/tutorial003.py!}
```
{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
这个函数在 `yield`之前的部分,会在应用启动前执行。
函数在 `yield` 之前的第一部分,会在应用启动前执行。
剩下的部分在 `yield` 之后,会在应用完成后执行。
`yield` 之后的部分,会在应用完成后执行。
## 异步上下文管理器
### 异步上下文管理器 { #async-context-manager }
如你所见,这个函数有一个装饰器 `@asynccontextmanager`
如果你检查一下,会看到这个函数被 `@asynccontextmanager` 装饰器修饰
它将函数转化为所谓的“**异步上下文管理器**”。
它将函数转换成一种叫做“**异步上下文管理器**”的东西
```Python hl_lines="1 13"
{!../../docs_src/events/tutorial003.py!}
```
{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
Python 中 **上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用
Python 中**上下文管理器**是你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用
```Python
with open("file.txt") as file:
file.read()
```
Python 的最近几个版本也有了一个**异步上下文管理器**,你可以通过 `async with` 来使用:
Python 的最近几个版本中,也有**异步上下文管理器**。你会用 `async with` 来使用
```Python
async with lifespan(app):
await do_stuff()
```
你可以像上面样创建一个上下文管理器或异步上下文管理器,它的作用是在进入 `with` 块时,执行 `yield` 之前的代码,并且在离开 `with` 块时,执行 `yield` 后面的代码。
当你像上面样创建一个上下文管理器或异步上下文管理器,它在进入 `with` 块之前执行 `yield` 之前的代码,并在退出 `with` 块之后执行 `yield` 之后的代码。
在我们上面的例子里,我们并不是直接使用,而是传给 FastAPI 来供其使用。
在我们上面的代码示例中,我们没有直接使用,而是把它传给 FastAPI,让 FastAPI 来使用
`FastAPI()` 的 `lifespan` 参数接一个**异步上下文管理器**所以我们可以把我们新定义的上下文管理器 `lifespan` 传给它。
`FastAPI` 应用的 `lifespan` 参数接一个**异步上下文管理器**因此我们可以把我们新定义的 `lifespan` 异步上下文管理器传给它。
```Python hl_lines="22"
{!../../docs_src/events/tutorial003.py!}
```
{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
## 替代事件(弃用)
## 替代事件(弃用) { #alternative-events-deprecated }
/// warning | 警告
配置**启动**和**关闭**事件的推荐方法是使用 `FastAPI()` 应用的 `lifespan` 参数,如前所示。如果你提供了一个 `lifespan` 参数,启动(`startup`)和关闭(`shutdown`事件处理器将不再生效。要么使用 `lifespan`,要么配置所有事件,两者不能共用。
处理 *startup**shutdown* 的推荐方式是像上面描述的那样使用 `FastAPI` 应用的 `lifespan` 参数。如果你提供了 `lifespan` 参数,`startup``shutdown` 事件处理器将不再被调用。要么`lifespan`,要么全用事件,不能同时使用。
你可以跳过这一部分。
大概可以跳过这一部分。
///
有一种替代方法可以定义在**启动**和**关闭**期间执行的逻辑。
有一种替代方法,用于定义在 *startup**shutdown* 期间执行的逻辑。
**FastAPI** 支持定义在应用启动前,或应用关闭时执行的事件处理器(函数)
你可以定义事件处理器(函数),它们需要在应用启动前执行,或应用正在关闭时执行。
事件函数可以声明为异步函数(`async def`),也可以声明为普通函数(`def`
这些函数可以`async def` 声明,也可以用普通的 `def` 声明
### `startup` 事件
### `startup` 事件 { #startup-event }
使用 `startup` 事件声明 `app` 启动前运行的函数
要添加一个应该在应用启动前运行的函数,用事件 `"startup"` 来声明它
{* ../../docs_src/events/tutorial001.py hl[8] *}
{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
本例中,`startup` 事件处理器函数为项目数据库(只是**字典**)提供了一些初始值
在这个例子中,`startup` 事件处理器函数会用一些值来初始化名为 “database” 的条目(只是一个 `dict`
**FastAPI** 支持多个事件处理器函数。
你可以添加多个事件处理器函数。
只有所有 `startup` 事件处理器运行完毕,**FastAPI** 应用才开始接收请求。
并且,只有所有 `startup` 事件处理器完成后,你的应用才开始接收请求。
### `shutdown` 事件
### `shutdown` 事件 { #shutdown-event }
使用 `shutdown` 事件声明 `app` 关闭时运行的函数
要添加一个应该在应用关闭时运行的函数,用事件 `"shutdown"` 来声明它
{* ../../docs_src/events/tutorial002.py hl[6] *}
{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
此处,`shutdown` 事件处理器函数在 `log.txt` 中写入一行文本 `Application shutdown`
这里,`shutdown` 事件处理器函数会向文件 `log.txt` 写入一行文本 `"Application shutdown"`
/// info | 说明
/// info | 信息
`open()` 函数中,`mode="a"` 指的是**追加**。因此这行文本会添加文件有内容之后,不会覆盖之前的内容。
`open()` 函数中,`mode="a"` 表示“追加append因此这行文本会添加文件有内容之后,不会覆盖之前的内容。
///
/// tip | 提示
注意,本例使用 Python `open()` 标准函数与文件交互
注意,在这种情况下我们使用了与文件交互的 Python 标准 `open()` 函数
这个函数执行 I/O输入/输出)操作,需要等待内容写磁盘。
因此,这涉及 I/Oinput/output)操作,需要等待内容写磁盘。
但 `open()` 函数不支持使用 `async` 与 `await`。
`open()`使用 `async``await`
因此,声明事件处理函数要使用 `def`,不能使用 `asnyc def`
所以,我们用标准的 `def` 而不是 `async def` 来声明事件处理器函数
///
### `startup` 和 `shutdown` 一起使用
### `startup` 和 `shutdown` 一起使用 { #startup-and-shutdown-together }
启动和关闭的逻辑很可能是连接在一起的,你可能希望启动某个东西然后结束它,获取一个资源然后释放它等等。
你的 *startup**shutdown* 逻辑很可能是有关联的:你可能启动某个东西然后结束它,获取一个资源然后释放它等等。
在不共享逻辑或变量的不同函数中处理这些逻辑比较困难,因为你需要在全局变量中存储值或使用类似的方式
在不共享逻辑或变量的分离函数中实现这些会更困难,因为你需要把值存储在全局变量中或使用类似的技巧
因此,推荐使用 `lifespan`
正因为如此,现在推荐你像上面解释的那样改用 `lifespan`
## 技术细节
## 技术细节 { #technical-details }
只是为好奇提供的技术细节。🤓
只是为好奇的技术宅提供的技术细节。🤓
在底层,这部分是<a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">生命周期协议</a>的一部分,参见 ASGI 技术规范,定义了称为启动(`startup`)和关闭(`shutdown`的事件。
在底层,在 ASGI 技术规范中,这是 <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a> 的一部分,它定义了名为 `startup``shutdown` 的事件。
/// info | 说明
/// info | 信息
有关事件处理器的详情,请参阅 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette 官档 - 事件</a>
你可以在 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette's Lifespan' docs</a> 中阅读更多关于 Starlette `lifespan` 处理器的内容
包括如何处理生命周期状态,这可以用于程序的其他部分
包括如何处理可以在你代码其他区域使用的 lifespan state
///
## 子应用
## 子应用 { #sub-applications }
🚨 **FastAPI** 只会触发主应用中的生命周期事件,不包括[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}中的
🚨 请记住这些生命周期事件startup 和 shutdown只会为主应用执行不会为[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}执行

View File

@@ -1,237 +1,208 @@
# 生成客户端
# 生成 SDKs { #generating-sdks }
因为 **FastAPI** 基于OpenAPI规范的自然您可以使用许多相匹配的工具包括自动生成API文档 (由 Swagger UI 提供)
因为 **FastAPI** 基于 **OpenAPI** 规范,它的 API 可以用一种许多工具都能理解的标准格式来描述
一个不太明显而又特别的优势是你可以为你的API针对不同的**编程语言**来**生成客户端**(有时候被叫做 <abbr title="Software Development Kits">**SDKs**</abbr> )
这使得你可以轻松生成最新的**文档**、多语言的客户端库(<abbr title="Software Development Kits">**SDKs**</abbr>),以及与代码保持同步的**测试**或**自动化工作流**
## OpenAPI 客户端生成
在本指南中,你将学习如何为你的 FastAPI 端生成一个 **TypeScript SDK**
有许多工具可以从**OpenAPI**生成客户端。
## 开源 SDK 生成器 { #open-source-sdk-generators }
一个常见的工具是 <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>。
一个通用的选项是 <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>,它支持**多种编程语言**,并且可以根据你的 OpenAPI 规范生成 SDK
如果您正在开发**前端**,一个非常有趣的替代方案是 <a href="https://github.com/hey-api/openapi-ts" class="external-link" target="_blank">openapi-ts</a>
对于 **TypeScript 客户端**<a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a> 是一个专门为 TypeScript 生态打造的解决方案,能提供更优化的体验
## 生成一个 TypeScript 前端客户端
你还可以在 <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a> 上发现更多 SDK 生成器。
/// tip | 提示
FastAPI 会自动生成 **OpenAPI 3.1** 规范,所以你使用的任何工具都必须支持这个版本。
///
## FastAPI 赞助商提供的 SDK 生成器 { #sdk-generators-from-fastapi-sponsors }
本节重点介绍由赞助 FastAPI 的公司提供的**风险投资支持**或**公司支持**的解决方案。这些产品会在高质量生成的 SDK 之上,提供**额外功能**与**集成**。
通过 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,这些公司帮助确保框架及其**生态系统**保持健康并且**可持续**。
他们的赞助也体现了对 FastAPI **社区**(你)的坚定承诺,表明他们不仅在意提供**优质服务**,也在支持一个**强大且繁荣的框架**——FastAPI。 🙇
例如,你可能会想试试:
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
其中一些解决方案也可能是开源的或提供免费层级,因此你可以在不做财务承诺的情况下试用它们。也有其他商业 SDK 生成器可用,并且可以在网上找到。 🤓
## 创建 TypeScript SDK { #create-a-typescript-sdk }
让我们从一个简单的 FastAPI 应用开始:
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item``ResponseMessage`
请注意,*路径操作* 使用 `Item``ResponseMessage` 这两个模型,来定义它们用于请求载荷和响应载荷的模型
### API 文档
### API 文档 { #api-docs }
如果访问API文档您将看到它具有在请求中发送和在响应中接收数据的**模式(schemas)**
如果访问 `/docs`,你会看到它有用于在请求中发送和在响应中接收数据的 **schemas**
<img src="/img/tutorial/generate-clients/image01.png">
可以看到这些模式,因为它们是用程序中的模型声明的。
可以看到这些 schema,因为它们是在应用中用模型声明的。
些信息可以在应用的 **OpenAPI模式** 被找到然后显示在API文档中通过Swagger UI
些信息在应用的 **OpenAPI schema** 中可用,然后显示在 API 文档中。
OpenAPI中包含的模型里有相同的信息可以用于 **生成客户端代码**
OpenAPI 中包含的、来自模型的这些信息,就可以用来**生成客户端代码**。
### 生成一个TypeScript 客户端
### Hey API { #hey-api }
现在我们有了带模型的应用,我们可以为前端生成客户端代码
一旦我们有了带模型的 FastAPI 应用,就可以使用 Hey API 来生成 TypeScript 客户端。最快的方式是通过 npx
#### 安装 `openapi-ts`
您可以使用以下工具在前端代码中安装 `openapi-ts`:
<div class="termy">
```console
$ npm install @hey-api/openapi-ts --save-dev
---> 100%
```sh
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
```
</div>
这会在 `./src/client` 中生成一个 TypeScript SDK。
#### 生成客户端代码
你可以在他们的网站上了解如何 <a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">安装 `@hey-api/openapi-ts`</a>,以及阅读关于<a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">生成输出</a>的说明。
要生成客户端代码,您可以使用现在将要安装的命令行应用程序 `openapi-ts`
### 使用 SDK { #using-the-sdk }
因为它安装在本地项目中,所以您可能无法直接使用此命令,但您可以将其放在 `package.json` 文件中。
它可能看起来是这样的:
```JSON hl_lines="7"
{
"name": "frontend-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios"
},
"author": "",
"license": "",
"devDependencies": {
"@hey-api/openapi-ts": "^0.27.38",
"typescript": "^4.6.2"
}
}
```
在这里添加 NPM `generate-client` 脚本后,您可以使用以下命令运行它:
<div class="termy">
```console
$ npm run generate-client
frontend-app@1.0.0 generate-client /home/user/code/frontend-app
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios
```
</div>
此命令将在 `./src/client` 中生成代码,并将在其内部使用 `axios`前端HTTP库
### 尝试客户端代码
现在您可以导入并使用客户端代码,它可能看起来像这样,请注意,您可以为这些方法使用自动补全:
现在你可以导入并使用客户端代码。它可能看起来像这样,请注意你会获得方法的自动补全:
<img src="/img/tutorial/generate-clients/image02.png">
您还将自动补全要发送的数据
你也会对要发送的载荷获得自动补全
<img src="/img/tutorial/generate-clients/image03.png">
/// tip
/// tip | 提示
请注意, `name` 和 `price` 的自动补全,是通过其在`Item`模型(FastAPI)中的定义实现的。
注意 `name``price` 的自动补全,它是在 FastAPI 应用的 `Item` 模型中定义的。
///
如果发送的数据字段不符,你会看到编辑器的错误提示:
对于你发送的数据,你会看到行内错误提示
<img src="/img/tutorial/generate-clients/image04.png">
响应(response)对象也有自动补全:
响应对象也有自动补全
<img src="/img/tutorial/generate-clients/image05.png">
## 带标签的 FastAPI 应用
## 带标签的 FastAPI 应用 { #fastapi-app-with-tags }
在许多情况下你的FastAPI应用程序会更复杂,你可能会使用标签来分隔不同组的*路径操作(path operations)*。
在很多情况下,你的 FastAPI 应用会更大,你可能会用标签来分隔不同组的*路径操作*。
例如,可以有一个用 `items` 的部分和另一个用于 `users` 的部分,它们可以用标签来分隔:
例如,可以有一个 **items** 的部分和另一个 **users** 的部分,并且它们可以用标签来分隔:
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
### 生成带标签的 TypeScript 客户端
### 生成带标签的 TypeScript 客户端 { #generate-a-typescript-client-with-tags }
如果您使用标签FastAPI应用生成客户端它通常也会根据标签分割客户端代码。
如果你为使用标签FastAPI 应用生成客户端,它通常也会基于标签来拆分客户端代码。
通过这种方式,您将能够为客户端代码进行正确地排序和分组:
这样你就能让客户端代码中的内容被正确地排序和分组:
<img src="/img/tutorial/generate-clients/image06.png">
在这个例中,有:
在这个例中,有:
* `ItemsService`
* `UsersService`
### 客户端方法名
### 客户端方法名 { #client-method-names }
现在生成的方法名像 `createItemItemsPost` 看起来不太简洁:
现在生成的方法名像 `createItemItemsPost` 看起来不太简洁
```TypeScript
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
```
...这是因为客户端生成器为每个 *路径操作* 使用OpenAPI内部 **操作 ID(operation ID)**。
...这是因为客户端生成器为每个*路径操作*使用 OpenAPI 内部**operation ID**
OpenAPI要求每个操作 ID 在所有 *路径操作* 中都是唯一的,因此 FastAPI 使用**函数名**、**路径**和**HTTP方法/操作**来生成此操作ID因为这样可以确保这些操作 ID 唯一
OpenAPI 要求每个 operation ID 在所有*路径操作*中都是唯一的,因此 FastAPI 使用**函数名**、**路径**和**HTTP 方法/操作**来生成该 operation ID因为这样能确保 operation ID 唯一。
但接下来我会告诉你如何改进。 🤓
## 自定义操作ID和更好的方法名
## 自定义 Operation ID 和更好的方法名 { #custom-operation-ids-and-better-method-names }
可以**修改**这些操作ID的**生成**方式,以使其更简,并客户端中具有**更简的方法名**。
可以**修改**这些 operation ID 的**生成**方式,让它们更简,并客户端有**更简的方法名**。
在这种情况下,必须确保每个操作ID在其他方面是**唯一**的。
在这种情况下,必须用其他方式确保每个 operation ID 是**唯一**的。
例如,可以确保每个*路径操作*都有一个标签,然后根据**标签**和*路径操作***名称**(函数名)来生成操作ID。
例如,可以确保每个*路径操作*都有一个标签,然后基于**标签**和*路径操作***名称**(函数名)来生成 operation ID。
### 自定义生成唯一ID函数
### 自定义生成唯一 ID 的函数 { #custom-generate-unique-id-function }
FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID**,也用于任何所需自定义模型的名称,用于请求或响应
FastAPI 为每个*路径操作*使用一个**唯一 ID**,它用于 **operation ID**,也用于请求或响应中任何所需自定义模型的名称。
你可以自定义该函数。它接一个 `APIRoute` 对象作为输入,并输出一个字符串。
你可以自定义该函数。它接一个 `APIRoute` 并输出一个字符串。
例如,以下是一个示例,它使用第一个标签(你可能只有一个标签)和*路径操作*名称(函数名)。
例如,下面这个示例使用第一个标签(你可能只有一个标签)和*路径操作*名称(函数名)。
然后你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**:
然后你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
### 使用自定义操作ID生成TypeScript客户端
### 使用自定义 Operation ID 生成 TypeScript 客户端 { #generate-a-typescript-client-with-custom-operation-ids }
现在,如果你再次生成客户端,你会发现它具有改的方法名
现在,如果你再次生成客户端,你会看到它具有改的方法名:
<img src="/img/tutorial/generate-clients/image07.png">
如你所见,现在方法名称中只包含标签函数名不再包含URL路径和HTTP操作的信息。
如你所见,现在方法名包含标签以及函数名,不再包含 URL 路径和 HTTP 操作的信息。
### 预处理用于客户端生成器OpenAPI规范
### 客户端生成器预处理 OpenAPI 规范 { #preprocess-the-openapi-specification-for-the-client-generator }
生成的代码仍然存在一些**重复的信息**。
生成的代码仍然一些**重复的信息**。
我们已经知道方法与 **items** 相关,因为它在 `ItemsService` 中(从标签中获取),但方法名中仍然标签名作为前缀。😕
我们已经知道这个方法与 **items** 相关,因为该词在 `ItemsService` 中(来自标签),但方法名中仍然还带着标签名作为前缀。😕
一般情况下对于OpenAPI我们可能仍希望保留它,因为这将确保操作ID是**唯一**。
对于 OpenAPI 总体来说,我们可能仍希望保留它,因为它能确保 operation ID **唯一**
但对于生成的客户端,我们可以在生成客户端之前**修改** OpenAPI 操作ID以使方法名称更加美观和**简洁**。
但对于生成的客户端,我们可以在生成客户端之前**修改** OpenAPI operation ID只是为了让方法名更好看、更**干净**。
我们可以 OpenAPI JSON 下载到一个名为`openapi.json`的文件中,然后使用以下脚本**删除此前缀的标签**
我们可以 OpenAPI JSON 下载到一个 `openapi.json` 文件中,然后用这样的脚本**移除这个带前缀的标签**
{* ../../docs_src/generate_clients/tutorial004.py *}
{* ../../docs_src/generate_clients/tutorial004_py39.py *}
通过这样做操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。
//// tab | Node.js
### 使用预处理的OpenAPI生成TypeScript客户端
现在由于最终结果保存在文件openapi.json中你可以修改 package.json 文件以使用此本地文件,例如:
```JSON hl_lines="7"
{
"name": "frontend-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
},
"author": "",
"license": "",
"devDependencies": {
"@hey-api/openapi-ts": "^0.27.38",
"typescript": "^4.6.2"
}
}
```Javascript
{!> ../../docs_src/generate_clients/tutorial004.js!}
```
生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能:
////
这样operation ID 会从类似 `items-get_items` 重命名为 `get_items`,从而让客户端生成器能生成更简单的方法名。
### 使用预处理后的 OpenAPI 生成 TypeScript 客户端 { #generate-a-typescript-client-with-the-preprocessed-openapi }
由于最终结果现在在一个 `openapi.json` 文件中,你需要更新你的输入位置:
```sh
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
```
生成新的客户端之后,你现在会有**干净的方法名**,以及所有的**自动补全**、**行内错误**等:
<img src="/img/tutorial/generate-clients/image08.png">
## 优点
## 优点 { #benefits }
使用自动生成的客户端时,你将获得以下自动补全功能
使用自动生成的客户端时,你将获得以下内容的**自动补全**
* 方法。
* 请求体中的数据、查询参数等。
* 响应数据
* body 中的请求载荷、查询参数等。
* 响应载荷
你还将获得针对所有内容的错误提示。
你还对所有内容获得**行内错误**提示。
每当你更新后端代码并**重新生成**前端代码时,新的*路径操作*作为方法可用,旧的方法将被删除,并且其他任何更改将反映生成的代码中。 🤓
并且每当你更新后端代码并**重新生成**前端时,任何新的*路径操作*都会作为方法可用,旧的方法会被移除,任何其他更改也会反映生成的代码中。 🤓
这也意味着如果有任何改,它自动**反映**在客户端代码中。如果你**构建**客户端,使用的数据存在**不匹配**时,它报错。
这也意味着如果有任何改,它自动**反映**在客户端代码中。如果你**构建**客户端,使用的数据存在**不匹配**时,它就会报错。
因此,你在开发周期早期**检测到多错误**,而不必等错误在生产环境中最终用户展示,然后尝试调试问题所在。 ✨
因此,你在开发周期非常早期**检测到多错误**,而不必等错误在生产环境中暴露给最终用户,然后再去尝试调试问题所在。 ✨

View File

@@ -1,12 +1,12 @@
# 高级用户指南
# 高级用户指南 { #advanced-user-guide }
## 额外特性
## 额外特性 { #additional-features }
主要的教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 应该足以让你了解 **FastAPI** 的所有主要特性。
你会在接下来的章节中了解到其他的选项、配置以及额外的特性。
你会在接下来的章节中到其他的选项、配置以及额外的特性。
/// tip
/// tip | 提示
接下来的章节**并不一定是**「高级的」。
@@ -14,8 +14,8 @@
///
## 先阅读教程
## 先阅读教程 { #read-the-tutorial-first }
可能仍会用到 **FastAPI** 主教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 中的大多数特性。
仍然可以用主 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 中的知识来使用 **FastAPI** 的大多数特性。
接下来的章节我们认为你已经读过 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank},并且假设你已经知晓其中主要思想。
并且接下来的章节假设你已经读过,并且假设你已经知晓其中主要思想。

View File

@@ -1,4 +1,4 @@
# 高级中间件
# 高级中间件 { #advanced-middleware }
用户指南介绍了如何为应用添加[自定义中间件](../tutorial/middleware.md){.internal-link target=_blank} 。
@@ -6,9 +6,9 @@
本章学习如何使用其它中间件。
## 添加 ASGI 中间件
## 添加 ASGI 中间件 { #adding-asgi-middlewares }
因为 **FastAPI** 基于 Starlette执行 <abbr title="Asynchronous Server Gateway Interface异步服务器网关界面">ASGI</abbr> 规范,所以可以使用任意 ASGI 中间件。
因为 **FastAPI** 基于 Starlette实现 <abbr title="Asynchronous Server Gateway Interface - 异步服务器网关接口">ASGI</abbr> 规范,所以可以使用任意 ASGI 中间件。
中间件不必是专为 FastAPI 或 Starlette 定制的,只要遵循 ASGI 规范即可。
@@ -24,7 +24,7 @@ app = SomeASGIApp()
new_app = UnicornMiddleware(app, some_config="rainbow")
```
但 FastAPI实际上是 Starlette提供了一种更简单的方式内部中间件处理服务器错误的同时,还能让自定义异常处理器正常运作。
但 FastAPI实际上是 Starlette提供了一种更简单的方式确保内部中间件处理服务器错误,并让自定义异常处理器正常运作。
为此,要使用 `app.add_middleware()` (与 CORS 中的示例一样)。
@@ -39,57 +39,59 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
`app.add_middleware()` 的第一个参数是中间件的类,其它参数则是要传递给中间件的参数。
## 集成中间件
## 集成中间件 { #integrated-middlewares }
**FastAPI** 为常见用例提供了一些中间件,下面介绍怎么使用这些中间件。
/// note | 技术细节
/// note | 注意
以下几个示例中也可以使用 `from starlette.middleware.something import SomethingMiddleware`
**FastAPI**`fastapi.middleware` 中提供的中间件只是为了方便开发者使用,但绝大多数可用的中间件都直接继承自 Starlette。
**FastAPI**`fastapi.middleware` 中提供的中间件只是为了方便开发者使用,但绝大多数可用的中间件都直接自 Starlette。
///
## `HTTPSRedirectMiddleware`
## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
强制所有传入请求必须是 `https``wss`
任何传向 `http``ws` 的请求都会被重定向至安全方案。
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *}
## `TrustedHostMiddleware`
## `TrustedHostMiddleware` { #trustedhostmiddleware }
强制所有传入请求都必须正确设置 `Host` 请求头,以防 HTTP 主机头攻击。
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
支持以下参数:
* `allowed_hosts` - 允许的域名(主机名)列表。`*.example.com` 等通配符域名可以匹配子域名,或使用 `allowed_hosts=["*"]` 允许任意主机名,或省略中间件。
* `allowed_hosts` - 允许的域名(主机名)列表。`*.example.com` 等通配符域名可以匹配子域名。要允许任意主机名,可以使用 `allowed_hosts=["*"]` 或省略中间件。
* `www_redirect` - 若设置为 True对允许的主机名的非 www 版本的请求,会被重定向到对应的 www 版本。默认为 `True`
如果传入的请求没有通过验证,则发送 `400` 响应。
## `GZipMiddleware`
## `GZipMiddleware` { #gzipmiddleware }
处理 `Accept-Encoding` 请求头中包含 `gzip` 请求的 GZip 响应。
处理 `Accept-Encoding` 请求头中包含 `"gzip"` 请求的 GZip 响应。
中间件会处理标准响应与流响应。
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
支持以下参数:
* `minimum_size` - 小于最小字节的响应使用 GZip。 默认值是 `500`
* `minimum_size` - 不对小于最小字节大小的响应使用 GZip。默认值是 `500`
* `compresslevel` - 用于 GZip 压缩时的级别。它是一个 1 到 9 的整数。默认值为 `9`。较低的值会让压缩更快但文件更大,而较高的值会让压缩更慢但文件更小。
## 其它中间件
## 其它中间件 { #other-middlewares }
除了上述中间件外FastAPI 还支持其它ASGI 中间件。
还有很多其它 ASGI 中间件。
例如:
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn 的 `ProxyHeadersMiddleware`</a>
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn 的 `ProxyHeadersMiddleware`</a>
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
其它可用中间件详见 <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette 官档 -  中间件</a> 及 <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome 列表</a>。
其它可用中间件详见 <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette中间件文档</a> 及 <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a>。

View File

@@ -1,130 +1,130 @@
# OpenAPI 回调
# OpenAPI 回调 { #openapi-callbacks }
可以创建触发外部 API 请求的*路径操作* API这个外部 API 可以是别人创建的,也可以是由您自己创建的
可以创建一个 API其中某个*路径操作*可以触发对某个由他人创建的*外部 API*的请求(很可能就是那个将会*使用*你 API 的开发者)
API 应用调用外部 API 时的流程叫做**回调**。因为外部开发者编写的软件发送请求至您的 API,然后的 API 要进行回调,并把请求发送至外部 API
当你的 API 应用调用*外部 API*时发生的流程称为“回调”。因为外部开发者编写的软件会向你的 API 发送请求,然后的 API 会*回调*,向某个*外部 API*发送请求(这个外部 API 很可能也是同一个开发者创建的)
此时,我们需要存档外部 API 的*信息*,比如应该有哪些*路径操作*返回什么样的请求体,应该返回哪种响应等。
在这种情况下,你可能想要记录这个外部 API *应该*是什么样的。它应该有哪些*路径操作*期望什么请求体,应该返回什么响应,等等。
## 使用回调的应用
## 回调的应用 { #an-app-with-callbacks }
示例如下
我们来看一个例子
假设要开发一个创建发票的应用。
想象你在开发一个允许创建发票的应用。
发票包括 `id``title`(可选)、`customer``total` 等属性
这些发票会有 `id``title`(可选)、`customer``total`
API 用户 (外部开发者)要在您的 API 内使用 POST 请求创建一发票记录
你的 API 用户(外部开发者)会通过 POST 请求在你的 API 中创建一发票。
(假设)您的 API 将:
然后你的 API 将会(我们假设)
* 把发票发送外部开发者的消费者
* 归集现金
* 把通知发送至 API 用户(外部开发者)
* 通过(从的 API)发送 POST 请求至外部 API (即**回调**)来完成
* 把发票发送外部开发者的某个客户。
* 收款。
* API 用户(外部开发者)发回一条通知。
* 这将通过(从*你的 API*)向该外部开发者提供的某个*外部 API*发送一个 POST 请求来完成(这就是“回调”)。
## 常规 **FastAPI** 应用
## 常规 **FastAPI** 应用 { #the-normal-fastapi-app }
添加回调前,首先看下常规 API 应用是什么样
我们先看看在添加回调前,常规 API 应用是什么样
常规 API 应用包含接收 `Invoice` 请求体的*路径操作*还有包含回调 URL 的查询参数 `callback_url`
它会有一个接收 `Invoice` 请求体的*路径操作*以及一个包含回调 URL 的查询参数 `callback_url`
这部分代码很常规,您对绝大多数代码应该都比较熟悉了:
这部分很常规,你对大多数代码可能都已经很熟悉了:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[10:14,37:54] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
/// tip | 提示
`callback_url` 查询参数使用 Pydantic 的 <a href="https://pydantic-docs.helpmanual.io/usage/types/#urls" class="external-link" target="_blank">URL</a> 类型。
`callback_url` 查询参数使用 Pydantic 的 <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> 类型。
///
此处唯一比较新的内容是*路径操作装饰器*`callbacks=invoices_callback_router.routes` 参数,下文介绍
唯一的新内容是*路径操作装饰器*的参数 `callbacks=invoices_callback_router.routes`。接下来我们会看到这是什么
## 存档回调
## 为回调编写文档 { #documenting-the-callback }
实际的回调代码高度依赖于自己的 API 应用。
实际的回调代码高度依赖于自己的 API 应用。
并且可能每个应用都各不相同
并且可能在不同应用之间差异很大
回调代码可能只有一两行,比如:
可能只有一两行代码,例如:
```Python
callback_url = "https://example.com/api/v1/invoices/events/"
requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
```
但回调最重要的部分可能是,根据 API 要发送给回调请求体的数据等内容,确保您的 API 用户(外部开发者)正确实现*外部 API*。
但回调可能最重要的部分,是确保你的 API 用户(外部开发者)根据*你的 API*在回调请求体中要发送的数据等内容,正确实现*外部 API*。
因此,我们下一步要做的是添加代码,为从 API 接收回调的*外部 API*存档
所以,我们接下来要做的是添加代码,用于记录那个*外部 API*为了接收来自*你的 API*的回调,*应该*是什么样的
部分文档在 `/docs` 下的 Swagger API 文档中显示,并且会告诉外部开发者如何构建*外部 API*。
份文档会显示在你的 API 的 `/docs`Swagger UI并让外部开发者知道如何构建这个*外部 API*。
本例没有实现回调本身(只是一行代码),只文档部分。
这个示例不会实现回调本身(那可能只是一行代码),只实现文档部分。
/// tip | 提示
实际的回调只是 HTTP 请求。
实际的回调只是一个 HTTP 请求。
实现回调时,要使用 <a href="https://www.encode.io/httpx/" class="external-link" target="_blank">HTTPX</a> 或 <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>。
当你自己实现回调时,可以使用类似 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> 或 <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a> 的库
///
## 编写回调文档代码
## 编写回调文档代码 { #write-the-callback-documentation-code }
应用执行这部分代码,只是用它来*记录 外部 API*
这段代码不会在你的应用执行,我们只需要它来*记录*那个*外部 API*应该是什么样的
,您已经知道用 **FastAPI** 创建自动 API 文档有多简单了。
是,你已经知道用 **FastAPI** API 轻松创建自动文档是多么简单了。
我们要使用与存档*外部 API* 相同的知识……通过创建外部 API 实现的*路径操作*的 API 调用的)。
所以我们会用同样的知识来记录*外部 API*应该是什么样的……通过创建外部 API 应该实现的*路径操作*的 API 将会调用的那些)。
/// tip | 提示
编写存档回调的代码时,假设您是*外部开发者*可能会用的上。并且当前在实现的是*外部 API*,不是*您自己的 API*。
编写用于记录回调的代码时,把自己想象成那个*外部开发者*可能会很有帮助。并且当前在实现*外部 API*不是*的 API*。
临时改变(为外部开发者的)视角能让您更清楚该如何放置*外部 API* 响应和请求体的参数与 Pydantic 模型等。
暂时采用这种(*外部开发者*的)视角,可以让你更直观地知道:在那个*外部 API*中,参数应该放在哪里,请求体和响应的 Pydantic 模型应该如何放置,等等。
///
### 创建回调 `APIRouter`
### 创建回调 `APIRouter` { #create-a-callback-apirouter }
首先,新建包含一些用于回调`APIRouter`
首先创建一个新`APIRouter`,它将包含一个或多个回调
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[5,26] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
### 创建回调*路径操作*
### 创建回调*路径操作* { #create-the-callback-path-operation }
创建回调*路径操作*使用之前创建的 `APIRouter`
创建回调*路径操作*使用上面创建的同一个 `APIRouter`
它看起来和常规 FastAPI *路径操作*差不多
它看起来应该就像一个普通的 FastAPI *路径操作*
* 声明要接收的请求体,例如`body: InvoiceEvent`
* 还要声明要返回的响应,例如`response_model=InvoiceEventReceived`
* 它应该声明要接收的请求体,例如 `body: InvoiceEvent`
* 并且它也可以声明要返回的响应,例如 `response_model=InvoiceEventReceived`
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[17:19,22:23,29:33] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
回调*路径操作*与常规*路径操作*有两点主要区别:
与普通*路径操作*相比,有 2 个主要区别:
* 它不需要任何实际代码,因为应用不会调用这段代码。它只用于存档*外部 API*。因此,函数的内容只需要 `pass` 就可以了
* *路径*可以包含 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>详见下文),可以使用带参数的变量,以及发送至您的 API 的原始请求的部分
* 它不需要任何实际代码,因为你的应用永远不会调用这段代码。它只用于记录*外部 API*。所以,这个函数可以只有 `pass`
* *路径*可以包含一个 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>下面会详细介绍可以使用带参数的变量,以及发送到*你的 API*的原始请求的部分内容。
### 回调路径表达式
### 回调路径表达式 { #the-callback-path-expression }
回调*路径*支持包含发送给您的 API 的原始请求的部分的 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>。
回调*路径*可以包含一个 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>,它可以包含发送到*你的 API*的原始请求的部分内容
本例中是**字符串**
在这个例子中,它是 `str`
```Python
"{$callback_url}/invoices/{$request.body.id}"
```
因此,如果的 API 用户(外部开发者)发送请求到您的 API
所以,如果的 API 用户(外部开发者)向*你的 API*发送请求到:
```
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
```
使用如下 JSON 请求体:
使用如下 JSON 请求体:
```JSON
{
@@ -134,13 +134,13 @@ https://yourapi.com/invoices/?callback_url=https://www.external.org/events
}
```
然后,您的 API会处理发票,并在某个点之后,发送回调请求至 `callback_url`(外部 API
那么*你的 API*会处理发票,并在之后的某个时间点,向 `callback_url`*外部 API*)发送回调请求
```
https://www.external.org/events/invoices/2expen51ve
```
JSON 请求体包含如下内容:
JSON 请求体包含类似如下内容:
```JSON
{
@@ -149,7 +149,7 @@ JSON 请求体包含如下内容:
}
```
它会预期*外部 API* 的响应包含如下 JSON 请求体:
并且它会期望那个*外部 API*返回一个包含如下 JSON 请求体的响应
```JSON
{
@@ -159,28 +159,28 @@ JSON 请求体包含如下内容:
/// tip | 提示
注意回调 URL包含 `callback_url` `https://www.external.org/events`中的查询参数,还有 JSON 请求体内部的发票 ID`2expen51ve`)。
注意回调 URL 同时包含了通过 `callback_url` 这个查询参数收到的 URL`https://www.external.org/events`,以及 JSON 请求体的发票 `id``2expen51ve`)。
///
### 添加回调路由
### 添加回调路由 { #add-the-callback-router }
至此,在上创建的回调路由里就包含了*回调路径操作*(外部开发者要在外部 API 中实现)。
此时,你在上创建的回调路由中已经有了所需的*回调路径操作*即那个*外部开发者*应该在*外部 API*中实现的那些)。
现在使用 API *路径操作装饰器*参数 `callbacks`,从回调路由传递属性 `.routes`(实际上只是路由/路径操作的**列表**
现在在*你的 API 路径操作装饰器*中使用参数 `callbacks`,从回调路由中传入属性 `.routes`实际上只是一个路由/*路径操作*的 `list`
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[36] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
/// tip | 提示
注意,不能把路由本身(`invoices_callback_router`传递给 `callback=`,要传递 `invoices_callback_router.routes` 中的 `.routes` 属性
注意,你传给 `callback=` 的不是路由本身(`invoices_callback_router`,而是 `.routes` 属性,即 `invoices_callback_router.routes`
///
### 查看文档
### 查看文档 { #check-the-docs }
现在,使用 Uvicorn 启动应用,打开 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>
现在你可以启动应用并访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>
就能看到文档的*路径操作*已经包含了**回调**的内容以及*外部 API*
你会看到文档中包含你的*路径操作*的 “Callbacks” 部分,展示*外部 API*应该是什么样的
<img src="/img/tutorial/openapi-callbacks/image01.png">

View File

@@ -1,55 +1,55 @@
# OpenAPI 网络钩子
# OpenAPI Webhooks { #openapi-webhooks }
有些情况下,可能想告诉的 API **用户**,您的应用程序可以携带一些数据调用*他们的*应用程序(给它们发送请求),通常是为了**通知**某种**事件**。
有些情况下,可能想告诉的 API **用户**:你的应用可以携带一些数据调用*他们的*应用(发送一个请求),通常是为了**通知**某种类型的**事件**。
这意味着,除了您的用户向的 API 发送请求的一般情况,**的 API**(或的应用)可以向**他们的系统**(他们的 API、他们的应用**发送请求**。
这意味着,与通常由你的用户向的 API 发送请求的流程不同,这里是**的 API**(或的应用)可以向**他们的系统**(他们的 API、他们的应用**发送请求**。
这通常被称为**网络钩子**Webhook
这通常被称为 **webhook**
## 使用网络钩子的步骤
## Webhooks 步骤 { #webhooks-steps }
通常的程是**您**在代码中**定义**发送的消息,**请求的主体**。
通常的程是**在代码中定义**你将发送的消息,也就是**请求体**。
还需要以某种方式定义的应用程序将在**何时**发送这些请求或事件。
还需要以某种方式定义的应用会在**哪些时刻**发送这些请求或事件。
**用户**会以某种方式(例如在某个网页仪表板上)定义的应用程序发送这些请求应该使用的 **URL**
并且,**你的用户**会以某种方式(例如在某个 Web 仪表板上)定义的应用应该将这些请求发送到**URL**
所有关于注册网络钩子的 URL 的**逻辑**以及发送这些请求的实际代码都由决定。可以在**自己的代码**中以任何想要的方式来编写
关于如何注册 webhook 的 URL 的所有**逻辑**以及实际发送这些请求的代码都由决定。可以在**自己的代码**中按你想要的方式来编写。
## 使用 `FastAPI` 和 OpenAPI 文档化网络钩子
## 使用 **FastAPI** 和 OpenAPI 文档化 webhooks { #documenting-webhooks-with-fastapi-and-openapi }
使用 **FastAPI**您可以利用 OpenAPI 来自定义这些网络钩子的名称、的应用可以发送的 HTTP 操作类型(例如 `POST``PUT` 等)以及的应用将发送的**请求体**。
使用 **FastAPI**通过 OpenAPI,你可以定义这些 webhook 的名称、的应用可以发送的 HTTP 操作类型(例如 `POST``PUT` 等)以及的应用将发送的请求**体**。
这能让的用户更轻松地**实现他们的 API** 来接收您的**网络钩子**请求,他们甚至可能能够自动生成一些自己的 API 代码。
这能让的用户更轻松地**实现他们的 API** 来接收你的 **webhook** 请求,他们甚至可能能够自动生成一些他们自己的 API 代码。
/// info
/// info | 信息
网络钩子在 OpenAPI 3.1.0 及以上版本中可用FastAPI `0.99.0` 及以上版本支持。
webhooks 在 OpenAPI 3.1.0 及以上版本中可用FastAPI `0.99.0` 及以上版本支持。
///
## 带有网络钩子的应用程序
## 带有 webhooks 的应用 { #an-app-with-webhooks }
创建一个 **FastAPI** 应用程序时,有一个 `webhooks` 属性可以用来定义网络钩子,方式与定义*路径操作*的时候相同,例如使用 `@app.webhooks.post()`
创建一个 **FastAPI** 应用时,有一个 `webhooks` 属性可用于定义 *webhooks*,方式与定义 *path operations* 相同,例如使用 `@app.webhooks.post()`
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
定义的网络钩子将被包含在 `OpenAPI` 的架构中,并出现在自动生成的**文档 UI** 中。
定义的 webhooks 最终会出现在 **OpenAPI** schema 和自动生成的 **docs UI** 中。
/// info
/// info | 信息
`app.webhooks` 对象实际上只是一个 `APIRouter` ,与在使用多个文件来构建应用程序时所使用的类型相同。
`app.webhooks` 对象实际上只是一个 `APIRouter`,与在使用多个文件来组织应用时会用到的类型相同。
///
注意使用网络钩子时,实际上并没有声明一个*路径*(比如 `/items/` 您传递的文本只是这个网络钩子的**标识符**(事件名称)。例如在 `@app.webhooks.post("new-subscription")` 中,网络钩子的名称是 `new-subscription`
注意使用 webhooks 时,实际上并没有声明一个 *path*(比如 `/items/`你传入的文本只是该 webhook 的**标识符**(事件名称)。例如在 `@app.webhooks.post("new-subscription")` 中,webhook 的名称是 `new-subscription`
这是因为我们预**的用户**会以其他方式(例如通过网页仪表板)来定义他们希望接收网络钩子的请求的实际 **URL 路径**
这是因为我们预**的用户**会以其他方式(例如通过 Web 仪表板)来定义他们希望接收 webhook 请求的实际 **URL path**
### 查看文档
### 查看文档 { #check-the-docs }
现在可以启动的应用程序并访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
现在可以启动的应用并访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>
会看到的文档不仅有正常的*路径操作*显示,现在还多了一些**网络钩子**
会看到的文档不仅有正常的 *path operations*,现在还多了一些 **webhooks**
<img src="/img/tutorial/openapi-webhooks/image01.png">

View File

@@ -1,54 +1,172 @@
# 路径操作的高级配置
# 路径操作的高级配置 { #path-operation-advanced-configuration }
## OpenAPI 的 operationId
## OpenAPI 的 operationId { #openapi-operationid }
/// warning
/// warning | 警告
如果你并非 OpenAPI 的专家,你可能不需要这部分内容。
如果你不是 OpenAPI 的专家,你可能不需要这部分内容。
///
你可以在路径操作中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`
你可以在*路径操作*中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`
务必确保每个操作路径`operation_id` 都是唯一的。
你必须确保每个操作的 `operation_id` 都是唯一的。
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
### 使用 *路径操作函数* 的函数名作为 operationId
### 使用*路径操作函数*的函数名作为 operationId { #using-the-path-operation-function-name-as-the-operationid }
如果你想用你的 API 的函数名作为 `operationId` 的名字,你可以遍历一遍 API 的函数名,然后使用们的 `APIRoute.name` 重写每个 *路径操作* `operation_id`
如果你想用你的 API 的函数名作为 `operationId`,你可以遍历它们,然后使用们的 `APIRoute.name` 覆盖每个*路径操作*`operation_id`
你应该在添加了所有 *路径操作* 之后执行此操作。
你应该在添加了所有*路径操作*之后执行此操作。
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12,13,14,15,16,17,18,19,20,21,24] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
/// tip
/// tip | 提示
如果你手动调用 `app.openapi()`,你应该在此之前更新 `operationId`
///
/// warning
/// warning | 警告
如果你这样做,务必确保你的每个 *路径操作函数* 的名字唯一
如果你这样做,你必须确保你的每个*路径操作函数*都有唯一的名字。
即使它们在不同的模块中Python 文件)。
///
## 从 OpenAPI 中排除
## 从 OpenAPI 中排除 { #exclude-from-openapi }
使用参数 `include_in_schema` 并将其设置为 `False` ,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了)。
要从生成的 OpenAPI schema因此也从自动化文档系统中排除一个*路径操作*,使用参数 `include_in_schema` 并将其设置为 `False`
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
## docstring 高级描述
## docstring 提供高级描述 { #advanced-description-from-docstring }
你可以限制 *路径操作函数*`docstring`用于 OpenAPI 的行数。
你可以限制用于 OpenAPI 的*路径操作函数*的 docstring 行数。
添加一个 `\f` (一个「换页」的转义字符)可以使 **FastAPI**那一位置截断用于 OpenAPI 的输出。
添加一个 `\f`(一个转义的“换页”字符)会让 **FastAPI**此处截断用于 OpenAPI 的输出。
剩余部分不会出现在文档中,但其他工具(如 Sphinx可以使用剩余部分。
它不会显示在文档中,但其他工具(如 Sphinx将能够使用剩余部分。
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19,20,21,22,23,24,25,26,27,28,29] *}
## 额外响应 { #additional-responses }
你可能已经见过如何为一个*路径操作*声明 `response_model``status_code`
这定义了一个*路径操作*的主响应的元数据。
你还可以声明额外的响应,包括它们的模型、状态码等。
文档中有一个完整的章节介绍它,你可以在[OpenAPI 中的额外响应](additional-responses.md){.internal-link target=_blank}阅读。
## OpenAPI Extra { #openapi-extra }
当你在应用中声明一个*路径操作*时,**FastAPI** 会自动生成该*路径操作*相关的元数据,并将其包含在 OpenAPI schema 中。
/// note | 注意
在 OpenAPI 规范中,它被称为 <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>。
///
它包含关于*路径操作*的所有信息,并用于生成自动化文档。
它包括 `tags``parameters``requestBody``responses` 等。
这个针对特定*路径操作*的 OpenAPI schema 通常由 **FastAPI** 自动生成,但你也可以扩展它。
/// tip | 提示
这是一个较底层的扩展点。
如果你只需要声明额外的响应,更方便的方式是使用[OpenAPI 中的额外响应](additional-responses.md){.internal-link target=_blank}。
///
你可以使用参数 `openapi_extra` 来扩展一个*路径操作*的 OpenAPI schema。
### OpenAPI 扩展 { #openapi-extensions }
例如,这个 `openapi_extra` 可以用于声明 [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions)
{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
如果你打开自动 API 文档,你的扩展会显示在对应*路径操作*的底部。
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
如果你查看生成的 OpenAPI在你的 API 中的 `/openapi.json`),你也会看到你的扩展作为该特定*路径操作*的一部分:
```JSON hl_lines="22"
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"x-aperture-labs-portal": "blue"
}
}
}
}
```
### 自定义 OpenAPI *路径操作* schema { #custom-openapi-path-operation-schema }
`openapi_extra` 中的字典会与为该*路径操作*自动生成的 OpenAPI schema 进行深度合并。
因此,你可以向自动生成的 schema 中添加额外数据。
例如,你可以决定用你自己的代码读取并校验请求,而不使用 FastAPI 基于 Pydantic 的自动特性,但你仍然希望在 OpenAPI schema 中定义该请求。
你可以用 `openapi_extra` 来实现:
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
在这个示例中,我们没有声明任何 Pydantic 模型。事实上,请求体甚至不会作为 JSON 被 <abbr title="converted from some plain format, like bytes, into Python objects - 从某种普通格式(例如 bytes转换为 Python 对象">parsed</abbr>,而是直接以 `bytes` 读取,并且函数 `magic_data_reader()` 负责以某种方式解析它。
尽管如此,我们仍然可以为请求体声明预期的 schema。
### 自定义 OpenAPI 内容类型 { #custom-openapi-content-type }
使用同样的技巧,你可以用一个 Pydantic 模型来定义 JSON Schema然后将其包含在该*路径操作*的自定义 OpenAPI schema 部分中。
而且即使请求中的数据类型不是 JSON你也可以这样做。
例如,在这个应用中,我们不使用 FastAPI 的集成功能从 Pydantic 模型中提取 JSON Schema也不使用针对 JSON 的自动校验。实际上,我们声明请求的内容类型为 YAML而不是 JSON
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
尽管如此,虽然我们没有使用默认的集成功能,我们仍然使用一个 Pydantic 模型来手动生成我们希望以 YAML 接收的数据的 JSON Schema。
然后我们直接使用请求,并将 body 提取为 `bytes`。这意味着 FastAPI 甚至不会尝试将请求负载解析为 JSON。
然后在我们的代码中,我们直接解析该 YAML 内容,接着我们再次使用同一个 Pydantic 模型来校验 YAML 内容:
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
/// tip | 提示
这里我们复用了同一个 Pydantic 模型。
同样地,我们也可以用其他方式来校验它。
///

View File

@@ -1,29 +1,31 @@
# 响应 - 更改状态码
# 响应 - 更改状态码 { #response-change-status-code }
你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。
你可能之前已经读到过,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。
但在某些情况下,你需要返回一个不同于默认值的状态码。
## 使用场景
## 使用场景 { #use-case }
例如假设你想默认返回一个HTTP状态码“OK”`200`
例如,假设你想默认返回一个 HTTP 状态码 “OK” `200`
但如果数据不存在你想创建它并返回一个HTTP状态码“CREATED”`201`
但如果数据不存在,你想创建它,并返回一个 HTTP 状态码 “CREATED” `201`
但你仍然希望能够使用`response_model`过滤和转换你返回的数据。
但你仍然希望能够使用 `response_model` 过滤和转换你返回的数据。
对于这些情况,你可以使用一个`Response`参数。
对于这些情况,你可以使用一个 `Response` 参数。
## 使用 `Response` 参数
## 使用 `Response` 参数 { #use-a-response-parameter }
你可以在你的*路径操作函数*中声明一个`Response`类型的参数就像你可以为cookies和头部做的那样)。
你可以在你的*路径操作函数*中声明一个 `Response` 类型的参数(就像你可以为 cookies 和 headers 做的那样)。
然后你可以在这个*临时*响应对象中设置`status_code`
然后你可以在这个*临时*响应对象中设置 `status_code`
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
然后你可以像平常一样返回任何你需要的对象(一个 `dict`一个数据库模型)。
**FastAPI**将使用这个临时响应来提取状态码也包括cookies和头部并将它们放入包含你返回的值的最终响应中该响应由任何`response_model`过滤
如果你声明了一个 `response_model`,它仍然会被用来过滤和转换你返回的对象
你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效
**FastAPI** 将使用这个*临时*响应来提取状态码(也包括 cookies 和 headers并将它们放入包含你返回的值的最终响应中该响应会被任何 `response_model` 过滤
你也可以在依赖项中声明 `Response` 参数,并在其中设置状态码。但请记住,最后设置的那个会生效。

View File

@@ -1,49 +1,51 @@
# 响应Cookies
# 响应 Cookies { #response-cookies }
## 使用 `Response` 参数
## 使用 `Response` 参数 { #use-a-response-parameter }
你可以在 *路径函数*定义一个类型为 `Response`的参数这样你就可以在这个临时响应对象中设置cookie了
你可以在你的 *路径操作函数*声明一个 `Response` 类型的参数。
{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *}
然后你就可以在这个 *临时* 响应对象中设置 cookies。
而且你还可以根据你的需要响应不同的对象,比如常用的 `dict`数据库model等。
{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
如果你定义了 `response_model`,程序会自动根据`response_model`来过滤和转换你响应的对象
然后你可以像平常一样返回任何你需要的对象(`dict`、数据库 model 等)
**FastAPI** 会使用这个 *临时* 响应对象去装在这些cookies信息 (同样还有headers和状态码等信息), 最终会将这些信息和通过`response_model`转化过的数据合并到最终的响应里
如果你声明了 `response_model`,它仍然会用于过滤和转换你返回的对象
你也可以在depend中定义`Response`参数并设置cookie和header
**FastAPI** 会使用这个 *临时* 响应来提取 cookies以及 headers 和 status code并把它们放到最终响应中最终响应包含你返回的值并会被任何 `response_model` 过滤
## 直接响应 `Response`
你也可以在依赖项中声明 `Response` 参数,并在其中设置 cookies以及 headers
你还可以在直接响应`Response`时直接创建cookies。
## 直接返回 `Response` { #return-a-response-directly }
你可以参考[Return a Response Directly](response-directly.md){.internal-link target=_blank}来创建response
可以在代码中直接返回 `Response` 时创建 cookies。
然后设置Cookies并返回
要做到这一点,你可以按 [Return a Response Directly](response-directly.md){.internal-link target=_blank} 中描述的方式创建一个响应。
{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
然后在其中设置 Cookies接着返回它
/// tip
{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
需要注意如果你直接反馈一个response对象而不是使用`Response`入参FastAPI则会直接反馈你封装的response对象。
/// tip | 提示
所以你需要确保你响应数据类型的正确性,如:你可以使用`JSONResponse`来兼容JSON的场景
请记住,如果你直接返回一个响应,而不是使用 `Response` 参数FastAPI 将直接返回它
同时,你也应当仅反馈通过`response_model`过滤过的数据
因此,你必须确保你的数据是正确的类型。例如,如果你返回的是 `JSONResponse`,那么它需要与 JSON 兼容
并且还要确保你没有发送任何本应被 `response_model` 过滤的数据。
///
### 更多信息
### 更多信息 { #more-info }
/// note | 技术细节
你也可以使用`from starlette.responses import Response` `from starlette.responses import JSONResponse`
你也可以使用 `from starlette.responses import Response``from starlette.responses import JSONResponse`
为了方便开发者,**FastAPI** 封装了相同数据类型,如`starlette.responses` `fastapi.responses`不过大部分response对象都是直接引用自Starlette。
**FastAPI** 为了方便你(开发者)提供了与 `starlette.responses` 相同的 `fastapi.responses`但大多数可用的响应都直接来自 Starlette。
因为`Response`对象可以非常便捷的设置headerscookies所以 **FastAPI** 同时也封装了`fastapi.Response`
并且由于 `Response` 经常被用来设置 headerscookies**FastAPI** 也在 `fastapi.Response` 中提供了它
///
如果你想查看所有可用的参数和选项,可以参考 <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette帮助文档</a>
查看所有可用的参数和选项,请查看 <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette 中的文档</a>

View File

@@ -1,66 +1,65 @@
# 直接返回响应
# 直接返回响应 { #return-a-response-directly }
当你创建一个 **FastAPI** *路径操作* 时,你可以正常返回以下任意一种数据:`dict``list`Pydantic 模型数据库模型等
当你创建一个 **FastAPI** *路径操作* 时,你通常可以从中返回任意数据:`dict``list`Pydantic 模型数据库模型等。
**FastAPI** 默认会使用 `jsonable_encoder` 将这些类型的返回值转换成 JSON 格式,`jsonable_encoder` [JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank} 中有阐述
默认情况下,**FastAPI** 会使用 [JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank} 中介绍的 `jsonable_encoder` 自动将该返回值转换为 JSON
然后,**FastAPI** 会在后台这些兼容 JSON 的数据(比如字典)放一个 `JSONResponse` 中,`JSONResponse` 会用来发送响应给客户端。
然后,会在后台这些兼容 JSON 的数据(例如 `dict`)放一个 `JSONResponse` 中,用于将响应发送给客户端。
是你可以在你的 *路径操作* 中直接返回一个 `JSONResponse`
你也可以直接从你的 *路径操作* 返回一个 `JSONResponse`
直接返回响应可能会有用处,比如返回自定义的响应头和 cookies
例如,这在返回自定义 headers 或 cookies 时可能会很有用
## 返回 `Response`
## 返回 `Response` { #return-a-response }
事实上,你可以返回任意 `Response` 或者任意 `Response`子类。
事实上,你可以返回任意 `Response` 或者它的任意子类。
/// tip | 小贴士
/// tip | 提示
`JSONResponse` 本身是一个 `Response` 的子类。
`JSONResponse` 本身是 `Response` 的子类。
///
当你返回一个 `Response` 时,**FastAPI** 会直接传递它。
**FastAPI** 不会用 Pydantic 模型任何数据转换,不会将响应内容转换成任何类型,等等。
不会使用 Pydantic 模型进行任何数据转换,不会内容转换成任何类型等。
种特性给你极大的可扩展性。你可以返回任何数据类型,重写任何数据声明或校验,等等。
给了你很大的灵活性。你可以返回任何数据类型、覆盖任何数据声明或校验等。
## 在 `Response` 中使用 `jsonable_encoder`
## 在 `Response` 中使用 `jsonable_encoder` { #using-the-jsonable-encoder-in-a-response }
由于 **FastAPI** 并未对你返回的 `Response` 做任何改,你必须确保已经准备好响应内容
因为 **FastAPI** 不会对你返回的 `Response` 做任何改,你必须确保它的内容已经准备好。
例如,如果不先将 Pydantic 模型转换为 `dict`,并所有数据类型(如 `datetime``UUID` 等)转换为兼容 JSON 的类型,不能将其放入JSONResponse中。
例如,如果不先将 Pydantic 模型转换为 `dict`,并所有数据类型(如 `datetime``UUID` 等)转换为兼容 JSON 的类型,你就不能将其放入 `JSONResponse` 中。
对于这些情况,在将数据传给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据
对于这些情况,你可以在把数据传给响应之前,使用 `jsonable_encoder` 来转换你的数据
{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
{* ../../docs_src/response_directly/tutorial001.py hl[4,6,20,21] *}
/// note | 技术细节
/// note | 注意 | 技术细节
你也可以使用 `from starlette.responses import JSONResponse`
出于方便,**FastAPI** 会提供与 `starlette.responses` 相同的 `fastapi.responses` 开发者。但大多数可用的响应都直接来自 Starlette。
**FastAPI** 提供与 `starlette.responses` 相同的 `fastapi.responses` 只是为了方便你(开发者。但大多数可用的响应都直接来自 Starlette。
///
## 返回自定义 `Response`
## 返回自定义 `Response` { #returning-a-custom-response }
上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而**FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict`等等
上面的例子展示了需要的所有部分,但还不是很有用,因为你本可以直接返回 `item`,而 **FastAPI** 会默认帮你把它放进 `JSONResponse` 中,将其转换 `dict` 等。默认情况下这些都会自动完成
现在,让我们看看你如何才能返回一个自定义响应。
现在,让我们看看你如何用它来返回自定义响应。
假设你想要返回一个 <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> 响应。
你可以把你的 XML 内容放到一个字符串中,放到一个 `Response`,然后返回
你可以把 XML 内容放到一个字符串中,把它放入 `Response`,然后返回
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
## 说明
## 说明 { #notes }
当你直接返回 `Response` 时,它的数据既没有校验,又不会进行转换(序列化),也不会自动生成文档。
当你直接返回 `Response` 时,它的数据不会被校验、转换(序列化),也不会自动生成文档。
但是你仍可以参考 [OpenApI 中的额外响应](additional-responses.md){.internal-link target=_blank} 给响应编写文档。
但是你仍可以按照 [OpenAPI 中的额外响应](additional-responses.md){.internal-link target=_blank} 中所述为它编写文档。
在后续章节中你可以了解到如何使用/声明这些自定义 `Response` 的同时还保留自动化的数据转换和文档等
你可以在后续章节中看到如何在仍然保留自动数据转换、文档等功能的同时,使用/声明这些自定义 `Response`

View File

@@ -1,39 +1,41 @@
# 响应头
# 响应头 { #response-headers }
## 使用 `Response` 参数
## 使用 `Response` 参数 { #use-a-response-parameter }
你可以在你的*路径操作函数*中声明一个`Response`类型的参数就像你可以为cookies做的那样
你可以在你的*路径操作函数*中声明一个 `Response` 类型的参数(就像你可以为 cookies 做的那样)。
然后你可以在这个*临时*响应对象中设置头部。
{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *}
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
**FastAPI**将使用这个临时响应来提取头部也包括cookies和状态码并将它们放入包含你返回的值的最终响应中该响应由任何`response_model`过滤
然后你可以像平常一样返回任何你需要的对象(一个 `dict`、一个数据库模型等)
你也可以在依赖项中声明`Response`参数并在其中设置头部和cookies
如果你声明了一个 `response_model`,它仍然会被用来过滤和转换你返回的对象
## 直接返回 `Response`
**FastAPI** 将使用这个*临时*响应来提取头部(也包括 cookies 和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何 `response_model` 过滤。
你也可以在直接返回`Response`时添加头部
你也可以在依赖项中声明 `Response` 参数,并在其中设置头部(和 cookies
## 直接返回 `Response` { #return-a-response-directly }
你也可以在直接返回 `Response` 时添加头部。
按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递:
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
/// note | 技术细节
你也可以使用`from starlette.responses import Response``from starlette.responses import JSONResponse`
你也可以使用 `from starlette.responses import Response``from starlette.responses import JSONResponse`
**FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`只是为了方便开发者。但是大多数可用的响应都直接来自Starlette。
**FastAPI** 提供了与 `fastapi.responses` 相同的 `starlette.responses`,只是为了方便你(开发者。但是,大多数可用的响应都直接来自 Starlette。
由于`Response`经常用于设置头部和cookies因此**FastAPI**还在`fastapi.Response`中提供了它。
由于 `Response` 经常用于设置头部和 cookies因此 **FastAPI** 还在 `fastapi.Response` 中提供了它。
///
## 自定义头部
## 自定义头部 { #custom-headers }
注意,可以使用'X-'前缀添加自定义专有头部。
记住,可以<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">使用 `X-` 前缀</a>添加自定义专有头部。
但是如果你有自定义头部你希望浏览器中的客户端能够看到它们你需要将它们添加到你的CORS配置中在[CORS跨源资源共享](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在<a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">StarletteCORS文档</a>中记录的`expose_headers`参数
但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的 CORS 配置中(在[CORS跨源资源共享](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">StarletteCORS 文档</a>中记录的参数 `expose_headers`

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