Sync fastapi docs from 11614be9 on 2026-03-11
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
# 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 | 기술 세부사항
|
||||
일부 텍스트
|
||||
///
|
||||
|
||||
/// 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-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` 섹션을 참고하세요.
|
||||
|
||||
////
|
||||
|
||||
## HTML "dfn" 요소 { #html-dfn-elements }
|
||||
|
||||
* <dfn title="어떤 방식으로든 서로 연결되고 함께 작동하도록 구성된 머신들의 집합입니다.">클러스터</dfn>
|
||||
* <dfn title="입력과 출력 계층 사이에 수많은 은닉 계층을 둔 인공 신경망을 사용하는 머신 러닝 방법으로, 이를 통해 포괄적인 내부 구조를 형성합니다">딥 러닝</dfn>
|
||||
|
||||
## 제목 { #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.)
|
||||
|
||||
* `foo`로서의 `int`
|
||||
* `bar`로서의 `str`
|
||||
* `baz`로서의 `list`
|
||||
|
||||
* 튜토리얼 - 사용자 가이드
|
||||
* 고급 사용자 가이드
|
||||
* 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` 섹션을 참고하세요.
|
||||
|
||||
////
|
||||
@@ -0,0 +1,247 @@
|
||||
# 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_py310.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_py310.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` 등을 포함할 수 있습니다.
|
||||
@@ -1,14 +1,14 @@
|
||||
# 추가 상태 코드 { #additional-status-codes }
|
||||
|
||||
기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 작업(path operation)*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다.
|
||||
기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 처리*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다.
|
||||
|
||||
기본 상태 코드 또는 *경로 작업*에서 설정한 상태 코드를 사용합니다.
|
||||
기본 상태 코드 또는 *경로 처리*에서 설정한 상태 코드를 사용합니다.
|
||||
|
||||
## 추가 상태 코드 { #additional-status-codes_1 }
|
||||
|
||||
기본 상태 코드와 별도로 추가 상태 코드를 반환하려면 `JSONResponse`와 같이 `Response`를 직접 반환하고 추가 상태 코드를 직접 설정할 수 있습니다.
|
||||
|
||||
예를 들어 항목을 업데이트할 수 있는 *경로 작업*이 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다.
|
||||
예를 들어 항목을 업데이트할 수 있는 *경로 처리*가 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다.
|
||||
|
||||
하지만 새로운 항목을 허용하기를 원할 것입니다. 그리고 항목이 이전에 존재하지 않았다면 이를 생성하고 HTTP 상태 코드 201 "Created"를 반환합니다.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
|
||||
|
||||
/// warning | 경고
|
||||
/// warning
|
||||
|
||||
위의 예제처럼 `Response`를 직접 반환하면 바로 반환됩니다.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법
|
||||
|
||||
이를 위해 `__call__` 메서드를 선언합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *}
|
||||
|
||||
이 경우, **FastAPI**는 추가 매개변수와 하위 의존성을 확인하기 위해 `__call__`을 사용하게 되며,
|
||||
나중에 *경로 처리 함수*에서 매개변수에 값을 전달할 때 이를 호출하게 됩니다.
|
||||
@@ -27,7 +27,7 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법
|
||||
|
||||
이제 `__init__`을 사용하여 의존성을 "매개변수화"할 수 있는 인스턴스의 매개변수를 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *}
|
||||
|
||||
이 경우, **FastAPI**는 `__init__`에 전혀 관여하지 않으며, 우리는 이 메서드를 코드에서 직접 사용하게 됩니다.
|
||||
|
||||
@@ -35,7 +35,7 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법
|
||||
|
||||
다음과 같이 이 클래스의 인스턴스를 생성할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *}
|
||||
|
||||
이렇게 하면 `checker.fixed_content` 속성에 `"bar"`라는 값을 담아 의존성을 "매개변수화"할 수 있습니다.
|
||||
|
||||
@@ -51,7 +51,7 @@ checker(q="somequery")
|
||||
|
||||
...그리고 이때 반환되는 값을 *경로 처리 함수*의 의존성 값으로, `fixed_content_included` 매개변수에 전달합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -79,13 +79,13 @@ checker(q="somequery")
|
||||
|
||||
### `yield`와 `scope`가 있는 의존성 { #dependencies-with-yield-and-scope }
|
||||
|
||||
0.121.0 버전에서 FastAPI는 `yield`가 있는 의존성에 대해 `Depends(scope="function")` 지원을 추가했습니다.
|
||||
0.121.0 버전에서 FastAPI는 `Depends(scope="function")` 지원을 추가했습니다.
|
||||
|
||||
`Depends(scope="function")`를 사용하면, `yield` 이후의 종료 코드는 *경로 처리 함수*가 끝난 직후(클라이언트에 응답이 반환되기 전)에 실행됩니다.
|
||||
|
||||
그리고 `Depends(scope="request")`(기본값)를 사용하면, `yield` 이후의 종료 코드는 응답이 전송된 후에 실행됩니다.
|
||||
|
||||
자세한 내용은 [Dependencies with `yield` - Early exit and `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) 문서를 참고하세요.
|
||||
자세한 내용은 [`yield`가 있는 의존성 - 조기 종료와 `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) 문서를 참고하세요.
|
||||
|
||||
### `yield`가 있는 의존성과 `StreamingResponse`, 기술 세부사항 { #dependencies-with-yield-and-streamingresponse-technical-details }
|
||||
|
||||
@@ -133,7 +133,7 @@ SQLModel(또는 SQLAlchemy)을 사용하면서 이런 특정 사용 사례가
|
||||
|
||||
그러면 세션이 데이터베이스 연결을 해제하여, 다른 요청들이 이를 사용할 수 있게 됩니다.
|
||||
|
||||
`yield`가 있는 의존성에서 조기 종료가 필요한 다른 사용 사례가 있다면, 여러분의 구체적인 사용 사례와 `yield`가 있는 의존성에 대한 조기 종료가 어떤 점에서 이득이 되는지를 포함해 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussion Question</a>을 생성해 주세요.
|
||||
`yield`가 있는 의존성에서 조기 종료가 필요한 다른 사용 사례가 있다면, 여러분의 구체적인 사용 사례와 `yield`가 있는 의존성에 대한 조기 종료가 어떤 점에서 이득이 되는지를 포함해 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions 질문</a>을 생성해 주세요.
|
||||
|
||||
`yield`가 있는 의존성에서 조기 종료에 대한 설득력 있는 사용 사례가 있다면, 조기 종료를 선택적으로 활성화할 수 있는 새로운 방법을 추가하는 것을 고려하겠습니다.
|
||||
|
||||
@@ -145,7 +145,7 @@ FastAPI 0.110.0 이전에는 `yield`가 있는 의존성을 사용한 다음 그
|
||||
|
||||
### 백그라운드 태스크와 `yield`가 있는 의존성, 기술 세부사항 { #background-tasks-and-dependencies-with-yield-technical-details }
|
||||
|
||||
FastAPI 0.106.0 이전에는 `yield` 이후에 예외를 발생시키는 것이 불가능했습니다. `yield`가 있는 의존성의 종료 코드는 응답이 전송된 *후에* 실행되었기 때문에, [Exception Handlers](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}가 이미 실행된 뒤였습니다.
|
||||
FastAPI 0.106.0 이전에는 `yield` 이후에 예외를 발생시키는 것이 불가능했습니다. `yield`가 있는 의존성의 종료 코드는 응답이 전송된 *후에* 실행되었기 때문에, [예외 핸들러](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}가 이미 실행된 뒤였습니다.
|
||||
|
||||
이는 주로 백그라운드 태스크 안에서 의존성이 "yield"한 동일한 객체들을 사용할 수 있게 하기 위한 설계였습니다. 백그라운드 태스크가 끝난 뒤에 종료 코드가 실행되었기 때문입니다.
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# 고급 Python 타입 { #advanced-python-types }
|
||||
|
||||
Python 타입을 다룰 때 유용할 수 있는 몇 가지 추가 아이디어를 소개합니다.
|
||||
|
||||
## `Union` 또는 `Optional` 사용 { #using-union-or-optional }
|
||||
|
||||
어떤 이유로 코드에서 `|`를 사용할 수 없다면, 예를 들어 타입 어노테이션이 아니라 `response_model=` 같은 곳이라면, 파이프 문자(`|`) 대신 `typing`의 `Union`을 사용할 수 있습니다.
|
||||
|
||||
예를 들어, 어떤 값이 `str` 또는 `None`이 될 수 있다고 선언할 수 있습니다:
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
|
||||
|
||||
def say_hi(name: Union[str, None]):
|
||||
print(f"Hi {name}!")
|
||||
```
|
||||
|
||||
`typing`에는 `None`이 될 수 있음을 선언하는 축약형으로 `Optional`도 있습니다.
|
||||
|
||||
아주 개인적인 관점에서의 팁입니다:
|
||||
|
||||
- 🚨 `Optional[SomeType]` 사용은 피하세요
|
||||
- 대신 ✨ **`Union[SomeType, None]`를 사용하세요** ✨.
|
||||
|
||||
둘은 동등하며 내부적으로도 같습니다. 하지만 단어 "optional"은 값이 선택 사항이라는 인상을 주는 반면, 실제 의미는 "값이 `None`이 될 수 있다"는 뜻입니다. 값이 선택 사항이 아니라 여전히 필수인 경우에도 그렇습니다.
|
||||
|
||||
`Union[SomeType, None]`가 의미를 더 명확하게 드러낸다고 생각합니다.
|
||||
|
||||
이는 단지 단어와 명칭의 문제입니다. 하지만 이런 단어가 여러분과 팀원이 코드를 어떻게 생각하는지에 영향을 줄 수 있습니다.
|
||||
|
||||
예를 들어, 다음 함수를 보세요:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def say_hi(name: Optional[str]):
|
||||
print(f"Hey {name}!")
|
||||
```
|
||||
|
||||
매개변수 `name`은 `Optional[str]`로 정의되어 있지만, 사실 선택적이지 않습니다. 이 매개변수 없이 함수를 호출할 수 없습니다:
|
||||
|
||||
```Python
|
||||
say_hi() # 이런, 에러가 발생합니다! 😱
|
||||
```
|
||||
|
||||
`name` 매개변수는 기본값이 없기 때문에 여전히 필수입니다(선택적이 아님). 대신, `name`에는 `None`을 전달할 수 있습니다:
|
||||
|
||||
```Python
|
||||
say_hi(name=None) # 작동합니다. None은 유효합니다 🎉
|
||||
```
|
||||
|
||||
좋은 소식은, 대부분의 경우 타입의 합집합을 정의할 때 그냥 `|`를 사용할 수 있다는 점입니다:
|
||||
|
||||
```python
|
||||
def say_hi(name: str | None):
|
||||
print(f"Hey {name}!")
|
||||
```
|
||||
|
||||
그래서 보통은 `Optional`과 `Union` 같은 이름에 대해 걱정하지 않으셔도 됩니다. 😎
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
`main.py` 파일은 다음과 같습니다:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py39/main.py *}
|
||||
{* ../../docs_src/async_tests/app_a_py310/main.py *}
|
||||
|
||||
`test_main.py` 파일에는 `main.py`에 대한 테스트가 있으며, 이제 다음과 같이 보일 수 있습니다:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py *}
|
||||
|
||||
## 실행하기 { #run-it }
|
||||
|
||||
@@ -56,7 +56,7 @@ $ pytest
|
||||
|
||||
`@pytest.mark.anyio` 마커는 pytest에게 이 테스트 함수가 비동기로 호출되어야 한다고 알려줍니다:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -66,7 +66,7 @@ $ pytest
|
||||
|
||||
그 다음 앱으로 `AsyncClient`를 만들고, `await`를 사용해 비동기 요청을 보낼 수 있습니다.
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *}
|
||||
|
||||
이는 다음과 동등합니다:
|
||||
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
# 프록시 뒤에서 실행하기 { #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_py310.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_py310.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_py310.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_py310.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_py310.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_py310.py hl[9] *}
|
||||
|
||||
그러면 OpenAPI 스키마에 포함되지 않습니다.
|
||||
|
||||
## 서브 애플리케이션 마운트하기 { #mounting-a-sub-application }
|
||||
|
||||
프록시에서 `root_path`를 사용하면서도, [서브 애플리케이션 - 마운트](sub-applications.md){.internal-link target=_blank}에 설명된 것처럼 서브 애플리케이션을 마운트해야 한다면, 기대하는 대로 일반적으로 수행할 수 있습니다.
|
||||
|
||||
FastAPI가 내부적으로 `root_path`를 똑똑하게 사용하므로, 그냥 동작합니다. ✨
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
하지만 반환하는 내용이 **JSON으로 직렬화 가능**하다고 확신하는 경우, 해당 내용을 응답 클래스에 직접 전달할 수 있으며, FastAPI가 반환 내용을 `jsonable_encoder`를 통해 처리한 뒤 응답 클래스에 전달하는 오버헤드를 피할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
|
||||
{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
* `HTMLResponse`를 임포트 합니다.
|
||||
* *경로 처리 데코레이터*의 `response_class` 매개변수로 `HTMLResponse`를 전달합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *}
|
||||
{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
위의 예제와 동일하게 `HTMLResponse`를 반환하는 코드는 다음과 같을 수 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
|
||||
{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *}
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
예를 들어, 다음과 같이 작성할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *}
|
||||
{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *}
|
||||
|
||||
이 예제에서, `generate_html_response()` 함수는 HTML을 `str`로 반환하는 대신 이미 `Response`를 생성하고 반환합니다.
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함시킵니다. 또한 `media_type`에 기반하여 Content-Type 헤더를 포함하며, 텍스트 타입의 경우 문자 집합을 추가 합니다.
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
|
||||
{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *}
|
||||
|
||||
### `HTMLResponse` { #htmlresponse }
|
||||
|
||||
@@ -146,7 +146,7 @@ FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함
|
||||
|
||||
텍스트 또는 바이트를 받아 일반 텍스트 응답을 반환합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *}
|
||||
|
||||
### `JSONResponse` { #jsonresponse }
|
||||
|
||||
@@ -180,7 +180,7 @@ FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
|
||||
{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -194,14 +194,14 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
|
||||
|
||||
`RedirectResponse`를 직접 반환할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *}
|
||||
|
||||
---
|
||||
|
||||
또는 `response_class` 매개변수에서 사용할 수도 있습니다:
|
||||
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *}
|
||||
|
||||
이 경우, *경로 처리* 함수에서 URL을 직접 반환할 수 있습니다.
|
||||
|
||||
@@ -211,13 +211,13 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
|
||||
|
||||
`status_code` 매개변수를 `response_class` 매개변수와 함께 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *}
|
||||
|
||||
### `StreamingResponse` { #streamingresponse }
|
||||
|
||||
비동기 제너레이터 또는 일반 제너레이터/이터레이터를 받아 응답 본문을 스트리밍 합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
|
||||
{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *}
|
||||
|
||||
#### 파일과 같은 객체를 사용한 `StreamingResponse` { #using-streamingresponse-with-file-like-objects }
|
||||
|
||||
@@ -227,7 +227,7 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
|
||||
|
||||
이 방식은 클라우드 스토리지, 비디오 처리 등의 다양한 라이브러리와 함께 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
|
||||
{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *}
|
||||
|
||||
1. 이것이 제너레이터 함수입니다. `yield` 문을 포함하고 있으므로 "제너레이터 함수"입니다.
|
||||
2. `with` 블록을 사용함으로써, 제너레이터 함수가 완료된 후 파일과 같은 객체가 닫히도록 합니다. 즉, 응답 전송이 끝난 후 닫힙니다.
|
||||
@@ -256,11 +256,11 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
|
||||
|
||||
파일 응답에는 적절한 `Content-Length`, `Last-Modified`, 및 `ETag` 헤더가 포함됩니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
|
||||
{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *}
|
||||
|
||||
또한 `response_class` 매개변수를 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
|
||||
{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *}
|
||||
|
||||
이 경우, 경로 처리 함수에서 파일 경로를 직접 반환할 수 있습니다.
|
||||
|
||||
@@ -274,7 +274,7 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
|
||||
|
||||
`CustomORJSONResponse`를 생성할 수 있습니다. 여기서 핵심은 `Response.render(content)` 메서드를 생성하여 내용을 `bytes`로 반환하는 것입니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
|
||||
{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *}
|
||||
|
||||
이제 다음 대신:
|
||||
|
||||
@@ -300,7 +300,7 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30
|
||||
|
||||
아래 예제에서 **FastAPI**는 모든 *경로 처리*에서 기본적으로 `JSONResponse` 대신 `ORJSONResponse`를 사용합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
|
||||
{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# 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으로 <dfn title="데이터를 전송 가능한 형식으로 변환하는 것">직렬화</dfn>할 수 있습니다.
|
||||
|
||||
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` 버전부터 사용할 수 있습니다. 🔖
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
`yield`를 사용해 비동기 함수 `lifespan()`을 다음과 같이 생성합니다:
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *}
|
||||
|
||||
여기서는 `yield` 이전에 (가짜) 모델 함수를 머신러닝 모델이 들어 있는 딕셔너리에 넣어 모델을 로드하는 비용이 큰 *시작* 작업을 시뮬레이션합니다. 이 코드는 애플리케이션이 **요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다.
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
먼저 주목할 점은 `yield`를 사용하여 비동기 함수를 정의하고 있다는 것입니다. 이는 `yield`를 사용하는 의존성과 매우 유사합니다.
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *}
|
||||
|
||||
함수의 첫 번째 부분, 즉 `yield` 이전의 코드는 애플리케이션이 시작되기 **전에** 실행됩니다.
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
이는 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환합니다.
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *}
|
||||
|
||||
파이썬에서 **컨텍스트 매니저**는 `with` 문에서 사용할 수 있는 것입니다. 예를 들어, `open()`은 컨텍스트 매니저로 사용할 수 있습니다:
|
||||
|
||||
@@ -82,7 +82,7 @@ async with lifespan(app):
|
||||
|
||||
`FastAPI` 앱의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받으므로, 새 `lifespan` 비동기 컨텍스트 매니저를 전달할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[22] *}
|
||||
|
||||
## 대체 이벤트(사용 중단) { #alternative-events-deprecated }
|
||||
|
||||
@@ -104,7 +104,7 @@ async with lifespan(app):
|
||||
|
||||
애플리케이션이 시작되기 전에 실행되어야 하는 함수를 추가하려면, `"startup"` 이벤트로 선언합니다:
|
||||
|
||||
{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
|
||||
{* ../../docs_src/events/tutorial001_py310.py hl[8] *}
|
||||
|
||||
이 경우, `startup` 이벤트 핸들러 함수는 "database"(그냥 `dict`) 항목을 일부 값으로 초기화합니다.
|
||||
|
||||
@@ -116,7 +116,7 @@ async with lifespan(app):
|
||||
|
||||
애플리케이션이 종료될 때 실행되어야 하는 함수를 추가하려면, `"shutdown"` 이벤트로 선언합니다:
|
||||
|
||||
{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
|
||||
{* ../../docs_src/events/tutorial002_py310.py hl[6] *}
|
||||
|
||||
여기서 `shutdown` 이벤트 핸들러 함수는 텍스트 한 줄 `"Application shutdown"`을 `log.txt` 파일에 기록합니다.
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
# 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_py310.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_py310.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_py310.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_py310.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)**할 경우 오류가 발생합니다.
|
||||
|
||||
따라서 운영 환경에서 최종 사용자에게 오류가 노출된 뒤 문제를 추적하는 대신, 개발 사이클 초기에 **많은 오류를 매우 빨리 감지**할 수 있습니다. ✨
|
||||
@@ -1,4 +1,4 @@
|
||||
# 심화 사용자 안내서 - 도입부 { #advanced-user-guide }
|
||||
# 심화 사용자 안내서 { #advanced-user-guide }
|
||||
|
||||
## 추가 기능 { #additional-features }
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# 고급 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_py310.py hl[2,6] *}
|
||||
|
||||
## `TrustedHostMiddleware` { #trustedhostmiddleware }
|
||||
|
||||
HTTP Host Header 공격을 방어하기 위해, 들어오는 모든 요청에 올바르게 설정된 `Host` 헤더가 있어야 하도록 강제합니다.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial002_py310.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_py310.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>를 확인하세요.
|
||||
@@ -0,0 +1,186 @@
|
||||
# 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">
|
||||
@@ -0,0 +1,55 @@
|
||||
# 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_py310.py hl[9:12,15:20] *}
|
||||
|
||||
여러분이 정의한 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">
|
||||
@@ -0,0 +1,172 @@
|
||||
# 경로 처리 고급 구성 { #path-operation-advanced-configuration }
|
||||
|
||||
## OpenAPI operationId { #openapi-operationid }
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
OpenAPI “전문가”가 아니라면, 아마 이 내용은 필요하지 않을 것입니다.
|
||||
|
||||
///
|
||||
|
||||
매개변수 `operation_id`를 사용해 *경로 처리*에 사용할 OpenAPI `operationId`를 설정할 수 있습니다.
|
||||
|
||||
각 작업마다 고유하도록 보장해야 합니다.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.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_py310.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_py310.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_py310.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_py310.py hl[19:36, 39:40] *}
|
||||
|
||||
이 예시에서는 어떤 Pydantic 모델도 선언하지 않았습니다. 사실 요청 바디는 JSON으로 <dfn title="bytes 같은 일반 형식에서 Python 객체로 변환">파싱</dfn>되지도 않고, `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_py310.py hl[15:20, 22] *}
|
||||
|
||||
그럼에도 기본 통합 기능을 사용하지 않더라도, YAML로 받고자 하는 데이터에 대한 JSON Schema를 수동으로 생성하기 위해 Pydantic 모델을 여전히 사용합니다.
|
||||
|
||||
그 다음 요청을 직접 사용하고, 바디를 `bytes`로 추출합니다. 이는 FastAPI가 요청 페이로드를 JSON으로 파싱하려고 시도조차 하지 않는다는 뜻입니다.
|
||||
|
||||
그리고 코드에서 YAML 콘텐츠를 직접 파싱한 뒤, 다시 같은 Pydantic 모델을 사용해 YAML 콘텐츠를 검증합니다:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
여기서는 같은 Pydantic 모델을 재사용합니다.
|
||||
|
||||
하지만 마찬가지로, 다른 방식으로 검증할 수도 있습니다.
|
||||
|
||||
///
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
그리고 이 *임시* 응답 객체에서 `status_code`를 설정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
|
||||
{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *}
|
||||
|
||||
그리고 평소처럼 필요한 어떤 객체든 반환할 수 있습니다(`dict`, 데이터베이스 모델 등).
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
그런 다음 해당 *임시* 응답 객체에서 쿠키를 설정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
|
||||
{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *}
|
||||
|
||||
그런 다음 일반적으로 하듯이 필요한 어떤 객체든 반환할 수 있습니다(`dict`, 데이터베이스 모델 등).
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
그런 다음 쿠키를 설정하고 반환하면 됩니다:
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
|
||||
{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ Pydantic 모델로 데이터 변환을 수행하지 않으며, 내용을 다른
|
||||
|
||||
XML 내용을 문자열에 넣고, 이를 `Response`에 넣어 반환할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
|
||||
{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *}
|
||||
|
||||
## 참고 사항 { #notes }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
그런 다음, 여러분은 해당 *임시* 응답 객체에서 헤더를 설정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
|
||||
{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *}
|
||||
|
||||
그 후, 일반적으로 사용하듯이 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다.
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
|
||||
`Response`를 직접 반환할 때에도 헤더를 추가할 수 있습니다.
|
||||
|
||||
[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성하고, 헤더를 추가 매개변수로 전달하세요.
|
||||
[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성하고, 헤더를 추가 매개변수로 전달하세요:
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
|
||||
{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *}
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`from starlette.responses import Response`나 `from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 해당 *임시* 응답에서 헤더(쿠키와 상태 코드도 포함)를 추출하여, 여러분이 반환한 값을 포함하는 최종 응답에 `response_model`로 필터링된 값을 넣습니다.
|
||||
**FastAPI**는 개발자인 여러분의 편의를 위해 `starlette.responses`와 동일한 것을 `fastapi.responses`로도 제공합니다. 하지만 사용 가능한 대부분의 응답은 Starlette에서 직접 제공합니다.
|
||||
|
||||
그리고 `Response`는 헤더와 쿠키를 설정하는 데 자주 사용될 수 있으므로, **FastAPI**는 `fastapi.Response`로도 이를 제공합니다.
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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_py310.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_py310.py hl[1,12:24] *}
|
||||
|
||||
이는 다음과 비슷합니다:
|
||||
|
||||
```Python
|
||||
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
|
||||
# 어떤 오류를 반환
|
||||
...
|
||||
```
|
||||
|
||||
하지만 `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_py310.py hl[26:30] *}
|
||||
@@ -0,0 +1,19 @@
|
||||
# 고급 보안 { #advanced-security }
|
||||
|
||||
## 추가 기능 { #additional-features }
|
||||
|
||||
[튜토리얼 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank}에서 다룬 내용 외에도, 보안을 처리하기 위한 몇 가지 추가 기능이 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
다음 섹션들은 **반드시 "고급"이라고 할 수는 없습니다**.
|
||||
|
||||
그리고 사용 사례에 따라, 그중 하나에 해결책이 있을 수도 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 먼저 튜토리얼 읽기 { #read-the-tutorial-first }
|
||||
|
||||
다음 섹션은 주요 [튜토리얼 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank}을 이미 읽었다고 가정합니다.
|
||||
|
||||
모두 동일한 개념을 기반으로 하지만, 몇 가지 추가 기능을 사용할 수 있게 해줍니다.
|
||||
@@ -0,0 +1,274 @@
|
||||
# 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`를 사용할 수도 있습니다.
|
||||
@@ -0,0 +1,302 @@
|
||||
# 설정과 환경 변수 { #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_py310.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_py310.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_py310/config.py *}
|
||||
|
||||
그리고 `main.py` 파일에서 이를 사용합니다:
|
||||
|
||||
{* ../../docs_src/settings/app01_py310/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_py310/config.py hl[10] *}
|
||||
|
||||
이제는 기본 인스턴스 `settings = Settings()`를 생성하지 않는다는 점에 유의하세요.
|
||||
|
||||
### 메인 앱 파일 { #the-main-app-file }
|
||||
|
||||
이제 새로운 `config.Settings()`를 반환하는 의존성을 생성합니다.
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`@lru_cache`는 조금 뒤에 다룹니다.
|
||||
|
||||
지금은 `get_settings()`가 일반 함수라고 가정해도 됩니다.
|
||||
|
||||
///
|
||||
|
||||
그 다음 *경로 처리 함수*에서 이를 의존성으로 요구하고, 필요한 어디서든 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *}
|
||||
|
||||
### 설정과 테스트 { #settings-and-testing }
|
||||
|
||||
그 다음, `get_settings`에 대한 의존성 override를 만들어 테스트 중에 다른 설정 객체를 제공하기가 매우 쉬워집니다:
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/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_py310/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_py310/main.py hl[1,11] *}
|
||||
|
||||
그 다음 요청들에서 의존성으로 `get_settings()`가 다시 호출될 때마다, `get_settings()`의 내부 코드를 실행해서 새 `Settings` 객체를 만드는 대신, 첫 호출에서 반환된 동일한 객체를 계속 반환합니다.
|
||||
|
||||
#### `lru_cache` 기술 세부사항 { #lru-cache-technical-details }
|
||||
|
||||
`@lru_cache`는 데코레이션한 함수가 매번 다시 계산하는 대신, 첫 번째에 반환된 동일한 값을 반환하도록 함수를 수정합니다(즉, 매번 함수 코드를 실행하지 않습니다).
|
||||
|
||||
따라서 아래의 함수는 인자 조합마다 한 번씩 실행됩니다. 그리고 각각의 인자 조합에 대해 반환된 값은, 함수가 정확히 같은 인자 조합으로 호출될 때마다 반복해서 사용됩니다.
|
||||
|
||||
예를 들어 다음 함수가 있다면:
|
||||
|
||||
```Python
|
||||
@lru_cache
|
||||
def say_hi(name: str, salutation: str = "Ms."):
|
||||
return f"Hello {salutation} {name}"
|
||||
```
|
||||
|
||||
프로그램은 다음과 같이 실행될 수 있습니다:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant code as Code
|
||||
participant function as say_hi()
|
||||
participant execute as Execute function
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick", salutation="Mr.")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
```
|
||||
|
||||
우리의 의존성 `get_settings()`의 경우, 함수가 어떤 인자도 받지 않으므로 항상 같은 값을 반환합니다.
|
||||
|
||||
이렇게 하면 거의 전역 변수처럼 동작합니다. 하지만 의존성 함수를 사용하므로 테스트를 위해 쉽게 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할 수 있습니다.
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
먼저, 메인 최상위 **FastAPI** 애플리케이션과 그 *경로 처리*를 생성합니다:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *}
|
||||
|
||||
### 하위 응용프로그램 { #sub-application }
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
이 하위 응용프로그램은 또 다른 표준 FastAPI 애플리케이션이지만, "마운트"될 애플리케이션입니다:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *}
|
||||
|
||||
### 하위 응용프로그램 마운트 { #mount-the-sub-application }
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
이 경우 `/subapi` 경로에 마운트됩니다:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *}
|
||||
|
||||
### 자동 API 문서 확인 { #check-the-automatic-api-docs }
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ $ pip install jinja2
|
||||
* 템플릿을 반환할 *경로 처리*에 `Request` 매개변수를 선언합니다.
|
||||
* 생성한 `templates`를 사용하여 `TemplateResponse`를 렌더링하고 반환합니다. 템플릿의 이름, 요청 객체 및 Jinja2 템플릿 내에서 사용될 키-값 쌍이 포함된 "컨텍스트" 딕셔너리도 전달합니다.
|
||||
|
||||
{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *}
|
||||
{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
테스트에서 `lifespan`을 실행해야 하는 경우, `with` 문과 함께 `TestClient`를 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *}
|
||||
{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *}
|
||||
|
||||
|
||||
["공식 Starlette 문서 사이트에서 테스트에서 라이프스팬 실행하기."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)에 대한 자세한 내용을 더 읽을 수 있습니다.
|
||||
|
||||
더 이상 권장되지 않는 `startup` 및 `shutdown` 이벤트의 경우, 다음과 같이 `TestClient`를 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *}
|
||||
{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
이를 위해 `with` 문에서 `TestClient`를 사용하여 WebSocket에 연결합니다:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *}
|
||||
{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
자세한 내용은 Starlette의 <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">testing WebSockets</a> 문서를 확인하세요.
|
||||
자세한 내용은 Starlette의 <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">WebSocket 테스트</a> 문서를 확인하세요.
|
||||
|
||||
///
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
이를 위해서는 요청에 직접 접근해야 합니다.
|
||||
|
||||
{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *}
|
||||
{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *}
|
||||
|
||||
*경로 처리 함수* 매개변수를 `Request` 타입으로 선언하면 **FastAPI**가 해당 매개변수에 `Request`를 전달하는 것을 알게 됩니다.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ $ pip install websockets
|
||||
|
||||
그리고 백엔드와 WebSockets을 사용해 통신하려면 아마도 프런트엔드의 유틸리티를 사용할 것입니다.
|
||||
|
||||
또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 응용 프로그램을 가질 수도 있습니다.
|
||||
또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 애플리케이션을 가질 수도 있습니다.
|
||||
|
||||
혹은 WebSocket 엔드포인트와 통신할 수 있는 다른 방법이 있을 수도 있습니다.
|
||||
|
||||
@@ -38,13 +38,13 @@ $ pip install websockets
|
||||
|
||||
그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *}
|
||||
{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *}
|
||||
|
||||
## `websocket` 생성하기 { #create-a-websocket }
|
||||
|
||||
**FastAPI** 응용 프로그램에서 `websocket`을 생성합니다:
|
||||
**FastAPI** 애플리케이션에서 `websocket`을 생성합니다:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *}
|
||||
{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *}
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
@@ -58,13 +58,13 @@ $ pip install websockets
|
||||
|
||||
WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *}
|
||||
{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *}
|
||||
|
||||
여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다.
|
||||
|
||||
## 시도해보기 { #try-it }
|
||||
|
||||
파일 이름이 `main.py`라고 가정하고 다음으로 응용 프로그램을 실행합니다:
|
||||
파일 이름이 `main.py`라고 가정하고 다음으로 애플리케이션을 실행합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -86,7 +86,7 @@ $ fastapi dev main.py
|
||||
|
||||
<img src="/img/tutorial/websockets/image02.png">
|
||||
|
||||
그리고 WebSockets가 포함된 **FastAPI** 응용 프로그램이 응답을 돌려줄 것입니다:
|
||||
그리고 WebSockets가 포함된 **FastAPI** 애플리케이션이 응답을 돌려줄 것입니다:
|
||||
|
||||
<img src="/img/tutorial/websockets/image03.png">
|
||||
|
||||
@@ -109,7 +109,7 @@ WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할
|
||||
|
||||
이들은 다른 FastAPI 엔드포인트/*경로 처리*와 동일하게 동작합니다:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
|
||||
{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -121,7 +121,7 @@ WebSocket이기 때문에 `HTTPException`을 발생시키는 것은 적절하지
|
||||
|
||||
### 종속성을 가진 WebSockets 시도해보기 { #try-the-websockets-with-dependencies }
|
||||
|
||||
파일 이름이 `main.py`라고 가정하고 다음으로 응용 프로그램을 실행합니다:
|
||||
파일 이름이 `main.py`라고 가정하고 다음으로 애플리케이션을 실행합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -154,7 +154,7 @@ $ fastapi dev main.py
|
||||
|
||||
WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 그러면 이 예제처럼 이를 잡아 처리할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
|
||||
{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *}
|
||||
|
||||
테스트해보기:
|
||||
|
||||
|
||||
@@ -1,32 +1,48 @@
|
||||
# WSGI 포함하기 - Flask, Django 그 외 { #including-wsgi-flask-django-others }
|
||||
# WSGI 포함하기 - Flask, Django 등 { #including-wsgi-flask-django-others }
|
||||
|
||||
[서브 응용 프로그램 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤편에서](behind-a-proxy.md){.internal-link target=_blank}에서 보았듯이 WSGI 응용 프로그램들을 마운트 할 수 있습니다.
|
||||
[서브 애플리케이션 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤에서](behind-a-proxy.md){.internal-link target=_blank}에서 본 것처럼 WSGI 애플리케이션을 마운트할 수 있습니다.
|
||||
|
||||
이를 위해 `WSGIMiddleware`를 사용해 WSGI 응용 프로그램(예: Flask, Django 등)을 감쌀 수 있습니다.
|
||||
이를 위해 `WSGIMiddleware`를 사용해 WSGI 애플리케이션(예: Flask, Django 등)을 감쌀 수 있습니다.
|
||||
|
||||
## `WSGIMiddleware` 사용하기 { #using-wsgimiddleware }
|
||||
|
||||
`WSGIMiddleware`를 불러와야 합니다.
|
||||
/// info | 정보
|
||||
|
||||
그런 다음, WSGI(예: Flask) 응용 프로그램을 미들웨어로 포장합니다.
|
||||
이를 사용하려면 `a2wsgi`를 설치해야 합니다. 예: `pip install a2wsgi`
|
||||
|
||||
그 후, 해당 경로에 마운트합니다.
|
||||
///
|
||||
|
||||
{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *}
|
||||
`a2wsgi`에서 `WSGIMiddleware`를 import 해야 합니다.
|
||||
|
||||
그런 다음, WSGI(예: Flask) 애플리케이션을 미들웨어로 감쌉니다.
|
||||
|
||||
그리고 해당 경로에 마운트합니다.
|
||||
|
||||
{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이전에 `fastapi.middleware.wsgi`의 `WSGIMiddleware` 사용을 권장했지만 이제는 더 이상 권장되지 않습니다.
|
||||
|
||||
대신 `a2wsgi` 패키지 사용을 권장합니다. 사용 방법은 동일합니다.
|
||||
|
||||
단, `a2wsgi` 패키지가 설치되어 있고 `a2wsgi`에서 `WSGIMiddleware`를 올바르게 import 하는지만 확인하세요.
|
||||
|
||||
///
|
||||
|
||||
## 확인하기 { #check-it }
|
||||
|
||||
이제 `/v1/` 경로에 있는 모든 요청은 Flask 응용 프로그램에서 처리됩니다.
|
||||
이제 `/v1/` 경로에 있는 모든 요청은 Flask 애플리케이션에서 처리됩니다.
|
||||
|
||||
그리고 나머지는 **FastAPI**에 의해 처리됩니다.
|
||||
|
||||
실행하면 <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>으로 이동해서 Flask의 응답을 볼 수 있습니다:
|
||||
실행하고 <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>로 이동하면 Flask의 응답을 볼 수 있습니다:
|
||||
|
||||
```txt
|
||||
Hello, World from Flask!
|
||||
```
|
||||
|
||||
그리고 다음으로 이동하면 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> **FastAPI**의 응답을 볼 수 있습니다:
|
||||
그리고 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>로 이동하면 **FastAPI**의 응답을 볼 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
# 대안, 영감, 비교 { #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 시스템에 필요한 주요 기능 중 하나는 데이터 "<dfn title="마샬링, 변환이라고도 함">직렬화</dfn>"입니다. 이는 코드(Python)에서 데이터를 가져와 네트워크로 전송할 수 있는 형태로 변환하는 것을 의미합니다. 예를 들어 데이터베이스의 데이터를 담은 객체를 JSON 객체로 변환하거나, `datetime` 객체를 문자열로 변환하는 등의 작업입니다.
|
||||
|
||||
API에 또 하나 크게 필요한 기능은 데이터 검증입니다. 특정 파라미터를 기준으로 데이터가 유효한지 확인하는 것입니다. 예를 들어 어떤 필드가 `int`인지, 임의의 문자열이 아닌지 확인하는 식입니다. 이는 특히 들어오는 데이터에 유용합니다.
|
||||
|
||||
데이터 검증 시스템이 없다면, 모든 검사를 코드에서 수동으로 해야 합니다.
|
||||
|
||||
이런 기능들을 제공하기 위해 Marshmallow가 만들어졌습니다. 훌륭한 라이브러리이며, 저도 이전에 많이 사용했습니다.
|
||||
|
||||
하지만 Python type hints가 존재하기 전에 만들어졌습니다. 그래서 각 <dfn title="데이터가 어떻게 구성되어야 하는지에 대한 정의">스키마</dfn>를 정의하려면 Marshmallow가 제공하는 특정 유틸리티와 클래스를 사용해야 합니다.
|
||||
|
||||
/// check | **FastAPI**에 영감을 준 것
|
||||
|
||||
데이터 타입과 검증을 제공하는 "schema"를 코드로 정의하고, 이를 자동으로 활용하기.
|
||||
|
||||
///
|
||||
|
||||
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs }
|
||||
|
||||
API에 필요한 또 다른 큰 기능은 들어오는 요청에서 데이터를 <dfn title="읽어서 Python 데이터로 변환하기">파싱</dfn>하는 것입니다.
|
||||
|
||||
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는 경량 <dfn title="비동기 Python 웹 애플리케이션을 구축하기 위한 새로운 표준">ASGI</dfn> 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} 섹션을 확인하세요.
|
||||
+203
-169
@@ -1,18 +1,18 @@
|
||||
# 동시성과 async / await
|
||||
# 동시성과 async / await { #concurrency-and-async-await }
|
||||
|
||||
*경로 작동 함수*에서의 `async def` 문법에 대한 세부사항과 비동기 코드, 동시성 및 병렬성에 대한 배경
|
||||
*경로 처리 함수*에서의 `async def` 문법에 대한 세부사항과 비동기 코드, 동시성 및 병렬성에 대한 배경
|
||||
|
||||
## 바쁘신 경우
|
||||
## 바쁘신가요? { #in-a-hurry }
|
||||
|
||||
<strong>요약</strong>
|
||||
<abbr title="too long; didn't read - 너무 길어서 읽지 않음"><strong>TL;DR:</strong></abbr>
|
||||
|
||||
다음과 같이 `await`를 사용해 호출하는 제3의 라이브러리를 사용하는 경우:
|
||||
다음과 같이 `await`를 사용해 호출하라고 안내하는 제3자 라이브러리를 사용하는 경우:
|
||||
|
||||
```Python
|
||||
results = await some_library()
|
||||
```
|
||||
|
||||
다음처럼 *경로 작동 함수*를 `async def`를 사용해 선언하십시오:
|
||||
다음처럼 *경로 처리 함수*를 `async def`를 사용해 선언하십시오:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
@@ -29,7 +29,7 @@ async def read_results():
|
||||
|
||||
---
|
||||
|
||||
데이터베이스, API, 파일시스템 등과 의사소통하는 제3의 라이브러리를 사용하고, 그것이 `await`를 지원하지 않는 경우(현재 거의 모든 데이터베이스 라이브러리가 그러합니다), *경로 작동 함수*를 일반적인 `def`를 사용해 선언하십시오:
|
||||
데이터베이스, API, 파일시스템 등과 의사소통하는 제3자 라이브러리를 사용하고, 그것이 `await` 사용을 지원하지 않는 경우(현재 대부분의 데이터베이스 라이브러리가 그러합니다), *경로 처리 함수*를 일반적인 `def`를 사용해 선언하십시오:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
@@ -40,23 +40,23 @@ def results():
|
||||
|
||||
---
|
||||
|
||||
만약 당신의 응용프로그램이 (어째서인지) 다른 무엇과 의사소통하고 그것이 응답하기를 기다릴 필요가 없다면 `async def`를 사용하십시오.
|
||||
만약 여러분의 애플리케이션이 (어째서인지) 다른 어떤 것과도 통신하고 그 응답을 기다릴 필요가 없다면, 내부에서 `await`를 사용할 필요가 없더라도 `async def`를 사용하세요.
|
||||
|
||||
---
|
||||
|
||||
모르겠다면, 그냥 `def`를 사용하십시오.
|
||||
잘 모르겠다면, 일반적인 `def`를 사용하세요.
|
||||
|
||||
---
|
||||
|
||||
**참고**: *경로 작동 함수*에서 필요한만큼 `def`와 `async def`를 혼용할 수 있고, 가장 알맞은 것을 선택해서 정의할 수 있습니다. FastAPI가 자체적으로 알맞은 작업을 수행할 것입니다.
|
||||
**참고**: *경로 처리 함수*에서 필요한 만큼 `def`와 `async def`를 혼용할 수 있으며, 각각에 대해 가장 알맞은 옵션을 선택해 정의하면 됩니다. FastAPI가 올바르게 처리합니다.
|
||||
|
||||
어찌되었든, 상기 어떠한 경우라도, FastAPI는 여전히 비동기적으로 작동하고 매우 빠릅니다.
|
||||
어쨌든 위의 어떤 경우에서도 FastAPI는 여전히 비동기적으로 동작하며 매우 빠릅니다.
|
||||
|
||||
그러나 상기 작업을 수행함으로써 어느 정도의 성능 최적화가 가능합니다.
|
||||
하지만 위의 단계를 따르면, 몇 가지 성능 최적화를 할 수 있습니다.
|
||||
|
||||
## 기술적 세부사항
|
||||
## 기술적 세부사항 { #technical-details }
|
||||
|
||||
최신 파이썬 버전은 `async`와 `await` 문법과 함께 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 지원합니다.
|
||||
최신 파이썬 버전은 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 **`async` 및 `await`** 문법과 함께 지원합니다.
|
||||
|
||||
아래 섹션들에서 해당 문장을 부분별로 살펴보겠습니다:
|
||||
|
||||
@@ -64,251 +64,283 @@ def results():
|
||||
* **`async`와 `await`**
|
||||
* **코루틴**
|
||||
|
||||
## 비동기 코드
|
||||
## 비동기 코드 { #asynchronous-code }
|
||||
|
||||
비동기 코드란 언어 💬 가 코드의 어느 한 부분에서, 컴퓨터 / 프로그램🤖에게 *다른 무언가*가 어딘가에서 끝날 때까지 기다려야한다고 말하는 방식입니다. *다른 무언가*가 “느린-파일" 📝 이라고 불린다고 가정해봅시다.
|
||||
비동기 코드는 언어 💬 가 코드의 어느 한 부분에서 컴퓨터/프로그램 🤖 에게, 어느 시점에는 어딘가에서 *다른 무언가*가 끝날 때까지 기다려야 한다고 말할 수 있는 방법이 있다는 의미입니다. 그 *다른 무언가*를 "slow-file" 📝 이라고 해보겠습니다.
|
||||
|
||||
따라서 “느린-파일” 📝이 끝날때까지 컴퓨터는 다른 작업을 수행할 수 있습니다.
|
||||
따라서 그 시간 동안 컴퓨터는 "slow-file" 📝 이 끝나는 동안 다른 작업을 하러 갈 수 있습니다.
|
||||
|
||||
그 다음 컴퓨터 / 프로그램 🤖 은 다시 기다리고 있기 때문에 기회가 있을 때마다 다시 돌아오거나, 혹은 당시에 수행해야하는 작업들이 완료될 때마다 다시 돌아옵니다. 그리고 그것 🤖 은 기다리고 있던 작업 중 어느 것이 이미 완료되었는지, 그것 🤖 이 해야하는 모든 작업을 수행하면서 확인합니다.
|
||||
그 다음 컴퓨터/프로그램 🤖 은 다시 기다리는 중이기 때문에 기회가 있을 때마다 돌아오거나, 혹은 그 시점에 해야 할 작업을 모두 끝낼 때마다 돌아옵니다. 그리고 기다리던 작업 중 이미 끝난 것이 있는지 확인하면서, 해야 했던 작업을 수행합니다.
|
||||
|
||||
다음으로, 그것 🤖 은 완료할 첫번째 작업에 착수하고(우리의 "느린-파일" 📝 이라고 가정합시다) 그에 대해 수행해야하는 작업을 계속합니다.
|
||||
다음으로, 완료된 첫 번째 작업(우리의 "slow-file" 📝 이라고 해보겠습니다)을 가져와서, 그에 대해 해야 했던 작업을 계속합니다.
|
||||
|
||||
"다른 무언가를 기다리는 것"은 일반적으로 비교적 "느린" (프로세서와 RAM 메모리 속도에 비해) <abbr title="Input and Output">I/O</abbr> 작업을 의미합니다. 예를 들면 다음의 것들을 기다리는 것입니다:
|
||||
이 "다른 무언가를 기다리는 것"은 일반적으로 프로세서와 RAM 메모리 속도에 비해 상대적으로 "느린" <abbr title="Input and Output - 입력/출력">I/O</abbr> 작업을 의미합니다. 예를 들어 다음을 기다리는 것입니다:
|
||||
|
||||
* 네트워크를 통해 클라이언트로부터 전송되는 데이터
|
||||
* 네트워크를 통해 클라이언트가 수신할, 당신의 프로그램으로부터 전송되는 데이터
|
||||
* 시스템이 읽고 프로그램에 전달할 디스크 내의 파일 내용
|
||||
* 당신의 프로그램이 시스템에 전달하는, 디스크에 작성될 내용
|
||||
* 네트워크를 통해 클라이언트가 데이터를 보내는 것
|
||||
* 네트워크를 통해 클라이언트가 여러분의 프로그램이 보낸 데이터를 받는 것
|
||||
* 시스템이 디스크의 파일 내용을 읽어서 프로그램에 전달하는 것
|
||||
* 프로그램이 시스템에 전달한 내용을 디스크에 쓰는 것
|
||||
* 원격 API 작업
|
||||
* 완료될 데이터베이스 작업
|
||||
* 결과를 반환하는 데이터베이스 쿼리
|
||||
* 기타
|
||||
* 데이터베이스 작업이 완료되는 것
|
||||
* 데이터베이스 쿼리가 결과를 반환하는 것
|
||||
* 기타 등등
|
||||
|
||||
수행 시간의 대부분이 <abbr title="Input and Output">I/O</abbr> 작업을 기다리는데에 소요되기 때문에, "I/O에 묶인" 작업이라고 불립니다.
|
||||
실행 시간의 대부분이 <abbr title="Input and Output - 입력/출력">I/O</abbr> 작업을 기다리는 데 소비되기 때문에, 이를 "I/O bound" 작업이라고 부릅니다.
|
||||
|
||||
이것은 "비동기"라고 불리는데 컴퓨터 / 프로그램이 작업 결과를 가지고 일을 수행할 수 있도록, 느린 작업에 "동기화"되어 아무것도 하지 않으면서 작업이 완료될 정확한 시점만을 기다릴 필요가 없기 때문입니다.
|
||||
이것은 컴퓨터/프로그램이 느린 작업과 "동기화"되어, 아무것도 하지 않은 채 그 작업이 끝나는 정확한 시점만 기다렸다가 결과를 가져와 일을 계속할 필요가 없기 때문에 "비동기"라고 불립니다.
|
||||
|
||||
이 대신에, "비동기" 시스템에서는, 작업은 일단 완료되면, 컴퓨터 / 프로그램이 수행하고 있는 일을 완료하고 이후 다시 돌아와서 그것의 결과를 받아 이를 사용해 작업을 지속할 때까지 잠시 (몇 마이크로초) 대기할 수 있습니다.
|
||||
대신 "비동기" 시스템에서는, 작업이 끝나면 컴퓨터/프로그램이 하러 갔던 일을 마칠 때까지 잠시(몇 마이크로초) 줄에서 기다렸다가, 다시 돌아와 결과를 받아 이를 사용해 작업을 계속할 수 있습니다.
|
||||
|
||||
"동기"("비동기"의 반대)는 컴퓨터 / 프로그램이 상이한 작업들간 전환을 하기 전에 그것이 대기를 동반하게 될지라도 모든 순서를 따르기 때문에 "순차"라는 용어로도 흔히 불립니다.
|
||||
"동기"(“비동기”의 반대)는 보통 "순차"라는 용어로도 불리는데, 컴퓨터/프로그램이 다른 작업으로 전환하기 전에 모든 단계를 순서대로 따르기 때문이며, 그 단계들에 기다림이 포함되어 있더라도 마찬가지입니다.
|
||||
|
||||
### 동시성과 버거
|
||||
### 동시성과 햄버거 { #concurrency-and-burgers }
|
||||
|
||||
위에서 설명한 **비동기** 코드에 대한 개념은 종종 **"동시성"**이라고도 불립니다. 이것은 **"병렬성"**과는 다릅니다.
|
||||
위에서 설명한 **비동기** 코드에 대한 개념은 때때로 **"동시성"**이라고도 불립니다. 이는 **"병렬성"**과는 다릅니다.
|
||||
|
||||
**동시성**과 **병렬성**은 모두 "동시에 일어나는 서로 다른 일들"과 관련이 있습니다.
|
||||
**동시성**과 **병렬성**은 모두 "대략 같은 시간에 일어나는 서로 다른 일들"과 관련이 있습니다.
|
||||
|
||||
하지만 *동시성*과 *병렬성*의 세부적인 개념에는 꽤 차이가 있습니다.
|
||||
|
||||
차이를 확인하기 위해, 다음의 버거에 대한 이야기를 상상해보십시오:
|
||||
차이를 보기 위해, 다음의 햄버거 이야기를 상상해보세요:
|
||||
|
||||
### 동시 버거
|
||||
### 동시 햄버거 { #concurrent-burgers }
|
||||
|
||||
당신은 짝사랑 상대 😍 와 패스트푸드 🍔 를 먹으러 갔습니다. 당신은 점원 💁 이 당신 앞에 있는 사람들의 주문을 받을 동안 줄을 서서 기다리고 있습니다.
|
||||
여러분은 짝사랑 상대와 패스트푸드를 먹으러 갔고, 점원이 여러분 앞 사람들의 주문을 받는 동안 줄을 서서 기다립니다. 😍
|
||||
|
||||
이제 당신의 순서가 되어서, 당신은 당신과 짝사랑 상대 😍 를 위한 두 개의 고급스러운 버거 🍔 를 주문합니다.
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration">
|
||||
|
||||
당신이 돈을 냅니다 💸.
|
||||
이제 여러분 차례가 되어, 여러분과 짝사랑 상대를 위해 매우 고급스러운 햄버거 2개를 주문합니다. 🍔🍔
|
||||
|
||||
점원 💁 은 주방 👨🍳 에 요리를 하라고 전달하고, 따라서 그들은 당신의 버거 🍔 를 준비해야한다는 사실을 알게됩니다(그들이 지금은 당신 앞 고객들의 주문을 준비하고 있을지라도 말입니다).
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration">
|
||||
|
||||
점원 💁 은 당신의 순서가 적힌 번호표를 줍니다.
|
||||
점원은 주방의 요리사에게 무언가를 말해, (지금은 앞선 손님들의 주문을 준비하고 있더라도) 여러분의 햄버거를 준비해야 한다는 것을 알게 합니다.
|
||||
|
||||
기다리는 동안, 당신은 짝사랑 상대 😍 와 함께 테이블을 고르고, 자리에 앉아 오랫동안 (당신이 주문한 버거는 꽤나 고급스럽기 때문에 준비하는데 시간이 조금 걸립니다 ✨🍔✨) 대화를 나눕니다.
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration">
|
||||
|
||||
짝사랑 상대 😍 와 테이블에 앉아서 버거 🍔 를 기다리는 동안, 그 사람 😍 이 얼마나 멋지고, 사랑스럽고, 똑똑한지 감탄하며 시간을 보냅니다 ✨😍✨.
|
||||
여러분이 돈을 냅니다. 💸
|
||||
|
||||
짝사랑 상대 😍 와 기다리면서 얘기하는 동안, 때때로, 당신은 당신의 차례가 되었는지 보기 위해 카운터의 번호를 확인합니다.
|
||||
점원은 여러분 차례 번호를 줍니다.
|
||||
|
||||
그러다 어느 순간, 당신의 차례가 됩니다. 카운터에 가서, 버거 🍔 를 받고, 테이블로 다시 돌아옵니다.
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration">
|
||||
|
||||
당신과 짝사랑 상대 😍 는 버거 🍔 를 먹으며 좋은 시간을 보냅니다 ✨.
|
||||
기다리는 동안, 여러분은 짝사랑 상대와 함께 자리를 고르고 앉아 오랫동안 대화를 나눕니다(여러분의 햄버거는 매우 고급스럽기 때문에 준비하는 데 시간이 좀 걸립니다).
|
||||
|
||||
짝사랑 상대와 테이블에 앉아 햄버거를 기다리는 동안, 그 사람이 얼마나 멋지고 귀엽고 똑똑한지 감탄하며 시간을 보낼 수 있습니다 ✨😍✨.
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration">
|
||||
|
||||
기다리며 대화하는 동안, 때때로 여러분은 카운터에 표시되는 번호를 확인해 여러분 차례인지 봅니다.
|
||||
|
||||
그러다 어느 순간 마침내 여러분 차례가 됩니다. 여러분은 카운터에 가서 햄버거를 받고, 테이블로 돌아옵니다.
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration">
|
||||
|
||||
여러분과 짝사랑 상대는 햄버거를 먹으며 좋은 시간을 보냅니다. ✨
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
|
||||
|
||||
/// info | 정보
|
||||
|
||||
아름다운 일러스트: <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
당신이 이 이야기에서 컴퓨터 / 프로그램 🤖 이라고 상상해보십시오.
|
||||
이 이야기에서 여러분이 컴퓨터/프로그램 🤖 이라고 상상해보세요.
|
||||
|
||||
줄을 서서 기다리는 동안, 당신은 아무것도 하지 않고 😴 당신의 차례를 기다리며, 어떠한 "생산적인" 일도 하지 않습니다. 하지만 점원 💁 이 (음식을 준비하지는 않고) 주문을 받기만 하기 때문에 줄이 빨리 줄어들어서 괜찮습니다.
|
||||
줄을 서 있는 동안, 여러분은 그냥 쉬고 😴, 차례를 기다리며, 그다지 "생산적인" 일을 하지 않습니다. 하지만 점원은 주문만 받지(음식을 준비하진 않기) 때문에 줄이 빠르게 줄어들어 괜찮습니다.
|
||||
|
||||
그다음, 당신이 차례가 오면, 당신은 실제로 "생산적인" 일 🤓 을 합니다. 당신은 메뉴를 보고, 무엇을 먹을지 결정하고, 짝사랑 상대 😍 의 선택을 묻고, 돈을 내고 💸 , 맞는 카드를 냈는지 확인하고, 비용이 제대로 지불되었는지 확인하고, 주문이 제대로 들어갔는지 확인을 하는 작업 등등을 수행합니다.
|
||||
그 다음 여러분 차례가 되면, 여러분은 실제로 "생산적인" 일을 합니다. 메뉴를 처리하고, 무엇을 먹을지 결정하고, 짝사랑 상대의 선택을 확인하고, 결제하고, 올바른 현금이나 카드를 냈는지 확인하고, 정확히 청구되었는지 확인하고, 주문에 올바른 항목들이 들어갔는지 확인하는 등등을 합니다.
|
||||
|
||||
하지만 이후에는, 버거 🍔 를 아직 받지 못했음에도, 버거가 준비될 때까지 기다려야 🕙 하기 때문에 점원 💁 과의 작업은 "일시정지" ⏸ 상태입니다.
|
||||
하지만 그 다음에는, 아직 햄버거를 받지 못했더라도, 햄버거가 준비될 때까지 기다려야 🕙 하므로 점원과의 작업은 "일시정지" ⏸ 상태입니다.
|
||||
|
||||
하지만 번호표를 받고 카운터에서 나와 테이블에 앉으면, 당신은 짝사랑 상대 😍 와 그 "작업" ⏯ 🤓 에 번갈아가며 🔀 집중합니다. 그러면 당신은 다시 짝사랑 상대 😍 에게 작업을 거는 매우 "생산적인" 일 🤓 을 합니다.
|
||||
하지만 번호를 들고 카운터에서 벗어나 테이블에 앉으면, 여러분은 짝사랑 상대에게 관심을 전환 🔀 하고, 그에 대한 "작업" ⏯ 🤓 을 할 수 있습니다. 그러면 여러분은 다시 짝사랑 상대에게 작업을 거는 매우 "생산적인" 일을 하게 됩니다 😍.
|
||||
|
||||
점원 💁 이 카운터 화면에 당신의 번호를 표시함으로써 "버거 🍔 가 준비되었습니다"라고 해도, 당신은 즉시 뛰쳐나가지는 않을 것입니다. 당신은 당신의 번호를 갖고있고, 다른 사람들은 그들의 번호를 갖고있기 때문에, 아무도 당신의 버거 🍔 를 훔쳐가지 않는다는 사실을 알기 때문입니다.
|
||||
그 다음 점원 💁 이 카운터 화면에 여러분 번호를 띄워 "햄버거를 만들었어요"라고 말하지만, 표시된 번호가 여러분 차례로 바뀌었다고 해서 즉시 미친 듯이 뛰어가지는 않습니다. 여러분은 여러분 번호를 갖고 있고, 다른 사람들은 그들의 번호를 갖고 있으니, 아무도 여러분 햄버거를 훔쳐갈 수 없다는 것을 알기 때문입니다.
|
||||
|
||||
그래서 당신은 짝사랑 상대 😍 가 이야기를 끝낼 때까지 기다린 후 (현재 작업 완료 ⏯ / 진행 중인 작업 처리 🤓 ), 정중하게 미소짓고 버거를 가지러 가겠다고 말합니다 ⏸.
|
||||
그래서 여러분은 짝사랑 상대가 이야기를 끝낼 때까지 기다린 다음(현재 작업 ⏯ / 처리 중인 작업 🤓 을 끝내고), 부드럽게 미소 지으며 햄버거를 가지러 가겠다고 말합니다 ⏸.
|
||||
|
||||
그다음 당신은 카운터에 가서 🔀 , 초기 작업을 이제 완료하고 ⏯ , 버거 🍔 를 받고, 감사하다고 말하고 테이블로 가져옵니다. 이로써 카운터와의 상호작용 단계 / 작업이 종료됩니다 ⏹.
|
||||
그 다음 여러분은 카운터로 가서 🔀, 이제 끝난 초기 작업 ⏯ 으로 돌아와 햄버거를 받고, 감사 인사를 하고, 테이블로 가져옵니다. 이로써 카운터와 상호작용하는 그 단계/작업이 끝납니다 ⏹. 그리고 이는 새로운 작업인 "햄버거 먹기" 🔀 ⏯ 를 만들지만, 이전 작업인 "햄버거 받기"는 끝났습니다 ⏹.
|
||||
|
||||
이전 작업인 "버거 받기"가 종료되면 ⏹ "버거 먹기"라는 새로운 작업이 생성됩니다 🔀 ⏯.
|
||||
### 병렬 햄버거 { #parallel-burgers }
|
||||
|
||||
### 병렬 버거
|
||||
이제 이것이 "동시 햄버거"가 아니라 "병렬 햄버거"라고 상상해봅시다.
|
||||
|
||||
이제 "동시 버거"가 아닌 "병렬 버거"를 상상해보십시오.
|
||||
여러분은 짝사랑 상대와 함께 병렬 패스트푸드를 먹으러 갑니다.
|
||||
|
||||
당신은 짝사랑 상대 😍 와 함께 병렬 패스트푸드 🍔 를 먹으러 갔습니다.
|
||||
여러분은 여러 명(예: 8명)의 점원이 동시에 요리사이기도 하여 여러분 앞 사람들의 주문을 받는 동안 줄을 서 있습니다.
|
||||
|
||||
당신은 여러명(8명이라고 가정합니다)의 점원이 당신 앞 사람들의 주문을 받으며 동시에 요리 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 도 하는 동안 줄을 서서 기다립니다.
|
||||
여러분 앞의 모든 사람들은, 8명의 점원 각각이 다음 주문을 받기 전에 바로 햄버거를 준비하러 가기 때문에, 카운터를 떠나지 않고 햄버거가 준비될 때까지 기다립니다.
|
||||
|
||||
당신 앞 모든 사람들이 버거가 준비될 때까지 카운터에서 떠나지 않고 기다립니다 🕙 . 왜냐하면 8명의 직원들이 다음 주문을 받기 전에 버거를 준비하러 가기 때문입니다.
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration">
|
||||
|
||||
마침내 당신의 차례가 왔고, 당신은 당신과 짝사랑 상대 😍 를 위한 두 개의 고급스러운 버거 🍔 를 주문합니다.
|
||||
마침내 여러분 차례가 되어, 여러분과 짝사랑 상대를 위해 매우 고급스러운 햄버거 2개를 주문합니다.
|
||||
|
||||
당신이 비용을 지불합니다 💸 .
|
||||
여러분이 돈을 냅니다 💸.
|
||||
|
||||
점원이 주방에 갑니다 👨🍳 .
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration">
|
||||
|
||||
당신은 번호표가 없기 때문에 누구도 당신의 버거 🍔 를 대신 가져갈 수 없도록 카운터에 서서 기다립니다 🕙 .
|
||||
점원은 주방으로 갑니다.
|
||||
|
||||
당신과 짝사랑 상대 😍 는 다른 사람이 새치기해서 버거를 가져가지 못하게 하느라 바쁘기 때문에 🕙 , 짝사랑 상대에게 주의를 기울일 수 없습니다 😞 .
|
||||
여러분은 번호표가 없으므로, 다른 사람이 여러분보다 먼저 햄버거를 가져가지 못하도록 카운터 앞에 서서 기다립니다 🕙.
|
||||
|
||||
이것은 "동기" 작업이고, 당신은 점원/요리사 👨🍳 와 "동기화" 되었습니다. 당신은 기다리고 🕙 , 점원/요리사 👨🍳 가 버거 🍔 준비를 완료한 후 당신에게 주거나, 누군가가 그것을 가져가는 그 순간에 그 곳에 있어야합니다.
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration">
|
||||
|
||||
카운터 앞에서 오랫동안 기다린 후에 🕙 , 점원/요리사 👨🍳 가 당신의 버거 🍔 를 가지고 돌아옵니다.
|
||||
여러분과 짝사랑 상대는 햄버거가 나오면 다른 사람이 끼어들어 가져가지 못하게 하느라 바쁘기 때문에, 짝사랑 상대에게 집중할 수 없습니다. 😞
|
||||
|
||||
당신은 버거를 받고 짝사랑 상대와 함께 테이블로 돌아옵니다.
|
||||
이것은 "동기" 작업이며, 여러분은 점원/요리사 👨🍳 와 "동기화"되어 있습니다. 점원/요리사 👨🍳 가 햄버거를 완성해 여러분에게 주는 정확한 순간에 그 자리에 있어야 하므로, 여러분은 기다려야 🕙 하고, 그렇지 않으면 다른 사람이 가져갈 수도 있습니다.
|
||||
|
||||
단지 먹기만 하다가, 다 먹었습니다 🍔 ⏹.
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration">
|
||||
|
||||
카운터 앞에서 기다리면서 🕙 너무 많은 시간을 허비했기 때문에 대화를 하거나 작업을 걸 시간이 거의 없었습니다 😞 .
|
||||
그러다 점원/요리사 👨🍳 가 카운터 앞에서 오랫동안 기다린 🕙 끝에 마침내 햄버거를 가지고 돌아옵니다.
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration">
|
||||
|
||||
여러분은 햄버거를 받아 짝사랑 상대와 테이블로 갑니다.
|
||||
|
||||
그냥 먹고, 끝입니다. ⏹
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration">
|
||||
|
||||
대부분의 시간을 카운터 앞에서 기다리는 데 🕙 썼기 때문에, 대화하거나 작업을 걸 시간은 많지 않았습니다. 😞
|
||||
|
||||
/// info | 정보
|
||||
|
||||
아름다운 일러스트: <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
이 병렬 버거 시나리오에서, 당신은 기다리고 🕙 , 오랜 시간동안 "카운터에서 기다리는" 🕙 데에 주의를 기울이는 ⏯ 두 개의 프로세서(당신과 짝사랑 상대😍)를 가진 컴퓨터 / 프로그램 🤖 입니다.
|
||||
이 병렬 햄버거 시나리오에서, 여러분은 두 개의 프로세서(여러분과 짝사랑 상대)를 가진 컴퓨터/프로그램 🤖 이며, 둘 다 기다리고 🕙 오랫동안 "카운터에서 기다리기" 🕙 에 주의를 ⏯ 기울입니다.
|
||||
|
||||
패스트푸드점에는 8개의 프로세서(점원/요리사) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 가 있습니다. 동시 버거는 단 두 개(한 명의 직원과 한 명의 요리사) 💁 👨🍳 만을 가지고 있었습니다.
|
||||
패스트푸드점에는 8개의 프로세서(점원/요리사)가 있습니다. 동시 햄버거 가게는 2개(점원 1명, 요리사 1명)만 있었을 것입니다.
|
||||
|
||||
하지만 여전히, 병렬 버거 예시가 최선은 아닙니다 😞 .
|
||||
하지만 여전히 최종 경험은 그다지 좋지 않습니다. 😞
|
||||
|
||||
---
|
||||
|
||||
이 예시는 버거🍔 이야기와 결이 같습니다.
|
||||
이것이 햄버거의 병렬 버전에 해당하는 이야기입니다. 🍔
|
||||
|
||||
더 "현실적인" 예시로, 은행을 상상해보십시오.
|
||||
좀 더 "현실적인" 예시로, 은행을 상상해보세요.
|
||||
|
||||
최근까지, 대다수의 은행에는 다수의 은행원들 👨💼👨💼👨💼👨💼 과 긴 줄 🕙🕙🕙🕙🕙🕙🕙🕙 이 있습니다.
|
||||
최근까지 대부분의 은행에는 여러 은행원 👨💼👨💼👨💼👨💼 과 긴 줄 🕙🕙🕙🕙🕙🕙🕙🕙 이 있었습니다.
|
||||
|
||||
모든 은행원들은 한 명 한 명의 고객들을 차례로 상대합니다 👨💼⏯ .
|
||||
모든 은행원이 한 고객씩 순서대로 모든 일을 처리합니다 👨💼⏯.
|
||||
|
||||
그리고 당신은 오랫동안 줄에서 기다려야하고 🕙 , 그렇지 않으면 당신의 차례를 잃게 됩니다.
|
||||
그리고 여러분은 오랫동안 줄에서 기다려야 🕙 하며, 그렇지 않으면 차례를 잃습니다.
|
||||
|
||||
아마 당신은 은행 🏦 심부름에 짝사랑 상대 😍 를 데려가고 싶지는 않을 것입니다.
|
||||
아마 은행 🏦 업무를 보러 갈 때 짝사랑 상대 😍 를 데려가고 싶지는 않을 것입니다.
|
||||
|
||||
### 버거 예시의 결론
|
||||
### 햄버거 예시의 결론 { #burger-conclusion }
|
||||
|
||||
"짝사랑 상대와의 패스트푸드점 버거" 시나리오에서, 오랜 기다림 🕙 이 있기 때문에 동시 시스템 ⏸🔀⏯ 을 사용하는 것이 더 합리적입니다.
|
||||
"짝사랑 상대와의 패스트푸드점 햄버거" 시나리오에서는 기다림 🕙 이 많기 때문에, 동시 시스템 ⏸🔀⏯ 을 사용하는 것이 훨씬 더 합리적입니다.
|
||||
|
||||
대다수의 웹 응용프로그램의 경우가 그러합니다.
|
||||
대부분의 웹 애플리케이션이 그렇습니다.
|
||||
|
||||
매우 많은 수의 유저가 있지만, 서버는 그들의 요청을 전송하기 위해 그닥-좋지-않은 연결을 기다려야 합니다 🕙 .
|
||||
매우 많은 사용자들이 있고, 서버는 그들의 좋지 않은 연결을 통해 요청이 전송되기를 기다립니다 🕙.
|
||||
|
||||
그리고 응답이 돌아올 때까지 다시 기다려야 합니다 🕙 .
|
||||
그리고 응답이 돌아오기를 다시 기다립니다 🕙.
|
||||
|
||||
이 "기다림" 🕙 은 마이크로초 단위이지만, 모두 더해지면, 결국에는 매우 긴 대기시간이 됩니다.
|
||||
이 "기다림" 🕙 은 마이크로초 단위로 측정되지만, 모두 합치면 결국 꽤 많은 대기 시간이 됩니다.
|
||||
|
||||
따라서 웹 API를 위해 비동기 ⏸🔀⏯ 코드를 사용하는 것이 합리적입니다.
|
||||
그래서 웹 API에는 비동기 ⏸🔀⏯ 코드를 사용하는 것이 매우 합리적입니다.
|
||||
|
||||
대부분의 존재하는 유명한 파이썬 프레임워크 (Flask와 Django 등)은 새로운 비동기 기능들이 파이썬에 존재하기 전에 만들어졌습니다. 그래서, 그들의 배포 방식은 병렬 실행과 새로운 기능만큼 강력하지는 않은 예전 버전의 비동기 실행을 지원합니다.
|
||||
이러한 종류의 비동기성은 NodeJS가 인기 있는 이유(비록 NodeJS가 병렬은 아니지만)이자, 프로그래밍 언어로서 Go의 강점입니다.
|
||||
|
||||
비동기 웹 파이썬(ASGI)에 대한 주요 명세가 웹소켓을 지원하기 위해 Django에서 개발 되었음에도 그렇습니다.
|
||||
그리고 이것이 **FastAPI**로 얻는 것과 같은 수준의 성능입니다.
|
||||
|
||||
이러한 종류의 비동기성은 (NodeJS는 병렬적이지 않음에도) NodeJS가 사랑받는 이유이고, 프로그래밍 언어로서의 Go의 강점입니다.
|
||||
또한 병렬성과 비동기성을 동시에 사용할 수 있으므로, 대부분의 테스트된 NodeJS 프레임워크보다 더 높은 성능을 얻고, C에 더 가까운 컴파일 언어인 Go와 동등한 성능을 얻을 수 있습니다 <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(모두 Starlette 덕분입니다)</a>.
|
||||
|
||||
그리고 **FastAPI**를 사용함으로써 동일한 성능을 낼 수 있습니다.
|
||||
### 동시성이 병렬성보다 더 나은가요? { #is-concurrency-better-than-parallelism }
|
||||
|
||||
또한 병렬성과 비동기성을 동시에 사용할 수 있기 때문에, 대부분의 테스트가 완료된 NodeJS 프레임워크보다 더 높은 성능을 얻고 C에 더 가까운 컴파일 언어인 Go와 동등한 성능을 얻을 수 있습니다<a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(모두 Starlette 덕분입니다)</a>.
|
||||
아니요! 그게 이 이야기의 교훈은 아닙니다.
|
||||
|
||||
### 동시성이 병렬성보다 더 나은가?
|
||||
동시성은 병렬성과 다릅니다. 그리고 많은 기다림이 포함되는 **특정한** 시나리오에서는 더 낫습니다. 그 때문에 웹 애플리케이션 개발에서는 일반적으로 병렬성보다 훨씬 더 낫습니다. 하지만 모든 것에 해당하진 않습니다.
|
||||
|
||||
그렇지 않습니다! 그것이 이야기의 교훈은 아닙니다.
|
||||
그래서 균형을 맞추기 위해, 다음의 짧은 이야기를 상상해보세요:
|
||||
|
||||
동시성은 병렬성과 다릅니다. 그리고 그것은 많은 대기를 필요로하는 **특정한** 시나리오에서는 더 낫습니다. 이로 인해, 웹 응용프로그램 개발에서 동시성이 병렬성보다 일반적으로 훨씬 낫습니다. 하지만 모든 경우에 그런 것은 아닙니다.
|
||||
|
||||
따라서, 균형을 맞추기 위해, 다음의 짧은 이야기를 상상해보십시오:
|
||||
|
||||
> 당신은 크고, 더러운 집을 청소해야합니다.
|
||||
> 여러분은 크고 더러운 집을 청소해야 합니다.
|
||||
|
||||
*네, 이게 전부입니다*.
|
||||
|
||||
---
|
||||
|
||||
어디에도 대기 🕙 는 없고, 집안 곳곳에서 해야하는 많은 작업들만 있습니다.
|
||||
어디에도 기다림 🕙 은 없고, 집의 여러 장소에서 해야 할 일이 많을 뿐입니다.
|
||||
|
||||
버거 예시처럼 처음에는 거실, 그 다음은 부엌과 같은 식으로 순서를 정할 수도 있으나, 무엇도 기다리지 🕙 않고 계속해서 청소 작업만 수행하기 때문에, 순서는 아무런 영향을 미치지 않습니다.
|
||||
햄버거 예시처럼 거실부터, 그 다음은 부엌처럼 순서를 정할 수도 있지만, 어떤 것도 기다리지 🕙 않고 계속 청소만 하기 때문에, 순서는 아무런 영향을 주지 않습니다.
|
||||
|
||||
순서가 있든 없든 동일한 시간이 소요될 것이고(동시성) 동일한 양의 작업을 하게 될 것입니다.
|
||||
순서가 있든 없든(동시성) 끝내는 데 걸리는 시간은 같고, 같은 양의 일을 하게 됩니다.
|
||||
|
||||
하지만 이 경우에서, 8명의 전(前)-점원/요리사이면서-현(現)-청소부 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 를 고용할 수 있고, 그들 각자(그리고 당신)가 집의 한 부분씩 맡아 청소를 한다면, 당신은 **병렬적**으로 작업을 수행할 수 있고, 조금의 도움이 있다면, 훨씬 더 빨리 끝낼 수 있습니다.
|
||||
하지만 이 경우, 전(前) 점원/요리사이자 현(現) 청소부가 된 8명을 데려올 수 있고, 각자(그리고 여러분)가 집의 구역을 하나씩 맡아 청소한다면, 추가 도움과 함께 모든 일을 **병렬**로 수행하여 훨씬 더 빨리 끝낼 수 있습니다.
|
||||
|
||||
이 시나리오에서, (당신을 포함한) 각각의 청소부들은 프로세서가 될 것이고, 각자의 역할을 수행합니다.
|
||||
이 시나리오에서 (여러분을 포함한) 각 청소부는 프로세서가 되어, 맡은 일을 수행합니다.
|
||||
|
||||
실행 시간의 대부분이 대기가 아닌 실제 작업에 소요되고, 컴퓨터에서 작업은 <abbr title="Central Processing Unit">CPU</abbr>에서 이루어지므로, 이러한 문제를 "CPU에 묶였"다고 합니다.
|
||||
그리고 실행 시간의 대부분이 기다림이 아니라 실제 작업에 쓰이고, 컴퓨터에서 작업은 <abbr title="Central Processing Unit - 중앙 처리 장치">CPU</abbr>가 수행하므로, 이런 문제를 "CPU bound"라고 부릅니다.
|
||||
|
||||
---
|
||||
|
||||
CPU에 묶인 연산에 관한 흔한 예시는 복잡한 수학 처리를 필요로 하는 경우입니다.
|
||||
CPU bound 작업의 흔한 예시는 복잡한 수학 처리가 필요한 것들입니다.
|
||||
|
||||
예를 들어:
|
||||
|
||||
* **오디오** 또는 **이미지** 처리.
|
||||
* **컴퓨터 비전**: 하나의 이미지는 수백개의 픽셀로 구성되어있고, 각 픽셀은 3개의 값 / 색을 갖고 있으며, 일반적으로 해당 픽셀들에 대해 동시에 무언가를 계산해야하는 처리.
|
||||
* **머신러닝**: 일반적으로 많은 "행렬"과 "벡터" 곱셈이 필요합니다. 거대한 스프레드 시트에 수들이 있고 그 수들을 동시에 곱해야 한다고 생각해보십시오.
|
||||
* **딥러닝**: 머신러닝의 하위영역으로, 동일한 예시가 적용됩니다. 단지 이 경우에는 하나의 스프레드 시트에 곱해야할 수들이 있는 것이 아니라, 거대한 세트의 스프레드 시트들이 있고, 많은 경우에, 이 모델들을 만들고 사용하기 위해 특수한 프로세서를 사용합니다.
|
||||
* **오디오** 또는 **이미지** 처리
|
||||
* **컴퓨터 비전**: 이미지는 수백만 개의 픽셀로 구성되며, 각 픽셀은 3개의 값/색을 갖습니다. 보통 그 픽셀들에 대해 동시에 무언가를 계산해야 합니다.
|
||||
* **머신러닝**: 보통 많은 "matrix"와 "vector" 곱셈이 필요합니다. 숫자가 있는 거대한 스프레드시트를 생각하고, 그 모든 수를 동시에 곱한다고 생각해보세요.
|
||||
* **딥러닝**: 머신러닝의 하위 분야이므로 동일하게 적용됩니다. 다만 곱해야 할 숫자가 있는 스프레드시트가 하나가 아니라, 아주 큰 집합이며, 많은 경우 그 모델을 만들고/또는 사용하기 위해 특별한 프로세서를 사용합니다.
|
||||
|
||||
### 동시성 + 병렬성: 웹 + 머신러닝
|
||||
### 동시성 + 병렬성: 웹 + 머신러닝 { #concurrency-parallelism-web-machine-learning }
|
||||
|
||||
**FastAPI**를 사용하면 웹 개발에서는 매우 흔한 동시성의 이점을 (NodeJS의 주된 매력만큼) 얻을 수 있습니다.
|
||||
**FastAPI**를 사용하면 웹 개발에서 매우 흔한 동시성의 이점을( NodeJS의 주요 매력과 같은) 얻을 수 있습니다.
|
||||
|
||||
뿐만 아니라 머신러닝 시스템과 같이 **CPU에 묶인** 작업을 위해 병렬성과 멀티프로세싱(다수의 프로세스를 병렬적으로 동작시키는 것)을 이용하는 것도 가능합니다.
|
||||
또한 머신러닝 시스템처럼 **CPU bound** 워크로드에 대해 병렬성과 멀티프로세싱(여러 프로세스를 병렬로 실행)을 활용할 수도 있습니다.
|
||||
|
||||
파이썬이 **데이터 사이언스**, 머신러닝과 특히 딥러닝에 의 주된 언어라는 간단한 사실에 더해서, 이것은 FastAPI를 데이터 사이언스 / 머신러닝 웹 API와 응용프로그램에 (다른 것들보다) 좋은 선택지가 되게 합니다.
|
||||
이것은 파이썬이 **데이터 사이언스**, 머신러닝, 특히 딥러닝의 주요 언어라는 단순한 사실과 더해져, FastAPI를 데이터 사이언스/머신러닝 웹 API 및 애플리케이션(그 외에도 많은 것들)에 매우 잘 맞는 선택으로 만들어 줍니다.
|
||||
|
||||
배포시 병렬을 어떻게 가능하게 하는지 알고싶다면, [배포](deployment/index.md){.internal-link target=_blank}문서를 참고하십시오.
|
||||
프로덕션에서 이 병렬성을 어떻게 달성하는지 보려면 [배포](deployment/index.md){.internal-link target=_blank} 섹션을 참고하세요.
|
||||
|
||||
## `async`와 `await`
|
||||
## `async`와 `await` { #async-and-await }
|
||||
|
||||
최신 파이썬 버전에는 비동기 코드를 정의하는 매우 직관적인 방법이 있습니다. 이는 이것을 평범한 "순차적" 코드로 보이게 하고, 적절한 순간에 당신을 위해 "대기"합니다.
|
||||
최신 파이썬 버전에는 비동기 코드를 정의하는 매우 직관적인 방법이 있습니다. 이 방법은 이를 평범한 "순차" 코드처럼 보이게 하고, 적절한 순간에 여러분을 위해 "기다림"을 수행합니다.
|
||||
|
||||
연산이 결과를 전달하기 전에 대기를 해야하고 새로운 파이썬 기능들을 지원한다면, 이렇게 코드를 작성할 수 있습니다:
|
||||
결과를 주기 전에 기다림이 필요한 작업이 있고, 이러한 새로운 파이썬 기능을 지원한다면, 다음과 같이 작성할 수 있습니다:
|
||||
|
||||
```Python
|
||||
burgers = await get_burgers(2)
|
||||
```
|
||||
|
||||
여기서 핵심은 `await`입니다. 이것은 파이썬에게 `burgers` 결과를 저장하기 이전에 `get_burgers(2)`의 작업이 완료되기를 🕙 기다리라고 ⏸ 말합니다. 이로 인해, 파이썬은 그동안 (다른 요청을 받는 것과 같은) 다른 작업을 수행해도 된다는 것을 🔀 ⏯ 알게될 것입니다.
|
||||
여기서 핵심은 `await`입니다. 이는 파이썬에게 `get_burgers(2)`가 그 일을 끝낼 때까지 🕙 기다리도록 ⏸ 말하고, 그 결과를 `burgers`에 저장하기 전에 완료되기를 기다리라고 합니다. 이를 통해 파이썬은 그동안(예: 다른 요청을 받는 것처럼) 다른 일을 하러 갈 수 있다는 것 🔀 ⏯ 을 알게 됩니다.
|
||||
|
||||
`await`가 동작하기 위해, 이것은 비동기를 지원하는 함수 내부에 있어야 합니다. 이를 위해서 함수를 `async def`를 사용해 정의하기만 하면 됩니다:
|
||||
`await`가 동작하려면, 이 비동기성을 지원하는 함수 내부에 있어야 합니다. 그러려면 `async def`로 선언하기만 하면 됩니다:
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def get_burgers(number: int):
|
||||
# Do some asynchronous stuff to create the burgers
|
||||
# 햄버거를 만들기 위한 비동기 처리를 수행
|
||||
return burgers
|
||||
```
|
||||
|
||||
...`def`를 사용하는 대신:
|
||||
...`def` 대신:
|
||||
|
||||
```Python hl_lines="2"
|
||||
# This is not asynchronous
|
||||
# 비동기가 아닙니다
|
||||
def get_sequential_burgers(number: int):
|
||||
# Do some sequential stuff to create the burgers
|
||||
# 햄버거를 만들기 위한 순차 처리를 수행
|
||||
return burgers
|
||||
```
|
||||
|
||||
`async def`를 사용하면, 파이썬은 해당 함수 내에서 `await` 표현에 주의해야한다는 사실과, 해당 함수의 실행을 "일시정지"⏸하고 다시 돌아오기 전까지 다른 작업을 수행🔀할 수 있다는 것을 알게됩니다.
|
||||
`async def`를 사용하면, 파이썬은 그 함수 내부에서 `await` 표현식에 주의해야 하며, 그 함수의 실행을 "일시정지" ⏸ 하고 다시 돌아오기 전에 다른 일을 하러 갈 수 있다는 것 🔀 을 알게 됩니다.
|
||||
|
||||
`async def`f 함수를 호출하고자 할 때, "대기"해야합니다. 따라서, 아래는 동작하지 않습니다.
|
||||
`async def` 함수를 호출하고자 할 때는, 그 함수를 "await" 해야 합니다. 따라서 아래는 동작하지 않습니다:
|
||||
|
||||
```Python
|
||||
# This won't work, because get_burgers was defined with: async def
|
||||
# 동작하지 않습니다. get_burgers는 async def로 정의되었습니다
|
||||
burgers = get_burgers(2)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
따라서, `await`f를 사용해서 호출할 수 있는 라이브러리를 사용한다면, 다음과 같이 `async def`를 사용하는 *경로 작동 함수*를 생성해야 합니다:
|
||||
따라서, `await`로 호출할 수 있다고 말하는 라이브러리를 사용한다면, 다음과 같이 그것을 사용하는 *경로 처리 함수*를 `async def`로 만들어야 합니다:
|
||||
|
||||
```Python hl_lines="2-3"
|
||||
@app.get('/burgers')
|
||||
@@ -317,94 +349,96 @@ async def read_burgers():
|
||||
return burgers
|
||||
```
|
||||
|
||||
### 더 세부적인 기술적 사항
|
||||
### 더 세부적인 기술적 사항 { #more-technical-details }
|
||||
|
||||
`await`가 `async def`를 사용하는 함수 내부에서만 사용이 가능하다는 것을 눈치채셨을 것입니다.
|
||||
`await`는 `async def`로 정의된 함수 내부에서만 사용할 수 있다는 것을 눈치채셨을 것입니다.
|
||||
|
||||
하지만 동시에, `async def`로 정의된 함수들은 "대기"되어야만 합니다. 따라서, `async def`를 사용한 함수들은 역시 `async def`를 사용한 함수 내부에서만 호출될 수 있습니다.
|
||||
하지만 동시에, `async def`로 정의된 함수는 "await" 되어야 합니다. 따라서 `async def`를 가진 함수는 `async def`로 정의된 함수 내부에서만 호출될 수 있습니다.
|
||||
|
||||
그렇다면 닭이 먼저냐, 달걀이 먼저냐, 첫 `async` 함수를 어떻게 호출할 수 있겠습니까?
|
||||
그렇다면, 닭이 먼저냐 달걀이 먼저냐처럼, 첫 번째 `async` 함수는 어떻게 호출할 수 있을까요?
|
||||
|
||||
**FastAPI**를 사용해 작업한다면 이것을 걱정하지 않아도 됩니다. 왜냐하면 그 "첫" 함수는 당신의 *경로 작동 함수*가 될 것이고, FastAPI는 어떻게 올바르게 처리할지 알고있기 때문입니다.
|
||||
**FastAPI**로 작업한다면 걱정할 필요가 없습니다. 그 "첫" 함수는 여러분의 *경로 처리 함수*가 될 것이고, FastAPI는 올바르게 처리하는 방법을 알고 있기 때문입니다.
|
||||
|
||||
하지만 FastAPI를 사용하지 않고 `async` / `await`를 사용하고 싶다면, 이 역시 가능합니다.
|
||||
하지만 FastAPI 없이 `async` / `await`를 사용하고 싶다면, 그것도 가능합니다.
|
||||
|
||||
### 당신만의 비동기 코드 작성하기
|
||||
### 여러분만의 async 코드 작성하기 { #write-your-own-async-code }
|
||||
|
||||
Starlette(그리고 FastAPI)는 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>를 기반으로 하고있고, 따라서 파이썬 표준 라이브러리인 <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> 및 <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>와 호환됩니다.
|
||||
Starlette(그리고 **FastAPI**)는 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>를 기반으로 하고 있으며, 파이썬 표준 라이브러리 <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a>와 <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a> 모두와 호환됩니다.
|
||||
|
||||
특히, 코드에서 고급 패턴이 필요한 고급 동시성을 사용하는 경우 직접적으로 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>를 사용할 수 있습니다.
|
||||
특히, 코드에서 더 고급 패턴이 필요한 고급 동시성 사용 사례에서는 직접 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>를 사용할 수 있습니다.
|
||||
|
||||
FastAPI를 사용하지 않더라도, 높은 호환성 및 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>의 이점(예: *구조화된 동시성*)을 취하기 위해 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>를 사용해 비동기 응용프로그램을 작성할 수 있습니다.
|
||||
그리고 FastAPI를 사용하지 않더라도, 높은 호환성을 확보하고 그 이점(예: *structured concurrency*)을 얻기 위해 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>로 여러분만의 async 애플리케이션을 작성할 수도 있습니다.
|
||||
|
||||
### 비동기 코드의 다른 형태
|
||||
저는 AnyIO 위에 얇은 레이어로 또 다른 라이브러리를 만들었는데, 타입 어노테이션을 조금 개선하고 더 나은 **자동완성**, **인라인 오류** 등을 얻기 위한 것입니다. 또한 **이해**하고 **여러분만의 async 코드**를 작성하도록 돕는 친절한 소개와 튜토리얼도 제공합니다: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. 특히 **async 코드와 일반**(blocking/동기) 코드를 **결합**해야 한다면 아주 유용합니다.
|
||||
|
||||
파이썬에서 `async`와 `await`를 사용하게 된 것은 비교적 최근의 일입니다.
|
||||
### 비동기 코드의 다른 형태 { #other-forms-of-asynchronous-code }
|
||||
|
||||
하지만 이로 인해 비동기 코드 작업이 훨씬 간단해졌습니다.
|
||||
`async`와 `await`를 사용하는 이 스타일은 언어에서 비교적 최근에 추가되었습니다.
|
||||
|
||||
같은 (또는 거의 유사한) 문법은 최신 버전의 자바스크립트(브라우저와 NodeJS)에도 추가되었습니다.
|
||||
하지만 비동기 코드를 다루는 일을 훨씬 더 쉽게 만들어 줍니다.
|
||||
|
||||
하지만 그 이전에, 비동기 코드를 처리하는 것은 꽤 복잡하고 어려운 일이었습니다.
|
||||
거의 동일한 문법이 최근 브라우저와 NodeJS의 최신 JavaScript에도 포함되었습니다.
|
||||
|
||||
파이썬의 예전 버전이라면, 스레드 또는 <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>를 사용할 수 있을 것입니다. 하지만 코드를 이해하고, 디버깅하고, 이에 대해 생각하는게 훨씬 복잡합니다.
|
||||
하지만 그 이전에는 비동기 코드를 처리하는 것이 훨씬 더 복잡하고 어려웠습니다.
|
||||
|
||||
예전 버전의 NodeJS / 브라우저 자바스크립트라면, "콜백 함수"를 사용했을 것입니다. 그리고 이로 인해 "콜백 지옥"에 빠지게 될 수 있습니다.
|
||||
이전 버전의 파이썬에서는 스레드 또는 <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>를 사용할 수 있었을 것입니다. 하지만 코드를 이해하고, 디버깅하고, 이에 대해 생각하는 것이 훨씬 더 복잡합니다.
|
||||
|
||||
## 코루틴
|
||||
이전 버전의 NodeJS/브라우저 JavaScript에서는 "callback"을 사용했을 것입니다. 이는 "callback hell"로 이어집니다.
|
||||
|
||||
**코루틴**은 `async def` 함수가 반환하는 것을 칭하는 매우 고급스러운 용어일 뿐입니다. 파이썬은 그것이 시작되고 어느 시점에서 완료되지만 내부에 `await`가 있을 때마다 내부적으로 일시정지⏸될 수도 있는 함수와 유사한 것이라는 사실을 알고있습니다.
|
||||
## 코루틴 { #coroutines }
|
||||
|
||||
그러나 `async` 및 `await`와 함께 비동기 코드를 사용하는 이 모든 기능들은 "코루틴"으로 간단히 요약됩니다. 이것은 Go의 주된 핵심 기능인 "고루틴"에 견줄 수 있습니다.
|
||||
**코루틴**은 `async def` 함수가 반환하는 것에 대한 매우 고급스러운 용어일 뿐입니다. 파이썬은 그것이 함수와 비슷한 무언가로서 시작할 수 있고, 어느 시점에 끝나지만, 내부에 `await`가 있을 때마다 내부적으로도 일시정지 ⏸ 될 수 있다는 것을 알고 있습니다.
|
||||
|
||||
## 결론
|
||||
하지만 `async` 및 `await`와 함께 비동기 코드를 사용하는 이 모든 기능은 종종 "코루틴"을 사용한다고 요약됩니다. 이는 Go의 주요 핵심 기능인 "Goroutines"에 비견됩니다.
|
||||
|
||||
상기 문장을 다시 한 번 봅시다:
|
||||
## 결론 { #conclusion }
|
||||
|
||||
> 최신 파이썬 버전은 **`async` 및 `await`** 문법과 함께 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 지원합니다.
|
||||
위의 같은 문장을 다시 봅시다:
|
||||
|
||||
이제 이 말을 조금 더 이해할 수 있을 것입니다. ✨
|
||||
> 최신 파이썬 버전은 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 **`async` 및 `await`** 문법과 함께 지원합니다.
|
||||
|
||||
이것이 (Starlette을 통해) FastAPI를 강하게 하면서 그것이 인상적인 성능을 낼 수 있게 합니다.
|
||||
이제 더 이해가 될 것입니다. ✨
|
||||
|
||||
## 매우 세부적인 기술적 사항
|
||||
이 모든 것이 FastAPI(Starlette을 통해)를 구동하고, 인상적인 성능을 내게 하는 원동력입니다.
|
||||
|
||||
## 매우 세부적인 기술적 사항 { #very-technical-details }
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
이 부분은 넘어가도 됩니다.
|
||||
이 부분은 아마 건너뛰어도 됩니다.
|
||||
|
||||
이것들은 **FastAPI**가 내부적으로 어떻게 동작하는지에 대한 매우 세부적인 기술사항입니다.
|
||||
|
||||
만약 기술적 지식(코루틴, 스레드, 블록킹 등)이 있고 FastAPI가 어떻게 `async def` vs `def`를 다루는지 궁금하다면, 계속하십시오.
|
||||
(코루틴, 스레드, 블로킹 등) 같은 기술 지식이 꽤 있고 FastAPI가 `async def`와 일반 `def`를 어떻게 처리하는지 궁금하다면, 계속 읽어보세요.
|
||||
|
||||
///
|
||||
|
||||
### 경로 작동 함수
|
||||
### 경로 처리 함수 { #path-operation-functions }
|
||||
|
||||
경로 작동 함수를 `async def` 대신 일반적인 `def`로 선언하는 경우, (서버를 차단하는 것처럼) 그것을 직접 호출하는 대신 대기중인 외부 스레드풀에서 실행됩니다.
|
||||
*경로 처리 함수*를 `async def` 대신 일반적인 `def`로 선언하면, (서버를 블로킹할 수 있으므로 직접 호출하는 대신) 외부 스레드풀에서 실행되고 그 결과를 await 합니다.
|
||||
|
||||
만약 상기에 묘사된대로 동작하지 않는 비동기 프로그램을 사용해왔고 약간의 성능 향상 (약 100 나노초)을 위해 `def`를 사용해서 계산만을 위한 사소한 *경로 작동 함수*를 정의해왔다면, **FastAPI**는 이와는 반대라는 것에 주의하십시오. 이러한 경우에, *경로 작동 함수*가 블로킹 <abbr title="Input/Output: 디스크 읽기 또는 쓰기, 네트워크 통신.">I/O</abbr>를 수행하는 코드를 사용하지 않는 한 `async def`를 사용하는 편이 더 낫습니다.
|
||||
위에서 설명한 방식으로 동작하지 않는 다른 async 프레임워크를 사용해본 적이 있고, 아주 작은 성능 향상(약 100 나노초)을 위해 계산만 하는 사소한 *경로 처리 함수*를 일반 `def`로 정의하곤 했다면, **FastAPI**에서는 그 효과가 정반대가 될 수 있다는 점에 유의하세요. 이런 경우에는 *경로 처리 함수*에서 블로킹 <abbr title="Input/Output - 입력/출력: 디스크 읽기 또는 쓰기, 네트워크 통신.">I/O</abbr> 를 수행하는 코드를 사용하지 않는 한 `async def`를 사용하는 편이 더 낫습니다.
|
||||
|
||||
하지만 두 경우 모두, FastAPI가 당신이 전에 사용하던 프레임워크보다 [더 빠를](index.md#_11){.internal-link target=_blank} (최소한 비견될) 확률이 높습니다.
|
||||
그럼에도 두 경우 모두, **FastAPI**는 이전에 사용하던 프레임워크보다 [여전히 더 빠를](index.md#performance){.internal-link target=_blank} 가능성이 높습니다(또는 최소한 비슷합니다).
|
||||
|
||||
### 의존성
|
||||
### 의존성 { #dependencies }
|
||||
|
||||
의존성에도 동일하게 적용됩니다. 의존성이 `async def`가 아닌 표준 `def` 함수라면, 외부 스레드풀에서 실행됩니다.
|
||||
[의존성](tutorial/dependencies/index.md){.internal-link target=_blank}에도 동일하게 적용됩니다. 의존성이 `async def` 대신 표준 `def` 함수라면, 외부 스레드풀에서 실행됩니다.
|
||||
|
||||
### 하위-의존성
|
||||
### 하위 의존성 { #sub-dependencies }
|
||||
|
||||
함수 정의시 매개변수로 서로를 필요로하는 다수의 의존성과 하위-의존성을 가질 수 있고, 그 중 일부는 `async def`로, 다른 일부는 일반적인 `def`로 생성되었을 수 있습니다. 이것은 여전히 잘 동작하고, 일반적인 `def`로 생성된 것들은 "대기"되는 대신에 (스레드풀로부터) 외부 스레드에서 호출됩니다.
|
||||
서로를 필요로 하는 여러 의존성과 [하위 의존성](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}을 함수 정의의 매개변수로 가질 수 있으며, 그중 일부는 `async def`로, 다른 일부는 일반 `def`로 생성되었을 수 있습니다. 그래도 정상 동작하며, 일반 `def`로 생성된 것들은 "await"되는 대신 (스레드풀에서) 외부 스레드에서 호출됩니다.
|
||||
|
||||
### 다른 유틸리티 함수
|
||||
### 다른 유틸리티 함수 { #other-utility-functions }
|
||||
|
||||
직접 호출되는 다른 모든 유틸리티 함수는 일반적인 `def`나 `async def`로 생성될 수 있고 FastAPI는 이를 호출하는 방식에 영향을 미치지 않습니다.
|
||||
직접 호출하는 다른 모든 유틸리티 함수는 일반 `def`나 `async def`로 생성될 수 있으며, FastAPI는 호출 방식에 영향을 주지 않습니다.
|
||||
|
||||
이것은 FastAPI가 당신을 위해 호출하는 함수와는 반대입니다: *경로 작동 함수*와 의존성
|
||||
이는 FastAPI가 여러분을 위해 호출하는 함수(즉, *경로 처리 함수*와 의존성)와 대비됩니다.
|
||||
|
||||
만약 당신의 유틸리티 함수가 `def`를 사용한 일반적인 함수라면, 스레드풀에서가 아니라 직접 호출(당신이 코드에 작성한 대로)될 것이고, `async def`로 생성된 함수라면 코드에서 호출할 때 그 함수를 `await` 해야 합니다.
|
||||
유틸리티 함수가 `def`로 만든 일반 함수라면, 스레드풀이 아니라 직접(코드에 작성한 대로) 호출됩니다. 그리고 `async def`로 생성된 함수라면, 코드에서 호출할 때 그 함수를 `await` 해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
다시 말하지만, 이것은 당신이 이것에 대해 찾고있던 경우에 한해 유용할 매우 세부적인 기술사항입니다.
|
||||
다시 말하지만, 이것들은 아마도 이를 찾고 있었던 경우에 유용한 매우 세부적인 기술사항입니다.
|
||||
|
||||
그렇지 않은 경우, 상기의 가이드라인만으로도 충분할 것입니다: [바쁘신 경우](#_1).
|
||||
그렇지 않다면, 위 섹션의 가이드라인이면 충분합니다: <a href="#in-a-hurry">바쁘신가요?</a>.
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
# 배포 개념 { #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
|
||||
* 시작 시 실행
|
||||
* 재시작
|
||||
* 복제(실행 중인 프로세스 수)
|
||||
* 메모리
|
||||
* 시작 전 사전 단계
|
||||
|
||||
이 아이디어들을 이해하고 적용하는 방법을 알면, 배포를 구성하고 조정할 때 필요한 직관을 얻는 데 도움이 될 것입니다. 🤓
|
||||
|
||||
다음 섹션에서는 따라 할 수 있는 가능한 전략의 더 구체적인 예시를 제공하겠습니다. 🚀
|
||||
@@ -14,7 +14,7 @@ FastAPI 애플리케이션을 배포할 때 일반적인 접근 방법은 **리
|
||||
<summary>Dockerfile Preview 👀</summary>
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.9
|
||||
FROM python:3.14
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
@@ -145,8 +145,6 @@ Successfully installed fastapi pydantic
|
||||
* 다음 내용으로 `main.py` 파일을 만듭니다:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
@@ -158,7 +156,7 @@ def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
@@ -168,7 +166,7 @@ def read_item(item_id: int, q: Union[str, None] = None):
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)!
|
||||
FROM python:3.9
|
||||
FROM python:3.14
|
||||
|
||||
# (2)!
|
||||
WORKDIR /code
|
||||
@@ -245,14 +243,14 @@ Docker 지시어 <a href="https://docs.docker.com/reference/dockerfile/#cmd" cla
|
||||
✅ **Exec** form:
|
||||
|
||||
```Dockerfile
|
||||
# ✅ Do this
|
||||
# ✅ 이렇게 하세요
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
⛔️ **Shell** form:
|
||||
|
||||
```Dockerfile
|
||||
# ⛔️ Don't do this
|
||||
# ⛔️ 이렇게 하지 마세요
|
||||
CMD fastapi run app/main.py --port 80
|
||||
```
|
||||
|
||||
@@ -392,7 +390,7 @@ FastAPI가 단일 파일(예: `./app` 디렉터리 없이 `main.py`만 있는
|
||||
그런 다음 `Dockerfile`에서 해당 파일을 복사하도록 경로만 맞게 변경하면 됩니다:
|
||||
|
||||
```{ .dockerfile .annotate hl_lines="10 13" }
|
||||
FROM python:3.9
|
||||
FROM python:3.14
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
@@ -456,7 +454,7 @@ Traefik은 Docker, Kubernetes 등과 통합되어 있어, 이를 사용해 컨
|
||||
|
||||
## 복제 - 프로세스 개수 { #replication-number-of-processes }
|
||||
|
||||
**Kubernetes**, Docker Swarm Mode, Nomad 등의 복잡한 시스템으로 여러 머신에 분산된 컨테이너를 관리하는 <abbr title="A group of machines that are configured to be connected and work together in some way.">cluster</abbr>를 사용한다면, 각 컨테이너에서(**워커를 사용하는 Uvicorn** 같은) **프로세스 매니저**를 쓰는 대신, **클러스터 레벨**에서 **복제를 처리**하고 싶을 가능성이 큽니다.
|
||||
**Kubernetes**, Docker Swarm Mode, Nomad 등의 복잡한 시스템으로 여러 머신에 분산된 컨테이너를 관리하는 <dfn title="어떤 방식으로 연결되어 함께 동작하도록 구성된 머신의 그룹">클러스터</dfn>를 사용한다면, 각 컨테이너에서(**워커를 사용하는 Uvicorn** 같은) **프로세스 매니저**를 쓰는 대신, **클러스터 레벨**에서 **복제를 처리**하고 싶을 가능성이 큽니다.
|
||||
|
||||
Kubernetes 같은 분산 컨테이너 관리 시스템은 보통 들어오는 요청에 대한 **로드 밸런싱**을 지원하면서도, **컨테이너 복제**를 처리하는 통합된 방법을 가지고 있습니다. 모두 **클러스터 레벨**에서요.
|
||||
|
||||
@@ -501,7 +499,7 @@ HTTPS에 사용되는 동일한 **TLS 종료 프록시** 컴포넌트가 **로
|
||||
그런 경우에는 `--workers` 커맨드 라인 옵션을 사용해 실행할 워커 수를 설정할 수 있습니다:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
FROM python:3.9
|
||||
FROM python:3.14
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
@@ -580,7 +578,7 @@ Kubernetes를 사용한다면, 이는 아마도 <a href="https://kubernetes.io/d
|
||||
|
||||
그리고 여러 워커가 필요하다면, `--workers` 커맨드 라인 옵션을 간단히 사용하면 됩니다.
|
||||
|
||||
/// note Technical Details | 기술 세부사항
|
||||
/// note | 기술 세부사항
|
||||
|
||||
이 Docker 이미지는 Uvicorn이 죽은 워커를 관리하고 재시작하는 기능을 지원하지 않던 시기에 만들어졌습니다. 그래서 Gunicorn과 Uvicorn을 함께 사용해야 했고, Gunicorn이 Uvicorn 워커 프로세스를 관리하고 재시작하도록 하기 위해 상당한 복잡성이 추가되었습니다.
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# 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 앱을 어떻게 배포하는지까지 이해할 수 있게 될 것입니다. 🤓
|
||||
@@ -0,0 +1,231 @@
|
||||
# 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자로부터 다시 **갱신**해서 **재발급/재획득**해야 합니다.
|
||||
* 연결의 암호화는 **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 서버(아마 같은 클라우드 제공업체)에서 이를 설정합니다.
|
||||
|
||||
대개 클라우드 서버(가상 머신) 같은 것을 사용하게 되고, 거기에는 <dfn title="시간이 지나도 변하지 않음. 동적이지 않음">고정</dfn> **공개 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** 설정 방법을 여러 구체적인 예시로 보여드리겠습니다. 🔒
|
||||
@@ -0,0 +1,157 @@
|
||||
# 서버를 수동으로 실행하기 { #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
|
||||
* 시작 시 자동 실행
|
||||
* 재시작
|
||||
* 복제(실행 중인 프로세스 수)
|
||||
* 메모리
|
||||
* 시작 전 선행 단계
|
||||
|
||||
다음 장들에서 이 각각의 개념을 어떻게 생각해야 하는지, 그리고 이를 다루기 위한 전략의 구체적인 예시를 더 알려드리겠습니다. 🚀
|
||||
@@ -9,13 +9,13 @@
|
||||
* 메모리
|
||||
* 시작하기 전의 이전 단계
|
||||
|
||||
지금까지 문서의 모든 튜토리얼을 참고하면서, `fastapi` 명령처럼 Uvicorn을 실행하는 **서버 프로그램**을 사용해 **단일 프로세스**로 실행해 왔을 가능성이 큽니다.
|
||||
지금까지 문서의 모든 튜토리얼을 참고하면서, `fastapi` 명령어처럼 Uvicorn을 실행하는 **서버 프로그램**을 사용해 **단일 프로세스**로 실행해 왔을 가능성이 큽니다.
|
||||
|
||||
애플리케이션을 배포할 때는 **다중 코어**를 활용하고 더 많은 요청을 처리할 수 있도록 **프로세스 복제**를 하고 싶을 가능성이 큽니다.
|
||||
|
||||
이전 장의 [배포 개념들](concepts.md){.internal-link target=_blank}에서 본 것처럼, 사용할 수 있는 전략이 여러 가지 있습니다.
|
||||
|
||||
여기서는 `fastapi` 명령을 사용하거나 `uvicorn` 명령을 직접 사용해서, **워커 프로세스**와 함께 **Uvicorn**을 사용하는 방법을 보여드리겠습니다.
|
||||
여기서는 `fastapi` 명령어를 사용하거나 `uvicorn` 명령어를 직접 사용해서, **워커 프로세스**와 함께 **Uvicorn**을 사용하는 방법을 보여드리겠습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -27,11 +27,11 @@ Docker나 Kubernetes 같은 컨테이너를 사용하고 있다면, 다음 장
|
||||
|
||||
## 여러 워커 { #multiple-workers }
|
||||
|
||||
`--workers` 커맨드라인 옵션으로 여러 워커를 시작할 수 있습니다:
|
||||
`--workers` 명령어 옵션으로 여러 워커를 시작할 수 있습니다:
|
||||
|
||||
//// tab | `fastapi`
|
||||
|
||||
`fastapi` 명령을 사용한다면:
|
||||
`fastapi` 명령어를 사용한다면:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -81,7 +81,7 @@ $ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration
|
||||
|
||||
//// tab | `uvicorn`
|
||||
|
||||
`uvicorn` 명령을 직접 사용하는 편이 좋다면:
|
||||
`uvicorn` 명령어를 직접 사용하는 편이 좋다면:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -132,7 +132,7 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
|
||||
|
||||
## 요약 { #recap }
|
||||
|
||||
`fastapi` 또는 `uvicorn` 명령에서 `--workers` CLI 옵션을 사용해 여러 워커 프로세스를 실행하면, **멀티 코어 CPU**를 활용해 **여러 프로세스를 병렬로 실행**할 수 있습니다.
|
||||
`fastapi` 또는 `uvicorn` 명령어에서 `--workers` CLI 옵션을 사용해 여러 워커 프로세스를 실행하면, **멀티 코어 CPU**를 활용해 **여러 프로세스를 병렬로 실행**할 수 있습니다.
|
||||
|
||||
다른 배포 개념들을 직접 처리하면서 **자체 배포 시스템**을 구축하는 경우, 이러한 도구와 아이디어를 활용할 수 있습니다.
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ fastapi[standard]>=0.112.0,<0.113.0
|
||||
|
||||
## 이용 가능한 버전들 { #available-versions }
|
||||
|
||||
사용 가능한 버전(예: 현재 최신 버전이 무엇인지 확인하기 위해)은 [Release Notes](../release-notes.md){.internal-link target=_blank}에서 확인할 수 있습니다.
|
||||
사용 가능한 버전(예: 현재 최신 버전이 무엇인지 확인하기 위해)은 [릴리스 노트](../release-notes.md){.internal-link target=_blank}에서 확인할 수 있습니다.
|
||||
|
||||
## 버전들에 대해 { #about-versions }
|
||||
|
||||
@@ -66,7 +66,7 @@ fastapi>=0.45.0,<0.46.0
|
||||
|
||||
앱에 테스트를 추가해야 합니다.
|
||||
|
||||
**FastAPI**에서는 매우 쉽습니다(Starlette 덕분에). 문서를 확인해 보세요: [Testing](../tutorial/testing.md){.internal-link target=_blank}
|
||||
**FastAPI**에서는 매우 쉽습니다(Starlette 덕분에). 문서를 확인해 보세요: [테스트](../tutorial/testing.md){.internal-link target=_blank}
|
||||
|
||||
테스트를 갖춘 뒤에는 **FastAPI** 버전을 더 최신 버전으로 업그레이드하고, 테스트를 실행하여 모든 코드가 올바르게 동작하는지 확인하세요.
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// You could create an env var MY_NAME with
|
||||
// 환경 변수 MY_NAME을 다음과 같이 생성할 수 있습니다
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// Then you could use it with other programs, like
|
||||
// 그런 다음 다른 프로그램과 함께 사용할 수 있습니다. 예:
|
||||
$ echo "Hello $MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
@@ -37,10 +37,10 @@ Hello Wade Wilson
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Create an env var MY_NAME
|
||||
// 환경 변수 MY_NAME 생성
|
||||
$ $Env:MY_NAME = "Wade Wilson"
|
||||
|
||||
// Use it with other programs, like
|
||||
// 다른 프로그램과 함께 사용하기. 예:
|
||||
$ echo "Hello $Env:MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
@@ -78,20 +78,20 @@ print(f"Hello {name} from Python")
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Here we don't set the env var yet
|
||||
// 여기서는 아직 환경 변수를 설정하지 않습니다
|
||||
$ python main.py
|
||||
|
||||
// As we didn't set the env var, we get the default value
|
||||
// 환경 변수를 설정하지 않았으므로 기본값이 사용됩니다
|
||||
|
||||
Hello World from Python
|
||||
|
||||
// But if we create an environment variable first
|
||||
// 하지만 먼저 환경 변수를 생성하면
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// And then call the program again
|
||||
// 그리고 프로그램을 다시 실행하면
|
||||
$ python main.py
|
||||
|
||||
// Now it can read the environment variable
|
||||
// 이제 환경 변수를 읽을 수 있습니다
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
```
|
||||
@@ -105,20 +105,20 @@ Hello Wade Wilson from Python
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Here we don't set the env var yet
|
||||
// 여기서는 아직 환경 변수를 설정하지 않습니다
|
||||
$ python main.py
|
||||
|
||||
// As we didn't set the env var, we get the default value
|
||||
// 환경 변수를 설정하지 않았으므로 기본값이 사용됩니다
|
||||
|
||||
Hello World from Python
|
||||
|
||||
// But if we create an environment variable first
|
||||
// 하지만 먼저 환경 변수를 생성하면
|
||||
$ $Env:MY_NAME = "Wade Wilson"
|
||||
|
||||
// And then call the program again
|
||||
// 그리고 프로그램을 다시 실행하면
|
||||
$ python main.py
|
||||
|
||||
// Now it can read the environment variable
|
||||
// 이제 환경 변수를 읽을 수 있습니다
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
```
|
||||
@@ -136,14 +136,14 @@ Hello Wade Wilson from Python
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Create an env var MY_NAME in line for this program call
|
||||
// 이 프로그램 호출을 위해 같은 줄에서 환경 변수 MY_NAME 생성
|
||||
$ MY_NAME="Wade Wilson" python main.py
|
||||
|
||||
// Now it can read the environment variable
|
||||
// 이제 환경 변수를 읽을 수 있습니다
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
|
||||
// The env var no longer exists afterwards
|
||||
// 이후에는 해당 환경 변수가 존재하지 않습니다
|
||||
$ python main.py
|
||||
|
||||
Hello World from Python
|
||||
|
||||
+44
-52
@@ -1,83 +1,75 @@
|
||||
# FastAPI CLI
|
||||
# FastAPI CLI { #fastapi-cli }
|
||||
|
||||
**FastAPI CLI**는 FastAPI 애플리케이션을 실행하고, 프로젝트를 관리하는 등 다양한 작업을 수행할 수 있는 커맨드 라인 프로그램입니다.
|
||||
**FastAPI CLI**는 FastAPI 애플리케이션을 서빙하고, FastAPI 프로젝트를 관리하는 등 다양한 작업에 사용할 수 있는 커맨드 라인 프로그램입니다.
|
||||
|
||||
FastAPI를 설치할 때 (예: `pip install "fastapi[standard]"` 명령어를 사용할 경우), `fastapi-cli`라는 패키지가 포함됩니다. 이 패키지는 터미널에서 사용할 수 있는 `fastapi` 명령어를 제공합니다.
|
||||
FastAPI를 설치할 때(예: `pip install "fastapi[standard]"`), `fastapi-cli`라는 패키지가 포함되며, 이 패키지는 터미널에서 `fastapi` 명령어를 제공합니다.
|
||||
|
||||
개발용으로 FastAPI 애플리케이션을 실행하려면 다음과 같이 `fastapi dev` 명령어를 사용할 수 있습니다:
|
||||
개발용으로 FastAPI 애플리케이션을 실행하려면 `fastapi dev` 명령어를 사용할 수 있습니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
|
||||
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
|
||||
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
|
||||
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
|
||||
│ │
|
||||
│ 🐍 main.py │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
|
||||
<font color="#3465A4">INFO </font> Found importable FastAPI app
|
||||
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>
|
||||
|
||||
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
|
||||
│ │
|
||||
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
|
||||
following code:
|
||||
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
|
||||
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
|
||||
<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>
|
||||
|
||||
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
|
||||
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
|
||||
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
|
||||
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
|
||||
<font color="#4E9A06">INFO</font>: Waiting for application startup.
|
||||
<font color="#4E9A06">INFO</font>: Application startup complete.
|
||||
<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://127.0.0.1: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://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
|
||||
quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</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.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`fastapi`라고 불리는 명령어 프로그램은 **FastAPI CLI**입니다.
|
||||
`fastapi`라고 불리는 커맨드 라인 프로그램은 **FastAPI CLI**입니다.
|
||||
|
||||
FastAPI CLI는 Python 프로그램의 경로(예: `main.py`)를 인수로 받아, `FastAPI` 인스턴스(일반적으로 `app`으로 명명)를 자동으로 감지하고 올바른 임포트 과정을 결정한 후 이를 실행합니다.
|
||||
FastAPI CLI는 Python 프로그램의 경로(예: `main.py`)를 받아 `FastAPI` 인스턴스(일반적으로 `app`으로 이름을 붙임)를 자동으로 감지하고, 올바른 임포트 과정을 결정한 다음 서빙합니다.
|
||||
|
||||
프로덕션 환경에서는 `fastapi run` 명령어를 사용합니다. 🚀
|
||||
프로덕션에서는 대신 `fastapi run`을 사용합니다. 🚀
|
||||
|
||||
내부적으로, **FastAPI CLI**는 고성능의, 프로덕션에 적합한, ASGI 서버인 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>을 사용합니다. 😎
|
||||
내부적으로 **FastAPI CLI**는 고성능의, 프로덕션에 적합한 ASGI 서버인 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>을 사용합니다. 😎
|
||||
|
||||
## `fastapi dev`
|
||||
## `fastapi dev` { #fastapi-dev }
|
||||
|
||||
`fastapi dev` 명령을 실행하면 개발 모드가 시작됩니다.
|
||||
`fastapi dev`를 실행하면 개발 모드가 시작됩니다.
|
||||
|
||||
기본적으로 **자동 재시작(auto-reload)** 기능이 활성화되어, 코드에 변경이 생기면 서버를 자동으로 다시 시작합니다. 하지만 이 기능은 리소스를 많이 사용하며, 비활성화했을 때보다 안정성이 떨어질 수 있습니다. 따라서 개발 환경에서만 사용하는 것이 좋습니다. 또한, 서버는 컴퓨터가 자체적으로 통신할 수 있는 IP 주소(`localhost`)인 `127.0.0.1`에서 연결을 대기합니다.
|
||||
기본적으로 **auto-reload**가 활성화되어 코드에 변경이 생기면 서버를 자동으로 다시 로드합니다. 이는 리소스를 많이 사용하며, 비활성화했을 때보다 안정성이 떨어질 수 있습니다. 개발 환경에서만 사용해야 합니다. 또한 컴퓨터가 자신과만 통신하기 위한(`localhost`) IP인 `127.0.0.1`에서 연결을 대기합니다.
|
||||
|
||||
## `fastapi run`
|
||||
## `fastapi run` { #fastapi-run }
|
||||
|
||||
`fastapi run` 명령을 실행하면 기본적으로 프로덕션 모드로 FastAPI가 시작됩니다.
|
||||
`fastapi run`을 실행하면 기본적으로 프로덕션 모드로 FastAPI가 시작됩니다.
|
||||
|
||||
기본적으로 **자동 재시작(auto-reload)** 기능이 비활성화되어 있습니다. 또한, 사용 가능한 모든 IP 주소인 `0.0.0.0`에서 연결을 대기하므로 해당 컴퓨터와 통신할 수 있는 모든 사람이 공개적으로 액세스할 수 있습니다. 이는 일반적으로 컨테이너와 같은 프로덕션 환경에서 실행하는 방법입니다.
|
||||
기본적으로 **auto-reload**는 비활성화되어 있습니다. 또한 사용 가능한 모든 IP 주소를 의미하는 `0.0.0.0`에서 연결을 대기하므로, 해당 컴퓨터와 통신할 수 있는 누구에게나 공개적으로 접근 가능해집니다. 보통 프로덕션에서는 이렇게 실행하며, 예를 들어 컨테이너에서 이런 방식으로 실행합니다.
|
||||
|
||||
애플리케이션을 배포하는 방식에 따라 다르지만, 대부분 "종료 프록시(termination proxy)"를 활용해 HTTPS를 처리하는 것이 좋습니다. 배포 서비스 제공자가 이 작업을 대신 처리해줄 수도 있고, 직접 설정해야 할 수도 있습니다.
|
||||
대부분의 경우 위에 "termination proxy"를 두고 HTTPS를 처리하게(그리고 처리해야) 됩니다. 이는 애플리케이션을 배포하는 방식에 따라 달라지며, 제공자가 이 작업을 대신 처리해줄 수도 있고 직접 설정해야 할 수도 있습니다.
|
||||
|
||||
/// tip
|
||||
/// tip | 팁
|
||||
|
||||
자세한 내용은 [deployment documentation](deployment/index.md){.internal-link target=\_blank}에서 확인할 수 있습니다.
|
||||
자세한 내용은 [배포 문서](deployment/index.md){.internal-link target=_blank}에서 확인할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
+88
-88
@@ -1,43 +1,43 @@
|
||||
# 기능
|
||||
# 기능 { #features }
|
||||
|
||||
## FastAPI의 기능
|
||||
## FastAPI의 기능 { #fastapi-features }
|
||||
|
||||
**FastAPI**는 다음과 같은 기능을 제공합니다:
|
||||
|
||||
### 개방형 표준을 기반으로
|
||||
### 개방형 표준을 기반으로 { #based-on-open-standards }
|
||||
|
||||
* <abbr title="엔드포인트, 라우트로도 알려져 있습니다">경로</abbr><abbr title="POST, GET, PUT, DELETE와 같은 HTTP 메소드로 알려져 있습니다">작동</abbr>, 매개변수, 본문 요청, 보안 그 외의 선언을 포함한 API 생성을 위한 <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a>
|
||||
* <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (OpenAPI 자체가 JSON Schema를 기반으로 하고 있습니다)를 사용한 자동 데이터 모델 문서화.
|
||||
* 단순히 떠올려서 덧붙인 기능이 아닙니다. 세심한 검토를 거친 후, 이러한 표준을 기반으로 설계되었습니다.
|
||||
* 이는 또한 다양한 언어로 자동적인 **클라이언트 코드 생성**을 사용할 수 있게 지원합니다.
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a>: <dfn title="또한 다음으로도 불림: 엔드포인트, 라우트">경로</dfn> <dfn title="HTTP 메소드(POST, GET, PUT, DELETE 등)로도 알려짐">처리</dfn>, 매개변수, 요청 본문, 보안 등의 선언을 포함하여 API를 생성합니다.
|
||||
* <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a>를 사용한 자동 데이터 모델 문서화(OpenAPI 자체가 JSON Schema를 기반으로 하기 때문입니다).
|
||||
* 단순히 떠올려서 덧붙인 레이어가 아니라, 세심한 검토를 거친 뒤 이러한 표준을 중심으로 설계되었습니다.
|
||||
* 이는 또한 다양한 언어로 자동 **클라이언트 코드 생성**을 사용할 수 있게 해줍니다.
|
||||
|
||||
### 문서 자동화
|
||||
### 문서 자동화 { #automatic-docs }
|
||||
|
||||
대화형 API 문서와 웹 탐색 유저 인터페이스를 제공합니다. 프레임워크가 OpenAPI를 기반으로 하기에, 2가지 옵션이 기본적으로 들어간 여러 옵션이 존재합니다.
|
||||
대화형 API 문서와 탐색용 웹 사용자 인터페이스를 제공합니다. 프레임워크가 OpenAPI를 기반으로 하기에 여러 옵션이 있으며, 기본으로 2가지가 포함됩니다.
|
||||
|
||||
* 대화형 탐색 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>를 이용해, 브라우저에서 바로 여러분의 API를 호출하거나 테스트할 수 있습니다.
|
||||
* 대화형 탐색이 가능한 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>로 브라우저에서 직접 API를 호출하고 테스트할 수 있습니다.
|
||||
|
||||

|
||||
|
||||
* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>을 이용해 API 문서화를 대체할 수 있습니다.
|
||||
* <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>을 이용한 대체 API 문서화.
|
||||
|
||||

|
||||
|
||||
### 그저 현대 파이썬
|
||||
### 그저 현대 파이썬 { #just-modern-python }
|
||||
|
||||
(Pydantic 덕분에) FastAPI는 표준 **파이썬 3.6 타입** 선언에 기반하고 있습니다. 새로 배울 문법이 없습니다. 그저 표준적인 현대 파이썬입니다.
|
||||
( Pydantic 덕분에) 모든 것이 표준 **Python 타입** 선언을 기반으로 합니다. 새로 배울 문법이 없습니다. 그저 표준적인 현대 파이썬입니다.
|
||||
|
||||
만약 여러분이 파이썬 타입을 어떻게 사용하는지에 대한 2분 정도의 복습이 필요하다면 (비록 여러분이 FastAPI를 사용하지 않는다 하더라도), 다음의 짧은 자습서를 확인하세요: [파이썬 타입](python-types.md){.internal-link target=\_blank}.
|
||||
Python 타입을 어떻게 사용하는지 2분 정도 복습이 필요하다면(FastAPI를 사용하지 않더라도), 다음의 짧은 자습서를 확인하세요: [Python 타입](python-types.md){.internal-link target=_blank}.
|
||||
|
||||
여러분은 타입을 이용한 표준 파이썬을 다음과 같이 적을 수 있습니다:
|
||||
여러분은 타입이 있는 표준 Python을 다음과 같이 작성합니다:
|
||||
|
||||
```Python
|
||||
from datetime import date
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
# 변수를 str로 선언
|
||||
# 그 후 함수 안에서 편집기 지원을 받으세요
|
||||
# 변수를 str로 선언합니다
|
||||
# 그리고 함수 내부에서 편집기 지원을 받습니다
|
||||
def main(user_id: str):
|
||||
return user_id
|
||||
|
||||
@@ -49,7 +49,7 @@ class User(BaseModel):
|
||||
joined: date
|
||||
```
|
||||
|
||||
위의 코드는 다음과 같이 사용될 수 있습니다:
|
||||
그 다음 다음과 같이 사용할 수 있습니다:
|
||||
|
||||
```Python
|
||||
my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
|
||||
@@ -65,23 +65,23 @@ my_second_user: User = User(**second_user_data)
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`**second_user_data`가 뜻하는 것:
|
||||
`**second_user_data`는 다음을 의미합니다:
|
||||
|
||||
`second_user_data` 딕셔너리의 키와 값을 키-값 인자로서 바로 넘겨줍니다. 다음과 동일합니다: `User(id=4, name="Mary", joined="2018-11-30")`
|
||||
`second_user_data` `dict`의 키와 값을 키-값 인자로서 바로 넘겨주는 것으로, 다음과 동일합니다: `User(id=4, name="Mary", joined="2018-11-30")`
|
||||
|
||||
///
|
||||
|
||||
### 편집기 지원
|
||||
### 편집기 지원 { #editor-support }
|
||||
|
||||
모든 프레임워크는 사용하기 쉽고 직관적으로 설계되었으며, 좋은 개발 경험을 보장하기 위해 개발을 시작하기도 전에 모든 결정들은 여러 편집기에서 테스트됩니다.
|
||||
프레임워크 전체는 사용하기 쉽고 직관적으로 설계되었으며, 최고의 개발 경험을 보장하기 위해 개발을 시작하기도 전에 모든 결정은 여러 편집기에서 테스트되었습니다.
|
||||
|
||||
최근 파이썬 개발자 설문조사에서 <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">"자동 완성"이 가장 많이 사용되는 기능</a>이라는 것이 밝혀졌습니다.
|
||||
Python 개발자 설문조사에서 <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">가장 많이 사용되는 기능 중 하나가 "자동 완성"이라는 점</a>이 분명합니다.
|
||||
|
||||
**FastAPI** 프레임워크의 모든 부분은 이를 충족하기 위해 설계되었습니다. 자동완성은 어느 곳에서나 작동합니다.
|
||||
**FastAPI** 프레임워크 전체는 이를 만족하기 위해 만들어졌습니다. 자동 완성은 어디서나 작동합니다.
|
||||
|
||||
여러분은 문서로 다시 돌아올 일이 거의 없을 겁니다.
|
||||
문서로 다시 돌아올 일은 거의 없을 것입니다.
|
||||
|
||||
다음은 편집기가 어떻게 여러분을 도와주는지 보여줍니다:
|
||||
편집기가 여러분을 어떻게 도와줄 수 있는지 살펴보세요:
|
||||
|
||||
* <a href="https://code.visualstudio.com/" class="external-link" target="_blank">Visual Studio Code</a>에서:
|
||||
|
||||
@@ -91,111 +91,111 @@ my_second_user: User = User(**second_user_data)
|
||||
|
||||

|
||||
|
||||
여러분이 이전에 불가능하다고 고려했던 코드도 완성할 수 있을 겁니다. 예를 들어, 요청에서 전달되는 (중첩될 수도 있는)JSON 본문 내부에 있는 `price` 키입니다.
|
||||
이전에 불가능하다고 생각했을 코드에서도 자동 완성을 받을 수 있습니다. 예를 들어, 요청에서 전달되는(중첩될 수도 있는) JSON 본문 내부의 `price` 키 같은 경우입니다.
|
||||
|
||||
잘못된 키 이름을 적을 일도, 문서를 왔다 갔다할 일도 없으며, 혹은 마지막으로 `username` 또는 `user_name`을 사용했는지 찾기 위해 위 아래로 스크롤할 일도 없습니다.
|
||||
더 이상 잘못된 키 이름을 입력하거나, 문서 사이를 왔다 갔다 하거나, `username`을 썼는지 `user_name`을 썼는지 찾으려고 위아래로 스크롤할 필요가 없습니다.
|
||||
|
||||
### 토막 정보
|
||||
### 간결함 { #short }
|
||||
|
||||
어느 곳에서나 선택적 구성이 가능한 모든 것에 합리적인 기본값이 설정되어 있습니다. 모든 매개변수는 여러분이 필요하거나, 원하는 API를 정의하기 위해 미세하게 조정할 수 있습니다.
|
||||
선택적 구성을 어디서나 할 수 있도록 하면서도, 모든 것에 합리적인 **기본값**이 설정되어 있습니다. 모든 매개변수는 필요한 작업을 하거나 필요한 API를 정의하기 위해 미세하게 조정할 수 있습니다.
|
||||
|
||||
하지만 기본적으로 모든 것이 "그냥 작동합니다".
|
||||
하지만 기본적으로 모든 것이 **"그냥 작동합니다"**.
|
||||
|
||||
### 검증
|
||||
### 검증 { #validation }
|
||||
|
||||
* 다음을 포함한, 대부분의 (혹은 모든?) 파이썬 **데이터 타입** 검증할 수 있습니다:
|
||||
* 다음을 포함해 대부분(혹은 전부?)의 Python **데이터 타입**에 대한 검증:
|
||||
* JSON 객체 (`dict`).
|
||||
* 아이템 타입을 정의하는 JSON 배열 (`list`).
|
||||
* 최소 길이와 최대 길이를 정의하는 문자열 (`str`) 필드.
|
||||
* 최솟값과 최댓값을 가지는 숫자 (`int`, `float`), 그 외.
|
||||
* 최소/최대 길이를 정의하는 문자열(`str`) 필드.
|
||||
* 최소/최대 값을 가지는 숫자(`int`, `float`) 등.
|
||||
|
||||
* 다음과 같이 더욱 이색적인 타입에 대해 검증할 수 있습니다:
|
||||
* 다음과 같은 좀 더 이색적인 타입에 대한 검증:
|
||||
* URL.
|
||||
* 이메일.
|
||||
* Email.
|
||||
* UUID.
|
||||
* ...다른 것들.
|
||||
* ...그 외.
|
||||
|
||||
모든 검증은 견고하면서 잘 확립된 **Pydantic**에 의해 처리됩니다.
|
||||
모든 검증은 잘 확립되어 있고 견고한 **Pydantic**이 처리합니다.
|
||||
|
||||
### 보안과 인증
|
||||
### 보안과 인증 { #security-and-authentication }
|
||||
|
||||
보안과 인증이 통합되어 있습니다. 데이터베이스나 데이터 모델과의 타협없이 사용할 수 있습니다.
|
||||
보안과 인증이 통합되어 있습니다. 데이터베이스나 데이터 모델과 타협하지 않습니다.
|
||||
|
||||
다음을 포함하는, 모든 보안 스키마가 OpenAPI에 정의되어 있습니다.
|
||||
다음을 포함해 OpenAPI에 정의된 모든 보안 스키마:
|
||||
|
||||
* HTTP Basic.
|
||||
* **OAuth2** (**JWT tokens** 또한 포함). [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=\_blank}에 있는 자습서를 확인해 보세요.
|
||||
* **OAuth2**(**JWT tokens** 또한 포함). [JWT를 사용한 OAuth2](tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 자습서를 확인해 보세요.
|
||||
* 다음에 들어 있는 API 키:
|
||||
* 헤더.
|
||||
* 매개변수.
|
||||
* 쿠키 및 그 외.
|
||||
* 쿼리 매개변수.
|
||||
* 쿠키 등.
|
||||
|
||||
추가적으로 (**세션 쿠키**를 포함한) 모든 보안 기능은 Starlette에 있습니다.
|
||||
추가로 Starlette의 모든 보안 기능(**세션 쿠키** 포함)도 제공합니다.
|
||||
|
||||
모두 재사용할 수 있는 도구와 컴포넌트로 만들어져 있어 여러분의 시스템, 데이터 저장소, 관계형 및 NoSQL 데이터베이스 등과 쉽게 통합할 수 있습니다.
|
||||
모두 재사용 가능한 도구와 컴포넌트로 만들어져 있어, 여러분의 시스템, 데이터 저장소, 관계형 및 NoSQL 데이터베이스 등과 쉽게 통합할 수 있습니다.
|
||||
|
||||
### 의존성 주입
|
||||
### 의존성 주입 { #dependency-injection }
|
||||
|
||||
FastAPI는 사용하기 매우 간편하지만, 엄청난 <abbr title='"컴포넌트", "자원", "서비스", "제공자"로도 알려진'><strong>의존성 주입</strong></abbr>시스템을 포함하고 있습니다.
|
||||
FastAPI는 사용하기 매우 쉽지만, 매우 강력한 <dfn title='또한 다음으로도 불림: "컴포넌트", "자원", "서비스", "제공자"'><strong>의존성 주입</strong></dfn> 시스템을 포함하고 있습니다.
|
||||
|
||||
* 의존성은 의존성을 가질수도 있어, 이를 통해 의존성의 계층이나 **의존성의 "그래프"**를 형성합니다.
|
||||
* 모든 것이 프레임워크에 의해 **자동적으로 처리됩니다**.
|
||||
* 모든 의존성은 요청에서 데이터를 요구하여 자동 문서화와 **경로 작동 제약을 강화할 수 있습니다**.
|
||||
* 의존성에서 정의된 _경로 작동_ 매개변수에 대해서도 **자동 검증**이 이루어 집니다.
|
||||
* 복잡한 사용자의 인증 시스템, **데이터베이스 연결**, 등등을 지원합니다.
|
||||
* 데이터베이스, 프론트엔드 등과 관련되어 **타협하지 않아도 됩니다**. 하지만 그 모든 것과 쉽게 통합이 가능합니다.
|
||||
* 의존성도 의존성을 가질 수 있어, 의존성의 계층 또는 **의존성의 "그래프"**를 생성합니다.
|
||||
* 모든 것이 프레임워크에 의해 **자동으로 처리됩니다**.
|
||||
* 모든 의존성은 요청에서 데이터를 요구할 수 있으며, **경로 처리** 제약과 자동 문서화를 강화할 수 있습니다.
|
||||
* 의존성에 정의된 *경로 처리* 매개변수에 대해서도 **자동 검증**을 합니다.
|
||||
* 복잡한 사용자 인증 시스템, **데이터베이스 연결** 등을 지원합니다.
|
||||
* 데이터베이스, 프론트엔드 등과 **타협하지 않습니다**. 하지만 모두와 쉽게 통합할 수 있습니다.
|
||||
|
||||
### 제한 없는 "플러그인"
|
||||
### 제한 없는 "플러그인" { #unlimited-plug-ins }
|
||||
|
||||
또는 다른 방법으로, 그것들을 사용할 필요 없이 필요한 코드만 임포트할 수 있습니다.
|
||||
또 다른 방식으로는, 그것들이 필요 없습니다. 필요한 코드를 임포트해서 사용하면 됩니다.
|
||||
|
||||
어느 통합도 (의존성과 함께) 사용하기 쉽게 설계되어 있어, *경로 작동*에 사용된 것과 동일한 구조와 문법을 사용하여 2줄의 코드로 여러분의 어플리케이션에 사용할 "플러그인"을 만들 수 있습니다.
|
||||
어떤 통합이든(의존성과 함께) 사용하기 매우 간단하도록 설계되어 있어, *경로 처리*에 사용된 것과 동일한 구조와 문법을 사용해 2줄의 코드로 애플리케이션용 "플러그인"을 만들 수 있습니다.
|
||||
|
||||
### 테스트 결과
|
||||
### 테스트됨 { #tested }
|
||||
|
||||
* 100% <abbr title="자동적으로 테스트된 코드의 양">테스트 범위</abbr>.
|
||||
* 100% <abbr title="파이썬의 타입 어노테이션, 이를 통해 여러분의 편집기와 외부 도구는 여러분에게 더 나은 지원을 할 수 있습니다">타입이 명시된</abbr> 코드 베이스.
|
||||
* 상용 어플리케이션에서의 사용.
|
||||
* 100% <dfn title="자동으로 테스트되는 코드의 양">테스트 커버리지</dfn>.
|
||||
* 100% <dfn title="Python 타입 어노테이션으로, 이를 통해 편집기와 외부 도구가 더 나은 지원을 제공할 수 있습니다">타입 어노테이션</dfn> 코드 베이스.
|
||||
* 프로덕션 애플리케이션에서 사용됩니다.
|
||||
|
||||
## Starlette 기능
|
||||
## Starlette 기능 { #starlette-features }
|
||||
|
||||
**FastAPI**는 <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a>를 기반으로 구축되었으며, 이와 완전히 호환됩니다. 따라서, 여러분이 보유하고 있는 어떤 추가적인 Starlette 코드도 작동할 것입니다.
|
||||
**FastAPI**는 <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a>와 완전히 호환되며(또한 이를 기반으로 합니다). 따라서 추가로 가지고 있는 Starlette 코드도 모두 동작합니다.
|
||||
|
||||
`FastAPI`는 실제로 `Starlette`의 하위 클래스입니다. 그래서, 여러분이 이미 Starlette을 알고 있거나 사용하고 있으면, 대부분의 기능이 같은 방식으로 작동할 것입니다.
|
||||
`FastAPI`는 실제로 `Starlette`의 하위 클래스입니다. 그래서 Starlette을 이미 알고 있거나 사용하고 있다면, 대부분의 기능이 같은 방식으로 동작할 것입니다.
|
||||
|
||||
**FastAPI**를 사용하면 여러분은 **Starlette**의 기능 대부분을 얻게 될 것입니다(FastAPI가 단순히 Starlette를 강화했기 때문입니다):
|
||||
**FastAPI**를 사용하면 **Starlette**의 모든 기능을 얻게 됩니다(FastAPI는 Starlette에 강력한 기능을 더한 것입니다):
|
||||
|
||||
* 아주 인상적인 성능. 이는 <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">**NodeJS**와 **Go**와 동등하게 사용 가능한 가장 빠른 파이썬 프레임워크 중 하나입니다</a>.
|
||||
* 정말 인상적인 성능. <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">**NodeJS**와 **Go**에 버금가는, 사용 가능한 가장 빠른 Python 프레임워크 중 하나입니다</a>.
|
||||
* **WebSocket** 지원.
|
||||
* 프로세스 내의 백그라운드 작업.
|
||||
* 시작과 종료 이벤트.
|
||||
* 프로세스 내 백그라운드 작업.
|
||||
* 시작 및 종료 이벤트.
|
||||
* HTTPX 기반 테스트 클라이언트.
|
||||
* **CORS**, GZip, 정적 파일, 스트리밍 응답.
|
||||
* **세션과 쿠키** 지원.
|
||||
* 100% 테스트 범위.
|
||||
* 100% 타입이 명시된 코드 베이스.
|
||||
* 100% test coverage.
|
||||
* 100% type annotated codebase.
|
||||
|
||||
## Pydantic 기능
|
||||
## Pydantic 기능 { #pydantic-features }
|
||||
|
||||
**FastAPI**는 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>을 기반으로 하며 Pydantic과 완벽하게 호환됩니다. 그래서 어느 추가적인 Pydantic 코드를 여러분이 가지고 있든 작동할 것입니다.
|
||||
**FastAPI**는 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>과 완벽하게 호환되며(또한 이를 기반으로 합니다). 따라서 추가로 가지고 있는 Pydantic 코드도 모두 동작합니다.
|
||||
|
||||
Pydantic을 기반으로 하는, 데이터베이스를 위한 <abbr title="Object-Relational Mapper">ORM</abbr>, <abbr title="Object-Document Mapper">ODM</abbr>을 포함한 외부 라이브러리를 포함합니다.
|
||||
데이터베이스를 위한 <abbr title="Object-Relational Mapper - 객체-관계 매퍼">ORM</abbr>, <abbr title="Object-Document Mapper - 객체-문서 매퍼">ODM</abbr>과 같은, Pydantic을 기반으로 하는 외부 라이브러리도 포함합니다.
|
||||
|
||||
이는 모든 것이 자동으로 검증되기 때문에, 많은 경우에서 요청을 통해 얻은 동일한 객체를, **직접 데이터베이스로** 넘겨줄 수 있습니다.
|
||||
이는 모든 것이 자동으로 검증되기 때문에, 많은 경우 요청에서 얻은 동일한 객체를 **직접 데이터베이스로** 넘겨줄 수 있다는 의미이기도 합니다.
|
||||
|
||||
반대로도 마찬가지이며, 많은 경우에서 여러분은 **직접 클라이언트로** 그저 객체를 넘겨줄 수 있습니다.
|
||||
반대로도 마찬가지이며, 많은 경우 데이터베이스에서 얻은 객체를 **직접 클라이언트로** 그대로 넘겨줄 수 있습니다.
|
||||
|
||||
**FastAPI**를 사용하면 (모든 데이터 처리를 위해 FastAPI가 Pydantic을 기반으로 하기 있기에) **Pydantic**의 모든 기능을 얻게 됩니다:
|
||||
**FastAPI**를 사용하면(모든 데이터 처리를 위해 FastAPI가 Pydantic을 기반으로 하기에) **Pydantic**의 모든 기능을 얻게 됩니다:
|
||||
|
||||
* **어렵지 않은 언어**:
|
||||
* 새로운 스키마 정의 마이크로 언어를 배우지 않아도 됩니다.
|
||||
* 여러분이 파이썬 타입을 안다면, 여러분은 Pydantic을 어떻게 사용하는지 아는 겁니다.
|
||||
* 여러분의 **<abbr title="통합 개발 환경, 코드 편집기와 비슷합니다">IDE</abbr>/<abbr title="코드 에러를 확인하는 프로그램">린터</abbr>/뇌**와 잘 어울립니다:
|
||||
* Pydantic 데이터 구조는 단순 여러분이 정의한 클래스의 인스턴스이기 때문에, 자동 완성, 린팅, mypy 그리고 여러분의 직관까지 여러분의 검증된 데이터와 올바르게 작동합니다.
|
||||
* **No brainfuck**:
|
||||
* 새로운 스키마 정의 마이크로 언어를 배울 필요가 없습니다.
|
||||
* Python 타입을 알고 있다면 Pydantic 사용법도 알고 있는 것입니다.
|
||||
* 여러분의 **<abbr title="Integrated Development Environment - 통합 개발 환경: 코드 편집기와 비슷합니다">IDE</abbr>/<dfn title="코드 오류를 확인하는 프로그램">린터</dfn>/뇌**와 잘 어울립니다:
|
||||
* pydantic 데이터 구조는 여러분이 정의한 클래스 인스턴스일 뿐이므로, 자동 완성, 린팅, mypy, 그리고 직관까지도 검증된 데이터와 함께 제대로 작동합니다.
|
||||
* **복잡한 구조**를 검증합니다:
|
||||
* 계층적인 Pydantic 모델, 파이썬 `typing`의 `List`와 `Dict`, 그 외를 사용합니다.
|
||||
* 그리고 검증자는 복잡한 데이터 스키마를 명확하고 쉽게 정의 및 확인하며 JSON 스키마로 문서화합니다.
|
||||
* 여러분은 깊게 **중첩된 JSON** 객체를 가질 수 있으며, 이 객체 모두 검증하고 설명을 붙일 수 있습니다.
|
||||
* **확장 가능성**:
|
||||
* Pydantic은 사용자 정의 데이터 타입을 정의할 수 있게 하거나, 검증자 데코레이터가 붙은 모델의 메소드를 사용하여 검증을 확장할 수 있습니다.
|
||||
* 100% 테스트 범위.
|
||||
* 계층적인 Pydantic 모델, Python `typing`의 `List`와 `Dict` 등을 사용합니다.
|
||||
* 그리고 validator는 복잡한 데이터 스키마를 명확하고 쉽게 정의하고, 검사하고, JSON Schema로 문서화할 수 있게 해줍니다.
|
||||
* 깊게 **중첩된 JSON** 객체를 가질 수 있으며, 이를 모두 검증하고 주석을 달 수 있습니다.
|
||||
* **확장 가능**:
|
||||
* Pydantic은 사용자 정의 데이터 타입을 정의할 수 있게 하거나, validator decorator가 붙은 모델 메서드로 검증을 확장할 수 있습니다.
|
||||
* 100% test coverage.
|
||||
|
||||
+93
-106
@@ -1,4 +1,4 @@
|
||||
# FastAPI 지원 - 도움 받기
|
||||
# FastAPI 지원 - 도움 받기 { #help-fastapi-get-help }
|
||||
|
||||
**FastAPI** 가 마음에 드시나요?
|
||||
|
||||
@@ -10,9 +10,9 @@ FastAPI, 다른 사용자, 개발자를 응원하고 싶으신가요?
|
||||
|
||||
또한 도움을 받을 수 있는 방법도 몇 가지 있습니다.
|
||||
|
||||
## 뉴스레터 구독
|
||||
## 뉴스레터 구독 { #subscribe-to-the-newsletter }
|
||||
|
||||
[**FastAPI and friends** 뉴스레터](newsletter.md){.internal-link target=\_blank}를 구독하여 최신 정보를 유지할 수 있습니다:
|
||||
(자주 발송되지 않는) [**FastAPI and friends** 뉴스레터](newsletter.md){.internal-link target=_blank}를 구독하여 최신 정보를 유지할 수 있습니다:
|
||||
|
||||
* FastAPI and friends에 대한 뉴스 🚀
|
||||
* 가이드 📝
|
||||
@@ -20,65 +20,65 @@ FastAPI, 다른 사용자, 개발자를 응원하고 싶으신가요?
|
||||
* 획기적인 변화 🚨
|
||||
* 팁과 요령 ✅
|
||||
|
||||
## 트위터에서 FastAPI 팔로우하기
|
||||
## X(Twitter)에서 FastAPI 팔로우하기 { #follow-fastapi-on-x-twitter }
|
||||
|
||||
<a href="https://x.com/fastapi" class="external-link" target="_blank">**X (Twitter)**의 @fastapi를 팔로우</a>하여 **FastAPI** 에 대한 최신 뉴스를 얻을 수 있습니다. 🐦
|
||||
|
||||
## Star **FastAPI** in GitHub
|
||||
## GitHub에서 **FastAPI**에 Star 주기 { #star-fastapi-in-github }
|
||||
|
||||
GitHub에서 FastAPI에 "star"를 붙일 수 있습니다 (오른쪽 상단의 star 버튼을 클릭): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ⭐️
|
||||
|
||||
스타를 늘림으로써, 다른 사용자들이 좀 더 쉽게 찾을 수 있고, 많은 사람들에게 유용한 것임을 나타낼 수 있습니다.
|
||||
|
||||
## GitHub 저장소에서 릴리즈 확인
|
||||
## 릴리즈 확인을 위해 GitHub 저장소 보기 { #watch-the-github-repository-for-releases }
|
||||
|
||||
GitHub에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 watch 버튼을 클릭): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
GitHub에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 "watch" 버튼을 클릭): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
|
||||
여기서 "Releases only"을 선택할 수 있습니다.
|
||||
여기서 "Releases only"를 선택할 수 있습니다.
|
||||
|
||||
이렇게하면, **FastAPI** 의 버그 수정 및 새로운 기능의 구현 등의 새로운 자료 (최신 버전)이 있을 때마다 (이메일) 통지를 받을 수 있습니다.
|
||||
이렇게하면, **FastAPI** 의 버그 수정 및 새로운 기능의 구현 등의 새로운 릴리즈(새 버전)가 있을 때마다 (이메일) 통지를 받을 수 있습니다.
|
||||
|
||||
## 개발자와의 연결
|
||||
## 개발자와의 연결 { #connect-with-the-author }
|
||||
|
||||
<a href="https://tiangolo.com" class="external-link" target="_blank">개발자(Sebastián Ramírez / `tiangolo`)</a>와 연락을 취할 수 있습니다.
|
||||
개발자(작성자)인 <a href="https://tiangolo.com" class="external-link" target="_blank">저(Sebastián Ramírez / `tiangolo`)</a>와 연락을 취할 수 있습니다.
|
||||
|
||||
여러분은 할 수 있습니다:
|
||||
|
||||
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">**GitHub**에서 팔로우하기.</a>.
|
||||
* 당신에게 도움이 될 저의 다른 오픈소스 프로젝트를 확인하십시오.
|
||||
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">**GitHub**에서 팔로우하기</a>.
|
||||
* 여러분에게 도움이 될 저의 다른 오픈소스 프로젝트를 확인하십시오.
|
||||
* 새로운 오픈소스 프로젝트를 만들었을 때 확인하려면 팔로우 하십시오.
|
||||
* <a href="https://x.com/tiangolo" class="external-link" target="_blank">**X (Twitter)**</a> 또는 <a href="https://fosstodon.org/@tiangolo" class="external-link" target="_blank">Mastodon</a>에서 팔로우하기.
|
||||
* FastAPI의 사용 용도를 알려주세요 (그것을 듣는 것을 좋아합니다).
|
||||
* 발표나 새로운 툴 출시 소식을 받아보십시오.
|
||||
* <a href="https://x.com/fastapi" class="external-link" target="_blank">**X (Twitter)**의 @fastapi를 팔로우</a> (별도 계정에서) 할 수 있습니다.
|
||||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">**LinkedIn**에서 팔로우하기.</a>.
|
||||
* 새로운 툴의 발표나 출시 소식을 받아보십시오. (단, X (Twitter)를 더 자주 사용합니다 🤷♂).
|
||||
* <a href="https://x.com/fastapi" class="external-link" target="_blank">X(Twitter)에서 @fastapi를 팔로우</a> (별도 계정에서) 할 수 있습니다.
|
||||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">**LinkedIn**에서 팔로우하기</a>.
|
||||
* 새로운 툴의 발표나 출시 소식을 받아보십시오 (단, X (Twitter)를 더 자주 사용합니다 🤷♂).
|
||||
* <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> 또는 <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>에서 제가 작성한 내용을 읽어 보십시오 (또는 팔로우).
|
||||
* 다른 기사나 아이디어들을 읽고, 제가 만들어왔던 툴에 대해서도 읽으십시오.
|
||||
* 새로운 기사를 읽기 위해 팔로우 하십시오.
|
||||
* 다른 아이디어와 기사들을 읽고, 제가 만들어왔던 툴에 대해서도 읽으십시오.
|
||||
* 새로운 내용을 게시할 때 읽기 위해 팔로우 하십시오.
|
||||
|
||||
## **FastAPI**에 대한 트윗
|
||||
## **FastAPI**에 대해 트윗하기 { #tweet-about-fastapi }
|
||||
|
||||
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**에 대해 트윗</a> 하고 FastAPI가 마음에 드는 이유를 알려주세요. 🎉
|
||||
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**에 대해 트윗</a> 하고 저와 다른 사람들에게 FastAPI가 마음에 드는 이유를 알려주세요. 🎉
|
||||
|
||||
**FastAPI**가 어떻게 사용되고 있는지, 어떤 점이 마음에 들었는지, 어떤 프로젝트/회사에서 사용하고 있는지 등에 대해 듣고 싶습니다.
|
||||
|
||||
## FastAPI에 투표하기
|
||||
## FastAPI에 투표하기 { #vote-for-fastapi }
|
||||
|
||||
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">Slant에서 **FastAPI** 에 대해 투표하십시오</a>.
|
||||
* <a href="https://alternativeto.net/software/fastapi/about/" class="external-link" target="_blank">AlternativeTo에서 **FastAPI** 에 대해 투표하십시오</a>.
|
||||
* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">StackShare에서 **FastAPI** 에 대해 투표하십시오</a>.
|
||||
* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">StackShare에서 **FastAPI**를 사용한다고 표시하세요</a>.
|
||||
|
||||
## GitHub의 이슈로 다른사람 돕기
|
||||
## GitHub에서 질문으로 다른 사람 돕기 { #help-others-with-questions-in-github }
|
||||
|
||||
다른 사람들의 질문에 도움을 줄 수 있습니다:
|
||||
|
||||
* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub 디스커션</a>
|
||||
* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub 이슈</a>
|
||||
* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
|
||||
* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
|
||||
|
||||
많은 경우, 여러분은 이미 그 질문에 대한 답을 알고 있을 수도 있습니다. 🤓
|
||||
|
||||
만약 많은 사람들의 문제를 도와준다면, 공식적인 [FastAPI 전문가](fastapi-people.md#fastapi-experts){.internal-link target=\_blank} 가 될 것입니다. 🎉
|
||||
만약 많은 사람들의 질문을 도와준다면, 공식적인 [FastAPI 전문가](fastapi-people.md#fastapi-experts){.internal-link target=_blank}가 될 것입니다. 🎉
|
||||
|
||||
가장 중요한 점은: 친절하려고 노력하는 것입니다. 사람들은 좌절감을 안고 오며, 많은 경우 최선의 방식으로 질문하지 않을 수도 있습니다. 하지만 최대한 친절하게 대하려고 노력하세요. 🤗
|
||||
|
||||
@@ -86,183 +86,170 @@ GitHub에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 watch 버
|
||||
|
||||
---
|
||||
|
||||
다른 사람들의 질문 (디스커션 또는 이슈에서) 해결을 도울 수 있는 방법은 다음과 같습니다.
|
||||
다른 사람들의 질문(디스커션 또는 이슈에서) 해결을 도울 수 있는 방법은 다음과 같습니다.
|
||||
|
||||
### 질문 이해하기
|
||||
### 질문 이해하기 { #understand-the-question }
|
||||
|
||||
* 질문하는 사람이 가진 **목적**과 사용 사례를 이해할 수 있는지 확인하세요.
|
||||
|
||||
* 질문 (대부분은 질문입니다)이 **명확**한지 확인하세요.
|
||||
* 그런 다음 질문(대부분은 질문입니다)이 **명확**한지 확인하세요.
|
||||
|
||||
* 많은 경우, 사용자가 가정한 해결책에 대한 질문을 하지만, 더 **좋은** 해결책이 있을 수 있습니다. 문제와 사용 사례를 더 잘 이해하면 더 나은 **대안적인 해결책**을 제안할 수 있습니다.
|
||||
* 많은 경우 사용자가 상상한 해결책에 대한 질문을 하지만, 더 **좋은** 해결책이 있을 수 있습니다. 문제와 사용 사례를 더 잘 이해하면 더 나은 **대안적인 해결책**을 제안할 수 있습니다.
|
||||
|
||||
* 질문을 이해할 수 없다면, 더 **자세한 정보**를 요청하세요.
|
||||
|
||||
### 문제 재현하기
|
||||
### 문제 재현하기 { #reproduce-the-problem }
|
||||
|
||||
대부분의 경우, 질문은 질문자의 **원본 코드**와 관련이 있습니다.
|
||||
대부분의 경우 그리고 대부분의 질문에서는 질문자의 **원본 코드**와 관련된 내용이 있습니다.
|
||||
|
||||
많은 경우, 코드의 일부만 복사해서 올리지만, 그것만으로는 **문제를 재현**하기에 충분하지 않습니다.
|
||||
|
||||
* 질문자에게 <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">최소한의 재현 가능한 예제</a>를 제공해달라고 요청하세요. 이렇게 하면 코드를 **복사-붙여넣기**하여 직접 실행하고, 동일한 오류나 동작을 확인하거나 사용 사례를 더 잘 이해할 수 있습니다.
|
||||
* 질문자에게 <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">최소한의 재현 가능한 예제</a>를 제공해달라고 요청할 수 있습니다. 이렇게 하면 코드를 **복사-붙여넣기**하여 로컬에서 실행하고, 질문자가 보고 있는 것과 동일한 오류나 동작을 확인하거나 사용 사례를 더 잘 이해할 수 있습니다.
|
||||
|
||||
* 너그러운 마음이 든다면, 문제 설명만을 기반으로 직접 **예제를 만들어**볼 수도 있습니다. 하지만, 이는 시간이 많이 걸릴 수 있으므로, 먼저 질문을 명확히 해달라고 요청하는 것이 좋습니다.
|
||||
* 너그러운 마음이 든다면, 문제 설명만을 기반으로 직접 **예제를 만들어**볼 수도 있습니다. 다만 이는 시간이 많이 걸릴 수 있으므로, 먼저 문제를 명확히 해달라고 요청하는 것이 더 나을 수 있다는 점을 기억하세요.
|
||||
|
||||
### 해결책 제안하기
|
||||
### 해결책 제안하기 { #suggest-solutions }
|
||||
|
||||
* 질문을 충분히 이해한 후에는 가능한 **답변**을 제공할 수 있습니다.
|
||||
|
||||
* 많은 경우, 질문자의 **근본적인 문제나 사용 사례**를 이해하는 것이 중요합니다. 그들이 시도하는 방법보다 더 나은 해결책이 있을 수 있기 때문입니다.
|
||||
* 많은 경우, 질문자의 **근본적인 문제나 사용 사례**를 이해하는 것이 더 좋습니다. 그들이 시도하는 방법보다 더 나은 해결책이 있을 수 있기 때문입니다.
|
||||
|
||||
### 해결 요청하기
|
||||
### 종료 요청하기 { #ask-to-close }
|
||||
|
||||
질문자가 답변을 확인하고 나면, 당신이 문제를 해결했을 가능성이 높습니다. 축하합니다, **당신은 영웅입니다**! 🦸
|
||||
질문자가 답변을 하면, 여러분이 문제를 해결했을 가능성이 높습니다. 축하합니다, **여러분은 영웅입니다**! 🦸
|
||||
|
||||
* 이제 문제를 해결했다면, 질문자에게 다음을 요청할 수 있습니다.
|
||||
|
||||
* GitHub 디스커션에서: 댓글을 **답변**으로 표시하도록 요청하세요.
|
||||
* GitHub 이슈에서: 이슈를 **닫아달라고** 요청하세요.
|
||||
* GitHub Discussions에서: 댓글을 **답변**으로 표시하도록 요청하세요.
|
||||
* GitHub Issues에서: 이슈를 **닫아달라고** 요청하세요.
|
||||
|
||||
## GitHub 저장소 보기
|
||||
## GitHub 저장소 보기 { #watch-the-github-repository }
|
||||
|
||||
GitHub에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 watch 버튼을 클릭): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
GitHub에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 "watch" 버튼을 클릭): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
|
||||
"Releases only" 대신 "Watching"을 선택하면, 새로운 이슈나 질문이 생성될 때 알림을 받을 수 있습니다. 또한, 특정하게 새로운 이슈, 디스커션, PR 등만 알림 받도록 설정할 수도 있습니다.
|
||||
"Releases only" 대신 "Watching"을 선택하면 누군가가 새 이슈나 질문을 만들 때 알림을 받게 됩니다. 또한 새 이슈, 디스커션, PR 등만 알림을 받도록 지정할 수도 있습니다.
|
||||
|
||||
그런 다음 이런 이슈들을 해결 할 수 있도록 도움을 줄 수 있습니다.
|
||||
그런 다음 이런 질문들을 해결하도록 도와줄 수 있습니다.
|
||||
|
||||
## 이슈 생성하기
|
||||
## 질문하기 { #ask-questions }
|
||||
|
||||
GitHub 저장소에 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">새로운 이슈 생성</a>을 할 수 있습니다, 예를들면 다음과 같습니다:
|
||||
GitHub 저장소에서 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">새 질문을 생성</a>할 수 있습니다. 예를 들면 다음과 같습니다:
|
||||
|
||||
* **질문**을 하거나 **문제**에 대해 질문합니다.
|
||||
* 새로운 **기능**을 제안 합니다.
|
||||
|
||||
**참고**: 만약 이슈를 생성한다면, 저는 여러분에게 다른 사람들을 도와달라고 부탁할 것입니다. 😉
|
||||
**참고**: 만약 이렇게 한다면, 저는 여러분에게 다른 사람들도 도와달라고 요청할 것입니다. 😉
|
||||
|
||||
## Pull Requests 리뷰하기
|
||||
## Pull Request 리뷰하기 { #review-pull-requests }
|
||||
|
||||
다른 사람들의 pull request를 리뷰하는 데 도움을 줄 수 있습니다.
|
||||
다른 사람들이 만든 pull request를 리뷰하는 데 저를 도와줄 수 있습니다.
|
||||
|
||||
다시 한번 말하지만, 최대한 친절하게 리뷰해 주세요. 🤗
|
||||
|
||||
---
|
||||
|
||||
Pull Rrquest를 리뷰할 때 고려해야 할 사항과 방법은 다음과 같습니다:
|
||||
Pull request를 리뷰할 때 고려해야 할 사항과 방법은 다음과 같습니다:
|
||||
|
||||
### 문제 이해하기
|
||||
### 문제 이해하기 { #understand-the-problem }
|
||||
|
||||
* 먼저, 해당 pull request가 해결하려는 **문제를 이해하는지** 확인하세요. GitHub 디스커션 또는 이슈에서 더 긴 논의가 있었을 수도 있습니다.
|
||||
* 먼저, 해당 pull request가 해결하려는 **문제를 이해하는지** 확인하세요. GitHub Discussion 또는 이슈에서 더 긴 논의가 있었을 수도 있습니다.
|
||||
|
||||
* Pull request가 필요하지 않을 가능성도 있습니다. **다른 방식**으로 문제를 해결할 수 있다면, 그 방법을 제안하거나 질문할 수 있습니다.
|
||||
* Pull request가 실제로 필요하지 않을 가능성도 큽니다. 문제가 **다른 방식**으로 해결될 수 있기 때문입니다. 그런 경우 그 방법을 제안하거나 질문할 수 있습니다.
|
||||
|
||||
### 스타일에 너무 신경 쓰지 않기
|
||||
### 스타일에 너무 신경 쓰지 않기 { #dont-worry-about-style }
|
||||
|
||||
* 커밋 메시지 스타일 같은 것에 너무 신경 쓰지 않아도 됩니다. 저는 직접 커밋을 수정하여 squash and merge를 수행할 것입니다.
|
||||
* 커밋 메시지 스타일 같은 것에 너무 신경 쓰지 마세요. 저는 커밋을 수동으로 조정해서 squash and merge를 할 것입니다.
|
||||
|
||||
* 코드 스타일 규칙도 걱정할 필요 없습니다. 이미 자동화된 도구들이 이를 검사하고 있습니다.
|
||||
|
||||
스타일이나 일관성 관련 요청이 필요한 경우, 제가 직접 요청하거나 필요한 변경 사항을 추가 커밋으로 수정할 것입니다.
|
||||
그리고 다른 스타일이나 일관성 관련 필요 사항이 있다면, 제가 직접 요청하거나 필요한 변경 사항을 위에 커밋으로 추가할 것입니다.
|
||||
|
||||
### 코드 확인하기
|
||||
### 코드 확인하기 { #check-the-code }
|
||||
|
||||
* 코드를 읽고, **논리적으로 타당**한지 확인한 후 로컬에서 실행하여 문제가 해결되는지 확인하세요.
|
||||
* 코드를 확인하고 읽어서 말이 되는지 보고, **로컬에서 실행**해 실제로 문제가 해결되는지 확인하세요.
|
||||
|
||||
* 그런 다음, 확인했다고 **댓글**을 남겨 주세요. 그래야 제가 검토했음을 알 수 있습니다.
|
||||
* 그런 다음 그렇게 했다고 **댓글**로 남겨 주세요. 그래야 제가 정말로 확인했음을 알 수 있습니다.
|
||||
|
||||
/// info
|
||||
/// info | 정보
|
||||
|
||||
불행히도, 제가 단순히 여러 개의 승인만으로 PR을 신뢰할 수는 없습니다.
|
||||
|
||||
3개, 5개 이상의 승인이 달린 PR이 실제로는 깨져 있거나, 버그가 있거나, 주장하는 문제를 해결하지 못하는 경우가 여러 번 있었습니다. 😅
|
||||
여러 번, 설명이 그럴듯해서인지 3개, 5개 이상의 승인이 달린 PR이 있었지만, 제가 확인해보면 실제로는 깨져 있거나, 버그가 있거나, 주장하는 문제를 해결하지 못하는 경우가 있었습니다. 😅
|
||||
|
||||
따라서, 정말로 코드를 읽고 실행한 뒤, 댓글로 확인 내용을 남겨 주는 것이 매우 중요합니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
* PR을 더 단순하게 만들 수 있다면 그렇게 요청할 수 있지만, 너무 까다로울 필요는 없습니다. 주관적인 견해가 많이 있을 수 있기 때문입니다 (그리고 저도 제 견해가 있을 거예요 🙈). 따라서 핵심적인 부분에 집중하는 것이 좋습니다.
|
||||
* PR을 더 단순하게 만들 수 있다면 그렇게 요청할 수 있지만, 너무 까다로울 필요는 없습니다. 주관적인 견해가 많이 있을 수 있기 때문입니다(그리고 저도 제 견해가 있을 거예요 🙈). 따라서 핵심적인 부분에 집중하는 것이 좋습니다.
|
||||
|
||||
### 테스트
|
||||
### 테스트 { #tests }
|
||||
|
||||
* PR에 **테스트**가 포함되어 있는지 확인하는 데 도움을 주세요.
|
||||
|
||||
* PR을 적용하기 전에 테스트가 **실패**하는지 확인하세요. 🚨
|
||||
* PR 전에는 테스트가 **실패**하는지 확인하세요. 🚨
|
||||
|
||||
* PR을 적용한 후 테스트가 **통과**하는지 확인하세요. ✅
|
||||
* 그런 다음 PR 후에는 테스트가 **통과**하는지 확인하세요. ✅
|
||||
|
||||
* 많은 PR에는 테스트가 없습니다. 테스트를 추가하도록 **상기**시켜줄 수도 있고, 직접 테스트를 **제안**할 수도 있습니다. 이는 시간이 많이 소요되는 부분 중 하나이며, 그 부분을 많이 도와줄 수 있습니다.
|
||||
* 많은 PR에는 테스트가 없습니다. 테스트를 추가하도록 **상기**시켜줄 수도 있고, 직접 테스트를 **제안**할 수도 있습니다. 이는 시간이 가장 많이 드는 것들 중 하나이며, 그 부분을 많이 도와줄 수 있습니다.
|
||||
|
||||
* 그리고 시도한 내용을 댓글로 남겨주세요. 그러면 제가 확인했다는 걸 알 수 있습니다. 🤓
|
||||
|
||||
## Pull Request를 만드십시오
|
||||
## Pull Request 만들기 { #create-a-pull-request }
|
||||
|
||||
Pull Requests를 이용하여 소스코드에 [컨트리뷰트](contributing.md){.internal-link target=\_blank} 할 수 있습니다. 예를 들면 다음과 같습니다:
|
||||
Pull Requests를 이용하여 소스 코드에 [기여](contributing.md){.internal-link target=_blank}할 수 있습니다. 예를 들면 다음과 같습니다:
|
||||
|
||||
* 문서에서 발견한 오타를 수정할 때.
|
||||
* FastAPI 관련 문서, 비디오 또는 팟캐스트를 작성했거나 발견하여 <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">이 파일을 편집하여</a> 공유할 때.
|
||||
* FastAPI에 대한 글, 비디오, 팟캐스트를 작성했거나 발견했다면 <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">이 파일을 편집</a>하여 공유할 때.
|
||||
* 해당 섹션의 시작 부분에 링크를 추가해야 합니다.
|
||||
* 당신의 언어로 [문서 번역하는데](contributing.md#translations){.internal-link target=\_blank} 기여할 때.
|
||||
* 여러분의 언어로 [문서 번역에](contributing.md#translations){.internal-link target=_blank} 도움을 줄 때.
|
||||
* 다른 사람이 작성한 번역을 검토하는 것도 도울 수 있습니다.
|
||||
* 새로운 문서의 섹션을 제안할 때.
|
||||
* 기존 문제/버그를 수정할 때.
|
||||
* 새로운 문서 섹션을 제안할 때.
|
||||
* 기존 이슈/버그를 수정할 때.
|
||||
* 테스트를 반드시 추가해야 합니다.
|
||||
* 새로운 feature를 추가할 때.
|
||||
* 새로운 기능을 추가할 때.
|
||||
* 테스트를 반드시 추가해야 합니다.
|
||||
* 관련 문서가 필요하다면 반드시 추가해야 합니다.
|
||||
* 관련 문서가 있다면 반드시 추가해야 합니다.
|
||||
|
||||
## FastAPI 유지 관리에 도움 주기
|
||||
## FastAPI 유지 관리 돕기 { #help-maintain-fastapi }
|
||||
|
||||
**FastAPI**의 유지 관리를 도와주세요! 🤓
|
||||
**FastAPI** 유지를 도와주세요! 🤓
|
||||
|
||||
할 일이 많고, 그 중 대부분은 **여러분**이 할 수 있습니다.
|
||||
할 일이 많고, 그중 대부분은 **여러분**이 할 수 있습니다.
|
||||
|
||||
지금 할 수 있는 주요 작업은:
|
||||
|
||||
* [GitHub에서 다른 사람들의 질문에 도움 주기](#github_1){.internal-link target=_blank} (위의 섹션을 참조하세요).
|
||||
* [Pull Request 리뷰하기](#pull-requests){.internal-link target=_blank} (위의 섹션을 참조하세요).
|
||||
* [GitHub에서 질문으로 다른 사람 돕기](#help-others-with-questions-in-github){.internal-link target=_blank} (위의 섹션을 참조하세요).
|
||||
* [Pull Request 리뷰하기](#review-pull-requests){.internal-link target=_blank} (위의 섹션을 참조하세요).
|
||||
|
||||
이 두 작업이 **가장 많은 시간을 소모**하는 일입니다. 그것이 FastAPI 유지 관리의 주요 작업입니다.
|
||||
이 두 작업이 **가장 많은 시간을 소모**합니다. 이것이 FastAPI를 유지 관리하는 주요 작업입니다.
|
||||
|
||||
이 작업을 도와주신다면, **FastAPI 유지 관리에 도움을 주는 것**이며 그것이 **더 빠르고 더 잘 발전하는 것**을 보장하는 것입니다. 🚀
|
||||
이 작업을 도와주신다면, **FastAPI 유지를 돕는 것**이며 FastAPI가 **더 빠르고 더 잘 발전하는 것**을 보장하는 것입니다. 🚀
|
||||
|
||||
## 채팅에 참여하십시오
|
||||
## 채팅에 참여하기 { #join-the-chat }
|
||||
|
||||
👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">디스코드 채팅 서버</a> 👥 에 가입하고 FastAPI 커뮤니티에서 다른 사람들과 어울리세요.
|
||||
👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">Discord 채팅 서버</a> 👥 에 참여해서 FastAPI 커뮤니티의 다른 사람들과 어울리세요.
|
||||
|
||||
/// tip
|
||||
/// tip | 팁
|
||||
|
||||
질문이 있는 경우, <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub 디스커션</a> 에서 질문하십시오, [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank} 의 도움을 받을 가능성이 높습니다.
|
||||
질문은 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>에서 하세요. [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank}로부터 도움을 받을 가능성이 훨씬 높습니다.
|
||||
|
||||
다른 일반적인 대화에서만 채팅을 사용하십시오.
|
||||
채팅은 다른 일반적인 대화를 위해서만 사용하세요.
|
||||
|
||||
///
|
||||
|
||||
### 질문을 위해 채팅을 사용하지 마십시오
|
||||
### 질문을 위해 채팅을 사용하지 마세요 { #dont-use-the-chat-for-questions }
|
||||
|
||||
채팅은 더 많은 "자유로운 대화"를 허용하기 때문에, 너무 일반적인 질문이나 대답하기 어려운 질문을 쉽게 질문을 할 수 있으므로, 답변을 받지 못할 수 있습니다.
|
||||
채팅은 더 많은 "자유로운 대화"를 허용하기 때문에, 너무 일반적인 질문이나 답하기 어려운 질문을 쉽게 할 수 있어 답변을 받지 못할 수도 있다는 점을 기억하세요.
|
||||
|
||||
GitHub 이슈에서의 템플릿은 올바른 질문을 작성하도록 안내하여 더 쉽게 좋은 답변을 얻거나 질문하기 전에 스스로 문제를 해결할 수도 있습니다. 그리고 GitHub에서는 시간이 조금 걸리더라도 항상 모든 것에 답할 수 있습니다. 채팅 시스템에서는 개인적으로 그렇게 할 수 없습니다. 😅
|
||||
GitHub에서는 템플릿이 올바른 질문을 작성하도록 안내하여 더 쉽게 좋은 답변을 얻거나, 질문하기 전에 스스로 문제를 해결할 수도 있습니다. 그리고 GitHub에서는 시간이 조금 걸리더라도 제가 항상 모든 것에 답하도록 보장할 수 있습니다. 채팅 시스템에서는 제가 개인적으로 그렇게 할 수 없습니다. 😅
|
||||
|
||||
채팅 시스템에서의 대화 또한 GitHub에서 처럼 쉽게 검색할 수 없기 때문에 대화 중에 질문과 답변이 손실될 수 있습니다. 그리고 GitHub 이슈에 있는 것만 [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}가 되는 것으로 간주되므로, GitHub 이슈에서 더 많은 관심을 받을 것입니다.
|
||||
채팅 시스템에서의 대화 또한 GitHub만큼 쉽게 검색할 수 없기 때문에, 질문과 답변이 대화 속에서 사라질 수 있습니다. 그리고 GitHub에 있는 것만 [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}가 되는 것으로 인정되므로, GitHub에서 더 많은 관심을 받게 될 가능성이 큽니다.
|
||||
|
||||
반면, 채팅 시스템에는 수천 명의 사용자가 있기 때문에, 거의 항상 대화 상대를 찾을 가능성이 높습니다. 😄
|
||||
반면, 채팅 시스템에는 수천 명의 사용자가 있으므로, 거의 항상 대화 상대를 찾을 가능성이 높습니다. 😄
|
||||
|
||||
## 개발자 스폰서가 되십시오
|
||||
## 개발자 스폰서 되기 { #sponsor-the-author }
|
||||
|
||||
<a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub 스폰서</a> 를 통해 개발자를 경제적으로 지원할 수 있습니다.
|
||||
|
||||
감사하다는 말로 커피를 ☕️ 한잔 사줄 수 있습니다. 😄
|
||||
|
||||
또한 FastAPI의 실버 또는 골드 스폰서가 될 수 있습니다. 🏅🎉
|
||||
|
||||
## FastAPI를 강화하는 도구의 스폰서가 되십시오
|
||||
|
||||
문서에서 보았듯이, FastAPI는 Starlette과 Pydantic 라는 거인의 어깨에 타고 있습니다.
|
||||
|
||||
다음의 스폰서가 될 수 있습니다
|
||||
|
||||
* <a href="https://github.com/sponsors/samuelcolvin" class="external-link" target="_blank">Samuel Colvin (Pydantic)</a>
|
||||
* <a href="https://github.com/sponsors/encode" class="external-link" target="_blank">Encode (Starlette, Uvicorn)</a>
|
||||
여러분의 **제품/회사**가 **FastAPI**에 의존하거나 관련되어 있고, FastAPI 사용자를 대상으로 알리고 싶다면 <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub sponsors</a>를 통해 개발자(저)를 스폰서할 수 있습니다. 티어에 따라 문서에 배지 같은 추가 혜택을 받을 수도 있습니다. 🎁
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,81 +1,79 @@
|
||||
# 역사, 디자인 그리고 미래
|
||||
# 역사, 디자인 그리고 미래 { #history-design-and-future }
|
||||
|
||||
어느 날, [한 FastAPI 사용자](https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920)가 이렇게 물었습니다:
|
||||
얼마 전, <a href="https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920" class="external-link" target="_blank">한 **FastAPI** 사용자가 이렇게 물었습니다</a>:
|
||||
|
||||
> 이 프로젝트의 역사를 알려 주실 수 있나요? 몇 주 만에 멋진 결과를 낸 것 같아요. [...]
|
||||
> 이 프로젝트의 역사는 무엇인가요? 몇 주 만에 아무 데서도 갑자기 나타나 엄청나게 좋아진 것처럼 보이네요 [...]
|
||||
|
||||
여기서 그 역사에 대해 간단히 설명하겠습니다.
|
||||
|
||||
---
|
||||
## 대안 { #alternatives }
|
||||
|
||||
## 대안
|
||||
저는 여러 해 동안 복잡한 요구사항(머신러닝, 분산 시스템, 비동기 작업, NoSQL 데이터베이스 등)을 가진 API를 만들면서 여러 개발 팀을 이끌어 왔습니다.
|
||||
|
||||
저는 여러 해 동안 머신러닝, 분산 시스템, 비동기 작업, NoSQL 데이터베이스 같은 복잡한 요구사항을 가진 API를 개발하며 여러 팀을 이끌어 왔습니다.
|
||||
그 과정에서 많은 대안을 조사하고, 테스트하고, 사용해야 했습니다.
|
||||
|
||||
이 과정에서 많은 대안을 조사하고, 테스트하며, 사용해야 했습니다. **FastAPI**의 역사는 그 이전에 나왔던 여러 도구의 역사와 밀접하게 연관되어 있습니다.
|
||||
**FastAPI**의 역사는 상당 부분 그 이전에 있던 도구들의 역사입니다.
|
||||
|
||||
[대안](alternatives.md){.internal-link target=_blank} 섹션에서 언급된 것처럼:
|
||||
|
||||
> **FastAPI**는 이전에 나왔던 많은 도구들의 노력 없이는 존재하지 않았을 것입니다.
|
||||
>
|
||||
> 이전에 개발된 여러 도구들이 이 프로젝트에 영감을 주었습니다.
|
||||
>
|
||||
> 저는 오랫동안 새로운 프레임워크를 만드는 것을 피하고자 했습니다. 처음에는 **FastAPI**가 제공하는 기능들을 다양한 프레임워크와 플러그인, 도구들을 조합해 해결하려 했습니다.
|
||||
>
|
||||
> 하지만 결국에는 이 모든 기능을 통합하는 도구가 필요해졌습니다. 이전 도구들로부터 최고의 아이디어들을 모으고, 이를 최적의 방식으로 조합해야만 했습니다. 이는 :term:Python 3.6+ 타입 힌트 <type hints>와 같은, 이전에는 사용할 수 없었던 언어 기능이 가능했기 때문입니다.
|
||||
<blockquote markdown="1">
|
||||
|
||||
---
|
||||
**FastAPI**는 다른 사람들이 이전에 해온 작업이 없었다면 존재하지 않았을 것입니다.
|
||||
|
||||
## 조사
|
||||
그 전에 만들어진 많은 도구들이 이것의 탄생에 영감을 주었습니다.
|
||||
|
||||
여러 대안을 사용해 보며 다양한 도구에서 배운 점들을 모아 저와 개발팀에게 가장 적합한 방식을 찾았습니다.
|
||||
저는 여러 해 동안 새로운 프레임워크를 만드는 것을 피하고 있었습니다. 처음에는 **FastAPI**가 다루는 모든 기능을 여러 다른 프레임워크, 플러그인, 도구들을 사용해 해결하려고 했습니다.
|
||||
|
||||
예를 들어, 표준 :term:Python 타입 힌트 <type hints>에 기반하는 것이 이상적이라는 점이 명확했습니다.
|
||||
하지만 어느 시점에는, 이전 도구들의 최고의 아이디어를 가져와 가능한 한 최선의 방식으로 조합하고, 이전에는 존재하지 않았던 언어 기능(Python 3.6+ type hints)을 사용해 이 모든 기능을 제공하는 무언가를 만드는 것 외에는 다른 선택지가 없었습니다.
|
||||
|
||||
또한, 이미 존재하는 표준을 활용하는 것이 가장 좋은 접근법이라 판단했습니다.
|
||||
</blockquote>
|
||||
|
||||
그래서 **FastAPI**의 코드를 작성하기 전에 몇 달 동안 OpenAPI, JSON Schema, OAuth2 명세를 연구하며 이들의 관계와 겹치는 부분, 차이점을 이해했습니다.
|
||||
## 조사 { #investigation }
|
||||
|
||||
---
|
||||
이전의 모든 대안을 사용해 보면서, 각 도구로부터 배울 기회를 얻었고, 아이디어를 가져와 제가 일해온 개발 팀들과 저 자신에게 가장 적합하다고 찾은 방식으로 조합할 수 있었습니다.
|
||||
|
||||
## 디자인
|
||||
예를 들어, 이상적으로는 표준 Python 타입 힌트에 기반해야 한다는 점이 분명했습니다.
|
||||
|
||||
그 후, **FastAPI** 사용자가 될 개발자로서 사용하고 싶은 개발자 "API"를 디자인했습니다.
|
||||
또한, 가장 좋은 접근법은 이미 존재하는 표준을 사용하는 것이었습니다.
|
||||
|
||||
[Python Developer Survey](https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools)에 따르면 약 80%의 Python 개발자가 PyCharm, VS Code, Jedi 기반 편집기 등에서 개발합니다. 이 과정에서 여러 아이디어를 테스트했습니다.
|
||||
그래서 **FastAPI**의 코딩을 시작하기도 전에, OpenAPI, JSON Schema, OAuth2 등과 같은 명세를 몇 달 동안 공부했습니다. 이들의 관계, 겹치는 부분, 차이점을 이해하기 위해서였습니다.
|
||||
|
||||
대부분의 다른 편집기도 유사하게 동작하기 때문에, **FastAPI**의 이점은 거의 모든 편집기에서 누릴 수 있습니다.
|
||||
## 디자인 { #design }
|
||||
|
||||
이 과정을 통해 코드 중복을 최소화하고, 모든 곳에서 자동 완성, 타입 검사, 에러 확인 기능이 제공되는 최적의 방식을 찾아냈습니다.
|
||||
그 다음에는 (FastAPI를 사용하는 개발자로서) 사용자로서 갖고 싶었던 개발자 "API"를 디자인하는 데 시간을 썼습니다.
|
||||
|
||||
이 모든 것은 개발자들에게 최고의 개발 경험을 제공하기 위해 설계되었습니다.
|
||||
가장 인기 있는 Python 편집기들: PyCharm, VS Code, Jedi 기반 편집기에서 여러 아이디어를 테스트했습니다.
|
||||
|
||||
---
|
||||
약 80%의 사용자를 포함하는 최근 <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" class="external-link" target="_blank">Python Developer Survey</a>에 따르면 그렇습니다.
|
||||
|
||||
## 필요조건
|
||||
즉, **FastAPI**는 Python 개발자의 80%가 사용하는 편집기들로 특별히 테스트되었습니다. 그리고 대부분의 다른 편집기도 유사하게 동작하는 경향이 있으므로, 그 모든 이점은 사실상 모든 편집기에서 동작해야 합니다.
|
||||
|
||||
여러 대안을 테스트한 후, [Pydantic](https://docs.pydantic.dev/)을 사용하기로 결정했습니다.
|
||||
그렇게 해서 코드 중복을 가능한 한 많이 줄이고, 어디서나 자동 완성, 타입 및 에러 검사 등을 제공하는 최선의 방법을 찾을 수 있었습니다.
|
||||
|
||||
이후 저는 **Pydantic**이 JSON Schema와 완벽히 호환되도록 개선하고, 다양한 제약 조건 선언을 지원하며, 여러 편집기에서의 자동 완성과 타입 검사 기능을 향상하기 위해 기여했습니다.
|
||||
모든 개발자에게 최고의 개발 경험을 제공하는 방식으로 말입니다.
|
||||
|
||||
또한, 또 다른 주요 필요조건이었던 [Starlette](https://www.starlette.dev/)에도 기여했습니다.
|
||||
## 필요조건 { #requirements }
|
||||
|
||||
---
|
||||
여러 대안을 테스트한 후, 장점 때문에 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">**Pydantic**</a>을 사용하기로 결정했습니다.
|
||||
|
||||
## 개발
|
||||
그 후, JSON Schema를 완전히 준수하도록 하고, 제약 조건 선언을 정의하는 다양한 방식을 지원하며, 여러 편집기에서의 테스트를 바탕으로 편집기 지원(타입 검사, 자동 완성)을 개선하기 위해 기여했습니다.
|
||||
|
||||
**FastAPI**를 개발하기 시작할 즈음에는 대부분의 준비가 이미 완료된 상태였습니다. 설계가 정의되었고, 필요조건과 도구가 준비되었으며, 표준과 명세에 대한 지식도 충분했습니다.
|
||||
개발 과정에서, 또 다른 핵심 필요조건인 <a href="https://www.starlette.dev/" class="external-link" target="_blank">**Starlette**</a>에도 기여했습니다.
|
||||
|
||||
---
|
||||
## 개발 { #development }
|
||||
|
||||
## 미래
|
||||
**FastAPI** 자체를 만들기 시작했을 때쯤에는, 대부분의 조각들이 이미 갖춰져 있었고, 디자인은 정의되어 있었으며, 필요조건과 도구는 준비되어 있었고, 표준과 명세에 대한 지식도 명확하고 최신 상태였습니다.
|
||||
|
||||
현시점에서 **FastAPI**가 많은 사람들에게 유용하다는 것이 명백해졌습니다.
|
||||
## 미래 { #future }
|
||||
|
||||
여러 용도에 더 적합한 도구로서 기존 대안보다 선호되고 있습니다.
|
||||
이미 많은 개발자와 팀들이 **FastAPI**에 의존해 프로젝트를 진행 중입니다 (저와 제 팀도 마찬가지입니다).
|
||||
이 시점에는, **FastAPI**가 그 아이디어와 함께 많은 사람들에게 유용하다는 것이 이미 분명합니다.
|
||||
|
||||
하지만 여전히 개선해야 할 점과 추가할 기능들이 많이 남아 있습니다.
|
||||
많은 사용 사례에 더 잘 맞기 때문에 이전 대안들보다 선택되고 있습니다.
|
||||
|
||||
많은 개발자와 팀이 이미 자신의 프로젝트를 위해 **FastAPI**에 의존하고 있습니다(저와 제 팀도 포함해서요).
|
||||
|
||||
하지만 여전히, 앞으로 나올 개선 사항과 기능들이 많이 있습니다.
|
||||
|
||||
**FastAPI**의 미래는 밝습니다.
|
||||
|
||||
**FastAPI**는 밝은 미래로 나아가고 있습니다.
|
||||
그리고 [여러분의 도움](help-fastapi.md){.internal-link target=_blank}은 큰 힘이 됩니다.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# 이전 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_py310.py hl[9:13] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
함수는 예외를 `return`하는 것이지 `raise`하지 않는다는 점에 유의하세요. 예외를 발생시키는(`raise`) 작업은 내부 코드의 나머지 부분에서 수행됩니다.
|
||||
|
||||
///
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 보안, API 및 docs에 대해서 { #about-security-apis-and-docs }
|
||||
|
||||
프로덕션에서, 문서화된 사용자 인터페이스(UI)를 숨기는 것이 API를 보호하는 방법이 *되어서는 안 됩니다*.
|
||||
프로덕션에서, 문서화된 사용자 인터페이스(UI)를 숨기는 것이 API를 보호하는 방법이 되어서는 안 됩니다.
|
||||
|
||||
이는 API에 추가적인 보안을 제공하지 않으며, *경로 처리*는 여전히 동일한 위치에서 사용 할 수 있습니다.
|
||||
|
||||
@@ -29,7 +29,7 @@ API를 보호하고 싶다면, 예를 들어 다음과 같은 더 나은 방법
|
||||
|
||||
예를 들어:
|
||||
|
||||
{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *}
|
||||
{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *}
|
||||
|
||||
여기서 `openapi_url` 설정을 기본값인 `"/openapi.json"`으로 선언합니다.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ FastAPI는 이 구성을 **JSON** 형식으로 변환하여 JavaScript와 호환
|
||||
|
||||
그러나 `syntaxHighlight`를 `False`로 설정하여 구문 강조 기능을 비활성화할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *}
|
||||
|
||||
...그럼 Swagger UI에서 더 이상 구문 강조 기능이 표시되지 않습니다:
|
||||
|
||||
@@ -28,7 +28,7 @@ FastAPI는 이 구성을 **JSON** 형식으로 변환하여 JavaScript와 호환
|
||||
|
||||
동일한 방식으로 `"syntaxHighlight.theme"` 키를 사용하여 구문 강조 테마를 설정할 수 있습니다 (중간에 점이 포함된 것을 참고하십시오).
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *}
|
||||
|
||||
이 설정은 구문 강조 색상 테마를 변경합니다:
|
||||
|
||||
@@ -42,11 +42,11 @@ FastAPI는 대부분의 사용 사례에 적합한 몇 가지 기본 구성 매
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
|
||||
|
||||
`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 일부를 재정의할 수 있습니다.
|
||||
`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 어느 것이든 재정의할 수 있습니다.
|
||||
|
||||
예를 들어, `deepLinking`을 비활성화하려면 `swagger_ui_parameters`에 다음 설정을 전달할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *}
|
||||
|
||||
## 기타 Swagger UI 매개변수 { #other-swagger-ui-parameters }
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# 커스텀 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_py310.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_py310.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_py310.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_py310.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_py310.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_py310.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_py310.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와 상호작용할 수 있을 것입니다.
|
||||
@@ -0,0 +1,109 @@
|
||||
# 커스텀 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] *}
|
||||
@@ -0,0 +1,80 @@
|
||||
# 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_py310.py hl[1,4,7:9] *}
|
||||
|
||||
### OpenAPI 스키마 생성하기 { #generate-the-openapi-schema }
|
||||
|
||||
그다음 `custom_openapi()` 함수 안에서, 동일한 유틸리티 함수를 사용해 OpenAPI 스키마를 생성합니다:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *}
|
||||
|
||||
### OpenAPI 스키마 수정하기 { #modify-the-openapi-schema }
|
||||
|
||||
이제 OpenAPI 스키마의 `info` "object"에 커스텀 `x-logo`를 추가하여 ReDoc 확장을 더할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *}
|
||||
|
||||
### OpenAPI 스키마 캐시하기 { #cache-the-openapi-schema }
|
||||
|
||||
생성한 스키마를 저장하기 위한 "cache"로 `.openapi_schema` 프로퍼티를 사용할 수 있습니다.
|
||||
|
||||
이렇게 하면 사용자가 API 문서를 열 때마다 애플리케이션이 스키마를 매번 생성하지 않아도 됩니다.
|
||||
|
||||
스키마는 한 번만 생성되고, 이후 요청에서는 같은 캐시된 스키마가 사용됩니다.
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *}
|
||||
|
||||
### 메서드 오버라이드하기 { #override-the-method }
|
||||
|
||||
이제 `.openapi()` 메서드를 새 함수로 교체할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.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">
|
||||
@@ -0,0 +1,39 @@
|
||||
# 일반 - 사용 방법 - 레시피 { #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} 문서를 읽어보세요.
|
||||
@@ -0,0 +1,60 @@
|
||||
# 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_py310.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**에 대해 더 알아볼 수 있습니다.
|
||||
|
||||
또한 위에서 설명한 각 라이브러리에 대해서도 해당 링크에서 더 자세히 읽어볼 수 있습니다.
|
||||
@@ -0,0 +1,13 @@
|
||||
# How To - 레시피 { #how-to-recipes }
|
||||
|
||||
여기에서는 **여러 주제**에 대한 다양한 레시피(“how to” 가이드)를 볼 수 있습니다.
|
||||
|
||||
대부분의 아이디어는 어느 정도 **서로 독립적**이며, 대부분의 경우 **여러분의 프로젝트**에 직접 적용되는 경우에만 학습하면 됩니다.
|
||||
|
||||
프로젝트에 흥미롭고 유용해 보이는 것이 있다면 확인해 보세요. 그렇지 않다면 아마 건너뛰어도 됩니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
**FastAPI를 구조적으로 학습**하고 싶다면(권장), 대신 [튜토리얼 - 사용자 가이드](../tutorial/index.md){.internal-link target=_blank}를 장별로 읽어보세요.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,135 @@
|
||||
# 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">마이그레이션 가이드</a>가 있습니다.
|
||||
|
||||
여기에는 무엇이 바뀌었는지, 검증이 이제 어떻게 더 정확하고 엄격해졌는지, 가능한 주의사항 등도 포함되어 있습니다.
|
||||
|
||||
변경된 내용을 더 잘 이해하기 위해 읽어보면 좋습니다.
|
||||
|
||||
## 테스트 { #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로 모델을 그룹 단위로, 점진적인 단계로 마이그레이션을 시작하면 됩니다. 🚶
|
||||
@@ -0,0 +1,102 @@
|
||||
# 입력과 출력에 대해 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>
|
||||
@@ -0,0 +1,7 @@
|
||||
# 데이터베이스 테스트하기 { #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>에 대한 섹션도 포함되어 있습니다. 😎
|
||||
+12
-18
@@ -40,7 +40,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트
|
||||
* **빠름**: (Starlette과 Pydantic 덕분에) **NodeJS** 및 **Go**와 대등할 정도로 매우 높은 성능. [사용 가능한 가장 빠른 파이썬 프레임워크 중 하나](#performance).
|
||||
* **빠른 코드 작성**: 약 200%에서 300%까지 기능 개발 속도 증가. *
|
||||
* **적은 버그**: 사람(개발자)에 의한 에러 약 40% 감소. *
|
||||
* **직관적**: 훌륭한 편집기 지원. 모든 곳에서 <abbr title="also known as auto-complete, autocompletion, IntelliSense">자동완성</abbr>. 적은 디버깅 시간.
|
||||
* **직관적**: 훌륭한 편집기 지원. <dfn title="다른 말로는: 자동 완성, 자동완성, IntelliSense">자동완성</dfn>이 모든 곳에서 동작. 적은 디버깅 시간.
|
||||
* **쉬움**: 쉽게 사용하고 배우도록 설계. 적은 문서 읽기 시간.
|
||||
* **짧음**: 코드 중복 최소화. 각 매개변수 선언의 여러 기능. 적은 버그.
|
||||
* **견고함**: 준비된 프로덕션 용 코드를 얻으십시오. 자동 대화형 문서와 함께.
|
||||
@@ -127,7 +127,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트
|
||||
|
||||
<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>
|
||||
|
||||
웹 API 대신 터미널에서 사용할 <abbr title="Command Line Interface">CLI</abbr> 앱을 만들고 있다면, <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>를 확인해 보십시오.
|
||||
웹 API 대신 터미널에서 사용할 <abbr title="Command Line Interface - 커맨드 라인 인터페이스">CLI</abbr> 앱을 만들고 있다면, <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>를 확인해 보십시오.
|
||||
|
||||
**Typer**는 FastAPI의 동생입니다. 그리고 **CLI를 위한 FastAPI**가 되기 위해 생겼습니다. ⌨️ 🚀
|
||||
|
||||
@@ -161,8 +161,6 @@ $ pip install "fastapi[standard]"
|
||||
다음 내용으로 `main.py` 파일을 만드십시오:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
@@ -174,7 +172,7 @@ def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
@@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None):
|
||||
|
||||
여러분의 코드가 `async` / `await`을 사용한다면, `async def`를 사용하십시오:
|
||||
|
||||
```Python hl_lines="9 14"
|
||||
from typing import Union
|
||||
|
||||
```Python hl_lines="7 12"
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
@@ -197,7 +193,7 @@ async def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int, q: Union[str, None] = None):
|
||||
async def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
@@ -288,9 +284,7 @@ INFO: Application startup complete.
|
||||
|
||||
Pydantic 덕분에 표준 Python 타입을 사용해 본문을 선언합니다.
|
||||
|
||||
```Python hl_lines="4 9-12 25-27"
|
||||
from typing import Union
|
||||
|
||||
```Python hl_lines="2 7-10 23-25"
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -300,7 +294,7 @@ app = FastAPI()
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
is_offer: Union[bool, None] = None
|
||||
is_offer: bool | None = None
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@@ -309,7 +303,7 @@ def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
|
||||
|
||||
@@ -374,7 +368,7 @@ item: Item
|
||||
* 데이터 검증:
|
||||
* 데이터가 유효하지 않을 때 자동으로 생성하는 명확한 에러.
|
||||
* 깊이 중첩된 JSON 객체에 대한 유효성 검사.
|
||||
* 입력 데이터 <abbr title="also known as: serialization, parsing, marshalling">변환</abbr>: 네트워크에서 파이썬 데이터 및 타입으로 전송. 읽을 수 있는 것들:
|
||||
* 입력 데이터 <dfn title="다른 말로는: 직렬화, 파싱, 마샬링">변환</dfn>: 네트워크에서 파이썬 데이터 및 타입으로 전송. 읽을 수 있는 것들:
|
||||
* JSON.
|
||||
* 경로 매개변수.
|
||||
* 쿼리 매개변수.
|
||||
@@ -382,7 +376,7 @@ item: Item
|
||||
* 헤더.
|
||||
* 폼(Forms).
|
||||
* 파일.
|
||||
* 출력 데이터 <abbr title="also known as: serialization, parsing, marshalling">변환</abbr>: 파이썬 데이터 및 타입을 네트워크 데이터로 전환(JSON 형식으로):
|
||||
* 출력 데이터 <dfn title="다른 말로는: 직렬화, 파싱, 마샬링">변환</dfn>: 파이썬 데이터 및 타입을 네트워크 데이터로 전환(JSON 형식으로):
|
||||
* 파이썬 타입 변환 (`str`, `int`, `float`, `bool`, `list`, 등).
|
||||
* `datetime` 객체.
|
||||
* `UUID` 객체.
|
||||
@@ -445,7 +439,7 @@ item: Item
|
||||
|
||||
* 서로 다른 장소에서 **매개변수** 선언: **헤더**, **쿠키**, **폼 필드** 그리고 **파일**.
|
||||
* `maximum_length` 또는 `regex`처럼 **유효성 제약**하는 방법.
|
||||
* 강력하고 사용하기 쉬운 **<abbr title="also known as components, resources, providers, services, injectables">의존성 주입</abbr>** 시스템.
|
||||
* 강력하고 사용하기 쉬운 **<dfn title="다른 말로는: 컴포넌트, 리소스, 프로바이더, 서비스, 인젝터블">의존성 주입</dfn>** 시스템.
|
||||
* **OAuth2** 지원을 포함한 **JWT tokens** 및 **HTTP Basic**을 갖는 보안과 인증.
|
||||
* (Pydantic 덕분에) **깊은 중첩 JSON 모델**을 선언하는데 더 진보한 (하지만 마찬가지로 쉬운) 기술.
|
||||
* <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> 및 기타 라이브러리와의 **GraphQL** 통합.
|
||||
@@ -530,7 +524,7 @@ 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> - `request.form()`과 함께 form <abbr title="HTTP 요청에서 파이썬 데이터로 가는 문자열 변환">"parsing"</abbr> 지원을 원하면 필요.
|
||||
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - `request.form()`과 함께 form <dfn title="HTTP 요청에서 온 문자열을 파이썬 데이터로 변환">"파싱"</dfn> 지원을 원하면 필요.
|
||||
|
||||
FastAPI가 사용하는:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
템플릿은 일반적으로 특정 설정과 함께 제공되지만, 유연하고 커스터마이징이 가능하게 디자인 되었습니다. 이 특성들은 여러분이 프로젝트의 요구사항에 맞춰 수정, 적용을 할 수 있게 해주고, 템플릿이 완벽한 시작점이 되게 해줍니다. 🏁
|
||||
|
||||
많은 초기 설정, 보안, 데이터베이스 및 일부 API 엔드포인트가 이미 준비되어 있으므로, 여러분은 이 템플릿을 (프로젝트를) 시작하는 데 사용할 수 있습니다.
|
||||
많은 초기 설정, 보안, 데이터베이스 및 일부 API 엔드포인트가 이미 준비되어 있으므로, 여러분은 이 템플릿을 시작하는 데 사용할 수 있습니다.
|
||||
|
||||
GitHub 저장소: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI 템플릿</a>
|
||||
|
||||
|
||||
+35
-151
@@ -2,7 +2,7 @@
|
||||
|
||||
파이썬은 선택적으로 "타입 힌트(type hints)"(“type annotations”라고도 함)를 지원합니다.
|
||||
|
||||
이러한 **"타입 힌트"** 또는 애너테이션은 변수의 <abbr title="for example: str, int, float, bool">타입</abbr>을 선언할 수 있게 해주는 특수한 구문입니다.
|
||||
이러한 **"타입 힌트"** 또는 애너테이션은 변수의 <dfn title="예: str, int, float, bool">타입</dfn>을 선언할 수 있게 해주는 특수한 구문입니다.
|
||||
|
||||
변수의 타입을 선언하면 에디터와 도구가 더 나은 지원을 제공할 수 있습니다.
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
간단한 예제로 시작해봅시다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001_py39.py *}
|
||||
{* ../../docs_src/python_types/tutorial001_py310.py *}
|
||||
|
||||
이 프로그램을 호출하면 다음이 출력됩니다:
|
||||
|
||||
@@ -34,9 +34,9 @@ John Doe
|
||||
|
||||
* `first_name`과 `last_name`를 받습니다.
|
||||
* `title()`로 각각의 첫 글자를 대문자로 변환합니다.
|
||||
* 가운데에 공백을 두고 <abbr title="Puts them together, as one. With the contents of one after the other.">연결</abbr>합니다.
|
||||
* 가운데에 공백을 두고 <dfn title="서로를 하나로 합칩니다. 하나의 내용 뒤에 다른 것의 내용을 이어 붙입니다.">연결</dfn>합니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
|
||||
{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *}
|
||||
|
||||
### 수정하기 { #edit-it }
|
||||
|
||||
@@ -80,7 +80,7 @@ John Doe
|
||||
|
||||
이것들이 "타입 힌트"입니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *}
|
||||
|
||||
이것은 다음처럼 기본값을 선언하는 것과는 다릅니다:
|
||||
|
||||
@@ -108,7 +108,7 @@ John Doe
|
||||
|
||||
이 함수를 확인해보세요. 이미 타입 힌트가 있습니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *}
|
||||
|
||||
에디터가 변수의 타입을 알고 있기 때문에, 자동완성만 되는 게 아니라 오류 검사도 할 수 있습니다:
|
||||
|
||||
@@ -116,7 +116,7 @@ John Doe
|
||||
|
||||
이제 고쳐야 한다는 것을 알고, `age`를 `str(age)`로 문자열로 바꿉니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
|
||||
{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *}
|
||||
|
||||
## 타입 선언 { #declaring-types }
|
||||
|
||||
@@ -135,29 +135,32 @@ John Doe
|
||||
* `bool`
|
||||
* `bytes`
|
||||
|
||||
{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *}
|
||||
|
||||
### 타입 매개변수가 있는 Generic(제네릭) 타입 { #generic-types-with-type-parameters }
|
||||
### `typing` 모듈 { #typing-module }
|
||||
|
||||
`dict`, `list`, `set`, `tuple`처럼 다른 값을 담을 수 있는 데이터 구조가 있습니다. 그리고 내부 값에도 각자의 타입이 있을 수 있습니다.
|
||||
몇 가지 추가적인 사용 사례에서는 표준 라이브러리의 `typing` 모듈에서 무언가를 import해야 할 수 있습니다. 예를 들어 어떤 값이 "아무 타입"일 수 있다고 선언하려면, `typing`의 `Any`를 사용할 수 있습니다:
|
||||
|
||||
이렇게 내부 타입을 가지는 타입을 "**generic**" 타입이라고 부릅니다. 그리고 내부 타입까지 포함해 선언할 수도 있습니다.
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
이런 타입과 내부 타입을 선언하려면 표준 파이썬 모듈 `typing`을 사용할 수 있습니다. 이 모듈은 이러한 타입 힌트를 지원하기 위해 존재합니다.
|
||||
|
||||
#### 더 최신 버전의 Python { #newer-versions-of-python }
|
||||
def some_function(data: Any):
|
||||
print(data)
|
||||
```
|
||||
|
||||
`typing`을 사용하는 문법은 Python 3.6부터 최신 버전까지, Python 3.9, Python 3.10 등을 포함한 모든 버전과 **호환**됩니다.
|
||||
### Generic(제네릭) 타입 { #generic-types }
|
||||
|
||||
파이썬이 발전함에 따라 **더 최신 버전**에서는 이러한 타입 애너테이션 지원이 개선되며, 많은 경우 타입 애너테이션을 선언하기 위해 `typing` 모듈을 import해서 사용할 필요조차 없게 됩니다.
|
||||
일부 타입은 대괄호 안에 "타입 매개변수"를 받아 내부 타입을 정의할 수 있습니다. 예를 들어 "문자열의 리스트"는 `list[str]`로 선언합니다.
|
||||
|
||||
프로젝트에서 더 최신 버전의 파이썬을 선택할 수 있다면, 그 추가적인 단순함을 활용할 수 있습니다.
|
||||
이렇게 타입 매개변수를 받을 수 있는 타입을 **Generic types** 또는 **Generics**라고 부릅니다.
|
||||
|
||||
이 문서 전체에는 각 파이썬 버전과 호환되는 예제가 있습니다(차이가 있을 때).
|
||||
대괄호와 내부 타입을 사용해 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다:
|
||||
|
||||
예를 들어 "**Python 3.6+**"는 Python 3.6 이상(3.7, 3.8, 3.9, 3.10 등 포함)과 호환된다는 뜻입니다. 그리고 "**Python 3.9+**"는 Python 3.9 이상(3.10 등 포함)과 호환된다는 뜻입니다.
|
||||
|
||||
**최신 버전의 Python**을 사용할 수 있다면, 최신 버전용 예제를 사용하세요. 예를 들어 "**Python 3.10+**"처럼, 가장 **좋고 가장 단순한 문법**을 갖게 됩니다.
|
||||
* `list`
|
||||
* `tuple`
|
||||
* `set`
|
||||
* `dict`
|
||||
|
||||
#### List { #list }
|
||||
|
||||
@@ -169,7 +172,7 @@ John Doe
|
||||
|
||||
`list`는 내부 타입을 포함하는 타입이므로, 그 타입들을 대괄호 안에 넣습니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -195,7 +198,7 @@ John Doe
|
||||
|
||||
`tuple`과 `set`도 동일하게 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *}
|
||||
|
||||
이는 다음을 의미합니다:
|
||||
|
||||
@@ -210,7 +213,7 @@ John Doe
|
||||
|
||||
두 번째 타입 매개변수는 `dict`의 값을 위한 것입니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *}
|
||||
|
||||
이는 다음을 의미합니다:
|
||||
|
||||
@@ -222,44 +225,20 @@ John Doe
|
||||
|
||||
변수가 **여러 타입 중 어떤 것이든** 될 수 있다고 선언할 수 있습니다. 예를 들어 `int` 또는 `str`입니다.
|
||||
|
||||
Python 3.6 이상(3.10 포함)에서는 `typing`의 `Union` 타입을 사용하고, 대괄호 안에 허용할 수 있는 타입들을 넣을 수 있습니다.
|
||||
이를 정의하려면 두 타입을 <dfn title='“비트 단위 OR 연산자”라고도 하지만, 여기서는 그 의미와 관련이 없습니다'>세로 막대(`|`)</dfn>로 구분해 사용합니다.
|
||||
|
||||
Python 3.10에는 가능한 타입들을 <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>세로 막대(`|`)</abbr>로 구분해 넣을 수 있는 **새 문법**도 있습니다.
|
||||
|
||||
//// tab | Python 3.10+
|
||||
이는 두 타입 집합의 합집합(union) 안의 어느 것이든 될 수 있다는 뜻이므로 "유니온"이라고 부릅니다.
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial008b_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
두 경우 모두 이는 `item`이 `int` 또는 `str`일 수 있다는 뜻입니다.
|
||||
이는 `item`이 `int` 또는 `str`일 수 있다는 뜻입니다.
|
||||
|
||||
#### `None`일 수도 있음 { #possibly-none }
|
||||
|
||||
값이 `str` 같은 타입일 수도 있지만, `None`일 수도 있다고 선언할 수 있습니다.
|
||||
|
||||
Python 3.6 이상(3.10 포함)에서는 `typing` 모듈에서 `Optional`을 import해서 사용하여 선언할 수 있습니다.
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!../../docs_src/python_types/tutorial009_py39.py!}
|
||||
```
|
||||
|
||||
그냥 `str` 대신 `Optional[str]`을 사용하면, 값이 항상 `str`이라고 가정하고 있지만 실제로는 `None`일 수도 있는 상황에서 에디터가 오류를 감지하도록 도와줍니다.
|
||||
|
||||
`Optional[Something]`은 사실 `Union[Something, None]`의 축약이며, 서로 동등합니다.
|
||||
|
||||
또한 이는 Python 3.10에서 `Something | None`을 사용할 수 있다는 의미이기도 합니다:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="1"
|
||||
@@ -268,96 +247,7 @@ Python 3.6 이상(3.10 포함)에서는 `typing` 모듈에서 `Optional`을 impo
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ alternative
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
#### `Union` 또는 `Optional` 사용하기 { #using-union-or-optional }
|
||||
|
||||
Python 3.10 미만 버전을 사용한다면, 아주 **주관적인** 관점에서의 팁입니다:
|
||||
|
||||
* 🚨 `Optional[SomeType]` 사용을 피하세요
|
||||
* 대신 ✨ **`Union[SomeType, None]`을 사용하세요** ✨.
|
||||
|
||||
둘은 동등하고 내부적으로는 같은 것이지만, `Optional`이라는 단어가 값이 선택 사항인 것처럼 보일 수 있기 때문에 `Optional` 대신 `Union`을 권장합니다. 실제 의미는 값이 선택 사항이라는 뜻이 아니라, "값이 `None`일 수 있다"는 뜻이기 때문입니다. 선택 사항이 아니고 여전히 필수인 경우에도요.
|
||||
|
||||
`Union[SomeType, None]`이 의미를 더 명확하게 드러낸다고 생각합니다.
|
||||
|
||||
이건 단지 단어와 이름의 문제입니다. 하지만 그런 단어들이 여러분과 팀원이 코드에 대해 생각하는 방식에 영향을 줄 수 있습니다.
|
||||
|
||||
예로, 이 함수를 봅시다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
|
||||
|
||||
매개변수 `name`은 `Optional[str]`로 정의되어 있지만, **선택 사항이 아닙니다**. 매개변수 없이 함수를 호출할 수 없습니다:
|
||||
|
||||
```Python
|
||||
say_hi() # Oh, no, this throws an error! 😱
|
||||
```
|
||||
|
||||
기본값이 없기 때문에 `name` 매개변수는 **여전히 필수입니다**(*optional*이 아님). 그럼에도 `name`은 값으로 `None`을 허용합니다:
|
||||
|
||||
```Python
|
||||
say_hi(name=None) # This works, None is valid 🎉
|
||||
```
|
||||
|
||||
좋은 소식은 Python 3.10을 사용하면, 타입의 유니온을 정의하기 위해 간단히 `|`를 사용할 수 있어서 이런 걱정을 할 필요가 없다는 점입니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
|
||||
|
||||
그러면 `Optional`이나 `Union` 같은 이름에 대해 걱정할 필요도 없습니다. 😎
|
||||
|
||||
#### Generic(제네릭) 타입 { #generic-types }
|
||||
|
||||
대괄호 안에 타입 매개변수를 받는 이러한 타입들은 **Generic types** 또는 **Generics**라고 부릅니다. 예를 들면:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
대괄호와 내부 타입을 사용해, 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다:
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
* `set`
|
||||
* `dict`
|
||||
|
||||
그리고 이전 파이썬 버전과 마찬가지로 `typing` 모듈의 다음도 사용할 수 있습니다:
|
||||
|
||||
* `Union`
|
||||
* `Optional`
|
||||
* ...그 밖의 것들.
|
||||
|
||||
Python 3.10에서는 제네릭 `Union`과 `Optional`을 사용하는 대안으로, 타입 유니온을 선언하기 위해 <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>세로 막대(`|`)</abbr>를 사용할 수 있는데, 훨씬 더 좋고 단순합니다.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
대괄호와 내부 타입을 사용해, 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다:
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
* `set`
|
||||
* `dict`
|
||||
|
||||
그리고 `typing` 모듈의 제네릭들:
|
||||
|
||||
* `Union`
|
||||
* `Optional`
|
||||
* ...그 밖의 것들.
|
||||
|
||||
////
|
||||
그냥 `str` 대신 `str | None`을 사용하면, 값이 항상 `str`이라고 가정하고 있지만 실제로는 `None`일 수도 있는 상황에서 에디터가 오류를 감지하도록 도와줍니다.
|
||||
|
||||
### 타입으로서의 클래스 { #classes-as-types }
|
||||
|
||||
@@ -365,11 +255,11 @@ Python 3.10에서는 제네릭 `Union`과 `Optional`을 사용하는 대안으
|
||||
|
||||
이름을 가진 `Person` 클래스가 있다고 해봅시다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
|
||||
{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *}
|
||||
|
||||
그러면 `Person` 타입의 변수를 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
|
||||
{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *}
|
||||
|
||||
그리고 다시, 에디터의 모든 지원을 받을 수 있습니다:
|
||||
|
||||
@@ -405,19 +295,13 @@ Pydantic 공식 문서의 예시:
|
||||
|
||||
이 모든 것은 [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank}에서 실제로 많이 보게 될 것입니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
Pydantic은 기본값 없이 `Optional` 또는 `Union[Something, None]`을 사용할 때 특별한 동작이 있습니다. 이에 대해서는 Pydantic 문서의 <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>에서 더 읽을 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 메타데이터 애너테이션이 있는 타입 힌트 { #type-hints-with-metadata-annotations }
|
||||
|
||||
파이썬에는 `Annotated`를 사용해 이러한 타입 힌트에 **추가 <abbr title="Data about the data, in this case, information about the type, e.g. a description.">메타데이터</abbr>**를 넣을 수 있는 기능도 있습니다.
|
||||
파이썬에는 `Annotated`를 사용해 이러한 타입 힌트에 **추가 <dfn title="데이터에 대한 데이터, 여기서는 타입에 대한 정보(예: 설명)">메타데이터</dfn>**를 넣을 수 있는 기능도 있습니다.
|
||||
|
||||
Python 3.9부터 `Annotated`는 표준 라이브러리의 일부이므로, `typing`에서 import할 수 있습니다.
|
||||
`Annotated`는 `typing`에서 import할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
|
||||
{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *}
|
||||
|
||||
파이썬 자체는 이 `Annotated`로 아무것도 하지 않습니다. 그리고 에디터와 다른 도구들에게는 타입이 여전히 `str`입니다.
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# 리소스 { #resources }
|
||||
|
||||
추가 리소스, 외부 링크 등. ✈️
|
||||
추가 리소스, 외부 링크, 그리고 더 많은 자료. ✈️
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/// details | 🌐 AI와 사람이 함께한 번역
|
||||
|
||||
이 번역은 사람의 안내를 받아 AI가 만들었습니다. 🤝
|
||||
|
||||
원문의 의미를 오해하거나 부자연스러워 보이는 등 오류가 있을 수 있습니다. 🤖
|
||||
|
||||
[AI LLM을 더 잘 안내하는 데 도움을 주세요](https://fastapi.tiangolo.com/ko/contributing/#translations).
|
||||
|
||||
[영문 버전](ENGLISH_VERSION_URL)
|
||||
|
||||
///
|
||||
@@ -15,7 +15,7 @@ FastAPI에서는 응답을 반환한 *후에* 실행할 백그라운드 작업
|
||||
|
||||
먼저 `BackgroundTasks`를 임포트하고, `BackgroundTasks` 타입 선언으로 *경로 처리 함수*에 매개변수를 정의합니다:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *}
|
||||
|
||||
**FastAPI**가 `BackgroundTasks` 타입의 객체를 생성하고 해당 매개변수로 전달합니다.
|
||||
|
||||
@@ -31,13 +31,13 @@ FastAPI에서는 응답을 반환한 *후에* 실행할 백그라운드 작업
|
||||
|
||||
그리고 쓰기 작업은 `async`와 `await`를 사용하지 않으므로, 일반 `def`로 함수를 정의합니다:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *}
|
||||
|
||||
## 백그라운드 작업 추가 { #add-the-background-task }
|
||||
|
||||
*경로 처리 함수* 내부에서 `.add_task()` 메서드로 작업 함수를 *백그라운드 작업* 객체에 전달합니다:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *}
|
||||
|
||||
`.add_task()`는 다음 인자를 받습니다:
|
||||
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
# 더 큰 애플리케이션 - 여러 파일 { #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'은 Python 패키지입니다
|
||||
│ ├── __init__.py # 이 파일로 'app'이 'Python 패키지'가 됩니다
|
||||
│ ├── main.py # 'main' 모듈, 예: import app.main
|
||||
│ ├── dependencies.py # 'dependencies' 모듈, 예: import app.dependencies
|
||||
│ └── routers # 'routers'는 'Python 하위 패키지'입니다
|
||||
│ │ ├── __init__.py # 이 파일로 'routers'가 'Python 하위 패키지'가 됩니다
|
||||
│ │ ├── items.py # 'items' 서브모듈, 예: import app.routers.items
|
||||
│ │ └── users.py # 'users' 서브모듈, 예: import app.routers.users
|
||||
│ └── internal # 'internal'은 'Python 하위 패키지'입니다
|
||||
│ ├── __init__.py # 이 파일로 'internal'이 'Python 하위 패키지'가 됩니다
|
||||
│ └── admin.py # 'admin' 서브모듈, 예: 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_py310/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_py310/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_py310/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_py310/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_py310/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_py310/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_py310/main.py hl[1,3,7] title["app/main.py"] *}
|
||||
|
||||
### `APIRouter` import하기 { #import-the-apirouter }
|
||||
|
||||
이제 `APIRouter`가 있는 다른 submodule들을 import합니다:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/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_py310/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_py310/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 | 기술 세부사항
|
||||
|
||||
내부적으로는 `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_py310/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_py310/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_py310/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*도 함께 포함됩니다.
|
||||
@@ -48,8 +48,8 @@
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
별도 키가 전달된 `Field` 또한 여러분의 어플리케이션의 OpenAPI 스키마에 나타날 것입니다.
|
||||
이런 키가 OpenAPI 명세서, [the OpenAPI validator](https://validator.swagger.io/)같은 몇몇 OpenAPI 도구들에 포함되지 못할 수 있으며, 여러분이 생성한 스키마와 호환되지 않을 수 있습니다.
|
||||
별도 키가 전달된 `Field` 또한 여러분의 애플리케이션의 OpenAPI 스키마에 나타날 것입니다.
|
||||
이런 키가 OpenAPI 명세서의 일부가 아닐 수도 있으므로, [OpenAPI validator](https://validator.swagger.io/) 같은 몇몇 OpenAPI 도구들은 여러분이 생성한 스키마와 호환되지 않을 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -102,12 +102,6 @@
|
||||
|
||||
기본적으로 단일 값은 쿼리 매개변수로 해석되므로, 명시적으로 `Query`를 추가할 필요 없이 이렇게 하면 됩니다:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = None
|
||||
```
|
||||
|
||||
또는 Python 3.10 이상에서는:
|
||||
|
||||
```Python
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
@@ -164,7 +164,7 @@ images: list[Image]
|
||||
|
||||
이를 아래처럼:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
|
||||
{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *}
|
||||
|
||||
## 어디서나 편집기 지원 { #editor-support-everywhere }
|
||||
|
||||
@@ -194,7 +194,7 @@ Pydantic 모델 대신 `dict`로 직접 작업한다면 이런 종류의 편집
|
||||
|
||||
이 경우, `int` 키와 `float` 값을 가진 한 어떤 `dict`든 받아들입니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
|
||||
{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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}에 설명된 아이디어를 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
@@ -74,7 +74,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 }
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
|
||||
**본문**, **경로** 그리고 **쿼리** 매개변수 모두 동시에 선언할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 각각을 인지하고 데이터를 옳바른 위치에 가져올 것입니다.
|
||||
**FastAPI**는 각각을 인지하고 데이터를 올바른 위치에 가져올 것입니다.
|
||||
|
||||
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
|
||||
|
||||
@@ -153,9 +153,9 @@
|
||||
|
||||
/// note | 참고
|
||||
|
||||
FastAPI는 `q`의 값이 필요없음을 알게 될 것입니다. 기본 값이 `= None`이기 때문입니다.
|
||||
FastAPI는 `q`의 값이 필요없음을 기본 값 `= None` 때문에 알게 됩니다.
|
||||
|
||||
Python 3.10+의 `str | None` 또는 Python 3.9+의 `Union[str, None]`에 있는 `Union`은 FastAPI가 `q` 값이 필수가 아님을 판단하기 위해 사용하지 않습니다. 기본 값이 `= None`이기 때문에 필수가 아님을 알게 됩니다.
|
||||
`str | None`은 FastAPI가 값이 필수인지 아닌지를 판단하기 위해 사용하지 않습니다. 기본 값이 `= None`이므로 필수가 아님을 알게 됩니다.
|
||||
|
||||
하지만 타입 어노테이션을 추가하면 편집기가 더 나은 지원을 제공하고 오류를 감지할 수 있습니다.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 쿠키를 **제한**할 수 있습니다.
|
||||
|
||||
이제 API는 자신의 <abbr title="This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. 🍪">cookie consent</abbr>를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪
|
||||
이제 API는 자신의 <dfn title="혹시라도 오해할까 봐 하는 농담입니다. 쿠키 동의와는 아무 관련이 없지만, 이제 API도 불쌍한 쿠키를 거절할 수 있다는 점이 웃기네요. 쿠키 하나 드세요. 🍪">쿠키 동의</dfn>를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
|
||||
|
||||
@@ -54,9 +54,9 @@ Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forb
|
||||
|
||||
클라이언트가 **추가 쿠키**를 보내려고 시도하면, **오류** 응답을 받게 됩니다.
|
||||
|
||||
동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들, <abbr title="This is another joke. Don't pay attention to me. Have some coffee for your cookie. ☕">API가 거부</abbr>하는데도. 🍪
|
||||
동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들, <dfn title="이것도 농담입니다. 신경 쓰지 마세요. 쿠키와 함께 커피 한 잔 하세요. ☕">API가 거부</dfn>하는데도. 🍪
|
||||
|
||||
예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` <abbr title="Santa disapproves the lack of cookies. 🎅 Okay, no more cookie jokes.">쿠키가 허용되지 않는다</abbr>는 **오류** 응답을 받게 됩니다:
|
||||
예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` <dfn title="산타는 쿠키가 부족한 것을 못마땅해합니다. 🎅 좋아요, 이제 더 이상 쿠키 농담은 하지 않겠습니다.">쿠키가 허용되지 않는다</dfn>는 **오류** 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -73,4 +73,4 @@ Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forb
|
||||
|
||||
## 요약 { #summary }
|
||||
|
||||
**Pydantic 모델**을 사용하여 **FastAPI**에서 <abbr title="Have a last cookie before you go. 🍪">**쿠키**</abbr>를 선언할 수 있습니다. 😎
|
||||
**Pydantic 모델**을 사용하여 **FastAPI**에서 <dfn title="가시기 전에 마지막 쿠키 하나 드세요. 🍪">**쿠키**</dfn>를 선언할 수 있습니다. 😎
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
/// info
|
||||
|
||||
쿠키를 선언하기 위해서는 `Cookie`를 사용해야 합니다. 그렇지 않으면 해당 매개변수를 쿼리 매개변수로 해석하기 때문입니다.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
/// info
|
||||
|
||||
**브라우저는 쿠키를** 내부적으로 특별한 방식으로 처리하기 때문에, **JavaScript**가 쉽게 쿠키를 다루도록 허용하지 않는다는 점을 염두에 두세요.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
* 특정 HTTP 메서드(`POST`, `PUT`) 또는 와일드카드 `"*"`를 사용한 모든 메서드.
|
||||
* 특정 HTTP 헤더 또는 와일드카드 `"*"`를 사용한 모든 헤더.
|
||||
|
||||
{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
|
||||
{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *}
|
||||
|
||||
|
||||
`CORSMiddleware` 구현에서 사용하는 기본 매개변수는 기본적으로 제한적이므로, 브라우저가 Cross-Domain 컨텍스트에서 특정 출처, 메서드 또는 헤더를 사용할 수 있도록 하려면 이를 명시적으로 활성화해야 합니다.
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
## 더 많은 정보 { #more-info }
|
||||
|
||||
<abbr title="Cross-Origin Resource Sharing – 교차-출처 리소스 공유">CORS</abbr>에 대한 더 많은 정보는 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS 문서</a>를 참고하세요.
|
||||
<abbr title="Cross-Origin Resource Sharing - 교차-출처 리소스 공유">CORS</abbr>에 대한 더 많은 정보는 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS 문서</a>를 참고하세요.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
FastAPI 애플리케이션에서 `uvicorn`을 직접 임포트하여 실행합니다:
|
||||
|
||||
{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
|
||||
{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *}
|
||||
|
||||
### `__name__ == "__main__"` 에 대하여 { #about-name-main }
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ FastAPI가 실제로 확인하는 것은 그것이 "호출 가능(callable)"(함
|
||||
|
||||
위 코드에서 `CommonQueryParams`를 두 번 작성하는 방식에 주목하세요:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
@@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ non-Annotated
|
||||
//// tab | Python 3.10+ Annotated 미사용
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -137,7 +137,7 @@ FastAPI는 여기에서 선언된 매개변수들을 추출하고, 실제로 이
|
||||
|
||||
이 경우 첫 번째 `CommonQueryParams`는 다음에서:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, ...
|
||||
@@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ...
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ non-Annotated
|
||||
//// tab | Python 3.10+ Annotated 미사용
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -163,7 +163,7 @@ commons: CommonQueryParams ...
|
||||
|
||||
실제로는 이렇게만 작성해도 됩니다:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[Any, Depends(CommonQueryParams)]
|
||||
@@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ non-Annotated
|
||||
//// tab | Python 3.10+ Annotated 미사용
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -197,7 +197,7 @@ commons = Depends(CommonQueryParams)
|
||||
|
||||
하지만 `CommonQueryParams`를 두 번 작성하는 코드 반복이 있다는 것을 볼 수 있습니다:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
@@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ non-Annotated
|
||||
//// tab | Python 3.10+ Annotated 미사용
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -225,7 +225,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
|
||||
다음처럼 작성하는 대신:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
@@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ non-Annotated
|
||||
//// tab | Python 3.10+ Annotated 미사용
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
|
||||
...이렇게 작성합니다:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends()]
|
||||
@@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()]
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ non-Annotated
|
||||
//// tab | Python 3.10+ Annotated 미사용
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
# 경로 작동 데코레이터에서의 의존성 { #dependencies-in-path-operation-decorators }
|
||||
# 경로 처리 데코레이터에서의 의존성 { #dependencies-in-path-operation-decorators }
|
||||
|
||||
몇몇 경우에는, *경로 작동 함수* 안에서 의존성의 반환 값이 필요하지 않습니다.
|
||||
몇몇 경우에는, *경로 처리 함수* 안에서 의존성의 반환 값이 필요하지 않습니다.
|
||||
|
||||
또는 의존성이 값을 반환하지 않습니다.
|
||||
|
||||
그러나 여전히 실행/해결될 필요가 있습니다.
|
||||
|
||||
그런 경우에, `Depends`를 사용하여 *경로 작동 함수*의 매개변수로 선언하는 것보다 *경로 작동 데코레이터*에 `dependencies`의 `list`를 추가할 수 있습니다.
|
||||
그런 경우에, `Depends`를 사용하여 *경로 처리 함수*의 매개변수로 선언하는 대신 *경로 처리 데코레이터*에 `dependencies`의 `list`를 추가할 수 있습니다.
|
||||
|
||||
## *경로 작동 데코레이터*에 `dependencies` 추가하기 { #add-dependencies-to-the-path-operation-decorator }
|
||||
## *경로 처리 데코레이터*에 `dependencies` 추가하기 { #add-dependencies-to-the-path-operation-decorator }
|
||||
|
||||
*경로 작동 데코레이터*는 `dependencies`라는 선택적인 인자를 받습니다.
|
||||
*경로 처리 데코레이터*는 선택적인 인자 `dependencies`를 받습니다.
|
||||
|
||||
`Depends()`로 된 `list`이어야합니다:
|
||||
`Depends()`로 된 `list`이어야 합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *}
|
||||
|
||||
이러한 의존성들은 기존 의존성들과 같은 방식으로 실행/해결됩니다. 그러나 값은 (무엇이든 반환한다면) *경로 작동 함수*에 제공되지 않습니다.
|
||||
이러한 의존성들은 기존 의존성들과 같은 방식으로 실행/해결됩니다. 그러나 값은 (무엇이든 반환한다면) *경로 처리 함수*에 제공되지 않습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
일부 편집기에서는 사용되지 않는 함수 매개변수를 검사하고 오류로 표시합니다.
|
||||
|
||||
*경로 작동 데코레이터*에서 이러한 `dependencies`를 사용하면 편집기/도구 오류를 피하면서도 실행되도록 할 수 있습니다.
|
||||
*경로 처리 데코레이터*에서 이러한 `dependencies`를 사용하면 편집기/도구 오류를 피하면서도 실행되도록 할 수 있습니다.
|
||||
|
||||
또한 코드에서 사용되지 않는 매개변수를 보고 불필요하다고 생각할 수 있는 새로운 개발자의 혼란을 방지하는데 도움이 될 수 있습니다.
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
|
||||
(헤더같은) 요청 요구사항이나 하위-의존성을 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *}
|
||||
|
||||
### 오류 발생시키기 { #raise-exceptions }
|
||||
|
||||
다음 의존성은 기존 의존성과 동일하게 예외를 `raise`할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *}
|
||||
|
||||
### 값 반환하기 { #return-values }
|
||||
|
||||
@@ -58,12 +58,12 @@
|
||||
|
||||
그래서 이미 다른 곳에서 사용된 (값을 반환하는) 일반적인 의존성을 재사용할 수 있고, 비록 값은 사용되지 않지만 의존성은 실행될 것입니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *}
|
||||
|
||||
## *경로 작동* 모음에 대한 의존성 { #dependencies-for-a-group-of-path-operations }
|
||||
## *경로 처리* 모음에 대한 의존성 { #dependencies-for-a-group-of-path-operations }
|
||||
|
||||
나중에 여러 파일을 가지고 있을 수 있는 더 큰 애플리케이션을 구조화하는 법([더 큰 애플리케이션 - 여러 파일들](../../tutorial/bigger-applications.md){.internal-link target=_blank})을 읽을 때, *경로 작동* 모음에 대한 단일 `dependencies` 매개변수를 선언하는 법에 대해서 배우게 될 것입니다.
|
||||
나중에 여러 파일을 가지고 있을 수 있는 더 큰 애플리케이션을 구조화하는 법([더 큰 애플리케이션 - 여러 파일들](../../tutorial/bigger-applications.md){.internal-link target=_blank})을 읽을 때, *경로 처리* 모음에 대한 단일 `dependencies` 매개변수를 선언하는 법에 대해서 배우게 될 것입니다.
|
||||
|
||||
## 전역 의존성 { #global-dependencies }
|
||||
|
||||
다음으로 각 *경로 작동*에 적용되도록 `FastAPI` 애플리케이션 전체에 의존성을 추가하는 법을 볼 것입니다.
|
||||
다음으로 각 *경로 처리*에 적용되도록 `FastAPI` 애플리케이션 전체에 의존성을 추가하는 법을 볼 것입니다.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# `yield`를 사용하는 의존성 { #dependencies-with-yield }
|
||||
|
||||
FastAPI는 <abbr title='sometimes also called "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code", etc. – 때로는 "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code" 등으로도 불립니다'>작업 완료 후 추가 단계를 수행하는</abbr> 의존성을 지원합니다.
|
||||
FastAPI는 <dfn title='때로는 "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code" 등으로도 불립니다'>작업 완료 후 추가 단계</dfn>를 수행하는 의존성을 지원합니다.
|
||||
|
||||
이를 구현하려면 `return` 대신 `yield`를 사용하고, 추가로 실행할 단계 (코드)를 그 뒤에 작성하세요.
|
||||
|
||||
@@ -29,15 +29,15 @@ FastAPI는 <abbr title='sometimes also called "exit code", "cleanup code", "tear
|
||||
|
||||
응답을 생성하기 전에는 `yield`문을 포함하여 그 이전의 코드만이 실행됩니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *}
|
||||
|
||||
yield된 값은 *경로 처리* 및 다른 의존성들에 주입되는 값 입니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *}
|
||||
|
||||
`yield`문 다음의 코드는 응답을 생성한 후 실행됩니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -57,7 +57,7 @@ yield된 값은 *경로 처리* 및 다른 의존성들에 주입되는 값 입
|
||||
|
||||
마찬가지로, `finally`를 사용하여 예외 발생 여부와 관계 없이 종료 단계까 실행되도록 할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *}
|
||||
|
||||
## `yield`를 사용하는 하위 의존성 { #sub-dependencies-with-yield }
|
||||
|
||||
@@ -67,7 +67,7 @@ yield된 값은 *경로 처리* 및 다른 의존성들에 주입되는 값 입
|
||||
|
||||
예를 들어, `dependency_c`는 `dependency_b`에 의존할 수 있고, `dependency_b`는 `dependency_a`에 의존할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *}
|
||||
|
||||
이들 모두는 `yield`를 사용할 수 있습니다.
|
||||
|
||||
@@ -75,7 +75,7 @@ yield된 값은 *경로 처리* 및 다른 의존성들에 주입되는 값 입
|
||||
|
||||
그리고, `dependency_b`는 종료 코드를 위해 `dependency_a`의 값 (여기서는 `dep_a`로 명명) 이 사용 가능해야 합니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *}
|
||||
|
||||
같은 방식으로, `yield`를 사용하는 의존성과 `return`을 사용하는 의존성을 함께 사용할 수 있으며, 이들 중 일부가 다른 것들에 의존할 수 있습니다.
|
||||
|
||||
@@ -109,7 +109,7 @@ yield된 값은 *경로 처리* 및 다른 의존성들에 주입되는 값 입
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
|
||||
{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *}
|
||||
|
||||
예외를 잡고 그에 기반해 사용자 정의 응답을 생성하려면, [사용자 정의 예외 처리기](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}를 생성하세요.
|
||||
|
||||
@@ -117,7 +117,7 @@ yield된 값은 *경로 처리* 및 다른 의존성들에 주입되는 값 입
|
||||
|
||||
`yield`를 사용하는 의존성에서 `except`를 사용하여 예외를 포착하고 예외를 다시 발생시키지 않거나 (또는 새 예외를 발생시키지 않으면), FastAPI는 일반적인 Python에서와 마찬가지로 예외가 있었다는 것을 알아차릴 수 없습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
|
||||
{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *}
|
||||
|
||||
이 경우, `HTTPException`이나 유사한 예외를 발생시키지 않기 때문에 클라이언트는 마땅히 *HTTP 500 Internal Server Error* 응답을 보게 되지만, 서버에는 어떤 오류였는지에 대한 **로그**나 다른 표시가 **전혀 남지 않게 됩니다**. 😱
|
||||
|
||||
@@ -127,7 +127,7 @@ yield된 값은 *경로 처리* 및 다른 의존성들에 주입되는 값 입
|
||||
|
||||
`raise`를 사용하여 동일한 예외를 다시 발생시킬 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
|
||||
{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *}
|
||||
|
||||
이제 클라이언트는 동일한 *HTTP 500 Internal Server Error* 응답을 받게 되지만, 서버 로그에는 사용자 정의 `InternalError`가 기록됩니다. 😎
|
||||
|
||||
@@ -190,7 +190,7 @@ participant tasks as Background tasks
|
||||
|
||||
하지만 *경로 처리 함수*에서 반환한 뒤에는 더 이상 해당 의존성이 필요 없다는 것을 알고 있다면, `Depends(scope="function")`을 사용하여 FastAPI에 *경로 처리 함수*가 반환된 후, 하지만 **응답이 전송되기 전에** 의존성을 종료(닫기)해야 한다고 알려줄 수 있습니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
|
||||
{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *}
|
||||
|
||||
`Depends()`는 다음이 될 수 있는 `scope` 매개변수를 받습니다:
|
||||
|
||||
@@ -269,7 +269,7 @@ Python에서는 다음을 통해 컨텍스트 관리자를 생성할 수 있습
|
||||
**FastAPI**의 `yield`가 있는 의존성 내에서
|
||||
`with` 또는 `async with`문을 사용하여 이들을 활용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *}
|
||||
{* ../../docs_src/dependencies/tutorial010_py310.py hl[1:9,13] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
몇몇 유형의 애플리케이션에서는 애플리케이션 전체에 의존성을 추가하고 싶을 수 있습니다.
|
||||
|
||||
[*경로 처리 데코레이터*에 `dependencies` 추가하기](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}와 유사한 방법으로 `FastAPI` 애플리케케이션에 그것들을 추가할 수 있습니다.
|
||||
[*경로 처리 데코레이터*에 `dependencies` 추가하기](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}와 유사한 방법으로 `FastAPI` 애플리케이션에 그것들을 추가할 수 있습니다.
|
||||
|
||||
그런 경우에, 애플리케이션의 모든 *경로 처리*에 적용될 것입니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *}
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py310.py hl[17] *}
|
||||
|
||||
그리고 [*경로 처리 데코레이터*에 `dependencies` 추가하기](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 섹션의 모든 아이디어는 여전히 적용되지만, 이 경우에는 앱의 모든 *경로 처리*에 적용됩니다.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 의존성 { #dependencies }
|
||||
|
||||
**FastAPI**는 아주 강력하지만 직관적인 **<abbr title="also known as components, resources, providers, services, injectables">의존성 주입</abbr>** 시스템을 가지고 있습니다.
|
||||
**FastAPI**는 아주 강력하지만 직관적인 **<dfn title="컴포넌트, 리소스, 제공자, 서비스, 인젝터블로도 알려져 있음">의존성 주입</dfn>** 시스템을 가지고 있습니다.
|
||||
|
||||
이는 사용하기 아주 쉽게 설계했으며, 어느 개발자나 다른 컴포넌트와 **FastAPI**를 쉽게 통합할 수 있도록 만들었습니다.
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
* 선택적인 쿼리 매개변수 `q`, `str`을 자료형으로 가집니다.
|
||||
* 선택적인 쿼리 매개변수 `skip`, `int`를 자료형으로 가지며 기본 값은 `0`입니다.
|
||||
* 선택적인 쿼리 매개변수 `limit` that is an `int`, and by default is `100`.
|
||||
* 선택적인 쿼리 매개변수 `limit`, `int`를 자료형으로 가지며 기본 값은 `100`입니다.
|
||||
|
||||
그 후 위의 값을 포함한 `dict` 자료형으로 반환할 뿐입니다.
|
||||
|
||||
@@ -71,7 +71,7 @@ FastAPI는 0.95.0 버전부터 `Annotated`에 대한 지원을 (그리고 이를
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
|
||||
|
||||
비록 `Body`, `Query` 등을 사용하는 것과 같은 방식으로 여러분의 함수의 매개변수에 있는 `Depends`를 사용하지만, `Depends`는 약간 다르게 작동합니다.
|
||||
비록 `Depends`를 함수의 매개변수에서 `Body`, `Query` 등을 사용하는 것과 같은 방식으로 사용하지만, `Depends`는 약간 다르게 작동합니다.
|
||||
|
||||
`Depends`에 단일 매개변수만 전달했습니다.
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# 하위 의존성 { #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**는 그 하위 의존성을 요청당 한 번만 호출해야 한다는 것을 알고 있습니다.
|
||||
|
||||
그리고 같은 요청에 대해 동일한 의존성을 여러 번 호출하는 대신, 반환값을 <dfn title="계산/생성된 값을 저장해 두었다가, 다시 계산하지 않고 재사용하기 위한 유틸리티/시스템.">"캐시"</dfn>에 저장하고, 그 요청에서 해당 값이 필요한 모든 "dependants"에 전달합니다.
|
||||
|
||||
고급 시나리오로, 같은 요청에서 "cached" 값을 쓰는 대신 매 단계마다(아마도 여러 번) 의존성이 호출되어야 한다는 것을 알고 있다면, `Depends`를 사용할 때 `use_cache=False` 파라미터를 설정할 수 있습니다:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```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.10+ 비 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 | 팁
|
||||
|
||||
이 단순한 예시만 보면 그다지 유용해 보이지 않을 수도 있습니다.
|
||||
|
||||
하지만 **보안**에 관한 챕터에서 이것이 얼마나 유용한지 보게 될 것입니다.
|
||||
|
||||
또한 얼마나 많은 코드를 아껴주는지도 보게 될 것입니다.
|
||||
|
||||
///
|
||||
@@ -12,7 +12,7 @@ JSON 호환 가능 데이터만 수신하는 `fake_db` 데이터베이스가 존
|
||||
|
||||
예를 들면, `datetime` 객체는 JSON과 호환되지 않으므로 이 데이터베이스는 이를 받지 않습니다.
|
||||
|
||||
따라서 `datetime` 객체는 <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO format</a>의 데이터를 포함하는 `str`로 변환되어야 합니다.
|
||||
따라서 `datetime` 객체는 <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO 형식</a>의 데이터를 포함하는 `str`로 변환되어야 합니다.
|
||||
|
||||
같은 방식으로 이 데이터베이스는 Pydantic 모델(속성이 있는 객체)을 받지 않고, `dict`만을 받습니다.
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* **출력 모델**은 비밀번호를 가지면 안 됩니다.
|
||||
* **데이터베이스 모델**은 아마도 해시 처리된 비밀번호를 가질 필요가 있을 것입니다.
|
||||
|
||||
/// danger | 위험
|
||||
/// danger
|
||||
|
||||
절대 사용자의 비밀번호를 평문으로 저장하지 마세요. 항상 이후에 검증 가능한 "안전한 해시(secure hash)"로 저장하세요.
|
||||
|
||||
만약 이게 무엇인지 모르겠다면, [security chapters](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}에서 "password hash"가 무엇인지 배울 수 있습니다.
|
||||
만약 이게 무엇인지 모르겠다면, [보안 장](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}에서 "password hash"가 무엇인지 배울 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
@@ -132,7 +132,7 @@ UserInDB(
|
||||
)
|
||||
```
|
||||
|
||||
/// warning | 경고
|
||||
/// warning
|
||||
|
||||
추가적으로 제공된 함수 `fake_password_hasher`와 `fake_save_user`는 데이터 흐름을 시연하기 위한 예제일 뿐이며, 실제 보안을 제공하지 않습니다.
|
||||
|
||||
@@ -164,7 +164,7 @@ OpenAPI에서는 이를 `anyOf`로 정의합니다.
|
||||
|
||||
이를 위해 표준 Python 타입 힌트인 <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>을 사용할 수 있습니다:
|
||||
|
||||
/// note | 참고
|
||||
/// note
|
||||
|
||||
<a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>을 정의할 때는 더 구체적인 타입을 먼저 포함하고, 덜 구체적인 타입을 그 뒤에 나열해야 합니다. 아래 예제에서는 `Union[PlaneItem, CarItem]`에서 더 구체적인 `PlaneItem`이 `CarItem`보다 앞에 위치합니다.
|
||||
|
||||
@@ -190,9 +190,9 @@ some_variable: PlaneItem | CarItem
|
||||
|
||||
마찬가지로, 객체 리스트 형태의 응답을 선언할 수도 있습니다.
|
||||
|
||||
이를 위해 표준 Python의 `typing.List`를 사용하세요(또는 Python 3.9 이상에서는 단순히 `list`를 사용할 수 있습니다):
|
||||
이를 위해 표준 Python의 `list`를 사용하세요:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
|
||||
{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *}
|
||||
|
||||
## 임의의 `dict` 응답 { #response-with-arbitrary-dict }
|
||||
|
||||
@@ -200,9 +200,9 @@ Pydantic 모델을 사용하지 않고, 키와 값의 타입만 선언하여 평
|
||||
|
||||
이는 Pydantic 모델에 필요한 유효한 필드/속성 이름을 사전에 알 수 없는 경우에 유용합니다.
|
||||
|
||||
이 경우, `typing.Dict`를 사용할 수 있습니다(또는 Python 3.9 이상에서는 단순히 `dict`를 사용할 수 있습니다):
|
||||
이 경우, `dict`를 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
|
||||
{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *}
|
||||
|
||||
## 요약 { #recap }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
가장 단순한 FastAPI 파일은 다음과 같이 보일 것입니다:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py310.py *}
|
||||
|
||||
위 코드를 `main.py`에 복사합니다.
|
||||
|
||||
@@ -104,7 +104,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
|
||||
#### OpenAPI와 JSON 스키마 { #openapi-and-json-schema }
|
||||
|
||||
OpenAPI는 당신의 API에 대한 API 스키마를 정의합니다. 또한 이 스키마는 JSON 데이터 스키마의 표준인 **JSON 스키마**를 사용하여 당신의 API가 보내고 받는 데이터의 정의(또는 "스키마")를 포함합니다.
|
||||
OpenAPI는 여러분의 API에 대한 API 스키마를 정의합니다. 또한 이 스키마는 JSON 데이터 스키마의 표준인 **JSON 스키마**를 사용하여 여러분의 API가 보내고 받는 데이터의 정의(또는 "스키마")를 포함합니다.
|
||||
|
||||
#### `openapi.json` 확인 { #check-the-openapi-json }
|
||||
|
||||
@@ -183,9 +183,9 @@ Deploying to FastAPI Cloud...
|
||||
|
||||
### 1 단계: `FastAPI` 임포트 { #step-1-import-fastapi }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *}
|
||||
|
||||
`FastAPI`는 당신의 API를 위한 모든 기능을 제공하는 파이썬 클래스입니다.
|
||||
`FastAPI`는 여러분의 API를 위한 모든 기능을 제공하는 파이썬 클래스입니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
@@ -197,11 +197,11 @@ Deploying to FastAPI Cloud...
|
||||
|
||||
### 2 단계: `FastAPI` "인스턴스" 생성 { #step-2-create-a-fastapi-instance }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py310.py hl[3] *}
|
||||
|
||||
여기에서 `app` 변수는 `FastAPI` 클래스의 "인스턴스"가 됩니다.
|
||||
|
||||
이것은 당신의 모든 API를 생성하기 위한 상호작용의 주요 지점이 될 것입니다.
|
||||
이것은 여러분의 모든 API를 생성하기 위한 상호작용의 주요 지점이 될 것입니다.
|
||||
|
||||
### 3 단계: *경로 처리* 생성 { #step-3-create-a-path-operation }
|
||||
|
||||
@@ -266,12 +266,12 @@ API를 설계할 때 일반적으로 특정 행동을 수행하기 위해 특정
|
||||
|
||||
#### *경로 처리 데코레이터* 정의 { #define-a-path-operation-decorator }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py310.py hl[6] *}
|
||||
|
||||
`@app.get("/")`은 **FastAPI**에게 바로 아래에 있는 함수가 다음으로 이동하는 요청을 처리한다는 것을 알려줍니다:
|
||||
|
||||
* 경로 `/`
|
||||
* <abbr title="an HTTP GET method"><code>get</code> operation</abbr> 사용
|
||||
* <dfn title="HTTP GET 메소드"><code>get</code> 작동</dfn> 사용
|
||||
|
||||
/// info | `@decorator` 정보
|
||||
|
||||
@@ -320,7 +320,7 @@ API를 설계할 때 일반적으로 특정 행동을 수행하기 위해 특정
|
||||
* **작동**: 은 `get`입니다.
|
||||
* **함수**: 는 "데코레이터" 아래에 있는 함수입니다 (`@app.get("/")` 아래).
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *}
|
||||
|
||||
이것은 파이썬 함수입니다.
|
||||
|
||||
@@ -332,7 +332,7 @@ URL "`/`"에 대한 `GET` 작동을 사용하는 요청을 받을 때마다 **Fa
|
||||
|
||||
`async def`을 이용하는 대신 일반 함수로 정의할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
|
||||
{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
@@ -342,7 +342,7 @@ URL "`/`"에 대한 `GET` 작동을 사용하는 요청을 받을 때마다 **Fa
|
||||
|
||||
### 5 단계: 콘텐츠 반환 { #step-5-return-the-content }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *}
|
||||
|
||||
`dict`, `list`, 단일값을 가진 `str`, `int` 등을 반환할 수 있습니다.
|
||||
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
# 오류 처리 { #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_py310.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_py310.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_py310.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_py310.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_py310.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_py310.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_py310.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_py310.py hl[2:5,15,21] *}
|
||||
|
||||
이 예시에서는 매우 표현력 있는 메시지로 오류를 출력만 하고 있지만, 요지는 이해하셨을 겁니다. 예외를 사용한 뒤 기본 예외 핸들러를 그대로 재사용할 수 있습니다.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
관련 있는 **헤더 매개변수** 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다.
|
||||
|
||||
이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😎
|
||||
이를 통해 **여러 위치**에서 **모델을 재사용할** 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😎
|
||||
|
||||
/// note | 참고
|
||||
|
||||
@@ -36,7 +36,7 @@ Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forb
|
||||
|
||||
클라이언트가 **추가 헤더**를 보내려고 시도하면, **오류** 응답을 받게 됩니다.
|
||||
|
||||
예를 들어, 클라이언트가 `plumbus` 값으로 `tool` 헤더를 보내려고 하면, 클라이언트는 헤더 매개변수 `tool`이 허용 되지 않는다는 **오류** 응답을 받게 됩니다:
|
||||
예를 들어, 클라이언트가 `plumbus` 값으로 `tool` 헤더를 보내려고 하면, 클라이언트는 헤더 매개변수 `tool`이 허용되지 않는다는 **오류** 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ OpenAPI 명세 및 자동화된 API 문서 UI에 사용되는 다음 필드를
|
||||
|
||||
다음과 같이 설정할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *}
|
||||
{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -36,7 +36,7 @@ OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `url` 대신 `identifie
|
||||
|
||||
예:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
|
||||
{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *}
|
||||
|
||||
## 태그에 대한 메타데이터 { #metadata-for-tags }
|
||||
|
||||
@@ -58,7 +58,7 @@ OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `url` 대신 `identifie
|
||||
|
||||
태그에 대한 메타데이터를 생성하고 이를 `openapi_tags` 매개변수로 전달하세요:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
|
||||
{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *}
|
||||
|
||||
설명 안에 마크다운을 사용할 수 있다는 점에 유의하세요. 예를 들어 "login"은 굵게(**login**) 표시되고, "fancy"는 기울임꼴(_fancy_)로 표시됩니다.
|
||||
|
||||
@@ -72,7 +72,7 @@ OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `url` 대신 `identifie
|
||||
|
||||
`tags` 매개변수를 *경로 처리* (및 `APIRouter`)와 함께 사용하여 이를 서로 다른 태그에 할당하세요:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
|
||||
{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -100,7 +100,7 @@ OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `url` 대신 `identifie
|
||||
|
||||
예를 들어, 이를 `/api/v1/openapi.json`에서 제공하도록 설정하려면:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
|
||||
{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *}
|
||||
|
||||
OpenAPI 스키마를 완전히 비활성화하려면 `openapi_url=None`으로 설정할 수 있으며, 이를 사용하여 문서화 사용자 인터페이스도 비활성화됩니다.
|
||||
|
||||
@@ -117,4 +117,4 @@ OpenAPI 스키마를 완전히 비활성화하려면 `openapi_url=None`으로
|
||||
|
||||
예를 들어, Swagger UI를 `/documentation`에서 제공하고 ReDoc을 비활성화하려면:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
|
||||
{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
`yield`를 사용하는 의존성이 있다면, exit 코드는 미들웨어 *후에* 실행됩니다.
|
||||
|
||||
백그라운드 작업(뒤에서 보게 될 [Background Tasks](background-tasks.md){.internal-link target=_blank} 섹션에서 다룹니다)이 있다면, 모든 미들웨어 *후에* 실행됩니다.
|
||||
백그라운드 작업(뒤에서 보게 될 [백그라운드 작업](background-tasks.md){.internal-link target=_blank} 섹션에서 다룹니다)이 있다면, 모든 미들웨어 *후에* 실행됩니다.
|
||||
|
||||
///
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
* 그런 다음 해당 *경로 처리*가 생성한 `response`를 반환합니다.
|
||||
* 그런 다음 반환하기 전에 `response`를 추가로 수정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *}
|
||||
{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
예를 들어, 요청을 처리하고 응답을 생성하는 데 걸린 시간을 초 단위로 담는 사용자 정의 헤더 `X-Process-Time`을 추가할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
|
||||
{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -92,4 +92,4 @@ app.add_middleware(MiddlewareB)
|
||||
|
||||
다른 미들웨어에 대한 더 많은 정보는 나중에 [숙련된 사용자 안내서: 향상된 미들웨어](../advanced/middleware.md){.internal-link target=_blank}에서 확인할 수 있습니다.
|
||||
|
||||
다음 섹션에서 미들웨어로 <abbr title="Cross-Origin Resource Sharing">CORS</abbr>를 처리하는 방법을 보게 될 것입니다.
|
||||
다음 섹션에서 미들웨어로 <abbr title="Cross-Origin Resource Sharing - 교차 출처 리소스 공유">CORS</abbr>를 처리하는 방법을 보게 될 것입니다.
|
||||
|
||||
@@ -46,17 +46,17 @@
|
||||
|
||||
**FastAPI**는 일반 문자열과 동일한 방식으로 이를 지원합니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *}
|
||||
|
||||
## 요약과 설명 { #summary-and-description }
|
||||
|
||||
`summary`와 `description`을 추가할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *}
|
||||
|
||||
## 독스트링으로 만든 설명 { #description-from-docstring }
|
||||
|
||||
설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 처리* 설명을 함수 <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation – 문서화에 사용되는 함수 내부 첫 표현식의 여러 줄 문자열(어떤 변수에도 할당되지 않음)">docstring</abbr>에 선언할 수 있으며, **FastAPI**는 그곳에서 이를 읽습니다.
|
||||
설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 처리* 설명을 함수 <dfn title="문서화에 사용되는 함수 내부 첫 표현식의 여러 줄 문자열(어떤 변수에도 할당되지 않음)">독스트링</dfn>에 선언할 수 있으며, **FastAPI**는 그곳에서 이를 읽습니다.
|
||||
|
||||
독스트링에는 <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a>을 작성할 수 있으며, (독스트링의 들여쓰기를 고려하여) 올바르게 해석되고 표시됩니다.
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
`response_description` 매개변수로 응답에 관한 설명을 명시할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -90,9 +90,9 @@ OpenAPI는 각 *경로 처리*가 응답에 관한 설명을 요구할 것을
|
||||
|
||||
## *경로 처리* 지원중단하기 { #deprecate-a-path-operation }
|
||||
|
||||
*경로 처리*를 제거하지 않고 <abbr title="obsolete, recommended not to use it – 구식이며 사용하지 않는 것이 권장됨">deprecated</abbr>로 표시해야 한다면, `deprecated` 매개변수를 전달하면 됩니다:
|
||||
*경로 처리*를 제거하지 않고 <dfn title="구식이며 사용하지 않는 것이 권장됨">지원중단</dfn>으로 표시해야 한다면, `deprecated` 매개변수를 전달하면 됩니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *}
|
||||
|
||||
대화형 문서에서 지원중단으로 명확하게 표시됩니다:
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이
|
||||
|
||||
따라서 함수를 다음과 같이 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *}
|
||||
|
||||
하지만 `Annotated`를 사용하면 이 문제가 없다는 점을 기억하세요. `Query()`나 `Path()`에 함수 매개변수 기본값을 사용하지 않기 때문에, 순서는 중요하지 않습니다.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *}
|
||||
|
||||
## 필요한 대로 매개변수 정렬하기, 트릭 { #order-the-parameters-as-you-need-tricks }
|
||||
|
||||
@@ -83,13 +83,13 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이
|
||||
|
||||
파이썬은 `*`으로 아무것도 하지 않지만, 뒤따르는 모든 매개변수는 키워드 인자(키-값 쌍)로 호출되어야 함을 알게 됩니다. 이는 <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>로도 알려져 있습니다. 기본값이 없더라도 마찬가지입니다.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *}
|
||||
|
||||
### `Annotated`를 쓰면 더 좋습니다 { #better-with-annotated }
|
||||
|
||||
`Annotated`를 사용하면 함수 매개변수 기본값을 사용하지 않기 때문에 이 문제가 발생하지 않으며, 아마 `*`도 사용할 필요가 없다는 점을 기억하세요.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *}
|
||||
|
||||
## 숫자 검증: 크거나 같음 { #number-validations-greater-than-or-equal }
|
||||
|
||||
@@ -97,7 +97,7 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이
|
||||
|
||||
여기서 `ge=1`인 경우, `item_id`는 `1`보다 "`g`reater than or `e`qual"(크거나 같은) 정수형 숫자여야 합니다.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *}
|
||||
|
||||
## 숫자 검증: 크거나 및 작거나 같음 { #number-validations-greater-than-and-less-than-or-equal }
|
||||
|
||||
@@ -106,7 +106,7 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이
|
||||
* `gt`: `g`reater `t`han
|
||||
* `le`: `l`ess than or `e`qual
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *}
|
||||
|
||||
## 숫자 검증: 부동소수, 크거나 및 작거나 { #number-validations-floats-greater-than-and-less-than }
|
||||
|
||||
@@ -118,7 +118,7 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이
|
||||
|
||||
<abbr title="less than"><code>lt</code></abbr> 역시 마찬가지입니다.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *}
|
||||
|
||||
## 요약 { #recap }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
파이썬의 포맷 문자열 리터럴에서 사용되는 문법을 이용하여 경로 "매개변수" 또는 "변수"를 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
|
||||
{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *}
|
||||
|
||||
경로 매개변수 `item_id`의 값은 함수의 `item_id` 인자로 전달됩니다.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
파이썬 표준 타입 어노테이션을 사용하여 함수에 있는 경로 매개변수의 타입을 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
|
||||
{* ../../docs_src/path_params/tutorial002_py310.py hl[7] *}
|
||||
|
||||
위의 예시에서, `item_id`는 `int`로 선언되었습니다.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
///
|
||||
|
||||
## 데이터 <abbr title="다음으로도 알려져 있습니다: 직렬화, 파싱, 마샬링">변환</abbr> { #data-conversion }
|
||||
## 데이터 <dfn title="다음으로도 알려져 있습니다: 직렬화, 파싱, 마샬링">변환</dfn> { #data-conversion }
|
||||
|
||||
이 예제를 실행하고 <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>을 열면, 다음 응답을 볼 수 있습니다:
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
함수가 받은(반환도 하는) 값은 문자열 `"3"`이 아니라 파이썬 `int` 형인 `3`입니다.
|
||||
|
||||
즉, 타입 선언을 하면 **FastAPI**는 자동으로 요청을 <abbr title="HTTP 요청에서 전달되는 문자열을 파이썬 데이터로 변환">"파싱"</abbr>합니다.
|
||||
즉, 타입 선언을 하면 **FastAPI**는 자동으로 요청을 <dfn title="HTTP 요청에서 전달되는 문자열을 파이썬 데이터로 변환">"파싱"</dfn>합니다.
|
||||
|
||||
///
|
||||
|
||||
@@ -116,21 +116,21 @@
|
||||
|
||||
사용자 ID를 이용해 특정 사용자의 정보를 가져오는 경로 `/users/{user_id}`도 있습니다.
|
||||
|
||||
*경로 처리*는 순차적으로 평가되기 때문에 `/users/{user_id}` 이전에 `/users/me`에 대한 경로가 먼저 선언되었는지 확인해야 합니다:
|
||||
*경로 처리*는 순차적으로 평가되기 때문에 `/users/me`에 대한 경로가 `/users/{user_id}` 이전에 먼저 선언되었는지 확인해야 합니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
|
||||
{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *}
|
||||
|
||||
그렇지 않으면 `/users/{user_id}`에 대한 경로가 `/users/me`에도 매칭되어, 매개변수 `user_id`에 `"me"` 값이 들어왔다고 "생각하게" 됩니다.
|
||||
|
||||
마찬가지로, 경로 처리를 재정의할 수는 없습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
|
||||
{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *}
|
||||
|
||||
경로가 먼저 매칭되기 때문에 첫 번째 것이 항상 사용됩니다.
|
||||
|
||||
## 사전정의 값 { #predefined-values }
|
||||
|
||||
만약 *경로 매개변수*를 받는 *경로 처리*가 있지만, 가능한 유효한 *경로 매개변수* 값들을 미리 정의하고 싶다면 파이썬 표준 <abbr title="열거형(Enumeration)">`Enum`</abbr>을 사용할 수 있습니다.
|
||||
만약 *경로 매개변수*를 받는 *경로 처리*가 있지만, 가능한 유효한 *경로 매개변수* 값들을 미리 정의하고 싶다면 파이썬 표준 <abbr title="열거형">`Enum`</abbr>을 사용할 수 있습니다.
|
||||
|
||||
### `Enum` 클래스 생성 { #create-an-enum-class }
|
||||
|
||||
@@ -140,11 +140,11 @@
|
||||
|
||||
가능한 값들에 해당하는 고정된 값의 클래스 어트리뷰트들을 만듭니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 머신 러닝 <abbr title="기술적으로는 딥 러닝 모델 아키텍처">모델</abbr>들의 이름입니다.
|
||||
혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 머신 러닝 <dfn title="기술적으로는 딥 러닝 모델 아키텍처">모델</dfn>들의 이름입니다.
|
||||
|
||||
///
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
|
||||
생성한 열거형 클래스(`ModelName`)를 사용하는 타입 어노테이션으로 *경로 매개변수*를 만듭니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *}
|
||||
|
||||
### 문서 확인 { #check-the-docs }
|
||||
|
||||
@@ -168,13 +168,13 @@
|
||||
|
||||
생성한 열거형 `ModelName`의 *열거형 멤버*와 비교할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *}
|
||||
|
||||
#### *열거형 값* 가져오기 { #get-the-enumeration-value }
|
||||
|
||||
`model_name.value` 또는 일반적으로 `your_enum_member.value`를 이용하여 실제 값(위 예시의 경우 `str`)을 가져올 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
|
||||
클라이언트에 반환하기 전에 해당 값(이 경우 문자열)으로 변환됩니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *}
|
||||
|
||||
클라이언트는 아래와 같은 JSON 응답을 얻게 됩니다:
|
||||
|
||||
@@ -227,7 +227,7 @@ Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으
|
||||
|
||||
따라서 다음과 같이 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
|
||||
{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -242,7 +242,7 @@ Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으
|
||||
**FastAPI**를 이용하면 짧고 직관적인 표준 파이썬 타입 선언을 사용하여 다음을 얻을 수 있습니다:
|
||||
|
||||
* 편집기 지원: 오류 검사, 자동완성 등
|
||||
* 데이터 "<abbr title="HTTP 요청에서 전달되는 문자열을 파이썬 데이터로 변환">parsing</abbr>"
|
||||
* 데이터 "<dfn title="HTTP 요청에서 전달되는 문자열을 파이썬 데이터로 변환">파싱</dfn>"
|
||||
* 데이터 검증
|
||||
* API 주석(Annotation)과 자동 문서
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**FastAPI**를 사용하면 매개변수에 대한 추가 정보 및 검증을 선언할 수 있습니다.
|
||||
|
||||
이 응용 프로그램을 예로 들어보겠습니다:
|
||||
이 애플리케이션을 예로 들어보겠습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
|
||||
|
||||
@@ -41,46 +41,22 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이
|
||||
|
||||
## `q` 매개변수의 타입에 `Annotated` 사용하기 { #use-annotated-in-the-type-for-the-q-parameter }
|
||||
|
||||
이전에 [Python Types Intro](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}에서 `Annotated`를 사용해 매개변수에 메타데이터를 추가할 수 있다고 말씀드린 것을 기억하시나요?
|
||||
이전에 [Python 타입 소개](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}에서 `Annotated`를 사용해 매개변수에 메타데이터를 추가할 수 있다고 말씀드린 것을 기억하시나요?
|
||||
|
||||
이제 FastAPI에서 사용할 차례입니다. 🚀
|
||||
|
||||
다음과 같은 타입 어노테이션이 있었습니다:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = None
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
여기서 `Annotated`로 감싸서 다음과 같이 만듭니다:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
q: Annotated[str | None] = None
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
q: Annotated[Union[str, None]] = None
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
두 버전 모두 같은 의미로, `q`는 `str` 또는 `None`이 될 수 있는 매개변수이며 기본값은 `None`입니다.
|
||||
|
||||
이제 재미있는 부분으로 넘어가 봅시다. 🎉
|
||||
@@ -109,7 +85,7 @@ q: Annotated[Union[str, None]] = None
|
||||
|
||||
## 대안(이전 방식): 기본값으로 `Query` 사용 { #alternative-old-query-as-the-default-value }
|
||||
|
||||
이전 FastAPI 버전(<abbr title="before 2023-03">0.95.0</abbr> 이전)에서는 `Annotated`에 넣는 대신, 매개변수의 기본값으로 `Query`를 사용해야 했습니다. 주변에서 이 방식을 사용하는 코드를 볼 가능성이 높기 때문에 설명해 드리겠습니다.
|
||||
이전 FastAPI 버전(<dfn title="2023-03 이전">0.95.0</dfn> 이전)에서는 `Annotated`에 넣는 대신, 매개변수의 기본값으로 `Query`를 사용해야 했습니다. 주변에서 이 방식을 사용하는 코드를 볼 가능성이 높기 때문에 설명해 드리겠습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
@@ -192,7 +168,7 @@ FastAPI 없이도 **다른 곳에서** 같은 함수를 **호출**할 수 있고
|
||||
|
||||
## 정규식 추가 { #add-regular-expressions }
|
||||
|
||||
매개변수와 일치해야 하는 <abbr title="문자열에 대한 검색 패턴을 정의하는 문자들의 순열인 정규 표현식(regular expression), regex 또는 regexp입니다.">정규 표현식</abbr> `pattern`을 정의할 수 있습니다:
|
||||
매개변수와 일치해야 하는 <dfn title="정규 표현식(regex 또는 regexp)은 문자열에 대한 검색 패턴을 정의하는 문자 시퀀스입니다.">정규 표현식</dfn> `pattern`을 정의할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
|
||||
|
||||
@@ -212,7 +188,7 @@ FastAPI 없이도 **다른 곳에서** 같은 함수를 **호출**할 수 있고
|
||||
|
||||
`q` 쿼리 매개변수에 `min_length`를 `3`으로 설정하고, 기본값을 `"fixedquery"`로 선언하고 싶다고 해봅시다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
@@ -242,7 +218,7 @@ q: Annotated[str | None, Query(min_length=3)] = None
|
||||
|
||||
따라서 `Query`를 사용하면서 값을 필수로 선언해야 할 때는, 기본값을 선언하지 않으면 됩니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *}
|
||||
|
||||
### 필수지만 `None` 가능 { #required-can-be-none }
|
||||
|
||||
@@ -293,7 +269,7 @@ http://localhost:8000/items/?q=foo&q=bar
|
||||
|
||||
제공된 값이 없으면 기본 `list` 값을 정의할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *}
|
||||
|
||||
다음으로 이동하면:
|
||||
|
||||
@@ -316,7 +292,7 @@ http://localhost:8000/items/
|
||||
|
||||
`list[str]` 대신 `list`를 직접 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
@@ -372,7 +348,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
|
||||
|
||||
이제는 더 이상 이 매개변수를 마음에 들어하지 않는다고 가정해 봅시다.
|
||||
|
||||
이 매개변수를 사용하는 클라이언트가 있기 때문에 한동안은 남겨둬야 하지만, 문서에서 <abbr title="obsolete, recommended not to use it – 구식이며, 사용하지 않는 것을 추천">deprecated</abbr>로 명확하게 보여주고 싶습니다.
|
||||
이 매개변수를 사용하는 클라이언트가 있기 때문에 한동안은 남겨둬야 하지만, 문서에서 <dfn title="구식이며, 사용하지 않기를 권장함">사용 중단됨</dfn>으로 명확하게 보여주고 싶습니다.
|
||||
|
||||
그렇다면 `deprecated=True` 매개변수를 `Query`로 전달합니다:
|
||||
|
||||
@@ -402,7 +378,7 @@ Pydantic에는 <a href="https://docs.pydantic.dev/latest/concepts/validators/#fi
|
||||
|
||||
///
|
||||
|
||||
예를 들어, 이 커스텀 validator는 <abbr title="ISBN means International Standard Book Number – 국제 표준 도서 번호">ISBN</abbr> 도서 번호의 경우 아이템 ID가 `isbn-`으로 시작하고, <abbr title="IMDB (Internet Movie Database) is a website with information about movies – 영화에 대한 정보를 제공하는 웹사이트">IMDB</abbr> 영화 URL ID의 경우 `imdb-`로 시작하는지 확인합니다:
|
||||
예를 들어, 이 커스텀 validator는 <abbr title="International Standard Book Number - 국제 표준 도서 번호">ISBN</abbr> 도서 번호의 경우 아이템 ID가 `isbn-`으로 시작하고, <abbr title="Internet Movie Database - 인터넷 영화 데이터베이스: 영화에 대한 정보를 제공하는 웹사이트">IMDB</abbr> 영화 URL ID의 경우 `imdb-`로 시작하는지 확인합니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
|
||||
|
||||
@@ -436,9 +412,9 @@ Pydantic에는 <a href="https://docs.pydantic.dev/latest/concepts/validators/#fi
|
||||
|
||||
#### 임의의 항목 { #a-random-item }
|
||||
|
||||
`data.items()`를 사용하면 각 딕셔너리 항목의 키와 값을 담은 튜플로 구성된 <abbr title="리스트, 세트 등처럼 for 루프로 순회할 수 있는 것">iterable object</abbr>를 얻습니다.
|
||||
`data.items()`를 사용하면 각 딕셔너리 항목의 키와 값을 담은 튜플로 구성된 <dfn title="리스트, 세트 등처럼 for 루프로 순회할 수 있는 것">이터러블 객체</dfn>를 얻습니다.
|
||||
|
||||
이 iterable object를 `list(data.items())`로 적절한 `list`로 변환합니다.
|
||||
이 이터러블 객체를 `list(data.items())`로 적절한 `list`로 변환합니다.
|
||||
|
||||
그 다음 `random.choice()`로 리스트에서 **무작위 값**을 얻어 `(id, name)` 형태의 튜플을 얻습니다. 예를 들어 `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")` 같은 값이 될 것입니다.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
경로 매개변수의 일부가 아닌 다른 함수 매개변수를 선언하면 "쿼리" 매개변수로 자동 해석합니다.
|
||||
|
||||
{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
|
||||
{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *}
|
||||
|
||||
쿼리는 URL에서 `?` 후에 나오고 `&`으로 구분되는 키-값 쌍의 집합입니다.
|
||||
|
||||
@@ -24,7 +24,7 @@ URL의 일부이므로 "자연스럽게" 문자열입니다.
|
||||
경로 매개변수에 적용된 동일한 프로세스가 쿼리 매개변수에도 적용됩니다:
|
||||
|
||||
* (당연히) 편집기 지원
|
||||
* 데이터 <abbr title="converting the string that comes from an HTTP request into Python data">"파싱"</abbr>
|
||||
* 데이터 <dfn title="HTTP 요청에서 온 문자열을 Python 데이터로 변환하는 것">"파싱"</dfn>
|
||||
* 데이터 검증
|
||||
* 자동 문서화
|
||||
|
||||
@@ -128,7 +128,7 @@ http://127.0.0.1:8000/items/foo?short=yes
|
||||
|
||||
그러나 쿼리 매개변수를 필수로 만들려면 단순히 기본값을 선언하지 않으면 됩니다:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
|
||||
{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *}
|
||||
|
||||
여기 쿼리 매개변수 `needy`는 `str`형인 필수 쿼리 매개변수입니다.
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ $ pip install python-multipart
|
||||
|
||||
`fastapi` 에서 `File` 과 `UploadFile` 을 임포트 합니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
|
||||
{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## `File` 매개변수 정의 { #define-file-parameters }
|
||||
|
||||
`Body` 및 `Form` 과 동일한 방식으로 파일의 매개변수를 생성합니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
|
||||
{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
@@ -54,7 +54,7 @@ File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본
|
||||
|
||||
`File` 매개변수를 `UploadFile` 타입으로 정의합니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *}
|
||||
{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *}
|
||||
|
||||
`UploadFile` 을 사용하는 것은 `bytes` 과 비교해 다음과 같은 장점이 있습니다:
|
||||
|
||||
@@ -121,7 +121,7 @@ HTML의 폼들(`<form></form>`)이 서버에 데이터를 전송하는 방식은
|
||||
|
||||
하지만 파일이 포함된 경우, `multipart/form-data`로 인코딩됩니다. `File`을 사용하였다면, **FastAPI**는 본문의 적합한 부분에서 파일을 가져와야 한다는 것을 인지합니다.
|
||||
|
||||
인코딩과 폼 필드에 대해 더 알고싶다면, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs for <code>POST</code></a>를 참고하기 바랍니다.
|
||||
인코딩과 폼 필드에 대해 더 알고싶다면, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network - 모질라 개발자 네트워크">MDN</abbr> web docs for <code>POST</code></a>를 참고하기 바랍니다.
|
||||
|
||||
///
|
||||
|
||||
@@ -143,7 +143,7 @@ HTML의 폼들(`<form></form>`)이 서버에 데이터를 전송하는 방식은
|
||||
|
||||
추가 메타데이터를 설정하기 위해 예를 들어 `UploadFile`과 함께 `File()`을 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
|
||||
{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *}
|
||||
|
||||
## 다중 파일 업로드 { #multiple-file-uploads }
|
||||
|
||||
@@ -153,7 +153,7 @@ HTML의 폼들(`<form></form>`)이 서버에 데이터를 전송하는 방식은
|
||||
|
||||
이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *}
|
||||
{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *}
|
||||
|
||||
선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다.
|
||||
|
||||
@@ -169,7 +169,7 @@ HTML의 폼들(`<form></form>`)이 서버에 데이터를 전송하는 방식은
|
||||
|
||||
이전과 같은 방식으로 `UploadFile`에 대해서도 `File()`을 사용해 추가 매개변수를 설정할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
|
||||
{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *}
|
||||
|
||||
## 요약 { #recap }
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ $ pip install python-multipart
|
||||
|
||||
**폼 필드**로 받고 싶은 필드를 **Pydantic 모델**로 선언한 다음, 매개변수를 `Form`으로 선언하면 됩니다:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
|
||||
{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *}
|
||||
|
||||
**FastAPI**는 요청에서 받은 **폼 데이터**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
|
||||
|
||||
@@ -48,7 +48,7 @@ $ pip install python-multipart
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 `extra` 필드를 `forbid`할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
|
||||
{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *}
|
||||
|
||||
클라이언트가 추가 데이터를 보내려고 하면 **오류** 응답을 받게 됩니다.
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ $ pip install python-multipart
|
||||
|
||||
## `File` 및 `Form` 임포트 { #import-file-and-form }
|
||||
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## `File` 및 `Form` 매개변수 정의 { #define-file-and-form-parameters }
|
||||
|
||||
`Body` 및 `Query`와 동일한 방식으로 파일과 폼의 매개변수를 생성합니다:
|
||||
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *}
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *}
|
||||
|
||||
파일과 폼 필드는 폼 데이터로 업로드되며, 파일과 폼 필드를 받게 됩니다.
|
||||
|
||||
|
||||
@@ -18,17 +18,17 @@ $ pip install python-multipart
|
||||
|
||||
`fastapi`에서 `Form`을 임포트합니다:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## `Form` 매개변수 정의하기 { #define-form-parameters }
|
||||
|
||||
`Body` 또는 `Query`와 동일한 방식으로 폼 매개변수를 만듭니다:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
예를 들어, OAuth2 사양을 사용할 수 있는 방법 중 하나("패스워드 플로우"라고 함)로 `username`과 `password`를 폼 필드로 보내야 합니다.
|
||||
|
||||
<abbr title="specification">spec</abbr>에서는 필드 이름이 `username` 및 `password`로 정확하게 명명되어야 하고, JSON이 아닌 폼 필드로 전송해야 합니다.
|
||||
<dfn title="사양">사양</dfn>에서는 필드 이름이 `username` 및 `password`로 정확하게 명명되어야 하고, JSON이 아닌 폼 필드로 전송해야 합니다.
|
||||
|
||||
`Form`을 사용하면 유효성 검사, 예제, 별칭(예: `username` 대신 `user-name`) 등을 포함하여 `Body`(및 `Query`, `Path`, `Cookie`)와 동일한 구성을 선언할 수 있습니다.
|
||||
|
||||
@@ -56,7 +56,7 @@ HTML 폼(`<form></form>`)이 데이터를 서버로 보내는 방식은 일반
|
||||
|
||||
그러나 폼에 파일이 포함된 경우, `multipart/form-data`로 인코딩합니다. 다음 장에서 파일 처리에 대해 읽을 겁니다.
|
||||
|
||||
이러한 인코딩 및 폼 필드에 대해 더 읽고 싶다면, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><code>POST</code>에 대한 <abbr title="Mozilla Developer Network">MDN</abbr> 웹 문서를 참조하세요</a>.
|
||||
이러한 인코딩 및 폼 필드에 대해 더 읽고 싶다면, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network - Mozilla 개발자 네트워크">MDN</abbr> 웹 문서를 참조하세요 <code>POST</code>에 대한</a>.
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ FastAPI는 Pydantic을 내부적으로 여러 방식으로 사용하여, 클래
|
||||
|
||||
가장 흔한 경우는 [고급 문서에서 나중에 설명하는 대로 Response를 직접 반환하는 것](../advanced/response-directly.md){.internal-link target=_blank}입니다.
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
|
||||
{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *}
|
||||
|
||||
이 간단한 경우는 반환 타입 어노테이션이 `Response` 클래스(또는 그 서브클래스)이기 때문에 FastAPI에서 자동으로 처리됩니다.
|
||||
|
||||
@@ -193,7 +193,7 @@ FastAPI는 Pydantic을 내부적으로 여러 방식으로 사용하여, 클래
|
||||
|
||||
타입 어노테이션에 `Response`의 서브클래스를 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
|
||||
{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *}
|
||||
|
||||
이는 `RedirectResponse`가 `Response`의 서브클래스이기 때문에 동작하며, FastAPI가 이 간단한 경우를 자동으로 처리합니다.
|
||||
|
||||
@@ -201,7 +201,7 @@ FastAPI는 Pydantic을 내부적으로 여러 방식으로 사용하여, 클래
|
||||
|
||||
하지만 유효한 Pydantic 타입이 아닌 다른 임의의 객체(예: 데이터베이스 객체)를 반환하고, 함수에서 그렇게 어노테이션하면, FastAPI는 그 타입 어노테이션으로부터 Pydantic 응답 모델을 만들려고 시도하다가 실패합니다.
|
||||
|
||||
또한, 유효한 Pydantic 타입이 아닌 타입이 하나 이상 포함된 여러 타입 간의 <abbr title='여러 타입 간 union은 "이 타입들 중 아무거나"를 의미합니다.'>union</abbr>이 있는 경우에도 동일합니다. 예를 들어, 아래는 실패합니다 💥:
|
||||
또한, 유효한 Pydantic 타입이 아닌 타입이 하나 이상 포함된 여러 타입 간의 <dfn title="여러 타입 간의 union은 '이 타입들 중 아무거나'를 의미합니다.">union</dfn>이 있는 경우에도 동일합니다. 예를 들어, 아래는 실패합니다 💥:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* `@app.delete()`
|
||||
* 등
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
@@ -43,7 +43,7 @@ FastAPI는 이를 알고 있으며, 응답 본문이 없다고 명시하는 Open
|
||||
|
||||
/// note | 참고
|
||||
|
||||
만약 HTTP 상태 코드가 무엇인지 이미 알고 있다면, 다음 섹션으로 넘어가세요.
|
||||
만약 HTTP 상태 코드가 무엇인지 이미 알고 있다면, 다음 섡션으로 넘어가세요.
|
||||
|
||||
///
|
||||
|
||||
@@ -66,7 +66,7 @@ HTTP에서는 응답의 일부로 3자리 숫자 상태 코드를 보냅니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
각 상태 코드와 어떤 코드가 어떤 용도인지 더 알고 싶다면 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr>의 HTTP 상태 코드에 관한 문서</a>를 확인하세요.
|
||||
각 상태 코드와 어떤 코드가 어떤 용도인지 더 알고 싶다면 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network - 모질라 개발자 네트워크">MDN</abbr>의 HTTP 상태 코드에 관한 문서</a>를 확인하세요.
|
||||
|
||||
///
|
||||
|
||||
@@ -74,7 +74,7 @@ HTTP에서는 응답의 일부로 3자리 숫자 상태 코드를 보냅니다.
|
||||
|
||||
이전 예시를 다시 확인해보겠습니다:
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *}
|
||||
|
||||
`201` 은 "생성됨"을 위한 상태 코드입니다.
|
||||
|
||||
@@ -82,7 +82,7 @@ HTTP에서는 응답의 일부로 3자리 숫자 상태 코드를 보냅니다.
|
||||
|
||||
`fastapi.status` 의 편의 변수를 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *}
|
||||
|
||||
이것들은 단지 편의를 위한 것으로, 동일한 숫자를 갖고 있지만, 이를 통해 편집기의 자동완성 기능으로 찾을 수 있습니다:
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를
|
||||
|
||||
이와 같이 하면 예제들은 그 본문 데이터의 내부 **JSON 스키마**의 일부가 될 것입니다.
|
||||
|
||||
그럼에도 불구하고, 지금 <abbr title="2023-08-26">이 문서를 작성하는 시간</abbr>에, 문서 UI를 보여주는 역할을 맡은 Swagger UI는 **JSON 스키마** 속 데이터를 위한 여러 예제의 표현을 지원하지 않습니다. 하지만 해결 방안을 밑에서 읽어보세요.
|
||||
그럼에도 불구하고, 지금 <dfn title="2023-08-26">이 문서를 작성하는 시간</dfn>에, 문서 UI를 보여주는 역할을 맡은 Swagger UI는 **JSON 스키마** 속 데이터를 위한 여러 예제의 표현을 지원하지 않습니다. 하지만 해결 방안을 밑에서 읽어보세요.
|
||||
|
||||
### OpenAPI-특화 `examples` { #openapi-specific-examples }
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# 보안 - 첫 단계 { #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_py310.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_py310.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_py310.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줄만으로도 이미 원시적인 형태의 보안을 갖추게 됩니다.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
이전 장에서 (의존성 주입 시스템을 기반으로 한) 보안 시스템은 *경로 처리 함수*에 `str`로 `token`을 제공했습니다:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
|
||||
{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *}
|
||||
|
||||
하지만 이는 여전히 그다지 유용하지 않습니다.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user