Sync fastapi docs from b5ca1324 on 2025-12-07
Issue Manager / issue-manager (push) Has been cancelled
Build Docs / changes (push) Has been cancelled
Build Docs / langs (push) Has been cancelled
Build Docs / build-docs (push) Has been cancelled
Build Docs / docs-all-green (push) Has been cancelled
Conflict detector / main (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi) (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi-slim) (push) Has been cancelled
Test Redistribute / test-redistribute-alls-green (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / test (pydantic-v1, 3.10) (push) Has been cancelled
Test / test (pydantic-v1, 3.11) (push) Has been cancelled
Test / test (pydantic-v1, 3.13) (push) Has been cancelled
Test / test (pydantic-v1, 3.8) (push) Has been cancelled
Test / test (pydantic-v1, 3.9) (push) Has been cancelled
Test / test (pydantic-v2, 3.10) (push) Has been cancelled
Test / test (pydantic-v2, 3.11) (push) Has been cancelled
Test / test (pydantic-v2, 3.12) (push) Has been cancelled
Test / test (pydantic-v2, 3.13) (push) Has been cancelled
Test / test (pydantic-v2, 3.14) (push) Has been cancelled
Test / test (pydantic-v2, 3.8) (push) Has been cancelled
Test / test (pydantic-v2, 3.9) (push) Has been cancelled
Test / coverage-combine (push) Has been cancelled
Test / check (push) Has been cancelled
Label Approved / label-approved (push) Has been cancelled
FastAPI People Contributors / job (push) Has been cancelled
FastAPI People Sponsors / job (push) Has been cancelled
Update Topic Repos / topic-repos (push) Has been cancelled
FastAPI People / job (push) Has been cancelled
Test / test (pydantic-v1, 3.12) (push) Has been cancelled
Issue Manager / issue-manager (push) Has been cancelled
Build Docs / changes (push) Has been cancelled
Build Docs / langs (push) Has been cancelled
Build Docs / build-docs (push) Has been cancelled
Build Docs / docs-all-green (push) Has been cancelled
Conflict detector / main (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi) (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi-slim) (push) Has been cancelled
Test Redistribute / test-redistribute-alls-green (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / test (pydantic-v1, 3.10) (push) Has been cancelled
Test / test (pydantic-v1, 3.11) (push) Has been cancelled
Test / test (pydantic-v1, 3.13) (push) Has been cancelled
Test / test (pydantic-v1, 3.8) (push) Has been cancelled
Test / test (pydantic-v1, 3.9) (push) Has been cancelled
Test / test (pydantic-v2, 3.10) (push) Has been cancelled
Test / test (pydantic-v2, 3.11) (push) Has been cancelled
Test / test (pydantic-v2, 3.12) (push) Has been cancelled
Test / test (pydantic-v2, 3.13) (push) Has been cancelled
Test / test (pydantic-v2, 3.14) (push) Has been cancelled
Test / test (pydantic-v2, 3.8) (push) Has been cancelled
Test / test (pydantic-v2, 3.9) (push) Has been cancelled
Test / coverage-combine (push) Has been cancelled
Test / check (push) Has been cancelled
Label Approved / label-approved (push) Has been cancelled
FastAPI People Contributors / job (push) Has been cancelled
FastAPI People Sponsors / job (push) Has been cancelled
Update Topic Repos / topic-repos (push) Has been cancelled
FastAPI People / job (push) Has been cancelled
Test / test (pydantic-v1, 3.12) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# 소개
|
||||
|
||||
FastAPI에 대한 디자인, 영감 등에 대해 🤓
|
||||
@@ -0,0 +1,41 @@
|
||||
# 추가 상태 코드
|
||||
|
||||
기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 작업(path operation)*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다.
|
||||
|
||||
기본 상태 코드 또는 *경로 작업*에서 설정한 상태 코드를 사용합니다.
|
||||
|
||||
## 추가 상태 코드
|
||||
|
||||
기본 상태 코드와 별도로 추가 상태 코드를 반환하려면 `JSONResponse`와 같이 `Response`를 직접 반환하고 추가 상태 코드를 직접 설정할 수 있습니다.
|
||||
|
||||
예를 들어 항목을 업데이트할 수 있는 *경로 작업*이 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다.
|
||||
|
||||
하지만 새로운 항목을 허용하기를 원할 것입니다. 항목이 이전에 존재하지 않았다면 이를 생성하고 HTTP 상태 코드 201 "Created"를 반환합니다.
|
||||
|
||||
이를 위해서는 `JSONResponse`를 가져와서 원하는 `status_code`를 설정하여 콘텐츠를 직접 반환합니다:
|
||||
|
||||
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
위의 예제처럼 `Response`를 직접 반환하면 바로 반환됩니다.
|
||||
|
||||
모델 등과 함께 직렬화되지 않습니다.
|
||||
|
||||
원하는 데이터가 있는지, 값이 유효한 JSON인지 확인합니다(`JSONResponse`를 사용하는 경우).
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술적 세부 정보
|
||||
|
||||
`from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자 여러분을 위한 편의성으로 `fastapi.responses`와 동일한 `starlette.responses`를 제공합니다. 그러나 사용 가능한 응답의 대부분은 Starlette에서 직접 제공됩니다. `status` 또한 마찬가지입니다.
|
||||
|
||||
///
|
||||
|
||||
## OpenAPI 및 API 문서
|
||||
|
||||
추가 상태 코드와 응답을 직접 반환하는 경우, FastAPI는 반환할 내용을 미리 알 수 있는 방법이 없기 때문에 OpenAPI 스키마(API 문서)에 포함되지 않습니다.
|
||||
|
||||
하지만 다음을 사용하여 코드에 이를 문서화할 수 있습니다: [추가 응답](additional-responses.md){.internal-link target=_blank}.
|
||||
@@ -0,0 +1,67 @@
|
||||
# 고급 의존성
|
||||
|
||||
## 매개변수화된 의존성
|
||||
|
||||
지금까지 본 모든 의존성은 고정된 함수 또는 클래스입니다.
|
||||
|
||||
하지만 여러 개의 함수나 클래스를 선언하지 않고도 의존성에 매개변수를 설정해야 하는 경우가 있을 수 있습니다.
|
||||
|
||||
예를 들어, `q` 쿼리 매개변수가 특정 고정된 내용을 포함하고 있는지 확인하는 의존성을 원한다고 가정해 봅시다.
|
||||
|
||||
이때 해당 고정된 내용을 매개변수화할 수 있길 바랍니다.
|
||||
|
||||
## "호출 가능한" 인스턴스
|
||||
|
||||
Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법이 있습니다.
|
||||
|
||||
클래스 자체(이미 호출 가능함)가 아니라 해당 클래스의 인스턴스에 대해 호출 가능하게 하는 것입니다.
|
||||
|
||||
이를 위해 `__call__` 메서드를 선언합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
|
||||
|
||||
이 경우, **FastAPI**는 추가 매개변수와 하위 의존성을 확인하기 위해 `__call__`을 사용하게 되며,
|
||||
나중에 *경로 연산 함수*에서 매개변수에 값을 전달할 때 이를 호출하게 됩니다.
|
||||
|
||||
## 인스턴스 매개변수화하기
|
||||
|
||||
이제 `__init__`을 사용하여 의존성을 "매개변수화"할 수 있는 인스턴스의 매개변수를 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
|
||||
|
||||
이 경우, **FastAPI**는 `__init__`에 전혀 관여하지 않으며, 우리는 이 메서드를 코드에서 직접 사용하게 됩니다.
|
||||
|
||||
## 인스턴스 생성하기
|
||||
|
||||
다음과 같이 이 클래스의 인스턴스를 생성할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *}
|
||||
|
||||
이렇게 하면 `checker.fixed_content` 속성에 `"bar"`라는 값을 담아 의존성을 "매개변수화"할 수 있습니다.
|
||||
|
||||
## 인스턴스를 의존성으로 사용하기
|
||||
|
||||
그런 다음, `Depends(FixedContentQueryChecker)` 대신 `Depends(checker)`에서 이 `checker` 인스턴스를 사용할 수 있으며,
|
||||
클래스 자체가 아닌 인스턴스 `checker`가 의존성이 됩니다.
|
||||
|
||||
의존성을 해결할 때 **FastAPI**는 이 `checker`를 다음과 같이 호출합니다:
|
||||
|
||||
```Python
|
||||
checker(q="somequery")
|
||||
```
|
||||
|
||||
...그리고 이때 반환되는 값을 *경로 연산 함수*의 `fixed_content_included` 매개변수로 전달합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
|
||||
|
||||
/// tip | 참고
|
||||
|
||||
이 모든 과정이 복잡하게 느껴질 수 있습니다. 그리고 지금은 이 방법이 얼마나 유용한지 명확하지 않을 수도 있습니다.
|
||||
|
||||
이 예시는 의도적으로 간단하게 만들었지만, 전체 구조가 어떻게 작동하는지 보여줍니다.
|
||||
|
||||
보안 관련 장에서는 이와 같은 방식으로 구현된 편의 함수들이 있습니다.
|
||||
|
||||
이 모든 과정을 이해했다면, 이러한 보안 도구들이 내부적으로 어떻게 작동하는지 이미 파악한 것입니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,108 @@
|
||||
# 비동기 테스트 코드 작성
|
||||
|
||||
이전 장에서 `TestClient` 를 이용해 **FastAPI** 어플리케이션 테스트를 작성하는 법을 배우셨을텐데요.
|
||||
지금까지는 `async` 키워드 사용없이 동기 함수의 테스트 코드를 작성하는 법만 익혔습니다.
|
||||
|
||||
하지만 비동기 함수를 사용하여 테스트 코드를 작성하는 것은 매우 유용할 수 있습니다.
|
||||
예를 들면 데이터베이스에 비동기로 쿼리하는 경우를 생각해봅시다.
|
||||
FastAPI 애플리케이션에 요청을 보내고, 비동기 데이터베이스 라이브러리를 사용하여 백엔드가 데이터베이스에 올바르게 데이터를 기록했는지 확인하고 싶을 때가 있을 겁니다.
|
||||
|
||||
이런 경우의 테스트 코드를 어떻게 비동기로 작성하는지 알아봅시다.
|
||||
|
||||
## pytest.mark.anyio
|
||||
|
||||
앞에서 작성한 테스트 함수에서 비동기 함수를 호출하고 싶다면, 테스트 코드도 비동기 함수여야합니다.
|
||||
AnyIO는 특정 테스트 함수를 비동기 함수로 호출 할 수 있는 깔끔한 플러그인을 제공합니다.
|
||||
|
||||
|
||||
## HTTPX
|
||||
|
||||
**FastAPI** 애플리케이션이 `async def` 대신 `def` 키워드로 선언된 함수를 사용하더라도, 내부적으로는 여전히 `비동기` 애플리케이션입니다.
|
||||
|
||||
`TestClient`는 pytest 표준을 사용하여 비동기 FastAPI 애플리케이션을 일반적인 `def` 테스트 함수 내에서 호출할 수 있도록 내부에서 마술을 부립니다. 하지만 이 마술은 비동기 함수 내부에서 사용할 때는 더 이상 작동하지 않습니다. 테스트를 비동기로 실행하면, 더 이상 테스트 함수 내부에서 `TestClient`를 사용할 수 없습니다.
|
||||
|
||||
`TestClient`는 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>를 기반으로 하고 있으며, 다행히 이를 직접 사용하여 API를 테스트할 수 있습니다.
|
||||
|
||||
## 예시
|
||||
|
||||
간단한 예시를 위해 [더 큰 어플리케이션 만들기](../ko/tutorial/bigger-applications.md){.internal-link target=_blank} 와 [테스트](../ko/tutorial/testing.md){.internal-link target=_blank}:에서 다룬 파일 구조와 비슷한 형태를 확인해봅시다:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
`main.py`는 아래와 같아야 합니다:
|
||||
|
||||
{* ../../docs_src/async_tests/main.py *}
|
||||
|
||||
`test_main.py` 파일은 `main.py`에 대한 테스트가 있을 텐데, 다음과 같을 수 있습니다:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py *}
|
||||
|
||||
## 실행하기
|
||||
|
||||
아래의 명령어로 테스트 코드를 실행합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 자세히 보기
|
||||
|
||||
`@pytest.mark.anyio` 마커는 pytest에게 이 테스트 함수가 비동기로 호출되어야 함을 알려줍니다:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[7] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
테스트 함수가 이제 `TestClient`를 사용할 때처럼 단순히 `def`가 아니라 `async def`로 작성된 점에 주목해주세요.
|
||||
|
||||
///
|
||||
|
||||
그 다음에 `AsyncClient` 로 앱을 만들고 비동기 요청을 `await` 키워드로 보낼 수 있습니다:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
|
||||
|
||||
위의 코드는:
|
||||
|
||||
```Python
|
||||
response = client.get('/')
|
||||
```
|
||||
|
||||
`TestClient` 에 요청을 보내던 것과 동일합니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
새로운 `AsyncClient`를 사용할 때 async/await를 사용하고 있다는 점에 주목하세요. 이 요청은 비동기적으로 처리됩니다.
|
||||
|
||||
///
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
만약의 어플리케이션이 Lifespan 이벤트에 의존성을 갖고 있다면 `AsyncClient` 가 이러한 이벤트를 실행시키지 않습니다.
|
||||
`AsyncClient` 가 테스트를 실행시켰다는 것을 확인하기 위해
|
||||
`LifespanManager` from <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.확인해주세요.
|
||||
|
||||
|
||||
///
|
||||
|
||||
## 그 외의 비동기 함수 호출
|
||||
|
||||
테스트 함수가 이제 비동기 함수이므로, FastAPI 애플리케이션에 요청을 보내는 것 외에도 다른 `async` 함수를 호출하고 `await` 키워드를 사용 할 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
테스트에 비동기 함수 호출을 통합할 때 (예: <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB의 MotorClient</a>를 사용할 때) `RuntimeError: Task attached to a different loop` 오류가 발생한다면, 이벤트 루프가 필요한 객체는 반드시 비동기 함수 내에서만 인스턴스화해야 한다는 점을 주의하세요!
|
||||
예를 들어 `@app.on_event("startup")` 콜백 내에서 인스턴스화하는 것이 좋습니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,313 @@
|
||||
# 사용자 정의 응답 - HTML, Stream, 파일, 기타
|
||||
|
||||
기본적으로, **FastAPI** 응답을 `JSONResponse`를 사용하여 반환합니다.
|
||||
|
||||
이를 재정의 하려면 [응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 본 것처럼 `Response`를 직접 반환하면 됩니다.
|
||||
|
||||
그러나 `Response` (또는 `JSONResponse`와 같은 하위 클래스)를 직접 반환하면, 데이터가 자동으로 변환되지 않으며 (심지어 `response_model`을 선언했더라도), 문서화가 자동으로 생성되지 않습니다(예를 들어, 생성된 OpenAPI의 일부로 HTTP 헤더 `Content-Type`에 특정 "미디어 타입"을 포함하는 경우).
|
||||
|
||||
하지만 *경로 작업 데코레이터*에서 `response_class` 매개변수를 사용하여 원하는 `Response`(예: 모든 `Response` 하위 클래스)를 선언할 수도 있습니다.
|
||||
|
||||
*경로 작업 함수*에서 반환하는 내용은 해당 `Response`안에 포함됩니다.
|
||||
|
||||
그리고 만약 그 `Response`가 `JSONResponse`와 `UJSONResponse`의 경우 처럼 JSON 미디어 타입(`application/json`)을 가지고 있다면, *경로 작업 데코레이터*에서 선언한 Pydantic의 `response_model`을 사용해 자동으로 변환(및 필터링) 됩니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
미디어 타입이 없는 응답 클래스를 사용하는 경우, FastAPI는 응답에 내용이 없을 것으로 예상하므로 생성된 OpenAPI 문서에서 응답 형식을 문서화하지 않습니다.
|
||||
|
||||
///
|
||||
|
||||
## `ORJSONResponse` 사용하기
|
||||
|
||||
예를 들어, 성능을 극대화하려는 경우, <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">orjson</a>을 설치하여 사용하고 응답을 `ORJSONResponse`로 설정할 수 있습니다.
|
||||
|
||||
사용하고자 하는 `Response` 클래스(하위 클래스)를 임포트한 후, **경로 작업 데코레이터*에서 선언하세요.
|
||||
|
||||
대규모 응답의 경우, 딕셔너리를 반환하는 것보다 `Response`를 반환하는 것이 훨씬 빠릅니다.
|
||||
|
||||
이유는 기본적으로, FastAPI가 내부의 모든 항목을 검사하고 JSON으로 직렬화할 수 있는지 확인하기 때문입니다. 이는 사용자 안내서에서 설명된 [JSON 호환 가능 인코더](../tutorial/encoder.md){.internal-link target=_blank}를 사용하는 방식과 동일합니다. 이를 통해 데이터베이스 모델과 같은 **임의의 객체**를 반환할 수 있습니다.
|
||||
|
||||
하지만 반환하는 내용이 **JSON으로 직렬화 가능**하다고 확신하는 경우, 해당 내용을 응답 클래스에 직접 전달할 수 있으며, FastAPI가 반환 내용을 `jsonable_encoder`를 통해 처리한 뒤 응답 클래스에 전달하는 오버헤드를 피할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`response_class` 매개변수는 응답의 "미디어 타입"을 정의하는 데에도 사용됩니다.
|
||||
|
||||
이 경우, HTTP 헤더 `Content-Type`은 `application/json`으로 설정됩니다.
|
||||
|
||||
그리고 이는 OpenAPI에 그대로 문서화됩니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`ORJSONResponse`는 FastAPI에서만 사용할 수 있고 Starlette에서는 사용할 수 없습니다.
|
||||
|
||||
///
|
||||
|
||||
## HTML 응답
|
||||
|
||||
**FastAPI**에서 HTML 응답을 직접 반환하려면 `HTMLResponse`를 사용하세요.
|
||||
|
||||
* `HTMLResponse`를 임포트 합니다.
|
||||
* *경로 작업 데코레이터*의 `response_class` 매개변수로 `HTMLResponse`를 전달합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`response_class` 매개변수는 응답의 "미디어 타입"을 정의하는 데에도 사용됩니다.
|
||||
|
||||
이 경우, HTTP 헤더 `Content-Type`은 `text/html`로 설정됩니다.
|
||||
|
||||
그리고 이는 OpenAPI에 그대로 문서화 됩니다.
|
||||
|
||||
///
|
||||
|
||||
### `Response` 반환하기
|
||||
|
||||
[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 본 것 처럼, *경로 작업*에서 응답을 직접 반환하여 재정의할 수도 있습니다.
|
||||
|
||||
위의 예제와 동일하게 `HTMLResponse`를 반환하는 코드는 다음과 같을 수 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
*경로 작업 함수*에서 직접 반환된 `Response`는 OpenAPI에 문서화되지 않습니다(예를들어, `Content-Type`이 문서화되지 않음) 자동 대화형 문서에서도 표시되지 않습니다.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
|
||||
물론 실제 `Content-Type` 헤더, 상태 코드 등은 반환된 `Response` 객체에서 가져옵니다.
|
||||
|
||||
///
|
||||
|
||||
### OpenAPI에 문서화하고 `Response` 재정의 하기
|
||||
|
||||
함수 내부에서 응답을 재정의하면서 동시에 OpenAPI에서 "미디어 타입"을 문서화하고 싶다면, `response_class` 매게변수를 사용하면서 `Response` 객체를 반환할 수 있습니다.
|
||||
|
||||
이 경우 `response_class`는 OpenAPI *경로 작업*을 문서화하는 데만 사용되고, 실제로는 여러분이 반환한 `Response`가 그대로 사용됩니다.
|
||||
|
||||
### `HTMLResponse`직접 반환하기
|
||||
|
||||
예를 들어, 다음과 같이 작성할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *}
|
||||
|
||||
이 예제에서, `generate_html_response()` 함수는 HTML을 `str`로 반환하는 대신 이미 `Response`를 생성하고 반환합니다.
|
||||
|
||||
`generate_html_response()`를 호출한 결과를 반환함으로써, 기본적인 **FastAPI** 기본 동작을 재정의 하는 `Response`를 이미 반환하고 있습니다.
|
||||
|
||||
하지만 `response_class`에 `HTMLResponse`를 함께 전달했기 때문에, FastAPI는 이를 OpenAPI 및 대화형 문서에서 `text/html`로 HTML을 문서화 하는 방법을 알 수 있습니다.
|
||||
|
||||
<img src="/img/tutorial/custom-response/image01.png">
|
||||
|
||||
## 사용 가능한 응답들
|
||||
|
||||
다음은 사용할 수 있는 몇가지 응답들 입니다.
|
||||
|
||||
`Response`를 사용하여 다른 어떤 것도 반환 할수 있으며, 직접 하위 클래스를 만들 수도 있습니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`from starlette.responses import HTMLResponse`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자인 여러분의 편의를 위해 `starlette.responses`를 `fastapi.responses`로 제공 하지만, 대부분의 사용 가능한 응답은 Starlette에서 직접 가져옵니다.
|
||||
|
||||
///
|
||||
|
||||
### `Response`
|
||||
|
||||
기본 `Response` 클래스는 다른 모든 응답 클래스의 부모 클래스 입니다.
|
||||
|
||||
이 클래스를 직접 반환할 수 있습니다.
|
||||
|
||||
다음 매개변수를 받을 수 있습니다:
|
||||
|
||||
* `content` - `str` 또는 `bytes`.
|
||||
* `status_code` - HTTP 상태코드를 나타내는 `int`.
|
||||
* `headers` - 문자열로 이루어진 `dict`.
|
||||
* `media_type` - 미디어 타입을 나타내는 `str` 예: `"text/html"`.
|
||||
|
||||
FastAPI (실제로는 Starlette)가 자동으로 `Content-Length` 헤더를 포함시킵니다. 또한 `media_type`에 기반하여 `Content-Type` 헤더를 포함하며, 텍스트 타입의 경우 문자 집합을 추가 합니다.
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
|
||||
|
||||
### `HTMLResponse`
|
||||
|
||||
텍스트 또는 바이트를 받아 HTML 응답을 반환합니다. 위에서 설명한 내용과 같습니다.
|
||||
|
||||
### `PlainTextResponse`
|
||||
|
||||
텍스트 또는 바이트를 받아 일반 텍스트 응답을 반환합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
|
||||
|
||||
### `JSONResponse`
|
||||
|
||||
데이터를 받아 `application/json`으로 인코딩된 응답을 반환합니다.
|
||||
|
||||
이는 위에서 설명했듯이 **FastAPI**에서 기본적으로 사용되는 응답 형식입니다.
|
||||
|
||||
### `ORJSONResponse`
|
||||
|
||||
<a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>을 사용하여 빠른 JSON 응답을 제공하는 대안입니다. 위에서 설명한 내용과 같습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
이를 사용하려면 `orjson`을 설치해야합니다. 예: `pip install orjson`.
|
||||
|
||||
///
|
||||
|
||||
### `UJSONResponse`
|
||||
|
||||
<a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>을 사용한 또 다른 JSON 응답 형식입니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
이 응답을 사용하려면 `ujson`을 설치해야합니다. 예: 'pip install ujson`.
|
||||
|
||||
///
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
`ujson` 은 일부 예외 경우를 처리하는 데 있어 Python 내장 구현보다 덜 엄격합니다.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`ORJSONResponse`가 더 빠른 대안일 가능성이 있습니다.
|
||||
|
||||
///
|
||||
|
||||
### `RedirectResponse`
|
||||
|
||||
HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 307(임시 리디렉션)으로 설정됩니다.
|
||||
|
||||
`RedirectResponse`를 직접 반환할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
|
||||
|
||||
---
|
||||
|
||||
또는 `response_class` 매개변수에서 사용할 수도 있습니다:
|
||||
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *}
|
||||
|
||||
이 경우, *경로 작업* 함수에서 URL을 직접 반환할 수 있습니다.
|
||||
|
||||
이 경우, 사용되는 `status_code`는 `RedirectResponse`의 기본값인 `307` 입니다.
|
||||
|
||||
---
|
||||
|
||||
`status_code` 매개변수를 `response_class` 매개변수와 함께 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *}
|
||||
|
||||
### `StreamingResponse`
|
||||
|
||||
비동기 제너레이터 또는 일반 제너레이터/이터레이터를 받아 응답 본문을 스트리밍 합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
|
||||
|
||||
#### 파일과 같은 객체를 사용한 `StreamingResponse`
|
||||
|
||||
파일과 같은 객체(예: `open()`으로 반환된 객체)가 있는 경우, 해당 파일과 같은 객체를 반복(iterate)하는 제너레이터 함수를 만들 수 있습니다.
|
||||
|
||||
이 방식으로, 파일 전체를 메모리에 먼저 읽어들일 필요 없이, 제너레이터 함수를 `StreamingResponse`에 전달하여 반환할 수 있습니다.
|
||||
|
||||
이 방식은 클라우드 스토리지, 비디오 처리 등의 다양한 라이브러리와 함께 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
|
||||
|
||||
1. 이것이 제너레이터 함수입니다. `yield` 문을 포함하고 있으므로 "제너레이터 함수"입니다.
|
||||
2. `with` 블록을 사용함으로써, 제너레이터 함수가 완료된 후 파일과 같은 객체가 닫히도록 합니다. 즉, 응답 전송이 끝난 후 닫힙니다.
|
||||
3. 이 `yield from`은 함수가 `file_like`라는 객체를 반복(iterate)하도록 합니다. 반복된 각 부분은 이 제너레이터 함수(`iterfile`)에서 생성된 것처럼 `yield` 됩니다.
|
||||
|
||||
이렇게 하면 "생성(generating)" 작업을 내부적으로 다른 무언가에 위임하는 제너레이터 함수가 됩니다.
|
||||
|
||||
이 방식을 사용하면 `with` 블록 안에서 파일을 열 수 있어, 작업이 완료된 후 파일과 같은 객체가 닫히는 것을 보장할 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
여기서 표준 `open()`을 사용하고 있기 때문에 `async`와 `await`를 지원하지 않습니다. 따라서 경로 작업은 일반 `def`로 선언합니다.
|
||||
|
||||
///
|
||||
|
||||
### `FileResponse`
|
||||
|
||||
파일을 비동기로 스트리밍하여 응답합니다.
|
||||
|
||||
다른 응답 유형과는 다른 인수를 사용하여 객체를 생성합니다:
|
||||
|
||||
* `path` - 스트리밍할 파일의 경로.
|
||||
* `headers` - 딕셔너리 형식의 사용자 정의 헤더.
|
||||
* `media_type` - 미디어 타입을 나타내는 문자열. 설정되지 않은 경우 파일 이름이나 경로를 사용하여 추론합니다.
|
||||
* `filename` - 설정된 경우 응답의 `Content-Disposition`에 포함됩니다.
|
||||
|
||||
파일 응답에는 적절한 `Content-Length`, `Last-Modified`, 및 `ETag` 헤더가 포함됩니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
|
||||
|
||||
또한 `response_class` 매개변수를 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *}
|
||||
|
||||
이 경우, 경로 작업 함수에서 파일 경로를 직접 반환할 수 있습니다.
|
||||
|
||||
## 사용자 정의 응답 클래스
|
||||
|
||||
`Response`를 상속받아 사용자 정의 응답 클래스를 생성하고 사용할 수 있습니다.
|
||||
|
||||
예를 들어, 포함된 `ORJSONResponse` 클래스에서 사용되지 않는 설정으로 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">orjson</a>을 사용하고 싶다고 가정해봅시다.
|
||||
|
||||
만약 들여쓰기 및 포맷된 JSON을 반환하고 싶다면, `orjson.OPT_INDENT_2` 옵션을 사용할 수 있습니다.
|
||||
|
||||
`CustomORJSONResponse`를 생성할 수 있습니다. 여기서 핵심은 `Response.render(content)` 메서드를 생성하여 내용을 `bytes`로 반환하는 것입니다:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *}
|
||||
|
||||
이제 다음 대신:
|
||||
|
||||
```json
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
이 응답은 이렇게 반환됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello World"
|
||||
}
|
||||
```
|
||||
|
||||
물론 JSON 포맷팅보다 더 유용하게 활용할 방법을 찾을 수 있을 것입니다. 😉
|
||||
|
||||
## 기본 응답 클래스
|
||||
|
||||
**FastAPI** 클래스 객체 또는 `APIRouter`를 생성할 때 기본적으로 사용할 응답 클래스를 지정할 수 있습니다.
|
||||
|
||||
이를 정의하는 매개변수는 `default_response_class`입니다.
|
||||
|
||||
아래 예제에서 **FastAPI**는 모든 경로 작업에서 기본적으로 `JSONResponse` 대신 `ORJSONResponse`를 사용합니다.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
여전히 이전처럼 *경로 작업*에서 `response_class`를 재정의할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 추가 문서화
|
||||
|
||||
OpenAPI에서 `responses`를 사용하여 미디어 타입 및 기타 세부 정보를 선언할 수도 있습니다: [OpenAPI에서 추가 응답](additional-responses.md){.internal-link target=_blank}.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Lifespan 이벤트
|
||||
|
||||
애플리케이션 **시작 전**에 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이는 이 코드가 **한 번**만 실행되며, **애플리케이션이 요청을 받기 시작하기 전**에 실행된다는 의미입니다.
|
||||
|
||||
마찬가지로, 애플리케이션이 **종료될 때** 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이 경우, 이 코드는 **한 번**만 실행되며, **여러 요청을 처리한 후**에 실행됩니다.
|
||||
|
||||
이 코드가 애플리케이션이 **요청을 받기 시작하기 전에** 실행되고, 요청 처리가 끝난 후 **종료 직전에** 실행되기 때문에 전체 애플리케이션의 **수명(Lifespan)**을 다룹니다. (잠시 후 "수명"이라는 단어가 중요해집니다 😉)
|
||||
|
||||
이 방법은 전체 애플리케이션에서 사용해야 하는 **자원**을 설정하거나 요청 간에 **공유되는** 자원을 설정하고, 또는 그 후에 **정리**하는 데 매우 유용할 수 있습니다. 예를 들어, 데이터베이스 연결 풀 또는 공유되는 머신러닝 모델을 로드하는 경우입니다.
|
||||
|
||||
|
||||
## 사용 사례
|
||||
|
||||
먼저 **사용 사례**를 예로 들어보고, 이를 어떻게 해결할 수 있는지 살펴보겠습니다.
|
||||
|
||||
우리가 요청을 처리하기 위해 사용하고 싶은 **머신러닝 모델**이 있다고 상상해 봅시다. 🤖
|
||||
|
||||
이 모델들은 요청 간에 공유되므로, 요청마다 모델이 하나씩 있는 것이 아니라, 여러 요청에서 동일한 모델을 사용합니다.
|
||||
|
||||
모델을 로드하는 데 **상당한 시간이 걸린다고 상상해 봅시다**, 왜냐하면 모델이 **디스크에서 많은 데이터를 읽어야** 하기 때문입니다. 따라서 모든 요청에 대해 모델을 매번 로드하고 싶지 않습니다.
|
||||
|
||||
모듈/파일의 최상위에서 모델을 로드할 수도 있지만, 그러면 **모델을 로드하는데** 시간이 걸리기 때문에, 단순한 자동화된 테스트를 실행할 때도 모델이 로드될 때까지 기다려야 해서 **테스트 속도가 느려집니다**.
|
||||
|
||||
이 문제를 해결하려고 하는 것입니다. 요청을 처리하기 전에 모델을 로드하되, 애플리케이션이 요청을 받기 시작하기 직전에만 로드하고, 코드가 로드되는 동안은 로드하지 않도록 하겠습니다.
|
||||
|
||||
## Lifespan
|
||||
|
||||
`FastAPI` 애플리케이션의 `lifespan` 매개변수와 "컨텍스트 매니저"를 사용하여 *시작*과 *종료* 로직을 정의할 수 있습니다. (컨텍스트 매니저가 무엇인지 잠시 후에 설명드리겠습니다.)
|
||||
|
||||
예제를 통해 시작하고, 그 후에 자세히 살펴보겠습니다.
|
||||
|
||||
우리는 `yield`를 사용하여 비동기 함수 `lifespan()`을 다음과 같이 생성합니다:
|
||||
|
||||
{* ../../docs_src/events/tutorial003.py hl[16,19] *}
|
||||
|
||||
여기서 우리는 모델을 로드하는 비싼 *시작* 작업을 시뮬레이션하고 있습니다. `yield` 앞에서 (가짜) 모델 함수를 머신러닝 모델이 담긴 딕셔너리에 넣습니다. 이 코드는 **애플리케이션이 요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다.
|
||||
|
||||
그리고 `yield` 직후에는 모델을 언로드합니다. 이 코드는 **애플리케이션이 요청 처리 완료 후**, *종료* 직전에 실행됩니다. 예를 들어, 메모리나 GPU와 같은 자원을 해제하는 작업을 할 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`shutdown`은 애플리케이션을 **종료**할 때 발생합니다.
|
||||
|
||||
새로운 버전을 시작해야 하거나, 그냥 실행을 멈추고 싶을 수도 있습니다. 🤷
|
||||
|
||||
///
|
||||
|
||||
### Lifespan 함수
|
||||
|
||||
먼저 주목할 점은, `yield`를 사용하여 비동기 함수(async function)를 정의하고 있다는 것입니다. 이는 `yield`를 사용한 의존성과 매우 유사합니다.
|
||||
|
||||
{* ../../docs_src/events/tutorial003.py hl[14:19] *}
|
||||
|
||||
함수의 첫 번째 부분, 즉 `yield` 이전의 코드는 애플리케이션이 시작되기 **전에** 실행됩니다.
|
||||
|
||||
그리고 `yield` 이후의 부분은 애플리케이션이 완료된 후 **나중에** 실행됩니다.
|
||||
|
||||
### 비동기 컨텍스트 매니저
|
||||
|
||||
함수를 확인해보면, `@asynccontextmanager`로 장식되어 있습니다.
|
||||
|
||||
이것은 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환시킵니다.
|
||||
|
||||
{* ../../docs_src/events/tutorial003.py hl[1,13] *}
|
||||
|
||||
파이썬에서 **컨텍스트 매니저**는 `with` 문에서 사용할 수 있는 것입니다. 예를 들어, `open()`은 컨텍스트 매니저로 사용할 수 있습니다:
|
||||
|
||||
```Python
|
||||
with open("file.txt") as file:
|
||||
file.read()
|
||||
```
|
||||
최근 버전의 파이썬에서는 **비동기 컨텍스트 매니저**도 있습니다. 이를 `async with`와 함께 사용합니다:
|
||||
|
||||
```Python
|
||||
async with lifespan(app):
|
||||
await do_stuff()
|
||||
```
|
||||
|
||||
컨텍스트 매니저나 위와 같은 비동기 컨텍스트 매니저를 만들면, `with` 블록에 들어가기 전에 `yield` 이전의 코드가 실행되고, `with` 블록을 벗어난 후에는 `yield` 이후의 코드가 실행됩니다.
|
||||
|
||||
위의 코드 예제에서는 직접 사용하지 않고, FastAPI에 전달하여 사용하도록 합니다.
|
||||
|
||||
`FastAPI` 애플리케이션의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받기 때문에, 새로운 `lifespan` 비동기 컨텍스트 매니저를 FastAPI에 전달할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/events/tutorial003.py hl[22] *}
|
||||
|
||||
## 대체 이벤트 (사용 중단)
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
*시작*과 *종료*를 처리하는 권장 방법은 위에서 설명한 대로 `FastAPI` 애플리케이션의 `lifespan` 매개변수를 사용하는 것입니다. `lifespan` 매개변수를 제공하면 `startup`과 `shutdown` 이벤트 핸들러는 더 이상 호출되지 않습니다. `lifespan`을 사용할지, 모든 이벤트를 사용할지 선택해야 하며 둘 다 사용할 수는 없습니다.
|
||||
|
||||
이 부분은 건너뛰셔도 좋습니다.
|
||||
|
||||
///
|
||||
|
||||
*시작*과 *종료* 동안 실행될 이 로직을 정의하는 대체 방법이 있습니다.
|
||||
|
||||
애플리케이션이 시작되기 전에 또는 종료될 때 실행해야 하는 이벤트 핸들러(함수)를 정의할 수 있습니다.
|
||||
|
||||
이 함수들은 `async def` 또는 일반 `def`로 선언할 수 있습니다.
|
||||
|
||||
### `startup` 이벤트
|
||||
|
||||
애플리케이션이 시작되기 전에 실행되어야 하는 함수를 추가하려면, `"startup"` 이벤트로 선언합니다:
|
||||
|
||||
{* ../../docs_src/events/tutorial001.py hl[8] *}
|
||||
|
||||
이 경우, `startup` 이벤트 핸들러 함수는 "database"라는 항목(단지 `dict`)을 일부 값으로 초기화합니다.
|
||||
|
||||
여러 개의 이벤트 핸들러 함수를 추가할 수 있습니다.
|
||||
|
||||
애플리케이션은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받기 시작하지 않습니다.
|
||||
|
||||
### `shutdown` 이벤트
|
||||
|
||||
애플리케이션이 종료될 때 실행되어야 하는 함수를 추가하려면, `"shutdown"` 이벤트로 선언합니다:
|
||||
|
||||
{* ../../docs_src/events/tutorial002.py hl[6] *}
|
||||
|
||||
여기서, `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트를 `log.txt` 파일에 기록합니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`open()` 함수에서 `mode="a"`는 "추가"를 의미하므로, 파일에 있는 기존 내용은 덮어쓰지 않고 새로운 줄이 추가됩니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 경우, 우리는 표준 파이썬 `open()` 함수를 사용하여 파일과 상호작용하고 있습니다.
|
||||
|
||||
따라서 I/O(입출력) 작업이 포함되어 있어 디스크에 기록되는 것을 "기다리는" 과정이 필요합니다.
|
||||
|
||||
하지만 `open()`은 `async`와 `await`를 사용하지 않습니다.
|
||||
|
||||
그래서 우리는 이벤트 핸들러 함수를 `async def` 대신 일반 `def`로 선언합니다.
|
||||
|
||||
///
|
||||
|
||||
### `startup`과 `shutdown`을 함께 사용
|
||||
|
||||
*시작*과 *종료* 로직이 연결될 가능성이 높습니다. 예를 들어, 무언가를 시작한 후 끝내거나, 자원을 획득한 후 해제하는 등의 작업을 할 수 있습니다.
|
||||
|
||||
이러한 작업을 별도의 함수로 처리하면 서로 로직이나 변수를 공유하지 않기 때문에 더 어려워집니다. 값들을 전역 변수에 저장하거나 비슷한 트릭을 사용해야 할 수 있습니다.
|
||||
|
||||
그렇기 때문에 위에서 설명한 대로 `lifespan`을 사용하는 것이 권장됩니다.
|
||||
|
||||
## 기술적 세부사항
|
||||
|
||||
호기심 많은 분들을 위한 기술적인 세부사항입니다. 🤓
|
||||
|
||||
ASGI 기술 사양에 따르면, 이는 <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>의 일부이며, `startup`과 `shutdown`이라는 이벤트를 정의합니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
Starlette의 `lifespan` 핸들러에 대해 더 읽고 싶다면 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette의 Lifespan 문서</a>에서 확인할 수 있습니다.
|
||||
|
||||
이 문서에는 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 서브 애플리케이션
|
||||
|
||||
🚨 이 lifespan 이벤트(`startup`과 `shutdown`)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요.
|
||||
@@ -0,0 +1,27 @@
|
||||
# 심화 사용자 안내서 - 도입부
|
||||
|
||||
## 추가 기능
|
||||
|
||||
메인 [자습서 - 사용자 안내서](../tutorial/index.md){.internal-link target=_blank}는 여러분이 **FastAPI**의 모든 주요 기능을 둘러보시기에 충분할 것입니다.
|
||||
|
||||
이어지는 장에서는 여러분이 다른 옵션, 구성 및 추가 기능을 보실 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
다음 장들이 **반드시 "심화"**인 것은 아닙니다.
|
||||
|
||||
그리고 여러분의 사용 사례에 대한 해결책이 그중 하나에 있을 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 자습서를 먼저 읽으십시오
|
||||
|
||||
여러분은 메인 [자습서 - 사용자 안내서](../tutorial/index.md){.internal-link target=_blank}의 지식으로 **FastAPI**의 대부분의 기능을 사용하실 수 있습니다.
|
||||
|
||||
이어지는 장들은 여러분이 메인 자습서 - 사용자 안내서를 이미 읽으셨으며 주요 아이디어를 알고 계신다고 가정합니다.
|
||||
|
||||
## TestDriven.io 강좌
|
||||
|
||||
여러분이 문서의 이 부분을 보완하시기 위해 심화-기초 강좌 수강을 희망하신다면 다음을 참고 하시기를 바랍니다: **TestDriven.io**의 <a href="https://testdriven.io/courses/tdd-fastapi/" class="external-link" target="_blank">FastAPI와 Docker를 사용한 테스트 주도 개발</a>.
|
||||
|
||||
그들은 현재 전체 수익의 10퍼센트를 **FastAPI** 개발에 기부하고 있습니다. 🎉 😄
|
||||
@@ -0,0 +1,96 @@
|
||||
# 고급 미들웨어
|
||||
|
||||
메인 튜토리얼에서 [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank}를 응용프로그램에 추가하는 방법을 읽으셨습니다.
|
||||
|
||||
그리고 [CORS with the `CORSMiddleware`](){.internal-link target=_blank}하는 방법도 보셨습니다.
|
||||
|
||||
이 섹션에서는 다른 미들웨어들을 사용하는 방법을 알아보겠습니다.
|
||||
|
||||
## ASGI 미들웨어 추가하기
|
||||
|
||||
**FastAPI**는 Starlette을 기반으로 하고 있으며, <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr> 사양을 구현하므로 ASGI 미들웨어를 사용할 수 있습니다.
|
||||
|
||||
미들웨어가 FastAPI나 Starlette용으로 만들어지지 않아도 ASGI 사양을 준수하는 한 동작할 수 있습니다.
|
||||
|
||||
일반적으로 ASGI 미들웨어는 첫 번째 인수로 ASGI 앱을 받는 클래스들입니다.
|
||||
|
||||
따라서 타사 ASGI 미들웨어 문서에서 일반적으로 다음과 같이 사용하도록 안내할 것입니다.
|
||||
|
||||
```Python
|
||||
from unicorn import UnicornMiddleware
|
||||
|
||||
app = SomeASGIApp()
|
||||
|
||||
new_app = UnicornMiddleware(app, some_config="rainbow")
|
||||
```
|
||||
|
||||
하지만 내부 미들웨어가 서버 오류를 처리하고 사용자 정의 예외 처리기가 제대로 작동하도록 하는 더 간단한 방법을 제공하는 FastAPI(실제로는 Starlette)가 있습니다.
|
||||
|
||||
이를 위해 `app.add_middleware()`를 사용합니다(CORS의 예에서와 같이).
|
||||
|
||||
```Python
|
||||
from fastapi import FastAPI
|
||||
from unicorn import UnicornMiddleware
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(UnicornMiddleware, some_config="rainbow")
|
||||
```
|
||||
|
||||
`app.add_middleware()`는 첫 번째 인수로 미들웨어 클래스와 미들웨어에 전달할 추가 인수를 받습니다.
|
||||
|
||||
## 통합 미들웨어
|
||||
|
||||
**FastAPI**에는 일반적인 사용 사례를 위한 여러 미들웨어가 포함되어 있으며, 사용 방법은 다음에서 살펴보겠습니다.
|
||||
|
||||
/// note | 기술 세부 사항
|
||||
|
||||
다음 예제에서는 `from starlette.middleware.something import SomethingMiddleware`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자의 편의를 위해 `fastapi.middleware`에 여러 미들웨어를 제공합니다. 그러나 사용 가능한 대부분의 미들웨어는 Starlette에서 직접 제공합니다.
|
||||
|
||||
///
|
||||
|
||||
## `HTTPSRedirectMiddleware`
|
||||
|
||||
들어오는 모든 요청이 `https` 또는 `wss`여야 합니다.
|
||||
|
||||
`http` 또는 `ws`로 들어오는 모든 요청은 대신 보안 체계로 리디렉션됩니다.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
|
||||
|
||||
## `TrustedHostMiddleware`
|
||||
|
||||
HTTP 호스트 헤더 공격을 방지하기 위해 모든 수신 요청에 올바르게 설정된 `Host` 헤더를 갖도록 강제합니다.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
|
||||
|
||||
다음 인수가 지원됩니다:
|
||||
|
||||
* `allowed_hosts` - 호스트 이름으로 허용해야 하는 도메인 이름 목록입니다. 일치하는 하위 도메인에 대해 `*.example.com`과 같은 와일드카드 도메인이 지원됩니다. 모든 호스트 이름을 허용하려면 `allowed_hosts=[“*”]`를 사용하거나 미들웨어를 생략하세요.
|
||||
|
||||
수신 요청의 유효성이 올바르게 확인되지 않으면 `400`이라는 응답이 전송됩니다.
|
||||
|
||||
## `GZipMiddleware`
|
||||
|
||||
`Accept-Encoding` 헤더에 `“gzip”`이 포함된 모든 요청에 대해 GZip 응답을 처리합니다.
|
||||
|
||||
미들웨어는 표준 응답과 스트리밍 응답을 모두 처리합니다.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
|
||||
|
||||
지원되는 인수는 다음과 같습니다:
|
||||
|
||||
* `minimum_size` - 이 최소 크기(바이트)보다 작은 응답은 GZip하지 않습니다. 기본값은 `500`입니다.
|
||||
* `compresslevel` - GZip 압축 중에 사용됩니다. 1에서 9 사이의 정수입니다. 기본값은 `9`입니다. 값이 낮을수록 압축 속도는 빨라지지만 파일 크기는 커지고, 값이 높을수록 압축 속도는 느려지지만 파일 크기는 작아집니다.
|
||||
|
||||
## 기타 미들웨어
|
||||
|
||||
다른 많은 ASGI 미들웨어가 있습니다.
|
||||
|
||||
예를 들어:
|
||||
|
||||
<a href=“https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py” class=“external-link” target=“_blank”>유비콘의 `ProxyHeadersMiddleware`></a>
|
||||
<a href=“https://github.com/florimondmanca/msgpack-asgi” class=“external-link” target=“_blank”>MessagePack</a>
|
||||
|
||||
사용 가능한 다른 미들웨어를 확인하려면 <a href=“https://www.starlette.dev/middleware/” class=“external-link” target=“_blank”>스타렛의 미들웨어 문서</a> 및 <a href=“https://github.com/florimondmanca/awesome-asgi” class=“external-link” target=“_blank”>ASGI Awesome List</a>를 참조하세요.
|
||||
@@ -0,0 +1,31 @@
|
||||
# 응답 - 상태 코드 변경
|
||||
|
||||
기본 [응답 상태 코드 설정](../tutorial/response-status-code.md){.internal-link target=_blank}이 가능하다는 걸 이미 알고 계실 겁니다.
|
||||
|
||||
하지만 경우에 따라 기본 설정과 다른 상태 코드를 반환해야 할 때가 있습니다.
|
||||
|
||||
## 사용 예
|
||||
|
||||
예를 들어 기본적으로 HTTP 상태 코드 "OK" `200`을 반환하고 싶다고 가정해 봅시다.
|
||||
|
||||
하지만 데이터가 존재하지 않으면 이를 새로 생성하고, HTTP 상태 코드 "CREATED" `201`을 반환하고자 할 때가 있을 수 있습니다.
|
||||
|
||||
이때도 여전히 `response_model`을 사용하여 반환하는 데이터를 필터링하고 변환하고 싶을 수 있습니다.
|
||||
|
||||
이런 경우에는 `Response` 파라미터를 사용할 수 있습니다.
|
||||
|
||||
## `Response` 파라미터 사용하기
|
||||
|
||||
*경로 작동 함수*에 `Response` 타입의 파라미터를 선언할 수 있습니다. (쿠키와 헤더에 대해 선언하는 것과 유사하게)
|
||||
|
||||
그리고 이 *임시* 응답 객체에서 `status_code`를 설정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
|
||||
|
||||
그리고 평소처럼 원하는 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다.
|
||||
|
||||
`response_model`을 선언했다면 반환된 객체는 여전히 필터링되고 변환됩니다.
|
||||
|
||||
**FastAPI**는 이 *임시* 응답 객체에서 상태 코드(쿠키와 헤더 포함)를 추출하여, `response_model`로 필터링된 반환 값을 최종 응답에 넣습니다.
|
||||
|
||||
또한, 의존성에서도 `Response` 파라미터를 선언하고 그 안에서 상태 코드를 설정할 수 있습니다. 단, 마지막으로 설정된 상태 코드가 우선 적용된다는 점을 유의하세요.
|
||||
@@ -0,0 +1,49 @@
|
||||
# 응답 쿠키
|
||||
|
||||
## `Response` 매개변수 사용하기
|
||||
|
||||
*경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다.
|
||||
|
||||
그런 다음 해당 *임시* 응답 객체에서 쿠키를 설정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *}
|
||||
|
||||
그런 다음 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다.
|
||||
|
||||
그리고 `response_model`을 선언했다면 반환한 객체를 거르고 변환하는 데 여전히 사용됩니다.
|
||||
|
||||
**FastAPI**는 그 *임시* 응답에서 쿠키(또한 헤더 및 상태 코드)를 추출하고, 반환된 값이 포함된 최종 응답에 이를 넣습니다. 이 값은 `response_model`로 걸러지게 됩니다.
|
||||
|
||||
또한 의존관계에서 `Response` 매개변수를 선언하고, 해당 의존성에서 쿠키(및 헤더)를 설정할 수도 있습니다.
|
||||
|
||||
## `Response`를 직접 반환하기
|
||||
|
||||
코드에서 `Response`를 직접 반환할 때도 쿠키를 생성할 수 있습니다.
|
||||
|
||||
이를 위해 [Response를 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성할 수 있습니다.
|
||||
|
||||
그런 다음 쿠키를 설정하고 반환하면 됩니다:
|
||||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
|
||||
/// tip
|
||||
|
||||
`Response` 매개변수를 사용하지 않고 응답을 직접 반환하는 경우, FastAPI는 이를 직접 반환한다는 점에 유의하세요.
|
||||
|
||||
따라서 데이터가 올바른 유형인지 확인해야 합니다. 예: `JSONResponse`를 반환하는 경우, JSON과 호환되는지 확인하세요.
|
||||
|
||||
또한 `response_model`로 걸러져야 할 데이터가 전달되지 않도록 확인하세요.
|
||||
|
||||
///
|
||||
|
||||
### 추가 정보
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
`from starlette.responses import Response` 또는 `from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자의 편의를 위해 `fastapi.responses`로 동일한 `starlette.responses`를 제공합니다. 그러나 대부분의 응답은 Starlette에서 직접 제공됩니다.
|
||||
|
||||
또한 `Response`는 헤더와 쿠키를 설정하는 데 자주 사용되므로, **FastAPI**는 이를 `fastapi.Response`로도 제공합니다.
|
||||
|
||||
///
|
||||
|
||||
사용 가능한 모든 매개변수와 옵션은 <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette 문서</a>에서 확인할 수 있습니다.
|
||||
@@ -0,0 +1,63 @@
|
||||
# 응답을 직접 반환하기
|
||||
|
||||
**FastAPI**에서 *경로 작업(path operation)*을 생성할 때, 일반적으로 `dict`, `list`, Pydantic 모델, 데이터베이스 모델 등의 데이터를 반환할 수 있습니다.
|
||||
|
||||
기본적으로 **FastAPI**는 [JSON 호환 가능 인코더](../tutorial/encoder.md){.internal-link target=_blank}에 설명된 `jsonable_encoder`를 사용해 해당 반환 값을 자동으로 `JSON`으로 변환합니다.
|
||||
|
||||
그런 다음, JSON 호환 데이터(예: `dict`)를 `JSONResponse`에 넣어 사용자의 응답을 전송하는 방식으로 처리됩니다.
|
||||
|
||||
그러나 *경로 작업*에서 `JSONResponse`를 직접 반환할 수도 있습니다.
|
||||
|
||||
예를 들어, 사용자 정의 헤더나 쿠키를 반환해야 하는 경우에 유용할 수 있습니다.
|
||||
|
||||
## `Response` 반환하기
|
||||
|
||||
사실, `Response` 또는 그 하위 클래스를 반환할 수 있습니다.
|
||||
|
||||
/// tip
|
||||
|
||||
`JSONResponse` 자체도 `Response`의 하위 클래스입니다.
|
||||
|
||||
///
|
||||
|
||||
그리고 `Response`를 반환하면 **FastAPI**가 이를 그대로 전달합니다.
|
||||
|
||||
Pydantic 모델로 데이터 변환을 수행하지 않으며, 내용을 다른 형식으로 변환하지 않습니다.
|
||||
|
||||
이로 인해 많은 유연성을 얻을 수 있습니다. 어떤 데이터 유형이든 반환할 수 있고, 데이터 선언이나 유효성 검사를 재정의할 수 있습니다.
|
||||
|
||||
## `Response`에서 `jsonable_encoder` 사용하기
|
||||
|
||||
**FastAPI**는 반환하는 `Response`에 아무런 변환을 하지 않으므로, 그 내용이 준비되어 있어야 합니다.
|
||||
|
||||
예를 들어, Pydantic 모델을 `dict`로 변환해 `JSONResponse`에 넣지 않으면 JSON 호환 유형으로 변환된 데이터 유형(예: `datetime`, `UUID` 등)이 사용되지 않습니다.
|
||||
|
||||
이러한 경우, 데이터를 응답에 전달하기 전에 `jsonable_encoder`를 사용하여 변환할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *}
|
||||
|
||||
/// note | 기술적 세부 사항
|
||||
|
||||
`from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자의 편의를 위해 `starlette.responses`를 `fastapi.responses`로 제공합니다. 그러나 대부분의 가능한 응답은 Starlette에서 직접 제공합니다.
|
||||
|
||||
///
|
||||
|
||||
## 사용자 정의 `Response` 반환하기
|
||||
위 예제는 필요한 모든 부분을 보여주지만, 아직 유용하지는 않습니다. 사실 데이터를 직접 반환하면 **FastAPI**가 이를 `JSONResponse`에 넣고 `dict`로 변환하는 등 모든 작업을 자동으로 처리합니다.
|
||||
|
||||
이제, 사용자 정의 응답을 반환하는 방법을 알아보겠습니다.
|
||||
|
||||
예를 들어 <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> 응답을 반환하고 싶다고 가정해보겠습니다.
|
||||
|
||||
XML 내용을 문자열에 넣고, 이를 `Response`에 넣어 반환할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
|
||||
|
||||
## 참고 사항
|
||||
`Response`를 직접 반환할 때, 그 데이터는 자동으로 유효성 검사되거나, 변환(직렬화)되거나, 문서화되지 않습니다.
|
||||
|
||||
그러나 [OpenAPI에서 추가 응답](additional-responses.md){.internal-link target=_blank}에서 설명된 대로 문서화할 수 있습니다.
|
||||
|
||||
이후 단락에서 자동 데이터 변환, 문서화 등을 사용하면서 사용자 정의 `Response`를 선언하는 방법을 확인할 수 있습니다.
|
||||
@@ -0,0 +1,41 @@
|
||||
# 응답 헤더
|
||||
|
||||
## `Response` 매개변수 사용하기
|
||||
|
||||
여러분은 *경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다 (쿠키와 같이 사용할 수 있습니다).
|
||||
|
||||
그런 다음, 여러분은 해당 *임시* 응답 객체에서 헤더를 설정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *}
|
||||
|
||||
그 후, 일반적으로 사용하듯이 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다.
|
||||
|
||||
`response_model`을 선언한 경우, 반환한 객체를 필터링하고 변환하는 데 여전히 사용됩니다.
|
||||
|
||||
**FastAPI**는 해당 *임시* 응답에서 헤더(쿠키와 상태 코드도 포함)를 추출하여, 여러분이 반환한 값을 포함하는 최종 응답에 `response_model`로 필터링된 값을 넣습니다.
|
||||
|
||||
또한, 종속성에서 `Response` 매개변수를 선언하고 그 안에서 헤더(및 쿠키)를 설정할 수 있습니다.
|
||||
|
||||
## `Response` 직접 반환하기
|
||||
|
||||
`Response`를 직접 반환할 때에도 헤더를 추가할 수 있습니다.
|
||||
|
||||
[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성하고, 헤더를 추가 매개변수로 전달하세요.
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
`from starlette.responses import Response`나 `from starlette.responses import JSONResponse`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 `starlette.responses`를 `fastapi.responses`로 개발자의 편의를 위해 직접 제공하지만, 대부분의 응답은 Starlette에서 직접 제공됩니다.
|
||||
|
||||
그리고 `Response`는 헤더와 쿠키를 설정하는 데 자주 사용될 수 있으므로, **FastAPI**는 `fastapi.Response`로도 이를 제공합니다.
|
||||
|
||||
///
|
||||
|
||||
## 커스텀 헤더
|
||||
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">‘X-’ 접두어를 사용하여</a> 커스텀 사설 헤더를 추가할 수 있습니다.
|
||||
|
||||
하지만, 여러분이 브라우저에서 클라이언트가 볼 수 있기를 원하는 커스텀 헤더가 있는 경우, CORS 설정에 이를 추가해야 합니다([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}에서 자세히 알아보세요). `expose_headers` 매개변수를 사용하여 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette의 CORS 설명서</a>에 문서화된 대로 설정할 수 있습니다.
|
||||
@@ -0,0 +1,67 @@
|
||||
# 하위 응용프로그램 - 마운트
|
||||
|
||||
만약 각각의 독립적인 OpenAPI와 문서 UI를 갖는 두 개의 독립적인 FastAPI 응용프로그램이 필요하다면, 메인 어플리케이션에 하나 (또는 그 이상의) 하위-응용프로그램(들)을 “마운트"해서 사용할 수 있습니다.
|
||||
|
||||
## **FastAPI** 응용프로그램 마운트
|
||||
|
||||
“마운트"이란 완전히 “독립적인" 응용프로그램을 특정 경로에 추가하여 해당 하위 응용프로그램에서 선언된 *경로 동작*을 통해 해당 경로 아래에 있는 모든 작업들을 처리할 수 있도록 하는 것을 의미합니다.
|
||||
|
||||
### 최상단 응용프로그램
|
||||
|
||||
먼저, 메인, 최상단의 **FastAPI** 응용프로그램과 이것의 *경로 동작*을 생성합니다:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *}
|
||||
|
||||
### 하위 응용프로그램
|
||||
|
||||
다음으로, 하위 응용프로그램과 이것의 *경로 동작*을 생성합니다:
|
||||
|
||||
이 하위 응용프로그램은 또 다른 표준 FastAPI 응용프로그램입니다. 다만 이것은 “마운트”될 것입니다:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *}
|
||||
|
||||
### 하위 응용프로그램 마운트
|
||||
|
||||
최상단 응용프로그램, `app`에 하위 응용프로그램, `subapi`를 마운트합니다.
|
||||
|
||||
이 예시에서, 하위 응용프로그램션은 `/subapi` 경로에 마운트 될 것입니다:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *}
|
||||
|
||||
### 자동으로 생성된 API 문서 확인
|
||||
|
||||
이제, `uvicorn`으로 메인 응용프로그램을 실행하십시오. 당신의 파일이 `main.py`라면, 이렇게 실행합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<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>에서 문서를 여십시오.
|
||||
|
||||
메인 응용프로그램의 *경로 동작*만을 포함하는, 메인 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image01.png">
|
||||
|
||||
다음으로, <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>에서 하위 응용프로그램의 문서를 여십시오.
|
||||
|
||||
하위 경로 접두사 `/subapi` 아래에 선언된 *경로 동작* 을 포함하는, 하위 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image02.png">
|
||||
|
||||
두 사용자 인터페이스 중 어느 하나를 사용해야하는 경우, 브라우저는 특정 응용프로그램 또는 하위 응용프로그램과 각각 통신할 수 있기 때문에 올바르게 동작할 것입니다.
|
||||
|
||||
### 기술적 세부사항: `root_path`
|
||||
|
||||
위에 설명된 것과 같이 하위 응용프로그램을 마운트하는 경우, FastAPI는 `root_path`라고 하는 ASGI 명세의 매커니즘을 사용하여 하위 응용프로그램에 대한 마운트 경로 통신을 처리합니다.
|
||||
|
||||
이를 통해, 하위 응용프로그램은 문서 UI를 위해 경로 접두사를 사용해야 한다는 사실을 인지합니다.
|
||||
|
||||
하위 응용프로그램에도 역시 다른 하위 응용프로그램을 마운트하는 것이 가능하며 FastAPI가 모든 `root_path` 들을 자동적으로 처리하기 때문에 모든 것은 올바르게 동작할 것입니다.
|
||||
|
||||
`root_path`와 이것을 사용하는 방법에 대해서는 [프록시의 뒷단](./behind-a-proxy.md){.internal-link target=_blank} 섹션에서 배울 수 있습니다.
|
||||
@@ -0,0 +1,127 @@
|
||||
# 템플릿
|
||||
|
||||
**FastAPI**와 함께 원하는 어떤 템플릿 엔진도 사용할 수 있습니다.
|
||||
|
||||
일반적인 선택은 Jinja2로, Flask와 다른 도구에서도 사용됩니다.
|
||||
|
||||
설정을 쉽게 할 수 있는 유틸리티가 있으며, 이를 **FastAPI** 애플리케이션에서 직접 사용할 수 있습니다(Starlette 제공).
|
||||
|
||||
## 의존성 설치
|
||||
|
||||
가상 환경을 생성하고(virtual environment{.internal-link target=_blank}), 활성화한 후 jinja2를 설치해야 합니다:
|
||||
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install jinja2
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 사용하기 `Jinja2Templates`
|
||||
|
||||
* `Jinja2Templates`를 가져옵니다.
|
||||
* 나중에 재사용할 수 있는 `templates` 객체를 생성합니다.
|
||||
* 템플릿을 반환할 경로 작업에 `Request` 매개변수를 선언합니다.
|
||||
* 생성한 `templates`를 사용하여 `TemplateResponse`를 렌더링하고 반환합니다. 템플릿의 이름, 요청 객체 및 Jinja2 템플릿 내에서 사용될 키-값 쌍이 포함된 "컨텍스트" 딕셔너리도 전달합니다.
|
||||
|
||||
|
||||
```Python hl_lines="4 11 15-18"
|
||||
{!../../docs_src/templates/tutorial001.py!}
|
||||
```
|
||||
|
||||
/// note | 참고
|
||||
|
||||
FastAPI 0.108.0 이전과 Starlette 0.29.0에서는 `name`이 첫 번째 매개변수였습니다.
|
||||
|
||||
또한 이전 버전에서는 `request` 객체가 Jinja2의 컨텍스트에서 키-값 쌍의 일부로 전달되었습니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`response_class=HTMLResponse`를 선언하면 문서 UI 응답이 HTML임을 알 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술 세부 사항
|
||||
`from starlette.templating import Jinja2Templates`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자를 위한 편리함으로 `fastapi.templating` 대신 `starlette.templating`을 제공합니다. 하지만 대부분의 사용 가능한 응답은 Starlette에서 직접 옵니다. `Request` 및 `StaticFiles`도 마찬가지입니다.
|
||||
///
|
||||
|
||||
## 템플릿 작성하기
|
||||
|
||||
그런 다음 `templates/item.html`에 템플릿을 작성할 수 있습니다. 예를 들면:
|
||||
|
||||
```jinja hl_lines="7"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
### 템플릿 컨텍스트 값
|
||||
|
||||
다음과 같은 HTML에서:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
Item ID: {{ id }}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...이는 전달한 "컨텍스트" `dict`에서 가져온 `id`를 표시합니다:
|
||||
|
||||
```Python
|
||||
{"id": id}
|
||||
```
|
||||
|
||||
예를 들어, ID가 `42`일 경우, 이는 다음과 같이 렌더링됩니다:
|
||||
|
||||
```html
|
||||
Item ID: 42
|
||||
```
|
||||
|
||||
### 템플릿 `url_for` 인수
|
||||
|
||||
템플릿 내에서 `url_for()`를 사용할 수도 있으며, 이는 *경로 작업 함수*에서 사용될 인수와 동일한 인수를 받습니다.
|
||||
|
||||
따라서 다음과 같은 부분에서:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
<a href="{{ url_for('read_item', id=id) }}">
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...이는 *경로 작업 함수* `read_item(id=id)`가 처리할 동일한 URL로 링크를 생성합니다.
|
||||
|
||||
예를 들어, ID가 `42`일 경우, 이는 다음과 같이 렌더링됩니다:
|
||||
```html
|
||||
<a href="/items/42">
|
||||
```
|
||||
|
||||
## 템플릿과 정적 파일
|
||||
|
||||
템플릿 내에서 `url_for()`를 사용할 수 있으며, 예를 들어 `name="static"`으로 마운트한 `StaticFiles`와 함께 사용할 수 있습니다.
|
||||
|
||||
```jinja hl_lines="4"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
이 예제에서는 `static/styles.css`에 있는 CSS 파일에 연결될 것입니다:
|
||||
|
||||
```CSS hl_lines="4"
|
||||
{!../../docs_src/templates/static/styles.css!}
|
||||
```
|
||||
|
||||
그리고 `StaticFiles`를 사용하고 있으므로, 해당 CSS 파일은 **FastAPI** 애플리케이션에서 `/static/styles.css` URL로 자동 제공됩니다.
|
||||
|
||||
## 더 많은 세부 사항
|
||||
|
||||
템플릿 테스트를 포함한 더 많은 세부 사항은 <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">Starlette의 템플릿 문서</a>를 확인하세요.
|
||||
@@ -0,0 +1,53 @@
|
||||
# 테스트 의존성 오버라이드
|
||||
|
||||
## 테스트 중 의존성 오버라이드하기
|
||||
|
||||
테스트를 진행하다 보면 의존성을 오버라이드해야 하는 경우가 있습니다.
|
||||
|
||||
원래 의존성을 실행하고 싶지 않을 수도 있습니다(또는 그 의존성이 가지고 있는 하위 의존성까지도 실행되지 않길 원할 수 있습니다).
|
||||
|
||||
대신, 테스트 동안(특정 테스트에서만) 사용될 다른 의존성을 제공하고, 원래 의존성이 사용되던 곳에서 사용할 수 있는 값을 제공하기를 원할 수 있습니다.
|
||||
|
||||
### 사용 사례: 외부 서비스
|
||||
|
||||
예를 들어, 외부 인증 제공자를 호출해야 하는 경우를 생각해봅시다.
|
||||
|
||||
토큰을 보내면 인증된 사용자를 반환합니다.
|
||||
|
||||
제공자는 요청당 요금을 부과할 수 있으며, 테스트를 위해 고정된 모의 사용자가 있는 경우보다 호출하는 데 시간이 더 걸릴 수 있습니다.
|
||||
|
||||
외부 제공자를 한 번만 테스트하고 싶을 수도 있지만 테스트를 실행할 때마다 반드시 호출할 필요는 없습니다.
|
||||
|
||||
이 경우 해당 공급자를 호출하는 종속성을 오버라이드하고 테스트에 대해서만 모의 사용자를 반환하는 사용자 지정 종속성을 사용할 수 있습니다.
|
||||
|
||||
### `app.dependency_overrides` 속성 사용하기
|
||||
|
||||
이런 경우를 위해 **FastAPI** 응용 프로그램에는 `app.dependency_overrides`라는 속성이 있습니다. 이는 간단한 `dict`입니다.
|
||||
|
||||
테스트를 위해 의존성을 오버라이드하려면, 원래 의존성(함수)을 키로 설정하고 오버라이드할 의존성(다른 함수)을 값으로 설정합니다.
|
||||
|
||||
그럼 **FastAPI**는 원래 의존성 대신 오버라이드된 의존성을 호출합니다.
|
||||
|
||||
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
**FastAPI** 애플리케이션 어디에서든 사용된 의존성에 대해 오버라이드를 설정할 수 있습니다.
|
||||
|
||||
원래 의존성은 *경로 동작 함수*, *경로 동작 데코레이터*(반환값을 사용하지 않는 경우), `.include_router()` 호출 등에서 사용될 수 있습니다.
|
||||
|
||||
FastAPI는 여전히 이를 오버라이드할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
그런 다음, `app.dependency_overrides`를 빈 `dict`로 설정하여 오버라이드를 재설정(제거)할 수 있습니다:
|
||||
|
||||
```python
|
||||
app.dependency_overrides = {}
|
||||
```
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
특정 테스트에서만 의존성을 오버라이드하고 싶다면, 테스트 시작 시(테스트 함수 내부) 오버라이드를 설정하고 테스트 종료 시(테스트 함수 끝부분) 재설정하면 됩니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,5 @@
|
||||
# 이벤트 테스트: 시작 - 종료
|
||||
|
||||
테스트에서 이벤트 핸들러(`startup` 및 `shutdown`)를 실행해야 하는 경우, `with` 문과 함께 `TestClient`를 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *}
|
||||
@@ -0,0 +1,13 @@
|
||||
# WebSocket 테스트하기
|
||||
|
||||
`TestClient`를 사용하여 WebSocket을 테스트할 수 있습니다.
|
||||
|
||||
이를 위해 `with` 문에서 `TestClient`를 사용하여 WebSocket에 연결합니다:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
자세한 내용은 Starlette의 <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank"> WebSocket 테스트</a>에 관한 설명서를 참고하시길 바랍니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,56 @@
|
||||
# `Request` 직접 사용하기
|
||||
|
||||
지금까지 요청에서 필요한 부분을 각 타입으로 선언하여 사용해 왔습니다.
|
||||
|
||||
다음과 같은 곳에서 데이터를 가져왔습니다:
|
||||
|
||||
* 경로의 파라미터로부터.
|
||||
* 헤더.
|
||||
* 쿠키.
|
||||
* 기타 등등.
|
||||
|
||||
이렇게 함으로써, **FastAPI**는 데이터를 검증하고 변환하며, API에 대한 문서를 자동화로 생성합니다.
|
||||
|
||||
하지만 `Request` 객체에 직접 접근해야 하는 상황이 있을 수 있습니다.
|
||||
|
||||
## `Request` 객체에 대한 세부 사항
|
||||
|
||||
**FastAPI**는 실제로 내부에 **Starlette**을 사용하며, 그 위에 여러 도구를 덧붙인 구조입니다. 따라서 여러분이 필요할 때 Starlette의 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> 객체를 직접 사용할 수 있습니다.
|
||||
|
||||
`Request` 객체에서 데이터를 직접 가져오는 경우(예: 본문을 읽기)에는 FastAPI가 해당 데이터를 검증하거나 변환하지 않으며, 문서화(OpenAPI를 통한 문서 자동화(로 생성된) API 사용자 인터페이스)도 되지 않습니다.
|
||||
|
||||
그러나 다른 매개변수(예: Pydantic 모델을 사용한 본문)는 여전히 검증, 변환, 주석 추가 등이 이루어집니다.
|
||||
|
||||
하지만 특정한 경우에는 `Request` 객체에 직접 접근하는 것이 유용할 수 있습니다.
|
||||
|
||||
## `Request` 객체를 직접 사용하기
|
||||
|
||||
여러분이 클라이언트의 IP 주소/호스트 정보를 *경로 작동 함수* 내부에서 가져와야 한다고 가정해 보겠습니다.
|
||||
|
||||
이를 위해서는 요청에 직접 접근해야 합니다.
|
||||
|
||||
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
|
||||
|
||||
*경로 작동 함수* 매개변수를 `Request` 타입으로 선언하면 **FastAPI**가 해당 매개변수에 `Request` 객체를 전달하는 것을 알게 됩니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 경우, 요청 매개변수와 함께 경로 매개변수를 선언한 것을 볼 수 있습니다.
|
||||
|
||||
따라서, 경로 매개변수는 추출되고 검증되며 지정된 타입으로 변환되고 OpenAPI로 주석이 추가됩니다.
|
||||
|
||||
이와 같은 방식으로, 다른 매개변수들을 평소처럼 선언하면서, 부가적으로 `Request`도 가져올 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## `Request` 설명서
|
||||
|
||||
여러분은 `Request` 객체에 대한 더 자세한 내용을 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">공식 Starlette 설명서 사이트</a>에서 읽어볼 수 있습니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`from starlette.requests import Request`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 여러분(개발자)를 위한 편의를 위해 이를 직접 제공하지만, 실제로는 Starlette에서 가져온 것입니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,186 @@
|
||||
# WebSockets
|
||||
|
||||
여러분은 **FastAPI**에서 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a>를 사용할 수 있습니다.
|
||||
|
||||
## `WebSockets` 설치
|
||||
|
||||
[가상 환경](../virtual-environments.md){.internal-link target=_blank)를 생성하고 활성화한 다음, `websockets`를 설치하세요:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install websockets
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## WebSockets 클라이언트
|
||||
|
||||
### 프로덕션 환경에서
|
||||
|
||||
여러분의 프로덕션 시스템에서는 React, Vue.js 또는 Angular와 같은 최신 프레임워크로 생성된 프런트엔드를 사용하고 있을 가능성이 높습니다.
|
||||
|
||||
백엔드와 WebSockets을 사용해 통신하려면 아마도 프런트엔드의 유틸리티를 사용할 것입니다.
|
||||
|
||||
또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 응용 프로그램을 가질 수도 있습니다.
|
||||
|
||||
혹은 WebSocket 엔드포인트와 통신할 수 있는 다른 방법이 있을 수도 있습니다.
|
||||
|
||||
---
|
||||
|
||||
하지만 이번 예제에서는 일부 자바스크립트를 포함한 간단한 HTML 문서를 사용하겠습니다. 모든 것을 긴 문자열 안에 넣습니다.
|
||||
|
||||
물론, 이는 최적의 방법이 아니며 프로덕션 환경에서는 사용하지 않을 것입니다.
|
||||
|
||||
프로덕션 환경에서는 위에서 설명한 옵션 중 하나를 사용하는 것이 좋습니다.
|
||||
|
||||
그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
|
||||
|
||||
## `websocket` 생성하기
|
||||
|
||||
**FastAPI** 응용 프로그램에서 `websocket`을 생성합니다:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
`from starlette.websockets import WebSocket`을 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자를 위한 편의를 위해 동일한 `WebSocket`을 직접 제공합니다. 하지만 이는 Starlette에서 가져옵니다.
|
||||
|
||||
///
|
||||
|
||||
## 메시지를 대기하고 전송하기
|
||||
|
||||
WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
|
||||
|
||||
여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다.
|
||||
|
||||
## 시도해보기
|
||||
|
||||
파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다:
|
||||
|
||||
<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>
|
||||
|
||||
브라우저에서 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>을 열어보세요.
|
||||
|
||||
간단한 페이지가 나타날 것입니다:
|
||||
|
||||
<img src="/img/tutorial/websockets/image01.png">
|
||||
|
||||
입력창에 메시지를 입력하고 전송할 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/websockets/image02.png">
|
||||
|
||||
**FastAPI** WebSocket 응용 프로그램이 응답을 돌려줄 것입니다:
|
||||
|
||||
<img src="/img/tutorial/websockets/image03.png">
|
||||
|
||||
여러 메시지를 전송(그리고 수신)할 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/websockets/image04.png">
|
||||
|
||||
모든 메시지는 동일한 WebSocket 연결을 사용합니다.
|
||||
|
||||
## `Depends` 및 기타 사용하기
|
||||
|
||||
WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할 수 있습니다:
|
||||
|
||||
* `Depends`
|
||||
* `Security`
|
||||
* `Cookie`
|
||||
* `Header`
|
||||
* `Path`
|
||||
* `Query`
|
||||
|
||||
이들은 다른 FastAPI 엔드포인트/*경로 작동*과 동일하게 동작합니다:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
WebSocket에서는 `HTTPException`을 발생시키는 것이 적합하지 않습니다. 대신 `WebSocketException`을 발생시킵니다.
|
||||
|
||||
명세서에 정의된 <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">유효한 코드</a>를 사용하여 종료 코드를 설정할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
### 종속성을 가진 WebSockets 테스트
|
||||
|
||||
파일 이름이 `main.py`라고 가정하고 응용 프로그램을 실행합니다:
|
||||
|
||||
<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>
|
||||
|
||||
브라우저에서 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>을 열어보세요.
|
||||
|
||||
다음과 같은 값을 설정할 수 있습니다:
|
||||
|
||||
* 경로에 사용된 "Item ID".
|
||||
* 쿼리 매개변수로 사용된 "Token".
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
쿼리 `token`은 종속성에 의해 처리됩니다.
|
||||
|
||||
///
|
||||
|
||||
이제 WebSocket에 연결하고 메시지를 전송 및 수신할 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/websockets/image05.png">
|
||||
|
||||
## 연결 해제 및 다중 클라이언트 처리
|
||||
|
||||
WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 이를 잡아 처리할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
|
||||
|
||||
테스트해보기:
|
||||
|
||||
* 여러 브라우저 탭에서 앱을 엽니다.
|
||||
* 각 탭에서 메시지를 작성합니다.
|
||||
* 한 탭을 닫아보세요.
|
||||
|
||||
`WebSocketDisconnect` 예외가 발생하며, 다른 모든 클라이언트가 다음과 같은 메시지를 수신합니다:
|
||||
|
||||
```
|
||||
Client #1596980209979 left the chat
|
||||
```
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
위 응용 프로그램은 여러 WebSocket 연결에 메시지를 브로드캐스트하는 방법을 보여주는 간단한 예제입니다.
|
||||
|
||||
그러나 모든 것을 메모리의 단일 리스트로 처리하므로, 프로세스가 실행 중인 동안만 동작하며 단일 프로세스에서만 작동합니다.
|
||||
|
||||
FastAPI와 쉽게 통합할 수 있으면서 더 견고하고 Redis, PostgreSQL 등을 지원하는 도구를 찾고 있다면, <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>를 확인하세요.
|
||||
|
||||
///
|
||||
|
||||
## 추가 정보
|
||||
|
||||
다음 옵션에 대한 자세한 내용을 보려면 Starlette의 문서를 확인하세요:
|
||||
|
||||
* <a href="https://www.starlette.dev/websockets/" class="external-link" target="_blank">`WebSocket` 클래스</a>.
|
||||
* <a href="https://www.starlette.dev/endpoints/#websocketendpoint" class="external-link" target="_blank">클래스 기반 WebSocket 처리</a>.
|
||||
@@ -0,0 +1,35 @@
|
||||
# WSGI 포함하기 - Flask, Django 그 외
|
||||
|
||||
[서브 응용 프로그램 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤편에서](behind-a-proxy.md){.internal-link target=_blank}에서 보았듯이 WSGI 응용 프로그램들을 다음과 같이 마운트 할 수 있습니다.
|
||||
|
||||
`WSGIMiddleware`를 사용하여 WSGI 응용 프로그램(예: Flask, Django 등)을 감쌀 수 있습니다.
|
||||
|
||||
## `WSGIMiddleware` 사용하기
|
||||
|
||||
`WSGIMiddleware`를 불러와야 합니다.
|
||||
|
||||
그런 다음, WSGI(예: Flask) 응용 프로그램을 미들웨어로 포장합니다.
|
||||
|
||||
그 후, 해당 경로에 마운트합니다.
|
||||
|
||||
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,23] *}
|
||||
|
||||
## 확인하기
|
||||
|
||||
이제 `/v1/` 경로에 있는 모든 요청은 Flask 응용 프로그램에서 처리됩니다.
|
||||
|
||||
그리고 나머지는 **FastAPI**에 의해 처리됩니다.
|
||||
|
||||
실행하면 <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> Flask의 응답을 볼 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,410 @@
|
||||
# 동시성과 async / await
|
||||
|
||||
*경로 작동 함수*에서의 `async def` 문법에 대한 세부사항과 비동기 코드, 동시성 및 병렬성에 대한 배경
|
||||
|
||||
## 바쁘신 경우
|
||||
|
||||
<strong>요약</strong>
|
||||
|
||||
다음과 같이 `await`를 사용해 호출하는 제3의 라이브러리를 사용하는 경우:
|
||||
|
||||
```Python
|
||||
results = await some_library()
|
||||
```
|
||||
|
||||
다음처럼 *경로 작동 함수*를 `async def`를 사용해 선언하십시오:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
async def read_results():
|
||||
results = await some_library()
|
||||
return results
|
||||
```
|
||||
|
||||
/// note | 참고
|
||||
|
||||
`async def`로 생성된 함수 내부에서만 `await`를 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
데이터베이스, API, 파일시스템 등과 의사소통하는 제3의 라이브러리를 사용하고, 그것이 `await`를 지원하지 않는 경우(현재 거의 모든 데이터베이스 라이브러리가 그러합니다), *경로 작동 함수*를 일반적인 `def`를 사용해 선언하십시오:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
def results():
|
||||
results = some_library()
|
||||
return results
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
만약 당신의 응용프로그램이 (어째서인지) 다른 무엇과 의사소통하고 그것이 응답하기를 기다릴 필요가 없다면 `async def`를 사용하십시오.
|
||||
|
||||
---
|
||||
|
||||
모르겠다면, 그냥 `def`를 사용하십시오.
|
||||
|
||||
---
|
||||
|
||||
**참고**: *경로 작동 함수*에서 필요한만큼 `def`와 `async def`를 혼용할 수 있고, 가장 알맞은 것을 선택해서 정의할 수 있습니다. FastAPI가 자체적으로 알맞은 작업을 수행할 것입니다.
|
||||
|
||||
어찌되었든, 상기 어떠한 경우라도, FastAPI는 여전히 비동기적으로 작동하고 매우 빠릅니다.
|
||||
|
||||
그러나 상기 작업을 수행함으로써 어느 정도의 성능 최적화가 가능합니다.
|
||||
|
||||
## 기술적 세부사항
|
||||
|
||||
최신 파이썬 버전은 `async`와 `await` 문법과 함께 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 지원합니다.
|
||||
|
||||
아래 섹션들에서 해당 문장을 부분별로 살펴보겠습니다:
|
||||
|
||||
* **비동기 코드**
|
||||
* **`async`와 `await`**
|
||||
* **코루틴**
|
||||
|
||||
## 비동기 코드
|
||||
|
||||
비동기 코드란 언어 💬 가 코드의 어느 한 부분에서, 컴퓨터 / 프로그램🤖에게 *다른 무언가*가 어딘가에서 끝날 때까지 기다려야한다고 말하는 방식입니다. *다른 무언가*가 “느린-파일" 📝 이라고 불린다고 가정해봅시다.
|
||||
|
||||
따라서 “느린-파일” 📝이 끝날때까지 컴퓨터는 다른 작업을 수행할 수 있습니다.
|
||||
|
||||
그 다음 컴퓨터 / 프로그램 🤖 은 다시 기다리고 있기 때문에 기회가 있을 때마다 다시 돌아오거나, 혹은 당시에 수행해야하는 작업들이 완료될 때마다 다시 돌아옵니다. 그리고 그것 🤖 은 기다리고 있던 작업 중 어느 것이 이미 완료되었는지, 그것 🤖 이 해야하는 모든 작업을 수행하면서 확인합니다.
|
||||
|
||||
다음으로, 그것 🤖 은 완료할 첫번째 작업에 착수하고(우리의 "느린-파일" 📝 이라고 가정합시다) 그에 대해 수행해야하는 작업을 계속합니다.
|
||||
|
||||
"다른 무언가를 기다리는 것"은 일반적으로 비교적 "느린" (프로세서와 RAM 메모리 속도에 비해) <abbr title="Input and Output">I/O</abbr> 작업을 의미합니다. 예를 들면 다음의 것들을 기다리는 것입니다:
|
||||
|
||||
* 네트워크를 통해 클라이언트로부터 전송되는 데이터
|
||||
* 네트워크를 통해 클라이언트가 수신할, 당신의 프로그램으로부터 전송되는 데이터
|
||||
* 시스템이 읽고 프로그램에 전달할 디스크 내의 파일 내용
|
||||
* 당신의 프로그램이 시스템에 전달하는, 디스크에 작성될 내용
|
||||
* 원격 API 작업
|
||||
* 완료될 데이터베이스 작업
|
||||
* 결과를 반환하는 데이터베이스 쿼리
|
||||
* 기타
|
||||
|
||||
수행 시간의 대부분이 <abbr title="Input and Output">I/O</abbr> 작업을 기다리는데에 소요되기 때문에, "I/O에 묶인" 작업이라고 불립니다.
|
||||
|
||||
이것은 "비동기"라고 불리는데 컴퓨터 / 프로그램이 작업 결과를 가지고 일을 수행할 수 있도록, 느린 작업에 "동기화"되어 아무것도 하지 않으면서 작업이 완료될 정확한 시점만을 기다릴 필요가 없기 때문입니다.
|
||||
|
||||
이 대신에, "비동기" 시스템에서는, 작업은 일단 완료되면, 컴퓨터 / 프로그램이 수행하고 있는 일을 완료하고 이후 다시 돌아와서 그것의 결과를 받아 이를 사용해 작업을 지속할 때까지 잠시 (몇 마이크로초) 대기할 수 있습니다.
|
||||
|
||||
"동기"("비동기"의 반대)는 컴퓨터 / 프로그램이 상이한 작업들간 전환을 하기 전에 그것이 대기를 동반하게 될지라도 모든 순서를 따르기 때문에 "순차"라는 용어로도 흔히 불립니다.
|
||||
|
||||
### 동시성과 버거
|
||||
|
||||
위에서 설명한 **비동기** 코드에 대한 개념은 종종 **"동시성"**이라고도 불립니다. 이것은 **"병렬성"**과는 다릅니다.
|
||||
|
||||
**동시성**과 **병렬성**은 모두 "동시에 일어나는 서로 다른 일들"과 관련이 있습니다.
|
||||
|
||||
하지만 *동시성*과 *병렬성*의 세부적인 개념에는 꽤 차이가 있습니다.
|
||||
|
||||
차이를 확인하기 위해, 다음의 버거에 대한 이야기를 상상해보십시오:
|
||||
|
||||
### 동시 버거
|
||||
|
||||
당신은 짝사랑 상대 😍 와 패스트푸드 🍔 를 먹으러 갔습니다. 당신은 점원 💁 이 당신 앞에 있는 사람들의 주문을 받을 동안 줄을 서서 기다리고 있습니다.
|
||||
|
||||
이제 당신의 순서가 되어서, 당신은 당신과 짝사랑 상대 😍 를 위한 두 개의 고급스러운 버거 🍔 를 주문합니다.
|
||||
|
||||
당신이 돈을 냅니다 💸.
|
||||
|
||||
점원 💁 은 주방 👨🍳 에 요리를 하라고 전달하고, 따라서 그들은 당신의 버거 🍔 를 준비해야한다는 사실을 알게됩니다(그들이 지금은 당신 앞 고객들의 주문을 준비하고 있을지라도 말입니다).
|
||||
|
||||
점원 💁 은 당신의 순서가 적힌 번호표를 줍니다.
|
||||
|
||||
기다리는 동안, 당신은 짝사랑 상대 😍 와 함께 테이블을 고르고, 자리에 앉아 오랫동안 (당신이 주문한 버거는 꽤나 고급스럽기 때문에 준비하는데 시간이 조금 걸립니다 ✨🍔✨) 대화를 나눕니다.
|
||||
|
||||
짝사랑 상대 😍 와 테이블에 앉아서 버거 🍔 를 기다리는 동안, 그 사람 😍 이 얼마나 멋지고, 사랑스럽고, 똑똑한지 감탄하며 시간을 보냅니다 ✨😍✨.
|
||||
|
||||
짝사랑 상대 😍 와 기다리면서 얘기하는 동안, 때때로, 당신은 당신의 차례가 되었는지 보기 위해 카운터의 번호를 확인합니다.
|
||||
|
||||
그러다 어느 순간, 당신의 차례가 됩니다. 카운터에 가서, 버거 🍔 를 받고, 테이블로 다시 돌아옵니다.
|
||||
|
||||
당신과 짝사랑 상대 😍 는 버거 🍔 를 먹으며 좋은 시간을 보냅니다 ✨.
|
||||
|
||||
---
|
||||
|
||||
당신이 이 이야기에서 컴퓨터 / 프로그램 🤖 이라고 상상해보십시오.
|
||||
|
||||
줄을 서서 기다리는 동안, 당신은 아무것도 하지 않고 😴 당신의 차례를 기다리며, 어떠한 "생산적인" 일도 하지 않습니다. 하지만 점원 💁 이 (음식을 준비하지는 않고) 주문을 받기만 하기 때문에 줄이 빨리 줄어들어서 괜찮습니다.
|
||||
|
||||
그다음, 당신이 차례가 오면, 당신은 실제로 "생산적인" 일 🤓 을 합니다. 당신은 메뉴를 보고, 무엇을 먹을지 결정하고, 짝사랑 상대 😍 의 선택을 묻고, 돈을 내고 💸 , 맞는 카드를 냈는지 확인하고, 비용이 제대로 지불되었는지 확인하고, 주문이 제대로 들어갔는지 확인을 하는 작업 등등을 수행합니다.
|
||||
|
||||
하지만 이후에는, 버거 🍔 를 아직 받지 못했음에도, 버거가 준비될 때까지 기다려야 🕙 하기 때문에 점원 💁 과의 작업은 "일시정지" ⏸ 상태입니다.
|
||||
|
||||
하지만 번호표를 받고 카운터에서 나와 테이블에 앉으면, 당신은 짝사랑 상대 😍 와 그 "작업" ⏯ 🤓 에 번갈아가며 🔀 집중합니다. 그러면 당신은 다시 짝사랑 상대 😍 에게 작업을 거는 매우 "생산적인" 일 🤓 을 합니다.
|
||||
|
||||
점원 💁 이 카운터 화면에 당신의 번호를 표시함으로써 "버거 🍔 가 준비되었습니다"라고 해도, 당신은 즉시 뛰쳐나가지는 않을 것입니다. 당신은 당신의 번호를 갖고있고, 다른 사람들은 그들의 번호를 갖고있기 때문에, 아무도 당신의 버거 🍔 를 훔쳐가지 않는다는 사실을 알기 때문입니다.
|
||||
|
||||
그래서 당신은 짝사랑 상대 😍 가 이야기를 끝낼 때까지 기다린 후 (현재 작업 완료 ⏯ / 진행 중인 작업 처리 🤓 ), 정중하게 미소짓고 버거를 가지러 가겠다고 말합니다 ⏸.
|
||||
|
||||
그다음 당신은 카운터에 가서 🔀 , 초기 작업을 이제 완료하고 ⏯ , 버거 🍔 를 받고, 감사하다고 말하고 테이블로 가져옵니다. 이로써 카운터와의 상호작용 단계 / 작업이 종료됩니다 ⏹.
|
||||
|
||||
이전 작업인 "버거 받기"가 종료되면 ⏹ "버거 먹기"라는 새로운 작업이 생성됩니다 🔀 ⏯.
|
||||
|
||||
### 병렬 버거
|
||||
|
||||
이제 "동시 버거"가 아닌 "병렬 버거"를 상상해보십시오.
|
||||
|
||||
당신은 짝사랑 상대 😍 와 함께 병렬 패스트푸드 🍔 를 먹으러 갔습니다.
|
||||
|
||||
당신은 여러명(8명이라고 가정합니다)의 점원이 당신 앞 사람들의 주문을 받으며 동시에 요리 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 도 하는 동안 줄을 서서 기다립니다.
|
||||
|
||||
당신 앞 모든 사람들이 버거가 준비될 때까지 카운터에서 떠나지 않고 기다립니다 🕙 . 왜냐하면 8명의 직원들이 다음 주문을 받기 전에 버거를 준비하러 가기 때문입니다.
|
||||
|
||||
마침내 당신의 차례가 왔고, 당신은 당신과 짝사랑 상대 😍 를 위한 두 개의 고급스러운 버거 🍔 를 주문합니다.
|
||||
|
||||
당신이 비용을 지불합니다 💸 .
|
||||
|
||||
점원이 주방에 갑니다 👨🍳 .
|
||||
|
||||
당신은 번호표가 없기 때문에 누구도 당신의 버거 🍔 를 대신 가져갈 수 없도록 카운터에 서서 기다립니다 🕙 .
|
||||
|
||||
당신과 짝사랑 상대 😍 는 다른 사람이 새치기해서 버거를 가져가지 못하게 하느라 바쁘기 때문에 🕙 , 짝사랑 상대에게 주의를 기울일 수 없습니다 😞 .
|
||||
|
||||
이것은 "동기" 작업이고, 당신은 점원/요리사 👨🍳 와 "동기화" 되었습니다. 당신은 기다리고 🕙 , 점원/요리사 👨🍳 가 버거 🍔 준비를 완료한 후 당신에게 주거나, 누군가가 그것을 가져가는 그 순간에 그 곳에 있어야합니다.
|
||||
|
||||
카운터 앞에서 오랫동안 기다린 후에 🕙 , 점원/요리사 👨🍳 가 당신의 버거 🍔 를 가지고 돌아옵니다.
|
||||
|
||||
당신은 버거를 받고 짝사랑 상대와 함께 테이블로 돌아옵니다.
|
||||
|
||||
단지 먹기만 하다가, 다 먹었습니다 🍔 ⏹.
|
||||
|
||||
카운터 앞에서 기다리면서 🕙 너무 많은 시간을 허비했기 때문에 대화를 하거나 작업을 걸 시간이 거의 없었습니다 😞 .
|
||||
|
||||
---
|
||||
|
||||
이 병렬 버거 시나리오에서, 당신은 기다리고 🕙 , 오랜 시간동안 "카운터에서 기다리는" 🕙 데에 주의를 기울이는 ⏯ 두 개의 프로세서(당신과 짝사랑 상대😍)를 가진 컴퓨터 / 프로그램 🤖 입니다.
|
||||
|
||||
패스트푸드점에는 8개의 프로세서(점원/요리사) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 가 있습니다. 동시 버거는 단 두 개(한 명의 직원과 한 명의 요리사) 💁 👨🍳 만을 가지고 있었습니다.
|
||||
|
||||
하지만 여전히, 병렬 버거 예시가 최선은 아닙니다 😞 .
|
||||
|
||||
---
|
||||
|
||||
이 예시는 버거🍔 이야기와 결이 같습니다.
|
||||
|
||||
더 "현실적인" 예시로, 은행을 상상해보십시오.
|
||||
|
||||
최근까지, 대다수의 은행에는 다수의 은행원들 👨💼👨💼👨💼👨💼 과 긴 줄 🕙🕙🕙🕙🕙🕙🕙🕙 이 있습니다.
|
||||
|
||||
모든 은행원들은 한 명 한 명의 고객들을 차례로 상대합니다 👨💼⏯ .
|
||||
|
||||
그리고 당신은 오랫동안 줄에서 기다려야하고 🕙 , 그렇지 않으면 당신의 차례를 잃게 됩니다.
|
||||
|
||||
아마 당신은 은행 🏦 심부름에 짝사랑 상대 😍 를 데려가고 싶지는 않을 것입니다.
|
||||
|
||||
### 버거 예시의 결론
|
||||
|
||||
"짝사랑 상대와의 패스트푸드점 버거" 시나리오에서, 오랜 기다림 🕙 이 있기 때문에 동시 시스템 ⏸🔀⏯ 을 사용하는 것이 더 합리적입니다.
|
||||
|
||||
대다수의 웹 응용프로그램의 경우가 그러합니다.
|
||||
|
||||
매우 많은 수의 유저가 있지만, 서버는 그들의 요청을 전송하기 위해 그닥-좋지-않은 연결을 기다려야 합니다 🕙 .
|
||||
|
||||
그리고 응답이 돌아올 때까지 다시 기다려야 합니다 🕙 .
|
||||
|
||||
이 "기다림" 🕙 은 마이크로초 단위이지만, 모두 더해지면, 결국에는 매우 긴 대기시간이 됩니다.
|
||||
|
||||
따라서 웹 API를 위해 비동기 ⏸🔀⏯ 코드를 사용하는 것이 합리적입니다.
|
||||
|
||||
대부분의 존재하는 유명한 파이썬 프레임워크 (Flask와 Django 등)은 새로운 비동기 기능들이 파이썬에 존재하기 전에 만들어졌습니다. 그래서, 그들의 배포 방식은 병렬 실행과 새로운 기능만큼 강력하지는 않은 예전 버전의 비동기 실행을 지원합니다.
|
||||
|
||||
비동기 웹 파이썬(ASGI)에 대한 주요 명세가 웹소켓을 지원하기 위해 Django에서 개발 되었음에도 그렇습니다.
|
||||
|
||||
이러한 종류의 비동기성은 (NodeJS는 병렬적이지 않음에도) NodeJS가 사랑받는 이유이고, 프로그래밍 언어로서의 Go의 강점입니다.
|
||||
|
||||
그리고 **FastAPI**를 사용함으로써 동일한 성능을 낼 수 있습니다.
|
||||
|
||||
또한 병렬성과 비동기성을 동시에 사용할 수 있기 때문에, 대부분의 테스트가 완료된 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명의 전(前)-점원/요리사이면서-현(現)-청소부 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳 를 고용할 수 있고, 그들 각자(그리고 당신)가 집의 한 부분씩 맡아 청소를 한다면, 당신은 **병렬적**으로 작업을 수행할 수 있고, 조금의 도움이 있다면, 훨씬 더 빨리 끝낼 수 있습니다.
|
||||
|
||||
이 시나리오에서, (당신을 포함한) 각각의 청소부들은 프로세서가 될 것이고, 각자의 역할을 수행합니다.
|
||||
|
||||
실행 시간의 대부분이 대기가 아닌 실제 작업에 소요되고, 컴퓨터에서 작업은 <abbr title="Central Processing Unit">CPU</abbr>에서 이루어지므로, 이러한 문제를 "CPU에 묶였"다고 합니다.
|
||||
|
||||
---
|
||||
|
||||
CPU에 묶인 연산에 관한 흔한 예시는 복잡한 수학 처리를 필요로 하는 경우입니다.
|
||||
|
||||
예를 들어:
|
||||
|
||||
* **오디오** 또는 **이미지** 처리.
|
||||
* **컴퓨터 비전**: 하나의 이미지는 수백개의 픽셀로 구성되어있고, 각 픽셀은 3개의 값 / 색을 갖고 있으며, 일반적으로 해당 픽셀들에 대해 동시에 무언가를 계산해야하는 처리.
|
||||
* **머신러닝**: 일반적으로 많은 "행렬"과 "벡터" 곱셈이 필요합니다. 거대한 스프레드 시트에 수들이 있고 그 수들을 동시에 곱해야 한다고 생각해보십시오.
|
||||
* **딥러닝**: 머신러닝의 하위영역으로, 동일한 예시가 적용됩니다. 단지 이 경우에는 하나의 스프레드 시트에 곱해야할 수들이 있는 것이 아니라, 거대한 세트의 스프레드 시트들이 있고, 많은 경우에, 이 모델들을 만들고 사용하기 위해 특수한 프로세서를 사용합니다.
|
||||
|
||||
### 동시성 + 병렬성: 웹 + 머신러닝
|
||||
|
||||
**FastAPI**를 사용하면 웹 개발에서는 매우 흔한 동시성의 이점을 (NodeJS의 주된 매력만큼) 얻을 수 있습니다.
|
||||
|
||||
뿐만 아니라 머신러닝 시스템과 같이 **CPU에 묶인** 작업을 위해 병렬성과 멀티프로세싱(다수의 프로세스를 병렬적으로 동작시키는 것)을 이용하는 것도 가능합니다.
|
||||
|
||||
파이썬이 **데이터 사이언스**, 머신러닝과 특히 딥러닝에 의 주된 언어라는 간단한 사실에 더해서, 이것은 FastAPI를 데이터 사이언스 / 머신러닝 웹 API와 응용프로그램에 (다른 것들보다) 좋은 선택지가 되게 합니다.
|
||||
|
||||
배포시 병렬을 어떻게 가능하게 하는지 알고싶다면, [배포](deployment/index.md){.internal-link target=_blank}문서를 참고하십시오.
|
||||
|
||||
## `async`와 `await`
|
||||
|
||||
최신 파이썬 버전에는 비동기 코드를 정의하는 매우 직관적인 방법이 있습니다. 이는 이것을 평범한 "순차적" 코드로 보이게 하고, 적절한 순간에 당신을 위해 "대기"합니다.
|
||||
|
||||
연산이 결과를 전달하기 전에 대기를 해야하고 새로운 파이썬 기능들을 지원한다면, 이렇게 코드를 작성할 수 있습니다:
|
||||
|
||||
```Python
|
||||
burgers = await get_burgers(2)
|
||||
```
|
||||
|
||||
여기서 핵심은 `await`입니다. 이것은 파이썬에게 `burgers` 결과를 저장하기 이전에 `get_burgers(2)`의 작업이 완료되기를 🕙 기다리라고 ⏸ 말합니다. 이로 인해, 파이썬은 그동안 (다른 요청을 받는 것과 같은) 다른 작업을 수행해도 된다는 것을 🔀 ⏯ 알게될 것입니다.
|
||||
|
||||
`await`가 동작하기 위해, 이것은 비동기를 지원하는 함수 내부에 있어야 합니다. 이를 위해서 함수를 `async def`를 사용해 정의하기만 하면 됩니다:
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def get_burgers(number: int):
|
||||
# Do some asynchronous stuff to create the burgers
|
||||
return burgers
|
||||
```
|
||||
|
||||
...`def`를 사용하는 대신:
|
||||
|
||||
```Python hl_lines="2"
|
||||
# This is not asynchronous
|
||||
def get_sequential_burgers(number: int):
|
||||
# Do some sequential stuff to create the burgers
|
||||
return burgers
|
||||
```
|
||||
|
||||
`async def`를 사용하면, 파이썬은 해당 함수 내에서 `await` 표현에 주의해야한다는 사실과, 해당 함수의 실행을 "일시정지"⏸하고 다시 돌아오기 전까지 다른 작업을 수행🔀할 수 있다는 것을 알게됩니다.
|
||||
|
||||
`async def`f 함수를 호출하고자 할 때, "대기"해야합니다. 따라서, 아래는 동작하지 않습니다.
|
||||
|
||||
```Python
|
||||
# This won't work, because get_burgers was defined with: async def
|
||||
burgers = get_burgers(2)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
따라서, `await`f를 사용해서 호출할 수 있는 라이브러리를 사용한다면, 다음과 같이 `async def`를 사용하는 *경로 작동 함수*를 생성해야 합니다:
|
||||
|
||||
```Python hl_lines="2-3"
|
||||
@app.get('/burgers')
|
||||
async def read_burgers():
|
||||
burgers = await get_burgers(2)
|
||||
return burgers
|
||||
```
|
||||
|
||||
### 더 세부적인 기술적 사항
|
||||
|
||||
`await`가 `async def`를 사용하는 함수 내부에서만 사용이 가능하다는 것을 눈치채셨을 것입니다.
|
||||
|
||||
하지만 동시에, `async def`로 정의된 함수들은 "대기"되어야만 합니다. 따라서, `async def`를 사용한 함수들은 역시 `async def`를 사용한 함수 내부에서만 호출될 수 있습니다.
|
||||
|
||||
그렇다면 닭이 먼저냐, 달걀이 먼저냐, 첫 `async` 함수를 어떻게 호출할 수 있겠습니까?
|
||||
|
||||
**FastAPI**를 사용해 작업한다면 이것을 걱정하지 않아도 됩니다. 왜냐하면 그 "첫" 함수는 당신의 *경로 작동 함수*가 될 것이고, FastAPI는 어떻게 올바르게 처리할지 알고있기 때문입니다.
|
||||
|
||||
하지만 FastAPI를 사용하지 않고 `async` / `await`를 사용하고 싶다면, 이 역시 가능합니다.
|
||||
|
||||
### 당신만의 비동기 코드 작성하기
|
||||
|
||||
Starlette(그리고 FastAPI)는 <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>를 사용할 수 있습니다.
|
||||
|
||||
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>를 사용해 비동기 응용프로그램을 작성할 수 있습니다.
|
||||
|
||||
### 비동기 코드의 다른 형태
|
||||
|
||||
파이썬에서 `async`와 `await`를 사용하게 된 것은 비교적 최근의 일입니다.
|
||||
|
||||
하지만 이로 인해 비동기 코드 작업이 훨씬 간단해졌습니다.
|
||||
|
||||
같은 (또는 거의 유사한) 문법은 최신 버전의 자바스크립트(브라우저와 NodeJS)에도 추가되었습니다.
|
||||
|
||||
하지만 그 이전에, 비동기 코드를 처리하는 것은 꽤 복잡하고 어려운 일이었습니다.
|
||||
|
||||
파이썬의 예전 버전이라면, 스레드 또는 <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>를 사용할 수 있을 것입니다. 하지만 코드를 이해하고, 디버깅하고, 이에 대해 생각하는게 훨씬 복잡합니다.
|
||||
|
||||
예전 버전의 NodeJS / 브라우저 자바스크립트라면, "콜백 함수"를 사용했을 것입니다. 그리고 이로 인해 "콜백 지옥"에 빠지게 될 수 있습니다.
|
||||
|
||||
## 코루틴
|
||||
|
||||
**코루틴**은 `async def` 함수가 반환하는 것을 칭하는 매우 고급스러운 용어일 뿐입니다. 파이썬은 그것이 시작되고 어느 시점에서 완료되지만 내부에 `await`가 있을 때마다 내부적으로 일시정지⏸될 수도 있는 함수와 유사한 것이라는 사실을 알고있습니다.
|
||||
|
||||
그러나 `async` 및 `await`와 함께 비동기 코드를 사용하는 이 모든 기능들은 "코루틴"으로 간단히 요약됩니다. 이것은 Go의 주된 핵심 기능인 "고루틴"에 견줄 수 있습니다.
|
||||
|
||||
## 결론
|
||||
|
||||
상기 문장을 다시 한 번 봅시다:
|
||||
|
||||
> 최신 파이썬 버전은 **`async` 및 `await`** 문법과 함께 **“코루틴”**이라고 하는 것을 사용하는 **“비동기 코드”**를 지원합니다.
|
||||
|
||||
이제 이 말을 조금 더 이해할 수 있을 것입니다. ✨
|
||||
|
||||
이것이 (Starlette을 통해) FastAPI를 강하게 하면서 그것이 인상적인 성능을 낼 수 있게 합니다.
|
||||
|
||||
## 매우 세부적인 기술적 사항
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
이 부분은 넘어가도 됩니다.
|
||||
|
||||
이것들은 **FastAPI**가 내부적으로 어떻게 동작하는지에 대한 매우 세부적인 기술사항입니다.
|
||||
|
||||
만약 기술적 지식(코루틴, 스레드, 블록킹 등)이 있고 FastAPI가 어떻게 `async def` vs `def`를 다루는지 궁금하다면, 계속하십시오.
|
||||
|
||||
///
|
||||
|
||||
### 경로 작동 함수
|
||||
|
||||
경로 작동 함수를 `async def` 대신 일반적인 `def`로 선언하는 경우, (서버를 차단하는 것처럼) 그것을 직접 호출하는 대신 대기중인 외부 스레드풀에서 실행됩니다.
|
||||
|
||||
만약 상기에 묘사된대로 동작하지 않는 비동기 프로그램을 사용해왔고 약간의 성능 향상 (약 100 나노초)을 위해 `def`를 사용해서 계산만을 위한 사소한 *경로 작동 함수*를 정의해왔다면, **FastAPI**는 이와는 반대라는 것에 주의하십시오. 이러한 경우에, *경로 작동 함수*가 블로킹 <abbr title="Input/Output: 디스크 읽기 또는 쓰기, 네트워크 통신.">I/O</abbr>를 수행하는 코드를 사용하지 않는 한 `async def`를 사용하는 편이 더 낫습니다.
|
||||
|
||||
하지만 두 경우 모두, FastAPI가 당신이 전에 사용하던 프레임워크보다 [더 빠를](index.md#_11){.internal-link target=_blank} (최소한 비견될) 확률이 높습니다.
|
||||
|
||||
### 의존성
|
||||
|
||||
의존성에도 동일하게 적용됩니다. 의존성이 `async def`가 아닌 표준 `def` 함수라면, 외부 스레드풀에서 실행됩니다.
|
||||
|
||||
### 하위-의존성
|
||||
|
||||
함수 정의시 매개변수로 서로를 필요로하는 다수의 의존성과 하위-의존성을 가질 수 있고, 그 중 일부는 `async def`로, 다른 일부는 일반적인 `def`로 생성되었을 수 있습니다. 이것은 여전히 잘 동작하고, 일반적인 `def`로 생성된 것들은 "대기"되는 대신에 (스레드풀로부터) 외부 스레드에서 호출됩니다.
|
||||
|
||||
### 다른 유틸리티 함수
|
||||
|
||||
직접 호출되는 다른 모든 유틸리티 함수는 일반적인 `def`나 `async def`로 생성될 수 있고 FastAPI는 이를 호출하는 방식에 영향을 미치지 않습니다.
|
||||
|
||||
이것은 FastAPI가 당신을 위해 호출하는 함수와는 반대입니다: *경로 작동 함수*와 의존성
|
||||
|
||||
만약 당신의 유틸리티 함수가 `def`를 사용한 일반적인 함수라면, 스레드풀에서가 아니라 직접 호출(당신이 코드에 작성한 대로)될 것이고, `async def`로 생성된 함수라면 코드에서 호출할 때 그 함수를 `await` 해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
다시 말하지만, 이것은 당신이 이것에 대해 찾고있던 경우에 한해 유용할 매우 세부적인 기술사항입니다.
|
||||
|
||||
그렇지 않은 경우, 상기의 가이드라인만으로도 충분할 것입니다: [바쁘신 경우](#_1).
|
||||
@@ -0,0 +1,34 @@
|
||||
# 벤치마크
|
||||
|
||||
독립적인 TechEmpower 벤치마크에 따르면 **FastAPI** 애플리케이션이 Uvicorn을 사용하여 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">가장 빠른 Python 프레임워크 중 하나</a>로 실행되며, Starlette와 Uvicorn 자체(내부적으로 FastAPI가 사용하는 도구)보다 조금 아래에 위치합니다.
|
||||
|
||||
그러나 벤치마크와 비교를 확인할 때 다음 사항을 염두에 두어야 합니다.
|
||||
|
||||
## 벤치마크와 속도
|
||||
|
||||
벤치마크를 확인할 때, 일반적으로 여러 가지 유형의 도구가 동등한 것으로 비교되는 것을 볼 수 있습니다.
|
||||
|
||||
특히, Uvicorn, Starlette, FastAPI가 함께 비교되는 경우가 많습니다(다른 여러 도구와 함께).
|
||||
|
||||
도구가 해결하는 문제가 단순할수록 성능이 더 좋아집니다. 그리고 대부분의 벤치마크는 도구가 제공하는 추가 기능을 테스트하지 않습니다.
|
||||
|
||||
계층 구조는 다음과 같습니다:
|
||||
|
||||
* **Uvicorn**: ASGI 서버
|
||||
* **Starlette**: (Uvicorn 사용) 웹 마이크로 프레임워크
|
||||
* **FastAPI**: (Starlette 사용) API 구축을 위한 데이터 검증 등 여러 추가 기능이 포함된 API 마이크로 프레임워크
|
||||
|
||||
* **Uvicorn**:
|
||||
* 서버 자체 외에는 많은 추가 코드가 없기 때문에 최고의 성능을 발휘합니다.
|
||||
* 직접 Uvicorn으로 응용 프로그램을 작성하지는 않을 것입니다. 즉, 사용자의 코드에는 적어도 Starlette(또는 **FastAPI**)에서 제공하는 모든 코드가 포함되어야 합니다. 그렇게 하면 최종 응용 프로그램은 프레임워크를 사용하고 앱 코드와 버그를 최소화하는 것과 동일한 오버헤드를 갖게 됩니다.
|
||||
* Uvicorn을 비교할 때는 Daphne, Hypercorn, uWSGI 등의 응용 프로그램 서버와 비교하세요.
|
||||
* **Starlette**:
|
||||
* Uvicorn 다음으로 좋은 성능을 발휘합니다. 사실 Starlette는 Uvicorn을 사용하여 실행됩니다. 따라서 더 많은 코드를 실행해야 하기 때문에 Uvicorn보다 "느려질" 수밖에 없습니다.
|
||||
* 하지만 경로 기반 라우팅 등 간단한 웹 응용 프로그램을 구축할 수 있는 도구를 제공합니다.
|
||||
* Starlette를 비교할 때는 Sanic, Flask, Django 등의 웹 프레임워크(또는 마이크로 프레임워크)와 비교하세요.
|
||||
* **FastAPI**:
|
||||
* Starlette가 Uvicorn을 사용하므로 Uvicorn보다 빨라질 수 없는 것과 마찬가지로, **FastAPI**는 Starlette를 사용하므로 더 빠를 수 없습니다.
|
||||
* FastAPI는 Starlette에 추가적으로 더 많은 기능을 제공합니다. API를 구축할 때 거의 항상 필요한 데이터 검증 및 직렬화와 같은 기능들이 포함되어 있습니다. 그리고 이를 사용하면 문서 자동화 기능도 제공됩니다(문서 자동화는 응용 프로그램 실행 시 오버헤드를 추가하지 않고 시작 시 생성됩니다).
|
||||
* FastAPI를 사용하지 않고 직접 Starlette(또는 Sanic, Flask, Responder 등)를 사용했다면 데이터 검증 및 직렬화를 직접 구현해야 합니다. 따라서 최종 응용 프로그램은 FastAPI를 사용한 것과 동일한 오버헤드를 가지게 될 것입니다. 많은 경우 데이터 검증 및 직렬화가 응용 프로그램에서 작성된 코드 중 가장 많은 부분을 차지합니다.
|
||||
* 따라서 FastAPI를 사용함으로써 개발 시간, 버그, 코드 라인을 줄일 수 있으며, FastAPI를 사용하지 않았을 때와 동일하거나 더 나은 성능을 얻을 수 있습니다(코드에서 모두 구현해야 하기 때문에).
|
||||
* FastAPI를 비교할 때는 Flask-apispec, NestJS, Molten 등 데이터 검증, 직렬화 및 문서화가 통합된 자동 데이터 검증, 직렬화 및 문서화를 제공하는 웹 응용 프로그램 프레임워크(또는 도구 집합)와 비교하세요.
|
||||
@@ -0,0 +1,13 @@
|
||||
# FastAPI를 클라우드 제공업체에서 배포하기
|
||||
|
||||
사실상 거의 **모든 클라우드 제공업체**를 사용하여 여러분의 FastAPI 애플리케이션을 배포할 수 있습니다.
|
||||
|
||||
대부분의 경우, 주요 클라우드 제공업체에서는 FastAPI를 배포할 수 있도록 가이드를 제공합니다.
|
||||
|
||||
## 클라우드 제공업체 - 후원자들
|
||||
|
||||
몇몇 클라우드 제공업체들은 [**FastAPI를 후원하며**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, 이를 통해 FastAPI와 FastAPI **생태계**가 지속적이고 건전한 **발전**을 할 수 있습니다.
|
||||
|
||||
이는 FastAPI와 **커뮤니티** (여러분)에 대한 진정한 헌신을 보여줍니다. 그들은 여러분에게 **좋은 서비스**를 제공할 뿐 만이 아니라 여러분이 **훌륭하고 건강한 프레임워크인** FastAPI 를 사용하길 원하기 때문입니다. 🙇
|
||||
|
||||
아래와 같은 서비스를 사용해보고 각 서비스의 가이드를 따를 수도 있습니다.
|
||||
@@ -0,0 +1,731 @@
|
||||
# 컨테이너의 FastAPI - 도커
|
||||
|
||||
FastAPI 어플리케이션을 배포할 때 일반적인 접근 방법은 **리눅스 컨테이너 이미지**를 생성하는 것입니다. 이 방법은 주로 <a href="https://www.docker.com/" class="external-link" target="_blank">**도커**</a>를 사용해 이루어집니다. 그런 다음 해당 컨테이너 이미지를 몇가지 방법으로 배포할 수 있습니다.
|
||||
|
||||
리눅스 컨테이너를 사용하는 데에는 **보안**, **반복 가능성**, **단순함** 등의 장점이 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
시간에 쫓기고 있고 이미 이런것들을 알고 있다면 [`Dockerfile`👇](#build-a-docker-image-for-fastapi)로 점프할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
<details>
|
||||
<summary>도커파일 미리보기 👀</summary>
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.9
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
COPY ./app /code/app
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
|
||||
# If running behind a proxy like Nginx or Traefik add --proxy-headers
|
||||
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 컨테이너란
|
||||
|
||||
컨테이너(주로 리눅스 컨테이너)는 어플리케이션의 의존성과 필요한 파일들을 모두 패키징하는 매우 **가벼운** 방법입니다. 컨테이너는 같은 시스템에 있는 다른 컨테이너(다른 어플리케이션이나 요소들)와 독립적으로 유지됩니다.
|
||||
|
||||
리눅스 컨테이너는 호스트(머신, 가상 머신, 클라우드 서버 등)와 같은 리눅스 커널을 사용해 실행됩니다. 이말은 리눅스 컨테이너가 (전체 운영체제를 모방하는 다른 가상 머신과 비교했을 때) 매우 가볍다는 것을 의미합니다.
|
||||
|
||||
이 방법을 통해, 컨테이너는 직접 프로세스를 실행하는 것과 비슷한 정도의 **적은 자원**을 소비합니다 (가상 머신은 훨씬 많은 자원을 소비할 것입니다).
|
||||
|
||||
컨테이너는 또한 그들만의 **독립된** 실행 프로세스 (일반적으로 하나의 프로세스로 충분합니다), 파일 시스템, 그리고 네트워크를 가지므로 배포, 보안, 개발 및 기타 과정을 단순화 합니다.
|
||||
|
||||
## 컨테이너 이미지란
|
||||
|
||||
**컨테이너**는 **컨테이너 이미지**를 실행한 것 입니다.
|
||||
|
||||
컨테이너 이미지란 컨테이너에 필요한 모든 파일, 환경 변수 그리고 디폴트 명령/프로그램의 **정적** 버전입니다. 여기서 **정적**이란 말은 컨테이너 **이미지**가 작동되거나 실행되지 않으며, 단지 패키지 파일과 메타 데이터라는 것을 의미합니다.
|
||||
|
||||
저장된 정적 컨텐츠인 **컨테이너 이미지**와 대조되게, **컨테이너**란 보통 실행될 수 있는 작동 인스턴스를 의미합니다.
|
||||
|
||||
**컨테이너**가 (**컨테이너 이미지**로 부터) 시작되고 실행되면, 컨테이너는 파일이나 환경 변수를 생성하거나 변경할 수 있습니다. 이러한 변화는 오직 컨테이너에서만 존재하며, 그 기반이 되는 컨테이너 이미지에는 지속되지 않습니다 (즉 디스크에는 저장되지 않습니다).
|
||||
|
||||
컨테이너 이미지는 **프로그램** 파일과 컨텐츠, 즉 `python`과 어떤 파일 `main.py`에 비교할 수 있습니다.
|
||||
|
||||
그리고 (**컨테이너 이미지**와 대비해서) **컨테이너**는 이미지의 실제 실행 인스턴스로 **프로세스**에 비교할 수 있습니다. 사실, 컨테이너는 **프로세스 러닝**이 있을 때만 실행됩니다 (그리고 보통 하나의 프로세스 입니다). 컨테이너는 내부에서 실행되는 프로세스가 없으면 종료됩니다.
|
||||
|
||||
## 컨테이너 이미지
|
||||
|
||||
도커는 **컨테이너 이미지**와 **컨테이너**를 생성하고 관리하는데 주요 도구 중 하나가 되어왔습니다.
|
||||
|
||||
그리고 <a href="https://hub.docker.com/" class="external-link" target="_blank">도커 허브</a>에 다양한 도구, 환경, 데이터베이스, 그리고 어플리케이션에 대해 미리 만들어진 **공식 컨테이너 이미지**가 공개되어 있습니다.
|
||||
|
||||
예를 들어, 공식 <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">파이썬 이미지</a>가 있습니다.
|
||||
|
||||
또한 다른 대상, 예를 들면 데이터베이스를 위한 이미지들도 있습니다:
|
||||
|
||||
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
|
||||
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
|
||||
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
|
||||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a> 등
|
||||
|
||||
미리 만들어진 컨테이너 이미지를 사용하면 서로 다른 도구들을 **결합**하기 쉽습니다. 대부분의 경우에, **공식 이미지들**을 사용하고 환경 변수를 통해 설정할 수 있습니다.
|
||||
|
||||
이런 방법으로 대부분의 경우에 컨테이너와 도커에 대해 배울 수 있으며 다양한 도구와 요소들에 대한 지식을 재사용할 수 있습니다.
|
||||
|
||||
따라서, 서로 다른 **다중 컨테이너**를 생성한 다음 이들을 연결할 수 있습니다. 예를 들어 데이터베이스, 파이썬 어플리케이션, 리액트 프론트엔드 어플리케이션을 사용하는 웹 서버에 대한 컨테이너를 만들어 이들의 내부 네트워크로 각 컨테이너를 연결할 수 있습니다.
|
||||
|
||||
모든 컨테이너 관리 시스템(도커나 쿠버네티스)은 이러한 네트워킹 특성을 포함하고 있습니다.
|
||||
|
||||
## 컨테이너와 프로세스
|
||||
|
||||
**컨테이너 이미지**는 보통 **컨테이너**를 시작하기 위해 필요한 메타데이터와 디폴트 커맨드/프로그램과 그 프로그램에 전달하기 위한 파라미터들을 포함합니다. 이는 커맨드 라인에서 프로그램을 실행할 때 필요한 값들과 유사합니다.
|
||||
|
||||
**컨테이너**가 시작되면, 해당 커맨드/프로그램이 실행됩니다 (그러나 다른 커맨드/프로그램을 실행하도록 오버라이드 할 수 있습니다).
|
||||
|
||||
컨테이너는 **메인 프로세스**(커맨드 또는 프로그램)이 실행되는 동안 실행됩니다.
|
||||
|
||||
컨테이너는 일반적으로 **단일 프로세스**를 가지고 있지만, 메인 프로세스의 서브 프로세스를 시작하는 것도 가능하며, 이 방법으로 하나의 컨테이너에 **다중 프로세스**를 가질 수 있습니다.
|
||||
|
||||
그러나 **최소한 하나의 실행중인 프로세스**를 가지지 않고서는 실행중인 컨테이너를 가질 수 없습니다. 만약 메인 프로세스가 중단되면, 컨테이너도 중단됩니다.
|
||||
|
||||
## FastAPI를 위한 도커 이미지 빌드하기
|
||||
|
||||
이제 무언가를 만들어 봅시다! 🚀
|
||||
|
||||
**공식 파이썬** 이미지에 기반하여, FastAPI를 위한 **도커 이미지**를 **맨 처음부터** 생성하는 방법을 보이겠습니다.
|
||||
|
||||
**대부분의 경우**에 다음과 같은 것들을 하게 됩니다. 예를 들면:
|
||||
|
||||
* **쿠버네티스** 또는 유사한 도구 사용하기
|
||||
* **라즈베리 파이**로 실행하기
|
||||
* 컨테이너 이미지를 실행할 클라우드 서비스 사용하기 등
|
||||
|
||||
### 요구 패키지
|
||||
|
||||
일반적으로는 어플리케이션의 특정 파일을 위한 **패키지 요구 조건**이 있을 것입니다.
|
||||
|
||||
그 요구 조건을 **설치**하는 방법은 여러분이 사용하는 도구에 따라 다를 것입니다.
|
||||
|
||||
가장 일반적인 방법은 패키지 이름과 버전이 줄 별로 기록된 `requirements.txt` 파일을 만드는 것입니다.
|
||||
|
||||
버전의 범위를 설정하기 위해서는 [FastAPI 버전들에 대하여](versions.md){.internal-link target=_blank}에 쓰여진 것과 같은 아이디어를 사용합니다.
|
||||
|
||||
예를 들어, `requirements.txt` 파일은 다음과 같을 수 있습니다:
|
||||
|
||||
```
|
||||
fastapi>=0.68.0,<0.69.0
|
||||
pydantic>=1.8.0,<2.0.0
|
||||
uvicorn>=0.15.0,<0.16.0
|
||||
```
|
||||
|
||||
그리고 일반적으로 패키지 종속성은 `pip`로 설치합니다. 예를 들어:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
Successfully installed fastapi pydantic uvicorn
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info | 정보
|
||||
|
||||
패키지 종속성을 정의하고 설치하기 위한 방법과 도구는 다양합니다.
|
||||
|
||||
나중에 아래 세션에서 Poetry를 사용한 예시를 보이겠습니다. 👇
|
||||
|
||||
///
|
||||
|
||||
### **FastAPI** 코드 생성하기
|
||||
|
||||
* `app` 디렉터리를 생성하고 이동합니다.
|
||||
* 빈 파일 `__init__.py`을 생성합니다.
|
||||
* 다음과 같은 `main.py`을 생성합니다:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
### 도커파일
|
||||
|
||||
이제 같은 프로젝트 디렉터리에 다음과 같은 파일 `Dockerfile`을 생성합니다:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
FROM python:3.9
|
||||
|
||||
# (2)
|
||||
WORKDIR /code
|
||||
|
||||
# (3)
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# (4)
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (5)
|
||||
COPY ./app /code/app
|
||||
|
||||
# (6)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. 공식 파이썬 베이스 이미지에서 시작합니다.
|
||||
|
||||
2. 현재 워킹 디렉터리를 `/code`로 설정합니다.
|
||||
|
||||
여기에 `requirements.txt` 파일과 `app` 디렉터리를 위치시킬 것입니다.
|
||||
|
||||
3. 요구 조건과 파일을 `/code` 디렉터리로 복사합니다.
|
||||
|
||||
처음에는 **오직** 요구 조건이 필요한 파일만 복사하고, 이외의 코드는 그대로 둡니다.
|
||||
|
||||
이 파일이 **자주 바뀌지 않기 때문에**, 도커는 파일을 탐지하여 이 단계의 **캐시**를 사용하여 다음 단계에서도 캐시를 사용할 수 있도록 합니다.
|
||||
|
||||
4. 요구 조건 파일에 있는 패키지 종속성을 설치합니다.
|
||||
|
||||
`--no-cache-dir` 옵션은 `pip`에게 다운로드한 패키지들을 로컬 환경에 저장하지 않도록 전달합니다. 이는 마치 같은 패키지를 설치하기 위해 오직 `pip`만 다시 실행하면 될 것 같지만, 컨테이너로 작업하는 경우 그렇지는 않습니다.
|
||||
|
||||
/// note | 노트
|
||||
|
||||
`--no-cache-dir` 는 오직 `pip`와 관련되어 있으며, 도커나 컨테이너와는 무관합니다.
|
||||
|
||||
///
|
||||
|
||||
`--upgrade` 옵션은 `pip`에게 설치된 패키지들을 업데이트하도록 합니다.
|
||||
|
||||
이전 단계에서 파일을 복사한 것이 **도커 캐시**에 의해 탐지되기 때문에, 이 단계에서도 가능한 한 **도커 캐시**를 사용하게 됩니다.
|
||||
|
||||
이 단계에서 캐시를 사용하면 **매번** 모든 종속성을 다운로드 받고 설치할 필요가 없어, 개발 과정에서 이미지를 지속적으로 생성하는 데에 드는 **시간**을 많이 **절약**할 수 있습니다.
|
||||
|
||||
5. `/code` 디렉터리에 `./app` 디렉터리를 복사합니다.
|
||||
|
||||
**자주 변경되는** 모든 코드를 포함하고 있기 때문에, 도커 **캐시**는 이 단계나 **이후의 단계에서** 잘 사용되지 않습니다.
|
||||
|
||||
그러므로 컨테이너 이미지 빌드 시간을 최적화하기 위해 `Dockerfile`의 **거의 끝 부분**에 입력하는 것이 중요합니다.
|
||||
|
||||
6. `uvicorn` 서버를 실행하기 위해 **커맨드**를 설정합니다.
|
||||
|
||||
`CMD`는 문자열 리스트를 입력받고, 각 문자열은 커맨드 라인의 각 줄에 입력할 문자열입니다.
|
||||
|
||||
이 커맨드는 **현재 워킹 디렉터리**에서 실행되며, 이는 위에서 `WORKDIR /code`로 설정한 `/code` 디렉터리와 같습니다.
|
||||
|
||||
프로그램이 `/code`에서 시작하고 그 속에 `./app` 디렉터리가 여러분의 코드와 함께 들어있기 때문에, **Uvicorn**은 이를 보고 `app`을 `app.main`으로부터 **불러 올** 것입니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
각 코드 라인을 코드의 숫자 버블을 클릭하여 리뷰할 수 있습니다. 👆
|
||||
|
||||
///
|
||||
|
||||
이제 여러분은 다음과 같은 디렉터리 구조를 가지고 있을 것입니다:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ └── main.py
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
#### TLS 종료 프록시의 배후
|
||||
|
||||
만약 여러분이 컨테이너를 Nginx 또는 Traefik과 같은 TLS 종료 프록시 (로드 밸런서) 뒤에서 실행하고 있다면, `--proxy-headers` 옵션을 더하는 것이 좋습니다. 이 옵션은 Uvicorn에게 어플리케이션이 HTTPS 등의 뒤에서 실행되고 있으므로 프록시에서 전송된 헤더를 신뢰할 수 있다고 알립니다.
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
#### 도커 캐시
|
||||
|
||||
이 `Dockerfile`에는 중요한 트릭이 있는데, 처음에는 **의존성이 있는 파일만** 복사하고, 나머지 코드는 그대로 둡니다. 왜 이런 방법을 써야하는지 설명하겠습니다.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
```
|
||||
|
||||
도커와 다른 도구들은 컨테이너 이미지를 **증가하는 방식으로 빌드**합니다. `Dockerfile`의 맨 윗 부분부터 시작해, 레이어 위에 새로운 레이어를 더하는 방식으로, `Dockerfile`의 각 지시 사항으로 부터 생성된 어떤 파일이든 더해갑니다.
|
||||
|
||||
도커 그리고 이와 유사한 도구들은 이미지 생성 시에 **내부 캐시**를 사용합니다. 만약 어떤 파일이 마지막으로 컨테이너 이미지를 빌드한 때로부터 바뀌지 않았다면, 파일을 다시 복사하여 새로운 레이어를 처음부터 생성하는 것이 아니라, 마지막에 생성했던 **같은 레이어를 재사용**합니다.
|
||||
|
||||
단지 파일 복사를 지양하는 것으로 효율이 많이 향상되는 것은 아니지만, 그 단계에서 캐시를 사용했기 때문에, **다음 단계에서도 마찬가지로 캐시를 사용**할 수 있습니다. 예를 들어, 다음과 같은 의존성을 설치하는 지시 사항을 위한 캐시를 사용할 수 있습니다:
|
||||
|
||||
```Dockerfile
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
```
|
||||
|
||||
패키지를 포함하는 파일은 **자주 변경되지 않습니다**. 따라서 해당 파일만 복사하므로서, 도커는 그 단계의 **캐시를 사용**할 수 있습니다.
|
||||
|
||||
그 다음으로, 도커는 **다음 단계에서** 의존성을 다운로드하고 설치하는 **캐시를 사용**할 수 있게 됩니다. 바로 이 과정에서 우리는 **많은 시간을 절약**하게 됩니다. ✨ ...그리고 기다리는 지루함도 피할 수 있습니다. 😪😆
|
||||
|
||||
패키지 의존성을 다운로드 받고 설치하는 데이는 **수 분이 걸릴 수 있지만**, **캐시**를 사용하면 최대 **수 초만에** 끝낼 수 있습니다.
|
||||
|
||||
또한 여러분이 개발 과정에서 코드의 변경 사항이 반영되었는지 확인하기 위해 컨테이너 이미지를 계속해서 빌드하면, 절약된 시간은 축적되어 더욱 커질 것입니다.
|
||||
|
||||
그리고 나서 `Dockerfile`의 거의 끝 부분에서, 모든 코드를 복사합니다. 이것이 **가장 빈번하게 변경**되는 부분이며, 대부분의 경우에 이 다음 단계에서는 캐시를 사용할 수 없기 때문에 가장 마지막에 둡니다.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./app /code/app
|
||||
```
|
||||
|
||||
### 도커 이미지 생성하기
|
||||
|
||||
이제 모든 파일이 제자리에 있으니, 컨테이너 이미지를 빌드합니다.
|
||||
|
||||
* (여러분의 `Dockerfile`과 `app` 디렉터리가 위치한) 프로젝트 디렉터리로 이동합니다.
|
||||
* FastAPI 이미지를 빌드합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ docker build -t myimage .
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
맨 끝에 있는 `.` 에 주목합시다. 이는 `./`와 동등하며, 도커에게 컨테이너 이미지를 빌드하기 위한 디렉터리를 알려줍니다.
|
||||
|
||||
이 경우에는 현재 디렉터리(`.`)와 같습니다.
|
||||
|
||||
///
|
||||
|
||||
### 도커 컨테이너 시작하기
|
||||
|
||||
* 여러분의 이미지에 기반하여 컨테이너를 실행합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ docker run -d --name mycontainer -p 80:80 myimage
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 체크하기
|
||||
|
||||
여러분의 도커 컨테이너 URL에서 실행 사항을 체크할 수 있습니다. 예를 들어: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> 또는 <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (또는 동일하게, 여러분의 도커 호스트를 이용해서 체크할 수도 있습니다).
|
||||
|
||||
아래와 비슷한 것을 보게 될 것입니다:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
## 인터랙티브 API 문서
|
||||
|
||||
이제 여러분은 <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> 또는 <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a>로 이동할 수 있습니다(또는, 여러분의 도커 호스트를 이용할 수 있습니다).
|
||||
|
||||
여러분은 자동으로 생성된 인터랙티브 API(<a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>에서 제공된)를 볼 수 있습니다:
|
||||
|
||||

|
||||
|
||||
## 대안 API 문서
|
||||
|
||||
또한 여러분은 <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> 또는 <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a>으로 이동할 수 있습니다(또는, 여러분의 도커 호스트를 이용할 수 있습니다).
|
||||
|
||||
여러분은 자동으로 생성된 대안 문서(<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>에서 제공된)를 볼 수 있습니다:
|
||||
|
||||

|
||||
|
||||
## 단일 파일 FastAPI로 도커 이미지 생성하기
|
||||
|
||||
만약 여러분의 FastAPI가 하나의 파일이라면, 예를 들어 `./app` 디렉터리 없이 `main.py` 파일만으로 이루어져 있다면, 파일 구조는 다음과 유사할 것입니다:
|
||||
|
||||
```
|
||||
.
|
||||
├── Dockerfile
|
||||
├── main.py
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
그러면 여러분들은 `Dockerfile` 내에 있는 파일을 복사하기 위해 그저 상응하는 경로를 바꾸기만 하면 됩니다:
|
||||
|
||||
```{ .dockerfile .annotate hl_lines="10 13" }
|
||||
FROM python:3.9
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (1)
|
||||
COPY ./main.py /code/
|
||||
|
||||
# (2)
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. `main.py` 파일을 `/code` 디렉터리로 곧바로 복사합니다(`./app` 디렉터리는 고려하지 않습니다).
|
||||
|
||||
2. Uvicorn을 실행해 `app` 객체를 (`app.main` 대신) `main`으로 부터 불러오도록 합니다.
|
||||
|
||||
그 다음 Uvicorn 커맨드를 조정해서 FastAPI 객체를 불러오는데 `app.main` 대신에 새로운 모듈 `main`을 사용하도록 합니다.
|
||||
|
||||
## 배포 개념
|
||||
|
||||
이제 컨테이너의 측면에서 [배포 개념](concepts.md){.internal-link target=_blank}에서 다루었던 것과 같은 배포 개념에 대해 이야기해 보겠습니다.
|
||||
|
||||
컨테이너는 주로 어플리케이션을 빌드하고 배포하기 위한 과정을 단순화하는 도구이지만, **배포 개념**에 대한 특정한 접근법을 강요하지 않기 때문에 가능한 배포 전략에는 여러가지가 있습니다.
|
||||
|
||||
**좋은 소식**은 서로 다른 전략들을 포괄하는 배포 개념이 있다는 점입니다. 🎉
|
||||
|
||||
컨테이너 측면에서 **배포 개념**을 리뷰해 보겠습니다:
|
||||
|
||||
* HTTPS
|
||||
* 구동하기
|
||||
* 재시작
|
||||
* 복제 (실행 중인 프로세스 개수)
|
||||
* 메모리
|
||||
* 시작하기 전 단계들
|
||||
|
||||
## HTTPS
|
||||
|
||||
만약 우리가 FastAPI 어플리케이션을 위한 **컨테이너 이미지**에만 집중한다면 (그리고 나중에 실행될 **컨테이너**에), HTTPS는 일반적으로 다른 도구에 의해 **외부적으로** 다루어질 것 입니다.
|
||||
|
||||
**HTTPS**와 **인증서**의 **자동** 취득을 다루는 것은 다른 컨테이너가 될 수 있는데, 예를 들어 <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>을 사용하는 것입니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
Traefik은 도커, 쿠버네티스, 그리고 다른 도구와 통합되어 있어 여러분의 컨테이너를 포함하는 HTTPS를 셋업하고 설정하는 것이 매우 쉽습니다.
|
||||
|
||||
///
|
||||
|
||||
대안적으로, HTTPS는 클라우드 제공자에 의해 서비스의 일환으로 다루어질 수도 있습니다 (이때도 어플리케이션은 여전히 컨테이너에서 실행될 것입니다).
|
||||
|
||||
## 구동과 재시작
|
||||
|
||||
여러분의 컨테이너를 **시작하고 실행하는** 데에 일반적으로 사용되는 도구는 따로 있습니다.
|
||||
|
||||
이는 **도커** 자체일 수도 있고, **도커 컴포즈**, **쿠버네티스**, **클라우드 서비스** 등이 될 수 있습니다.
|
||||
|
||||
대부분 (또는 전체) 경우에, 컨테이너를 구동하거나 고장시에 재시작하도록 하는 간단한 옵션이 있습니다. 예를 들어, 도커에서는, 커맨드 라인 옵션 `--restart` 입니다.
|
||||
|
||||
컨테이너를 사용하지 않고서는, 어플리케이션을 구동하고 재시작하는 것이 매우 번거롭고 어려울 수 있습니다. 하지만 **컨테이너를 사용한다면** 대부분의 경우에 이런 기능은 기본적으로 포함되어 있습니다. ✨
|
||||
|
||||
## 복제 - 프로세스 개수
|
||||
|
||||
만약 여러분이 **쿠버네티스**와 머신 <abbr title="A group of machines that are configured to be connected and work together in some way.">클러스터</abbr>, 도커 스왐 모드, 노마드, 또는 다른 여러 머신 위에 분산 컨테이너를 관리하는 복잡한 시스템을 다루고 있다면, 여러분은 각 컨테이너에서 (워커와 함께 사용하는 Gunicorn 같은) **프로세스 매니저** 대신 **클러스터 레벨**에서 **복제를 다루**고 싶을 것입니다.
|
||||
|
||||
쿠버네티스와 같은 분산 컨테이너 관리 시스템 중 일부는 일반적으로 들어오는 요청에 대한 **로드 밸런싱**을 지원하면서 **컨테이너 복제**를 다루는 통합된 방법을 가지고 있습니다. 모두 **클러스터 레벨**에서 말이죠.
|
||||
|
||||
이런 경우에, 여러분은 [위에서 묘사된 것](#dockerfile)처럼 **처음부터 도커 이미지를** 빌드해서, 의존성을 설치하고, Uvicorn 워커를 관리하는 Gunicorn 대신 **단일 Uvicorn 프로세스**를 실행하고 싶을 것입니다.
|
||||
|
||||
### 로드 밸런서
|
||||
|
||||
컨테이너로 작업할 때, 여러분은 일반적으로 **메인 포트의 상황을 감지하는** 요소를 가지고 있을 것입니다. 이는 **HTTPS**를 다루는 **TLS 종료 프록시**와 같은 다른 컨테이너일 수도 있고, 유사한 다른 도구일 수도 있습니다.
|
||||
|
||||
이 요소가 요청들의 **로드**를 읽어들이고 각 워커에게 (바라건대) **균형적으로** 분배한다면, 이 요소는 일반적으로 **로드 밸런서**라고 불립니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
HTTPS를 위해 사용된 **TLS 종료 프록시** 요소 또한 **로드 밸런서**가 될 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
또한 컨테이너로 작업할 때, 컨테이너를 시작하고 관리하기 위해 사용한 것과 동일한 시스템은 이미 해당 **로드 밸런서**로 부터 여러분의 앱에 해당하는 컨테이너로 **네트워크 통신**(예를 들어, HTTP 요청)을 전송하는 내부적인 도구를 가지고 있을 것입니다 (여기서도 로드 밸런서는 **TLS 종료 프록시**일 수 있습니다).
|
||||
|
||||
### 하나의 로드 밸런서 - 다중 워커 컨테이너
|
||||
|
||||
**쿠버네티스**나 또는 다른 분산 컨테이너 관리 시스템으로 작업할 때, 시스템 내부의 네트워킹 메커니즘을 이용함으로써 메인 **포트**를 감지하고 있는 단일 **로드 밸런서**는 여러분의 앱에서 실행되고 있는 **여러개의 컨테이너**에 통신(요청들)을 전송할 수 있게 됩니다.
|
||||
|
||||
여러분의 앱에서 실행되고 있는 각각의 컨테이너는 일반적으로 **하나의 프로세스**만 가질 것입니다 (예를 들어, FastAPI 어플리케이션에서 실행되는 하나의 Uvicorn 프로세스처럼). 이 컨테이너들은 모두 같은 것을 실행하는 점에서 **동일한 컨테이너**이지만, 프로세스, 메모리 등은 공유하지 않습니다. 이 방식으로 여러분은 CPU의 **서로 다른 코어들** 또는 **서로 다른 머신들**을 **병렬화**하는 이점을 얻을 수 있습니다.
|
||||
|
||||
또한 **로드 밸런서**가 있는 분산 컨테이너 시스템은 여러분의 앱에 있는 컨테이너 각각에 **차례대로 요청을 분산**시킬 것 입니다. 따라서 각 요청은 여러분의 앱에서 실행되는 여러개의 **복제된 컨테이너들** 중 하나에 의해 다루어질 것 입니다.
|
||||
|
||||
그리고 일반적으로 **로드 밸런서**는 여러분의 클러스터에 있는 *다른* 앱으로 가는 요청들도 다룰 수 있으며 (예를 들어, 다른 도메인으로 가거나 다른 URL 경로 접두사를 가지는 경우), 이 통신들을 클러스터에 있는 *바로 그 다른* 어플리케이션으로 제대로 전송할 수 있습니다.
|
||||
|
||||
### 단일 프로세스를 가지는 컨테이너
|
||||
|
||||
이 시나리오의 경우, 여러분은 이미 클러스터 레벨에서 복제를 다루고 있을 것이므로 **컨테이너 당 단일 (Uvicorn) 프로세스**를 가지고자 할 것입니다.
|
||||
|
||||
따라서, 여러분은 Gunicorn 이나 Uvicorn 워커, 또는 Uvicorn 워커를 사용하는 Uvicorn 매니저와 같은 프로세스 매니저를 가지고 싶어하지 **않을** 것입니다. 여러분은 컨테이너 당 **단일 Uvicorn 프로세스**를 가지고 싶어할 것입니다 (그러나 아마도 다중 컨테이너를 가질 것입니다).
|
||||
|
||||
이미 여러분이 클러스터 시스템을 관리하고 있으므로, (Uvicorn 워커를 관리하는 Gunicorn 이나 Uvicorn 처럼) 컨테이너 내에 다른 프로세스 매니저를 가지는 것은 **불필요한 복잡성**만 더하게 될 것입니다.
|
||||
|
||||
### 다중 프로세스를 가지는 컨테이너와 특수한 경우들
|
||||
|
||||
당연한 말이지만, 여러분이 내부적으로 **Uvicorn 워커 프로세스들**를 시작하는 **Gunicorn 프로세스 매니저**를 가지는 단일 컨테이너를 원하는 **특수한 경우**도 있을 것입니다.
|
||||
|
||||
그런 경우에, 여러분들은 **Gunicorn**을 프로세스 매니저로 포함하는 **공식 도커 이미지**를 사용할 수 있습니다. 이 프로세스 매니저는 다중 **Uvicorn 워커 프로세스들**을 실행하며, 디폴트 세팅으로 현재 CPU 코어에 기반하여 자동으로 워커 개수를 조정합니다. 이 사항에 대해서는 아래의 [Gunicorn과 함께하는 공식 도커 이미지 - Uvicorn](#official-docker-image-with-gunicorn-uvicorn)에서 더 다루겠습니다.
|
||||
|
||||
이런 경우에 해당하는 몇가지 예시가 있습니다:
|
||||
|
||||
#### 단순한 앱
|
||||
|
||||
만약 여러분의 어플리케이션이 **충분히 단순**해서 (적어도 아직은) 프로세스 개수를 파인-튠 할 필요가 없거나 클러스터가 아닌 **단일 서버**에서 실행하고 있다면, 여러분은 컨테이너 내에 프로세스 매니저를 사용하거나 (공식 도커 이미지에서) 자동으로 설정되는 디폴트 값을 사용할 수 있습니다.
|
||||
|
||||
#### 도커 구성
|
||||
|
||||
여러분은 **도커 컴포즈**로 (클러스터가 아닌) **단일 서버로** 배포할 수 있으며, 이 경우에 공유된 네트워크와 **로드 밸런싱**을 포함하는 (도커 컴포즈로) 컨테이너의 복제를 관리하는 단순한 방법이 없을 수도 있습니다.
|
||||
|
||||
그렇다면 여러분은 **프로세스 매니저**와 함께 내부에 **몇개의 워커 프로세스들**을 시작하는 **단일 컨테이너**를 필요로 할 수 있습니다.
|
||||
|
||||
#### Prometheus와 다른 이유들
|
||||
|
||||
여러분은 **단일 프로세스**를 가지는 **다중 컨테이너** 대신 **다중 프로세스**를 가지는 **단일 컨테이너**를 채택하는 **다른 이유**가 있을 수 있습니다.
|
||||
|
||||
예를 들어 (여러분의 장치 설정에 따라) Prometheus 익스포터와 같이 같은 컨테이너에 들어오는 **각 요청에 대해** 접근권한을 가지는 도구를 사용할 수 있습니다.
|
||||
|
||||
이 경우에 여러분이 **여러개의 컨테이너들**을 가지고 있다면, Prometheus가 **메트릭을 읽어 들일 때**, 디폴트로 **매번 하나의 컨테이너**(특정 리퀘스트를 관리하는 바로 그 컨테이너)로 부터 읽어들일 것입니다. 이는 모든 복제된 컨테이너에 대해 **축적된 메트릭들**을 읽어들이는 것과 대비됩니다.
|
||||
|
||||
그렇다면 이 경우에는 **다중 프로세스**를 가지는 **하나의 컨테이너**를 두어서 같은 컨테이너에서 모든 내부 프로세스에 대한 Prometheus 메트릭을 수집하는 로컬 도구(예를 들어 Prometheus 익스포터 같은)를 두어서 이 메그릭들을 하나의 컨테이너에 내에서 공유하는 방법이 더 단순할 것입니다.
|
||||
|
||||
---
|
||||
|
||||
요점은, 이 중의 **어느것도** 여러분들이 반드시 따라야하는 **확정된 사실**이 아니라는 것입니다. 여러분은 이 아이디어들을 **여러분의 고유한 이용 사례를 평가**하는데 사용하고, 여러분의 시스템에 가장 적합한 접근법이 어떤 것인지 결정하며, 다음의 개념들을 관리하는 방법을 확인할 수 있습니다:
|
||||
|
||||
* 보안 - HTTPS
|
||||
* 구동하기
|
||||
* 재시작
|
||||
* 복제 (실행 중인 프로세스 개수)
|
||||
* 메모리
|
||||
* 시작하기 전 단계들
|
||||
|
||||
## 메모리
|
||||
|
||||
만약 여러분이 **컨테이너 당 단일 프로세스**를 실행한다면, 여러분은 각 컨테이너(복제된 경우에는 여러개의 컨테이너들)에 대해 잘 정의되고, 안정적이며, 제한된 용량의 메모리 소비량을 가지고 있을 것입니다.
|
||||
|
||||
그러면 여러분의 컨테이너 관리 시스템(예를 들어 **쿠버네티스**) 설정에서 앞서 정의된 것과 같은 메모리 제한과 요구사항을 설정할 수 있습니다. 이런 방법으로 **가용 머신**이 필요로하는 메모리와 클러스터에 있는 가용 머신들을 염두에 두고 **컨테이너를 복제**할 수 있습니다.
|
||||
|
||||
만약 여러분의 어플리케이션이 **단순**하다면, 이것은 **문제가 되지 않을** 것이고, 고정된 메모리 제한을 구체화할 필요도 없을 것입니다. 하지만 여러분의 어플리케이션이 (예를 들어 **머신 러닝** 모델같이) **많은 메모리를 소요한다면**, 어플리케이션이 얼마나 많은 양의 메모리를 사용하는지 확인하고 **각 머신에서** 사용하는 **컨테이너의 수**를 조정할 필요가 있습니다 (그리고 필요에 따라 여러분의 클러스터에 머신을 추가할 수 있습니다).
|
||||
|
||||
만약 여러분이 **컨테이너 당 여러개의 프로세스**를 실행한다면 (예를 들어 공식 도커 이미지 처럼), 여러분은 시작된 프로세스 개수가 가용한 것 보다 **더 많은 메모리를 소비**하지 않는지 확인해야 합니다.
|
||||
|
||||
## 시작하기 전 단계들과 컨테이너
|
||||
|
||||
만약 여러분이 컨테이너(예를 들어 도커, 쿠버네티스)를 사용한다면, 여러분이 접근할 수 있는 주요 방법은 크게 두가지가 있습니다.
|
||||
|
||||
### 다중 컨테이너
|
||||
|
||||
만약 여러분이 **여러개의 컨테이너**를 가지고 있다면, 아마도 각각의 컨테이너는 **하나의 프로세스**를 가지고 있을 것입니다(예를 들어, **쿠버네티스** 클러스터에서). 그러면 여러분은 복제된 워커 컨테이너를 실행하기 **이전에**, 하나의 컨테이너에 있는 **이전의 단계들을** 수행하는 단일 프로세스를 가지는 **별도의 컨테이너들**을 가지고 싶을 것입니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
만약 여러분이 쿠버네티스를 사용하고 있다면, 아마도 이는 <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>일 것입니다.
|
||||
|
||||
///
|
||||
|
||||
만약 여러분의 이용 사례에서 이전 단계들을 **병렬적으로 여러번** 수행하는데에 문제가 없다면 (예를 들어 데이터베이스 이전을 실행하지 않고 데이터베이스가 준비되었는지 확인만 하는 경우), 메인 프로세스를 시작하기 전에 이 단계들을 각 컨테이너에 넣을 수 있습니다.
|
||||
|
||||
### 단일 컨테이너
|
||||
|
||||
만약 여러분의 셋업이 **다중 프로세스**(또는 하나의 프로세스)를 시작하는 **하나의 컨테이너**를 가지는 단순한 셋업이라면, 사전 단계들을 앱을 포함하는 프로세스를 시작하기 직전에 같은 컨테이너에서 실행할 수 있습니다. 공식 도커 이미지는 이를 내부적으로 지원합니다.
|
||||
|
||||
## Gunicorn과 함께하는 공식 도커 이미지 - Uvicorn
|
||||
|
||||
앞 챕터에서 자세하게 설명된 것 처럼, Uvicorn 워커와 같이 실행되는 Gunicorn을 포함하는 공식 도커 이미지가 있습니다: [서버 워커 - Uvicorn과 함께하는 Gunicorn](server-workers.md){.internal-link target=_blank}.
|
||||
|
||||
이 이미지는 주로 위에서 설명된 상황에서 유용할 것입니다: [다중 프로세스를 가지는 컨테이너와 특수한 경우들](#containers-with-multiple-processes-and-special-cases).
|
||||
|
||||
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
여러분이 이 베이스 이미지 또는 다른 유사한 이미지를 필요로 하지 **않을** 높은 가능성이 있으며, [위에서 설명된 것처럼: FastAPI를 위한 도커 이미지 빌드하기](#build-a-docker-image-for-fastapi) 처음부터 이미지를 빌드하는 것이 더 나을 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
이 이미지는 가능한 CPU 코어에 기반한 **몇개의 워커 프로세스**를 설정하는 **자동-튜닝** 메커니즘을 포함하고 있습니다.
|
||||
|
||||
이 이미지는 **민감한 디폴트** 값을 가지고 있지만, 여러분들은 여전히 **환경 변수** 또는 설정 파일을 통해 설정값을 수정하고 업데이트 할 수 있습니다.
|
||||
|
||||
또한 스크립트를 통해 <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**시작하기 전 사전 단계**</a>를 실행하는 것을 지원합니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
모든 설정과 옵션을 보려면, 도커 이미지 페이지로 이동합니다: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
|
||||
///
|
||||
|
||||
### 공식 도커 이미지에 있는 프로세스 개수
|
||||
|
||||
이 이미지에 있는 **프로세스 개수**는 가용한 CPU **코어들**로 부터 **자동으로 계산**됩니다.
|
||||
|
||||
이것이 의미하는 바는 이미지가 CPU로부터 **최대한의 성능**을 **쥐어짜낸다**는 것입니다.
|
||||
|
||||
여러분은 이 설정 값을 **환경 변수**나 기타 방법들로 조정할 수 있습니다.
|
||||
|
||||
그러나 프로세스의 개수가 컨테이너가 실행되고 있는 CPU에 의존한다는 것은 또한 **소요되는 메모리의 크기** 또한 이에 의존한다는 것을 의미합니다.
|
||||
|
||||
그렇기 때문에, 만약 여러분의 어플리케이션이 많은 메모리를 요구하고 (예를 들어 머신러닝 모델처럼), 여러분의 서버가 CPU 코어 수는 많지만 **적은 메모리**를 가지고 있다면, 여러분의 컨테이너는 가용한 메모리보다 많은 메모리를 사용하려고 시도할 수 있으며, 결국 퍼포먼스를 크게 떨어뜨릴 수 있습니다(심지어 고장이 날 수도 있습니다). 🚨
|
||||
|
||||
### `Dockerfile` 생성하기
|
||||
|
||||
이 이미지에 기반해 `Dockerfile`을 생성하는 방법은 다음과 같습니다:
|
||||
|
||||
```Dockerfile
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app
|
||||
```
|
||||
|
||||
### 더 큰 어플리케이션
|
||||
|
||||
만약 여러분이 [다중 파일을 가지는 더 큰 어플리케이션](../tutorial/bigger-applications.md){.internal-link target=_blank}을 생성하는 섹션을 따랐다면, 여러분의 `Dockerfile`은 대신 이렇게 생겼을 것입니다:
|
||||
|
||||
```Dockerfile hl_lines="7"
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app/app
|
||||
```
|
||||
|
||||
### 언제 사용할까
|
||||
|
||||
여러분들이 **쿠버네티스**(또는 유사한 다른 도구) 사용하거나 클러스터 레벨에서 다중 컨테이너를 이용해 이미 **사본**을 설정하고 있다면, 공식 베이스 이미지(또는 유사한 다른 이미지)를 사용하지 **않는** 것 좋습니다. 그런 경우에 여러분은 다음에 설명된 것 처럼 **처음부터 이미지를 빌드하는 것**이 더 낫습니다: [FastAPI를 위한 도커 이미지 빌드하기](#build-a-docker-image-for-fastapi).
|
||||
|
||||
이 이미지는 위의 [다중 프로세스를 가지는 컨테이너와 특수한 경우들](#containers-with-multiple-processes-and-special-cases)에서 설명된 특수한 경우에 대해서만 주로 유용할 것입니다. 예를 들어, 만약 여러분의 어플리케이션이 **충분히 단순**해서 CPU에 기반한 디폴트 프로세스 개수를 설정하는 것이 잘 작동한다면, 클러스터 레벨에서 수동으로 사본을 설정할 필요가 없을 것이고, 여러분의 앱에서 하나 이상의 컨테이너를 실행하지도 않을 것입니다. 또는 만약에 여러분이 **도커 컴포즈**로 배포하거나, 단일 서버에서 실행하거나 하는 경우에도 마찬가지입니다.
|
||||
|
||||
## 컨테이너 이미지 배포하기
|
||||
|
||||
컨테이너 (도커) 이미지를 완성한 뒤에 이를 배포하는 방법에는 여러가지 방법이 있습니다.
|
||||
|
||||
예를 들어:
|
||||
|
||||
* 단일 서버에서 **도커 컴포즈**로 배포하기
|
||||
* **쿠버네티스** 클러스터로 배포하기
|
||||
* 도커 스왐 모드 클러스터로 배포하기
|
||||
* 노마드 같은 다른 도구로 배포하기
|
||||
* 여러분의 컨테이너 이미지를 배포해주는 클라우드 서비스로 배포하기
|
||||
|
||||
## Poetry의 도커 이미지
|
||||
|
||||
만약 여러분들이 프로젝트 의존성을 관리하기 위해 <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a>를 사용한다면, 도커의 멀티-스테이지 빌딩을 사용할 수 있습니다:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
FROM python:3.9 as requirements-stage
|
||||
|
||||
# (2)
|
||||
WORKDIR /tmp
|
||||
|
||||
# (3)
|
||||
RUN pip install poetry
|
||||
|
||||
# (4)
|
||||
COPY ./pyproject.toml ./poetry.lock* /tmp/
|
||||
|
||||
# (5)
|
||||
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
|
||||
# (6)
|
||||
FROM python:3.9
|
||||
|
||||
# (7)
|
||||
WORKDIR /code
|
||||
|
||||
# (8)
|
||||
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
|
||||
|
||||
# (9)
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (10)
|
||||
COPY ./app /code/app
|
||||
|
||||
# (11)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. 첫 스테이지로, `requirements-stage`라고 이름 붙였습니다.
|
||||
|
||||
2. `/tmp`를 현재의 워킹 디렉터리로 설정합니다.
|
||||
|
||||
이 위치에 우리는 `requirements.txt` 파일을 생성할 것입니다.
|
||||
|
||||
3. 이 도커 스테이지에서 Poetry를 설치합니다.
|
||||
|
||||
4. 파일 `pyproject.toml`와 `poetry.lock`를 `/tmp` 디렉터리로 복사합니다.
|
||||
|
||||
`./poetry.lock*` (`*`로 끝나는) 파일을 사용하기 때문에, 파일이 아직 사용가능하지 않더라도 고장나지 않을 것입니다.
|
||||
|
||||
5. `requirements.txt` 파일을 생성합니다.
|
||||
|
||||
6. 이것이 마지막 스테이지로, 여기에 위치한 모든 것이 마지막 컨테이너 이미지에 포함될 것입니다.
|
||||
|
||||
7. 현재의 워킹 디렉터리를 `/code`로 설정합니다.
|
||||
|
||||
8. 파일 `requirements.txt`를 `/code` 디렉터리로 복사합니다.
|
||||
|
||||
이 파일은 오직 이전의 도커 스테이지에만 존재하며, 때문에 복사하기 위해서 `--from-requirements-stage` 옵션이 필요합니다.
|
||||
|
||||
9. 생성된 `requirements.txt` 파일에 패키지 의존성을 설치합니다.
|
||||
|
||||
10. `app` 디렉터리를 `/code` 디렉터리로 복사합니다.
|
||||
|
||||
11. `uvicorn` 커맨드를 실행하여, `app.main`에서 불러온 `app` 객체를 사용하도록 합니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
버블 숫자를 클릭해 각 줄이 하는 일을 알아볼 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
**도커 스테이지**란 `Dockefile`의 일부로서 나중에 사용하기 위한 파일들을 생성하기 위한 **일시적인 컨테이너 이미지**로 작동합니다.
|
||||
|
||||
첫 스테이지는 오직 **Poetry를 설치**하고 Poetry의 `pyproject.toml` 파일로부터 프로젝트 의존성을 위한 **`requirements.txt`를 생성**하기 위해 사용됩니다.
|
||||
|
||||
이 `requirements.txt` 파일은 **다음 스테이지**에서 `pip`로 사용될 것입니다.
|
||||
|
||||
마지막 컨테이너 이미지에는 **오직 마지막 스테이지만** 보존됩니다. 이전 스테이지(들)은 버려집니다.
|
||||
|
||||
Poetry를 사용할 때 **도커 멀티-스테이지 빌드**를 사용하는 것이 좋은데, 여러분들의 프로젝트 의존성을 설치하기 위해 마지막 컨테이너 이미지에 **오직** `requirements.txt` 파일만 필요하지, Poetry와 그 의존성은 있을 필요가 없기 때문입니다.
|
||||
|
||||
이 다음 (또한 마지막) 스테이지에서 여러분들은 이전에 설명된 것과 비슷한 방식으로 방식으로 이미지를 빌드할 수 있습니다.
|
||||
|
||||
### TLS 종료 프록시의 배후 - Poetry
|
||||
|
||||
이전에 언급한 것과 같이, 만약 여러분이 컨테이너를 Nginx 또는 Traefik과 같은 TLS 종료 프록시 (로드 밸런서) 뒤에서 실행하고 있다면, 커맨드에 `--proxy-headers` 옵션을 추가합니다:
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
컨테이너 시스템(예를 들어 **도커**나 **쿠버네티스**)을 사용하여 모든 **배포 개념**을 다루는 것은 꽤 간단합니다:
|
||||
|
||||
* HTTPS
|
||||
* 구동하기
|
||||
* 재시작
|
||||
* 복제 (실행 중인 프로세스 개수)
|
||||
* 메모리
|
||||
* 시작하기 전 단계들
|
||||
|
||||
대부분의 경우에서 여러분은 어떤 베이스 이미지도 사용하지 않고 공식 파이썬 도커 이미지에 기반해 **처음부터 컨테이너 이미지를 빌드**할 것입니다.
|
||||
|
||||
`Dockerfile`에 있는 지시 사항을 **순서대로** 다루고 **도커 캐시**를 사용하는 것으로 여러분은 **빌드 시간을 최소화**할 수 있으며, 이로써 생산성을 최대화할 수 있습니다 (그리고 지루함을 피할 수 있죠) 😎
|
||||
|
||||
특별한 경우에는, FastAPI를 위한 공식 도커 이미지를 사용할 수도 있습니다. 🤓
|
||||
@@ -0,0 +1,21 @@
|
||||
# 배포하기 - 들어가면서
|
||||
|
||||
**FastAPI**을 배포하는 것은 비교적 쉽습니다.
|
||||
|
||||
## 배포의 의미
|
||||
|
||||
**배포**란 애플리케이션을 **사용자가 사용**할 수 있도록 하는 데 필요한 단계를 수행하는 것을 의미합니다.
|
||||
|
||||
**웹 API**의 경우, 일반적으로 **사용자**가 중단이나 오류 없이 애플리케이션에 효율적으로 **접근**할 수 있도록 좋은 성능, 안정성 등을 제공하는 **서버 프로그램과** 함께 **원격 시스템**에 이를 설치하는 작업을 의미합니다.
|
||||
|
||||
이는 지속적으로 코드를 변경하고, 지우고, 수정하고, 개발 서버를 중지했다가 다시 시작하는 등의 **개발** 단계와 대조됩니다.
|
||||
|
||||
## 배포 전략
|
||||
|
||||
사용하는 도구나 특정 사례에 따라 여러 가지 방법이 있습니다.
|
||||
|
||||
배포도구들을 사용하여 직접 **서버에 배포**하거나, 배포작업의 일부를 수행하는 **클라우드 서비스** 또는 다른 방법을 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI** 애플리케이션을 배포할 때 선택할 수 있는 몇 가지 주요 방법을 보여 드리겠습니다 (대부분 다른 유형의 웹 애플리케이션에도 적용됩니다).
|
||||
|
||||
다음 차례에 자세한 내용과 이를 위한 몇 가지 기술을 볼 수 있습니다. ✨
|
||||
@@ -0,0 +1,183 @@
|
||||
# 서버 워커 - 구니콘과 유비콘
|
||||
|
||||
전단계에서의 배포 개념들을 다시 확인해보겠습니다:
|
||||
|
||||
* 보안 - HTTPS
|
||||
* 서버 시작과 동시에 실행하기
|
||||
* 재시작
|
||||
* **복제본 (실행 중인 프로세스의 숫자)**
|
||||
* 메모리
|
||||
* 시작하기 전의 여러 단계들
|
||||
|
||||
지금까지 문서의 모든 튜토리얼을 참고하여 **단일 프로세스**로 Uvicorn과 같은 **서버 프로그램**을 실행했을 것입니다.
|
||||
|
||||
애플리케이션을 배포할 때 **다중 코어**를 활용하고 더 많은 요청을 처리할 수 있도록 **프로세스 복제본**이 필요합니다.
|
||||
|
||||
전 과정이었던 [배포 개념들](concepts.md){.internal-link target=_blank}에서 본 것처럼 여러가지 방법이 존재합니다.
|
||||
|
||||
지금부터 <a href="https://gunicorn.org/" class="external-link" target="_blank">**구니콘**</a>을 **유비콘 워커 프로세스**와 함께 사용하는 방법을 알려드리겠습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
만약 도커와 쿠버네티스 같은 컨테이너를 사용하고 있다면 다음 챕터 [FastAPI와 컨테이너 - 도커](docker.md){.internal-link target=_blank}에서 더 많은 정보를 얻을 수 있습니다.
|
||||
|
||||
특히, 쿠버네티스에서 실행할 때는 구니콘을 사용하지 않고 대신 컨테이너당 하나의 유비콘 프로세스를 실행하는 것이 좋습니다. 이 장의 뒷부분에서 설명하겠습니다.
|
||||
|
||||
///
|
||||
|
||||
## 구니콘과 유비콘 워커
|
||||
|
||||
**Gunicorn**은 **WSGI 표준**을 주로 사용하는 애플리케이션 서버입니다. 이것은 구니콘이 플라스크와 쟝고와 같은 애플리케이션을 제공할 수 있다는 것을 의미합니다. 구니콘 자체는 최신 **<a href="https://asgi.readthedocs.io/en/latest/" class="external-link" target="_blank">ASGI 표준</a>**을 사용하기 때문에 FastAPI와 호환되지 않습니다.
|
||||
|
||||
하지만 구니콘은 **프로세스 관리자**역할을 하고 사용자에게 특정 **워커 프로세스 클래스**를 알려줍니다. 그런 다음 구니콘은 해당 클래스를 사용하여 하나 이상의 **워커 프로세스**를 시작합니다.
|
||||
|
||||
그리고 **유비콘**은 **구니콘과 호환되는 워커 클래스**가 있습니다.
|
||||
|
||||
이 조합을 사용하여 구니콘은 **프로세스 관리자** 역할을 하며 **포트**와 **IP**를 관찰하고, **유비콘 클래스**를 실행하는 워커 프로세스로 통신 정보를 **전송**합니다.
|
||||
|
||||
그리고 나서 구니콘과 호환되는 **유비콘 워커** 클래스는 구니콘이 보낸 데이터를 FastAPI에서 사용하기 위한 ASGI 표준으로 변환하는 일을 담당합니다.
|
||||
|
||||
## 구니콘과 유비콘 설치하기
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "uvicorn[standard]" gunicorn
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
이 명령어는 유비콘 `standard` 추가 패키지(좋은 성능을 위한)와 구니콘을 설치할 것입니다.
|
||||
|
||||
## 구니콘을 유비콘 워커와 함께 실행하기
|
||||
|
||||
설치 후 구니콘 실행하기:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80
|
||||
|
||||
[19499] [INFO] Starting gunicorn 20.1.0
|
||||
[19499] [INFO] Listening at: http://0.0.0.0:80 (19499)
|
||||
[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker
|
||||
[19511] [INFO] Booting worker with pid: 19511
|
||||
[19513] [INFO] Booting worker with pid: 19513
|
||||
[19514] [INFO] Booting worker with pid: 19514
|
||||
[19515] [INFO] Booting worker with pid: 19515
|
||||
[19511] [INFO] Started server process [19511]
|
||||
[19511] [INFO] Waiting for application startup.
|
||||
[19511] [INFO] Application startup complete.
|
||||
[19513] [INFO] Started server process [19513]
|
||||
[19513] [INFO] Waiting for application startup.
|
||||
[19513] [INFO] Application startup complete.
|
||||
[19514] [INFO] Started server process [19514]
|
||||
[19514] [INFO] Waiting for application startup.
|
||||
[19514] [INFO] Application startup complete.
|
||||
[19515] [INFO] Started server process [19515]
|
||||
[19515] [INFO] Waiting for application startup.
|
||||
[19515] [INFO] Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
각 옵션이 무엇을 의미하는지 살펴봅시다:
|
||||
|
||||
* 이것은 유비콘과 똑같은 문법입니다. `main`은 파이썬 모듈 네임 "`main`"을 의미하므로 `main.py`파일을 뜻합니다. 그리고 `app`은 **FastAPI** 어플리케이션이 들어 있는 변수의 이름입니다.
|
||||
* `main:app`이 파이썬의 `import` 문법과 흡사한 면이 있다는 걸 알 수 있습니다:
|
||||
|
||||
```Python
|
||||
from main import app
|
||||
```
|
||||
|
||||
* 곧, `main:app`안에 있는 콜론의 의미는 파이썬에서 `from main import app`에서의 `import`와 같습니다.
|
||||
* `--workers`: 사용할 워커 프로세스의 개수이며 숫자만큼의 유비콘 워커를 실행합니다. 이 예제에서는 4개의 워커를 실행합니다.
|
||||
* `--worker-class`: 워커 프로세스에서 사용하기 위한 구니콘과 호환되는 워커클래스.
|
||||
* 이런식으로 구니콘이 import하여 사용할 수 있는 클래스를 전달해줍니다:
|
||||
|
||||
```Python
|
||||
import uvicorn.workers.UvicornWorker
|
||||
```
|
||||
|
||||
* `--bind`: 구니콘이 관찰할 IP와 포트를 의미합니다. 콜론 (`:`)을 사용하여 IP와 포트를 구분합니다.
|
||||
* 만약에 `--bind 0.0.0.0:80` (구니콘 옵션) 대신 유비콘을 직접 실행하고 싶다면 `--host 0.0.0.0`과 `--port 80`을 사용해야 합니다.
|
||||
|
||||
출력에서 각 프로세스에 대한 **PID** (process ID)를 확인할 수 있습니다. (단순한 숫자입니다)
|
||||
|
||||
출력 내용:
|
||||
|
||||
* 구니콘 **프로세스 매니저**는 PID `19499`로 실행됩니다. (직접 실행할 경우 숫자가 다를 수 있습니다)
|
||||
* 다음으로 `Listening at: http://0.0.0.0:80`을 시작합니다.
|
||||
* 그런 다음 사용해야할 `uvicorn.workers.UvicornWorker`의 워커클래스를 탐지합니다.
|
||||
* 그리고 PID `19511`, `19513`, `19514`, 그리고 `19515`를 가진 **4개의 워커**를 실행합니다.
|
||||
|
||||
|
||||
또한 구니콘은 워커의 수를 유지하기 위해 **죽은 프로세스**를 관리하고 **재시작**하는 작업을 책임집니다. 이것은 이번 장 상단 목록의 **재시작** 개념을 부분적으로 도와주는 것입니다.
|
||||
|
||||
그럼에도 불구하고 필요할 경우 외부에서 **구니콘을 재시작**하고, 혹은 **서버를 시작할 때 실행**할 수 있도록 하고 싶어할 것입니다.
|
||||
|
||||
## 유비콘과 워커
|
||||
|
||||
유비콘은 몇 개의 **워커 프로세스**와 함께 실행할 수 있는 선택지가 있습니다.
|
||||
|
||||
그럼에도 불구하고, 유비콘은 워커 프로세스를 다루는 데에 있어서 구니콘보다 더 제한적입니다. 따라서 이 수준(파이썬 수준)의 프로세스 관리자를 사용하려면 구니콘을 프로세스 관리자로 사용하는 것이 좋습니다.
|
||||
|
||||
보통 이렇게 실행할 수 있습니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
|
||||
<font color="#A6E22E">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8080</b> (Press CTRL+C to quit)
|
||||
<font color="#A6E22E">INFO</font>: Started parent process [<font color="#A1EFE4"><b>27365</b></font>]
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27368</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27369</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27370</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27367</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
새로운 옵션인 `--workers`은 유비콘에게 4개의 워커 프로세스를 사용한다고 알려줍니다.
|
||||
|
||||
각 프로세스의 **PID**를 확인할 수 있습니다. `27365`는 상위 프로세스(**프로세스 매니저**), 그리고 각각의 워커프로세스는 `27368`, `27369`, `27370`, 그리고 `27367`입니다.
|
||||
|
||||
## 배포 개념들
|
||||
|
||||
여기에서는 **유비콘 워커 프로세스**를 관리하는 **구니콘**(또는 유비콘)을 사용하여 애플리케이션을 **병렬화**하고, CPU **멀티 코어**의 장점을 활용하고, **더 많은 요청**을 처리할 수 있는 방법을 살펴보았습니다.
|
||||
|
||||
워커를 사용하는 것은 배포 개념 목록에서 주로 **복제본** 부분과 **재시작**에 약간 도움이 되지만 다른 배포 개념들도 다루어야 합니다:
|
||||
|
||||
* **보안 - HTTPS**
|
||||
* **서버 시작과 동시에 실행하기**
|
||||
* ***재시작***
|
||||
* 복제본 (실행 중인 프로세스의 숫자)
|
||||
* **메모리**
|
||||
* **시작하기 전의 여러 단계들**
|
||||
|
||||
|
||||
## 컨테이너와 도커
|
||||
|
||||
다음 장인 [FastAPI와 컨테이너 - 도커](docker.md){.internal-link target=_blank}에서 다른 **배포 개념들**을 다루는 전략들을 알려드리겠습니다.
|
||||
|
||||
또한 간단한 케이스에서 사용할 수 있는, **구니콘과 유비콘 워커**가 포함돼 있는 **공식 도커 이미지**와 함께 몇 가지 기본 구성을 보여드리겠습니다.
|
||||
|
||||
그리고 단일 유비콘 프로세스(구니콘 없이)를 실행할 수 있도록 **사용자 자신의 이미지를 처음부터 구축**하는 방법도 보여드리겠습니다. 이는 간단한 과정이며, **쿠버네티스**와 같은 분산 컨테이너 관리 시스템을 사용할 때 수행할 작업입니다.
|
||||
|
||||
## 요약
|
||||
|
||||
당신은 **구니콘**(또는 유비콘)을 유비콘 워커와 함께 프로세스 관리자로 사용하여 **멀티-코어 CPU**를 활용하는 **멀티 프로세스를 병렬로 실행**할 수 있습니다.
|
||||
|
||||
다른 배포 개념을 직접 다루면서 **자신만의 배포 시스템**을 구성하는 경우 이러한 도구와 개념들을 활용할 수 있습니다.
|
||||
|
||||
다음 장에서 컨테이너(예: 도커 및 쿠버네티스)와 함께하는 **FastAPI**에 대해 배워보세요. 이러한 툴에는 다른 **배포 개념**들을 간단히 해결할 수 있는 방법이 있습니다. ✨
|
||||
@@ -0,0 +1,94 @@
|
||||
# FastAPI 버전들에 대하여
|
||||
|
||||
**FastAPI** 는 이미 많은 응용 프로그램과 시스템들을 만드는데 사용되고 있습니다. 그리고 100%의 테스트 정확성을 가지고 있습니다. 하지만 이것은 아직까지도 빠르게 발전하고 있습니다.
|
||||
|
||||
새로운 특징들이 빈번하게 추가되고, 오류들이 지속적으로 수정되고 있습니다. 그리고 코드가 계속적으로 향상되고 있습니다.
|
||||
|
||||
이것이 아직도 최신 버전이 `0.x.x`인 이유입니다. 이것은 각각의 버전들이 잠재적으로 변할 수 있다는 것을 보여줍니다. 이는 <a href="https://semver.org/" class="external-link" target="_blank">유의적 버전</a> 관습을 따릅니다.
|
||||
|
||||
지금 바로 **FastAPI**로 응용 프로그램을 만들 수 있습니다. 이때 (아마 지금까지 그래 왔던 것처럼), 사용하는 버전이 코드와 잘 맞는지 확인해야합니다.
|
||||
|
||||
## `fastapi` 버전을 표시
|
||||
|
||||
가장 먼저 해야할 것은 응용 프로그램이 잘 작동하는 가장 최신의 구체적인 **FastAPI** 버전을 표시하는 것입니다.
|
||||
|
||||
예를 들어, 응용 프로그램에 `0.45.0` 버전을 사용했다고 가정합니다.
|
||||
|
||||
만약에 `requirements.txt` 파일을 사용했다면, 다음과 같이 버전을 명세할 수 있습니다:
|
||||
|
||||
```txt
|
||||
fastapi==0.45.0
|
||||
```
|
||||
|
||||
이것은 `0.45.0` 버전을 사용했다는 것을 의미합니다.
|
||||
|
||||
또는 다음과 같이 표시할 수 있습니다:
|
||||
|
||||
```txt
|
||||
fastapi>=0.45.0,<0.46.0
|
||||
```
|
||||
|
||||
이것은 `0.45.0` 버전과 같거나 높으면서 `0.46.0` 버전 보다는 낮은 버전을 사용했다는 것을 의미합니다. 예를 들어, `0.45.2` 버전과 같은 경우는 해당 조건을 만족합니다.
|
||||
|
||||
만약에 Poetry, Pipenv, 또는 그밖의 다양한 설치 도구를 사용한다면, 패키지에 구체적인 버전을 정의할 수 있는 방법을 가지고 있을 것입니다.
|
||||
|
||||
## 이용가능한 버전들
|
||||
|
||||
[Release Notes](../release-notes.md){.internal-link target=_blank}를 통해 사용할 수 있는 버전들을 확인할 수 있습니다.(예를 들어, 가장 최신의 버전을 확인할 수 있습니다.)
|
||||
|
||||
|
||||
## 버전들에 대해
|
||||
|
||||
유의적 버전 관습을 따라서, `1.0.0` 이하의 모든 버전들은 잠재적으로 급변할 수 있습니다.
|
||||
|
||||
FastAPI는 오류를 수정하고, 일반적인 변경사항을 위해 "패치"버전의 관습을 따릅니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
여기서 말하는 "패치"란 버전의 마지막 숫자로, 예를 들어 `0.2.3` 버전에서 "패치"는 `3`을 의미합니다.
|
||||
|
||||
///
|
||||
|
||||
따라서 다음과 같이 버전을 표시할 수 있습니다:
|
||||
|
||||
```txt
|
||||
fastapi>=0.45.0,<0.46.0
|
||||
```
|
||||
|
||||
수정된 사항과 새로운 요소들이 "마이너" 버전에 추가되었습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
"마이너"란 버전 넘버의 가운데 숫자로, 예를 들어서 `0.2.3`의 "마이너" 버전은 `2`입니다.
|
||||
|
||||
///
|
||||
|
||||
## FastAPI 버전의 업그레이드
|
||||
|
||||
응용 프로그램을 검사해야합니다.
|
||||
|
||||
(Starlette 덕분에), **FastAPI** 를 이용하여 굉장히 쉽게 할 수 있습니다. [Testing](../tutorial/testing.md){.internal-link target=_blank}문서를 확인해 보십시오:
|
||||
|
||||
검사를 해보고 난 후에, **FastAPI** 버전을 더 최신으로 업그레이드 할 수 있습니다. 그리고 코드들이 테스트에 정상적으로 작동하는지 확인을 해야합니다.
|
||||
|
||||
만약에 모든 것이 정상 작동하거나 필요한 부분을 변경하고, 모든 검사를 통과한다면, 새로운 버전의 `fastapi`를 표시할 수 있습니다.
|
||||
|
||||
## Starlette에 대해
|
||||
|
||||
`starlette`의 버전은 표시할 수 없습니다.
|
||||
|
||||
서로다른 버전의 **FastAPI**가 구체적이고 새로운 버전의 Starlette을 사용할 것입니다.
|
||||
|
||||
그러므로 **FastAPI**가 알맞은 Starlette 버전을 사용하도록 하십시오.
|
||||
|
||||
## Pydantic에 대해
|
||||
|
||||
Pydantic은 **FastAPI** 를 위한 검사를 포함하고 있습니다. 따라서, 새로운 버전의 Pydantic(`1.0.0`이상)은 항상 FastAPI와 호환됩니다.
|
||||
|
||||
작업을 하고 있는 `1.0.0` 이상의 모든 버전과 `2.0.0` 이하의 Pydantic 버전을 표시할 수 있습니다.
|
||||
|
||||
예를 들어 다음과 같습니다:
|
||||
|
||||
```txt
|
||||
pydantic>=1.2.0,<2.0.0
|
||||
```
|
||||
@@ -0,0 +1,298 @@
|
||||
# 환경 변수
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
만약 "환경 변수"가 무엇이고, 어떻게 사용하는지 알고 계시다면, 이 챕터를 스킵하셔도 좋습니다.
|
||||
|
||||
///
|
||||
|
||||
환경 변수는 파이썬 코드의 **바깥**인, **운영 체제**에 존재하는 변수입니다. 파이썬 코드나 다른 프로그램에서 읽을 수 있습니다.
|
||||
|
||||
환경 변수는 애플리케이션 **설정**을 처리하거나, 파이썬의 **설치** 과정의 일부로 유용합니다.
|
||||
|
||||
## 환경 변수를 만들고 사용하기
|
||||
|
||||
파이썬 없이도, **셸 (터미널)** 에서 환경 변수를 **생성** 하고 사용할 수 있습니다.
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// You could create an env var MY_NAME with
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// Then you could use it with other programs, like
|
||||
$ echo "Hello $MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Create an env var MY_NAME
|
||||
$ $Env:MY_NAME = "Wade Wilson"
|
||||
|
||||
// Use it with other programs, like
|
||||
$ echo "Hello $Env:MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
## 파이썬에서 환경 변수 읽기
|
||||
|
||||
파이썬 **바깥**인 터미널에서(다른 도구로도 가능) 환경 변수를 생성도 할 수도 있고, 이를 **파이썬에서 읽을 수 있습니다.**
|
||||
|
||||
예를 들어 다음과 같은 `main.py` 파일이 있다고 합시다:
|
||||
|
||||
```Python hl_lines="3"
|
||||
import os
|
||||
|
||||
name = os.getenv("MY_NAME", "World")
|
||||
print(f"Hello {name} from Python")
|
||||
```
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 의 두 번째 인자는 반환할 기본값입니다.
|
||||
|
||||
여기서는 `"World"`를 넣었기에 기본값으로써 사용됩니다. 넣지 않으면 `None` 이 기본값으로 사용됩니다.
|
||||
|
||||
///
|
||||
|
||||
그러면 해당 파이썬 프로그램을 다음과 같이 호출할 수 있습니다:
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<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
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<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
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
환경변수는 코드 바깥에서 설정될 수 있지만, 코드에서 읽을 수 있고, 나머지 파일과 함께 저장(`git`에 커밋)할 필요가 없으므로, 구성이나 **설정** 에 사용하는 것이 일반적입니다.
|
||||
|
||||
**특정 프로그램 호출**에 대해서만 사용할 수 있는 환경 변수를 만들 수도 있습니다. 해당 프로그램에서만 사용할 수 있고, 해당 프로그램이 실행되는 동안만 사용할 수 있습니다.
|
||||
|
||||
그렇게 하려면 프로그램 바로 앞, 같은 줄에 환경 변수를 만들어야 합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Create an env var MY_NAME in line for this program call
|
||||
$ MY_NAME="Wade Wilson" python main.py
|
||||
|
||||
// Now it can read the environment variable
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
|
||||
// The env var no longer exists afterwards
|
||||
$ python main.py
|
||||
|
||||
Hello World from Python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
<a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a> 에서 좀 더 자세히 알아볼 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 타입과 검증
|
||||
|
||||
이 환경변수들은 오직 **텍스트 문자열**로만 처리할 수 있습니다. 텍스트 문자열은 파이썬 외부에 있으며 다른 프로그램 및 나머지 시스템(Linux, Windows, macOS 등 다른 운영 체제)과 호환되어야 합니다.
|
||||
|
||||
즉, 파이썬에서 환경 변수로부터 읽은 **모든 값**은 **`str`**이 되고, 다른 타입으로의 변환이나 검증은 코드에서 수행해야 합니다.
|
||||
|
||||
**애플리케이션 설정**을 처리하기 위한 환경 변수 사용에 대한 자세한 내용은 [고급 사용자 가이드 - 설정 및 환경 변수](./advanced/settings.md){.internal-link target=\_blank} 에서 확인할 수 있습니다.
|
||||
|
||||
## `PATH` 환경 변수
|
||||
|
||||
**`PATH`**라고 불리는, **특별한** 환경변수가 있습니다. 운영체제(Linux, Windows, macOS 등)에서 실행할 프로그램을 찾기위해 사용됩니다.
|
||||
|
||||
변수 `PATH`의 값은 Linux와 macOS에서는 콜론 `:`, Windows에서는 세미콜론 `;`으로 구분된 디렉토리로 구성된 긴 문자열입니다.
|
||||
|
||||
예를 들어, `PATH` 환경 변수는 다음과 같습니다:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
이는 시스템이 다음 디렉토리에서 프로그램을 찾아야 함을 의미합니다:
|
||||
|
||||
- `/usr/local/bin`
|
||||
- `/usr/bin`
|
||||
- `/bin`
|
||||
- `/usr/sbin`
|
||||
- `/sbin`
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
|
||||
```
|
||||
|
||||
이는 시스템이 다음 디렉토리에서 프로그램을 찾아야 함을 의미합니다:
|
||||
|
||||
- `C:\Program Files\Python312\Scripts`
|
||||
- `C:\Program Files\Python312`
|
||||
- `C:\Windows\System32`
|
||||
|
||||
////
|
||||
|
||||
터미널에 **명령어**를 입력하면 운영 체제는 `PATH` 환경 변수에 나열된 **각 디렉토리**에서 프로그램을 **찾습니다.**
|
||||
|
||||
예를 들어 터미널에 `python`을 입력하면 운영 체제는 해당 목록의 **첫 번째 디렉토리**에서 `python`이라는 프로그램을 찾습니다.
|
||||
|
||||
찾으면 **사용합니다**. 그렇지 않으면 **다른 디렉토리**에서 계속 찾습니다.
|
||||
|
||||
### 파이썬 설치와 `PATH` 업데이트
|
||||
|
||||
파이썬을 설치할 때, 아마 `PATH` 환경 변수를 업데이트 할 것이냐고 물어봤을 겁니다.
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
파이썬을 설치하고 그것이 `/opt/custompython/bin` 디렉토리에 있다고 가정해 보겠습니다.
|
||||
|
||||
`PATH` 환경 변수를 업데이트하도록 "예"라고 하면 설치 관리자가 `/opt/custompython/bin`을 `PATH` 환경 변수에 추가합니다.
|
||||
|
||||
다음과 같이 보일 수 있습니다:
|
||||
|
||||
```plaintext
|
||||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
|
||||
```
|
||||
|
||||
이렇게 하면 터미널에 `python`을 입력할 때, 시스템이 `/opt/custompython/bin`(마지막 디렉토리)에서 파이썬 프로그램을 찾아 사용합니다.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
파이썬을 설치하고 그것이 `C:\opt\custompython\bin` 디렉토리에 있다고 가정해 보겠습니다.
|
||||
|
||||
`PATH` 환경 변수를 업데이트하도록 "예"라고 하면 설치 관리자가 `C:\opt\custompython\bin`을 `PATH` 환경 변수에 추가합니다.
|
||||
|
||||
```plaintext
|
||||
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
|
||||
```
|
||||
|
||||
이렇게 하면 터미널에 `python`을 입력할 때, 시스템이 `C:\opt\custompython\bin`(마지막 디렉토리)에서 파이썬 프로그램을 찾아 사용합니다.
|
||||
|
||||
////
|
||||
|
||||
그래서, 다음과 같이 입력한다면:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
시스템은 `/opt/custompython/bin`에서 `python` 프로그램을 **찾아** 실행합니다.
|
||||
|
||||
다음과 같이 입력하는 것과 거의 같습니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ /opt/custompython/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
시스템은 `C:\opt\custompython\bin\python`에서 `python` 프로그램을 **찾아** 실행합니다.
|
||||
|
||||
다음과 같이 입력하는 것과 거의 같습니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ C:\opt\custompython\bin\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
이 정보는 [가상 환경](virtual-environments.md){.internal-link target=\_blank} 에 대해 알아볼 때 유용할 것입니다.
|
||||
|
||||
## 결론
|
||||
|
||||
이 문서를 읽고 **환경 변수**가 무엇이고 파이썬에서 어떻게 사용하는지 기본적으로 이해하셨을 겁니다.
|
||||
|
||||
또한 <a href="https://ko.wikipedia.org/wiki/환경_변수" class="external-link" target="_blank">환경 변수에 대한 위키피디아(한국어)</a>에서 이에 대해 자세히 알아볼 수 있습니다.
|
||||
|
||||
많은 경우에서, 환경 변수가 어떻게 유용하고 적용 가능한지 바로 명확하게 알 수는 없습니다. 하지만 개발할 때 다양한 시나리오에서 계속 나타나므로 이에 대해 아는 것이 좋습니다.
|
||||
|
||||
예를 들어, 다음 섹션인 [가상 환경](virtual-environments.md)에서 이 정보가 필요합니다.
|
||||
@@ -0,0 +1,83 @@
|
||||
# FastAPI CLI
|
||||
|
||||
**FastAPI CLI**는 FastAPI 애플리케이션을 실행하고, 프로젝트를 관리하는 등 다양한 작업을 수행할 수 있는 커맨드 라인 프로그램입니다.
|
||||
|
||||
FastAPI를 설치할 때 (예: `pip install "fastapi[standard]"` 명령어를 사용할 경우), `fastapi-cli`라는 패키지가 포함됩니다. 이 패키지는 터미널에서 사용할 수 있는 `fastapi` 명령어를 제공합니다.
|
||||
|
||||
개발용으로 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="#8AE234"><b>Python module file</b></font> ─╮
|
||||
│ │
|
||||
│ 🐍 main.py │
|
||||
│ │
|
||||
╰──────────────────────╯
|
||||
|
||||
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
|
||||
<font color="#3465A4">INFO </font> Found importable FastAPI app
|
||||
|
||||
╭─ <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> │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
|
||||
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
|
||||
|
||||
<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>
|
||||
|
||||
<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.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`fastapi`라고 불리는 명령어 프로그램은 **FastAPI CLI**입니다.
|
||||
|
||||
FastAPI CLI는 Python 프로그램의 경로(예: `main.py`)를 인수로 받아, `FastAPI` 인스턴스(일반적으로 `app`으로 명명)를 자동으로 감지하고 올바른 임포트 과정을 결정한 후 이를 실행합니다.
|
||||
|
||||
프로덕션 환경에서는 `fastapi run` 명령어를 사용합니다. 🚀
|
||||
|
||||
내부적으로, **FastAPI CLI**는 고성능의, 프로덕션에 적합한, ASGI 서버인 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>을 사용합니다. 😎
|
||||
|
||||
## `fastapi dev`
|
||||
|
||||
`fastapi dev` 명령을 실행하면 개발 모드가 시작됩니다.
|
||||
|
||||
기본적으로 **자동 재시작(auto-reload)** 기능이 활성화되어, 코드에 변경이 생기면 서버를 자동으로 다시 시작합니다. 하지만 이 기능은 리소스를 많이 사용하며, 비활성화했을 때보다 안정성이 떨어질 수 있습니다. 따라서 개발 환경에서만 사용하는 것이 좋습니다. 또한, 서버는 컴퓨터가 자체적으로 통신할 수 있는 IP 주소(`localhost`)인 `127.0.0.1`에서 연결을 대기합니다.
|
||||
|
||||
## `fastapi run`
|
||||
|
||||
`fastapi run` 명령을 실행하면 기본적으로 프로덕션 모드로 FastAPI가 시작됩니다.
|
||||
|
||||
기본적으로 **자동 재시작(auto-reload)** 기능이 비활성화되어 있습니다. 또한, 사용 가능한 모든 IP 주소인 `0.0.0.0`에서 연결을 대기하므로 해당 컴퓨터와 통신할 수 있는 모든 사람이 공개적으로 액세스할 수 있습니다. 이는 일반적으로 컨테이너와 같은 프로덕션 환경에서 실행하는 방법입니다.
|
||||
|
||||
애플리케이션을 배포하는 방식에 따라 다르지만, 대부분 "종료 프록시(termination proxy)"를 활용해 HTTPS를 처리하는 것이 좋습니다. 배포 서비스 제공자가 이 작업을 대신 처리해줄 수도 있고, 직접 설정해야 할 수도 있습니다.
|
||||
|
||||
/// tip
|
||||
|
||||
자세한 내용은 [deployment documentation](deployment/index.md){.internal-link target=\_blank}에서 확인할 수 있습니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,201 @@
|
||||
# 기능
|
||||
|
||||
## FastAPI의 기능
|
||||
|
||||
**FastAPI**는 다음과 같은 기능을 제공합니다:
|
||||
|
||||
### 개방형 표준을 기반으로
|
||||
|
||||
* <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를 기반으로 하고 있습니다)를 사용한 자동 데이터 모델 문서화.
|
||||
* 단순히 떠올려서 덧붙인 기능이 아닙니다. 세심한 검토를 거친 후, 이러한 표준을 기반으로 설계되었습니다.
|
||||
* 이는 또한 다양한 언어로 자동적인 **클라이언트 코드 생성**을 사용할 수 있게 지원합니다.
|
||||
|
||||
### 문서 자동화
|
||||
|
||||
대화형 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/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>을 이용해 API 문서화를 대체할 수 있습니다.
|
||||
|
||||

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

|
||||
|
||||
* <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>에서:
|
||||
|
||||

|
||||
|
||||
여러분이 이전에 불가능하다고 고려했던 코드도 완성할 수 있을 겁니다. 예를 들어, 요청에서 전달되는 (중첩될 수도 있는)JSON 본문 내부에 있는 `price` 키입니다.
|
||||
|
||||
잘못된 키 이름을 적을 일도, 문서를 왔다 갔다할 일도 없으며, 혹은 마지막으로 `username` 또는 `user_name`을 사용했는지 찾기 위해 위 아래로 스크롤할 일도 없습니다.
|
||||
|
||||
### 토막 정보
|
||||
|
||||
어느 곳에서나 선택적 구성이 가능한 모든 것에 합리적인 기본값이 설정되어 있습니다. 모든 매개변수는 여러분이 필요하거나, 원하는 API를 정의하기 위해 미세하게 조정할 수 있습니다.
|
||||
|
||||
하지만 기본적으로 모든 것이 "그냥 작동합니다".
|
||||
|
||||
### 검증
|
||||
|
||||
* 다음을 포함한, 대부분의 (혹은 모든?) 파이썬 **데이터 타입** 검증할 수 있습니다:
|
||||
* JSON 객체 (`dict`).
|
||||
* 아이템 타입을 정의하는 JSON 배열 (`list`).
|
||||
* 최소 길이와 최대 길이를 정의하는 문자열 (`str`) 필드.
|
||||
* 최솟값과 최댓값을 가지는 숫자 (`int`, `float`), 그 외.
|
||||
|
||||
* 다음과 같이 더욱 이색적인 타입에 대해 검증할 수 있습니다:
|
||||
* URL.
|
||||
* 이메일.
|
||||
* UUID.
|
||||
* ...다른 것들.
|
||||
|
||||
모든 검증은 견고하면서 잘 확립된 **Pydantic**에 의해 처리됩니다.
|
||||
|
||||
### 보안과 인증
|
||||
|
||||
보안과 인증이 통합되어 있습니다. 데이터베이스나 데이터 모델과의 타협없이 사용할 수 있습니다.
|
||||
|
||||
다음을 포함하는, 모든 보안 스키마가 OpenAPI에 정의되어 있습니다.
|
||||
|
||||
* HTTP Basic.
|
||||
* **OAuth2** (**JWT tokens** 또한 포함). [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=\_blank}에 있는 자습서를 확인해 보세요.
|
||||
* 다음에 들어 있는 API 키:
|
||||
* 헤더.
|
||||
* 매개변수.
|
||||
* 쿠키 및 그 외.
|
||||
|
||||
추가적으로 (**세션 쿠키**를 포함한) 모든 보안 기능은 Starlette에 있습니다.
|
||||
|
||||
모두 재사용할 수 있는 도구와 컴포넌트로 만들어져 있어 여러분의 시스템, 데이터 저장소, 관계형 및 NoSQL 데이터베이스 등과 쉽게 통합할 수 있습니다.
|
||||
|
||||
### 의존성 주입
|
||||
|
||||
FastAPI는 사용하기 매우 간편하지만, 엄청난 <abbr title='"컴포넌트", "자원", "서비스", "제공자"로도 알려진'><strong>의존성 주입</strong></abbr>시스템을 포함하고 있습니다.
|
||||
|
||||
* 의존성은 의존성을 가질수도 있어, 이를 통해 의존성의 계층이나 **의존성의 "그래프"**를 형성합니다.
|
||||
* 모든 것이 프레임워크에 의해 **자동적으로 처리됩니다**.
|
||||
* 모든 의존성은 요청에서 데이터를 요구하여 자동 문서화와 **경로 작동 제약을 강화할 수 있습니다**.
|
||||
* 의존성에서 정의된 _경로 작동_ 매개변수에 대해서도 **자동 검증**이 이루어 집니다.
|
||||
* 복잡한 사용자의 인증 시스템, **데이터베이스 연결**, 등등을 지원합니다.
|
||||
* 데이터베이스, 프론트엔드 등과 관련되어 **타협하지 않아도 됩니다**. 하지만 그 모든 것과 쉽게 통합이 가능합니다.
|
||||
|
||||
### 제한 없는 "플러그인"
|
||||
|
||||
또는 다른 방법으로, 그것들을 사용할 필요 없이 필요한 코드만 임포트할 수 있습니다.
|
||||
|
||||
어느 통합도 (의존성과 함께) 사용하기 쉽게 설계되어 있어, *경로 작동*에 사용된 것과 동일한 구조와 문법을 사용하여 2줄의 코드로 여러분의 어플리케이션에 사용할 "플러그인"을 만들 수 있습니다.
|
||||
|
||||
### 테스트 결과
|
||||
|
||||
* 100% <abbr title="자동적으로 테스트된 코드의 양">테스트 범위</abbr>.
|
||||
* 100% <abbr title="파이썬의 타입 어노테이션, 이를 통해 여러분의 편집기와 외부 도구는 여러분에게 더 나은 지원을 할 수 있습니다">타입이 명시된</abbr> 코드 베이스.
|
||||
* 상용 어플리케이션에서의 사용.
|
||||
|
||||
## Starlette 기능
|
||||
|
||||
**FastAPI**는 <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a>를 기반으로 구축되었으며, 이와 완전히 호환됩니다. 따라서, 여러분이 보유하고 있는 어떤 추가적인 Starlette 코드도 작동할 것입니다.
|
||||
|
||||
`FastAPI`는 실제로 `Starlette`의 하위 클래스입니다. 그래서, 여러분이 이미 Starlette을 알고 있거나 사용하고 있으면, 대부분의 기능이 같은 방식으로 작동할 것입니다.
|
||||
|
||||
**FastAPI**를 사용하면 여러분은 **Starlette**의 기능 대부분을 얻게 될 것입니다(FastAPI가 단순히 Starlette를 강화했기 때문입니다):
|
||||
|
||||
* 아주 인상적인 성능. 이는 <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">**NodeJS**와 **Go**와 동등하게 사용 가능한 가장 빠른 파이썬 프레임워크 중 하나입니다</a>.
|
||||
* **WebSocket** 지원.
|
||||
* 프로세스 내의 백그라운드 작업.
|
||||
* 시작과 종료 이벤트.
|
||||
* HTTPX 기반 테스트 클라이언트.
|
||||
* **CORS**, GZip, 정적 파일, 스트리밍 응답.
|
||||
* **세션과 쿠키** 지원.
|
||||
* 100% 테스트 범위.
|
||||
* 100% 타입이 명시된 코드 베이스.
|
||||
|
||||
## Pydantic 기능
|
||||
|
||||
**FastAPI**는 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a>을 기반으로 하며 Pydantic과 완벽하게 호환됩니다. 그래서 어느 추가적인 Pydantic 코드를 여러분이 가지고 있든 작동할 것입니다.
|
||||
|
||||
Pydantic을 기반으로 하는, 데이터베이스를 위한 <abbr title="Object-Relational Mapper">ORM</abbr>, <abbr title="Object-Document Mapper">ODM</abbr>을 포함한 외부 라이브러리를 포함합니다.
|
||||
|
||||
이는 모든 것이 자동으로 검증되기 때문에, 많은 경우에서 요청을 통해 얻은 동일한 객체를, **직접 데이터베이스로** 넘겨줄 수 있습니다.
|
||||
|
||||
반대로도 마찬가지이며, 많은 경우에서 여러분은 **직접 클라이언트로** 그저 객체를 넘겨줄 수 있습니다.
|
||||
|
||||
**FastAPI**를 사용하면 (모든 데이터 처리를 위해 FastAPI가 Pydantic을 기반으로 하기 있기에) **Pydantic**의 모든 기능을 얻게 됩니다:
|
||||
|
||||
* **어렵지 않은 언어**:
|
||||
* 새로운 스키마 정의 마이크로 언어를 배우지 않아도 됩니다.
|
||||
* 여러분이 파이썬 타입을 안다면, 여러분은 Pydantic을 어떻게 사용하는지 아는 겁니다.
|
||||
* 여러분의 **<abbr title="통합 개발 환경, 코드 편집기와 비슷합니다">IDE</abbr>/<abbr title="코드 에러를 확인하는 프로그램">린터</abbr>/뇌**와 잘 어울립니다:
|
||||
* Pydantic 데이터 구조는 단순 여러분이 정의한 클래스의 인스턴스이기 때문에, 자동 완성, 린팅, mypy 그리고 여러분의 직관까지 여러분의 검증된 데이터와 올바르게 작동합니다.
|
||||
* **복잡한 구조**를 검증합니다:
|
||||
* 계층적인 Pydantic 모델, 파이썬 `typing`의 `List`와 `Dict`, 그 외를 사용합니다.
|
||||
* 그리고 검증자는 복잡한 데이터 스키마를 명확하고 쉽게 정의 및 확인하며 JSON 스키마로 문서화합니다.
|
||||
* 여러분은 깊게 **중첩된 JSON** 객체를 가질 수 있으며, 이 객체 모두 검증하고 설명을 붙일 수 있습니다.
|
||||
* **확장 가능성**:
|
||||
* Pydantic은 사용자 정의 데이터 타입을 정의할 수 있게 하거나, 검증자 데코레이터가 붙은 모델의 메소드를 사용하여 검증을 확장할 수 있습니다.
|
||||
* 100% 테스트 범위.
|
||||
@@ -0,0 +1,269 @@
|
||||
# FastAPI 지원 - 도움 받기
|
||||
|
||||
**FastAPI** 가 마음에 드시나요?
|
||||
|
||||
FastAPI, 다른 사용자, 개발자를 응원하고 싶으신가요?
|
||||
|
||||
혹은 **FastAPI** 에 대해 도움이 필요하신가요?
|
||||
|
||||
아주 간단하게 응원할 수 있습니다 (몇 번의 클릭만으로).
|
||||
|
||||
또한 도움을 받을 수 있는 방법도 몇 가지 있습니다.
|
||||
|
||||
## 뉴스레터 구독
|
||||
|
||||
[**FastAPI and friends** 뉴스레터](newsletter.md){.internal-link target=\_blank}를 구독하여 최신 정보를 유지할 수 있습니다:
|
||||
|
||||
* FastAPI and friends에 대한 뉴스 🚀
|
||||
* 가이드 📝
|
||||
* 기능 ✨
|
||||
* 획기적인 변화 🚨
|
||||
* 팁과 요령 ✅
|
||||
|
||||
## 트위터에서 FastAPI 팔로우하기
|
||||
|
||||
<a href="https://x.com/fastapi" class="external-link" target="_blank">**X (Twitter)**의 @fastapi를 팔로우</a>하여 **FastAPI** 에 대한 최신 뉴스를 얻을 수 있습니다. 🐦
|
||||
|
||||
## 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에서 FastAPI를 "watch"할 수 있습니다 (오른쪽 상단 watch 버튼을 클릭): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
|
||||
여기서 "Releases only"을 선택할 수 있습니다.
|
||||
|
||||
이렇게하면, **FastAPI** 의 버그 수정 및 새로운 기능의 구현 등의 새로운 자료 (최신 버전)이 있을 때마다 (이메일) 통지를 받을 수 있습니다.
|
||||
|
||||
## 개발자와의 연결
|
||||
|
||||
<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://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://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**에 대한 트윗
|
||||
|
||||
<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에 투표하기
|
||||
|
||||
* <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>.
|
||||
|
||||
## 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>
|
||||
|
||||
많은 경우, 여러분은 이미 그 질문에 대한 답을 알고 있을 수도 있습니다. 🤓
|
||||
|
||||
만약 많은 사람들의 문제를 도와준다면, 공식적인 [FastAPI 전문가](fastapi-people.md#fastapi-experts){.internal-link target=\_blank} 가 될 것입니다. 🎉
|
||||
|
||||
가장 중요한 점은: 친절하려고 노력하는 것입니다. 사람들은 좌절감을 안고 오며, 많은 경우 최선의 방식으로 질문하지 않을 수도 있습니다. 하지만 최대한 친절하게 대하려고 노력하세요. 🤗
|
||||
|
||||
**FastAPI** 커뮤니티의 목표는 친절하고 환영하는 것입니다. 동시에, 괴롭힘이나 무례한 행동을 받아들이지 마세요. 우리는 서로를 돌봐야 합니다.
|
||||
|
||||
---
|
||||
|
||||
다른 사람들의 질문 (디스커션 또는 이슈에서) 해결을 도울 수 있는 방법은 다음과 같습니다.
|
||||
|
||||
### 질문 이해하기
|
||||
|
||||
* 질문하는 사람이 가진 **목적**과 사용 사례를 이해할 수 있는지 확인하세요.
|
||||
|
||||
* 질문 (대부분은 질문입니다)이 **명확**한지 확인하세요.
|
||||
|
||||
* 많은 경우, 사용자가 가정한 해결책에 대한 질문을 하지만, 더 **좋은** 해결책이 있을 수 있습니다. 문제와 사용 사례를 더 잘 이해하면 더 나은 **대안적인 해결책**을 제안할 수 있습니다.
|
||||
|
||||
* 질문을 이해할 수 없다면, 더 **자세한 정보**를 요청하세요.
|
||||
|
||||
### 문제 재현하기
|
||||
|
||||
대부분의 경우, 질문은 질문자의 **원본 코드**와 관련이 있습니다.
|
||||
|
||||
많은 경우, 코드의 일부만 복사해서 올리지만, 그것만으로는 **문제를 재현**하기에 충분하지 않습니다.
|
||||
|
||||
* 질문자에게 <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">최소한의 재현 가능한 예제</a>를 제공해달라고 요청하세요. 이렇게 하면 코드를 **복사-붙여넣기**하여 직접 실행하고, 동일한 오류나 동작을 확인하거나 사용 사례를 더 잘 이해할 수 있습니다.
|
||||
|
||||
* 너그러운 마음이 든다면, 문제 설명만을 기반으로 직접 **예제를 만들어**볼 수도 있습니다. 하지만, 이는 시간이 많이 걸릴 수 있으므로, 먼저 질문을 명확히 해달라고 요청하는 것이 좋습니다.
|
||||
|
||||
### 해결책 제안하기
|
||||
|
||||
* 질문을 충분히 이해한 후에는 가능한 **답변**을 제공할 수 있습니다.
|
||||
|
||||
* 많은 경우, 질문자의 **근본적인 문제나 사용 사례**를 이해하는 것이 중요합니다. 그들이 시도하는 방법보다 더 나은 해결책이 있을 수 있기 때문입니다.
|
||||
|
||||
### 해결 요청하기
|
||||
|
||||
질문자가 답변을 확인하고 나면, 당신이 문제를 해결했을 가능성이 높습니다. 축하합니다, **당신은 영웅입니다**! 🦸
|
||||
|
||||
* 이제 문제를 해결했다면, 질문자에게 다음을 요청할 수 있습니다.
|
||||
|
||||
* GitHub 디스커션에서: 댓글을 **답변**으로 표시하도록 요청하세요.
|
||||
* GitHub 이슈에서: 이슈를 **닫아달라고** 요청하세요.
|
||||
|
||||
## GitHub 저장소 보기
|
||||
|
||||
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 등만 알림 받도록 설정할 수도 있습니다.
|
||||
|
||||
그런 다음 이런 이슈들을 해결 할 수 있도록 도움을 줄 수 있습니다.
|
||||
|
||||
## 이슈 생성하기
|
||||
|
||||
GitHub 저장소에 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">새로운 이슈 생성</a>을 할 수 있습니다, 예를들면 다음과 같습니다:
|
||||
|
||||
* **질문**을 하거나 **문제**에 대해 질문합니다.
|
||||
* 새로운 **기능**을 제안 합니다.
|
||||
|
||||
**참고**: 만약 이슈를 생성한다면, 저는 여러분에게 다른 사람들을 도와달라고 부탁할 것입니다. 😉
|
||||
|
||||
## Pull Requests 리뷰하기
|
||||
|
||||
다른 사람들의 pull request를 리뷰하는 데 도움을 줄 수 있습니다.
|
||||
|
||||
다시 한번 말하지만, 최대한 친절하게 리뷰해 주세요. 🤗
|
||||
|
||||
---
|
||||
|
||||
Pull Rrquest를 리뷰할 때 고려해야 할 사항과 방법은 다음과 같습니다:
|
||||
|
||||
### 문제 이해하기
|
||||
|
||||
* 먼저, 해당 pull request가 해결하려는 **문제를 이해하는지** 확인하세요. GitHub 디스커션 또는 이슈에서 더 긴 논의가 있었을 수도 있습니다.
|
||||
|
||||
* Pull request가 필요하지 않을 가능성도 있습니다. **다른 방식**으로 문제를 해결할 수 있다면, 그 방법을 제안하거나 질문할 수 있습니다.
|
||||
|
||||
### 스타일에 너무 신경 쓰지 않기
|
||||
|
||||
* 커밋 메시지 스타일 같은 것에 너무 신경 쓰지 않아도 됩니다. 저는 직접 커밋을 수정하여 squash and merge를 수행할 것입니다.
|
||||
|
||||
* 코드 스타일 규칙도 걱정할 필요 없습니다. 이미 자동화된 도구들이 이를 검사하고 있습니다.
|
||||
|
||||
스타일이나 일관성 관련 요청이 필요한 경우, 제가 직접 요청하거나 필요한 변경 사항을 추가 커밋으로 수정할 것입니다.
|
||||
|
||||
### 코드 확인하기
|
||||
|
||||
* 코드를 읽고, **논리적으로 타당**한지 확인한 후 로컬에서 실행하여 문제가 해결되는지 확인하세요.
|
||||
|
||||
* 그런 다음, 확인했다고 **댓글**을 남겨 주세요. 그래야 제가 검토했음을 알 수 있습니다.
|
||||
|
||||
/// info
|
||||
|
||||
불행히도, 제가 단순히 여러 개의 승인만으로 PR을 신뢰할 수는 없습니다.
|
||||
|
||||
3개, 5개 이상의 승인이 달린 PR이 실제로는 깨져 있거나, 버그가 있거나, 주장하는 문제를 해결하지 못하는 경우가 여러 번 있었습니다. 😅
|
||||
|
||||
따라서, 정말로 코드를 읽고 실행한 뒤, 댓글로 확인 내용을 남겨 주는 것이 매우 중요합니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
* PR을 더 단순하게 만들 수 있다면 그렇게 요청할 수 있지만, 너무 까다로울 필요는 없습니다. 주관적인 견해가 많이 있을 수 있기 때문입니다 (그리고 저도 제 견해가 있을 거예요 🙈). 따라서 핵심적인 부분에 집중하는 것이 좋습니다.
|
||||
|
||||
### 테스트
|
||||
|
||||
* PR에 **테스트**가 포함되어 있는지 확인하는 데 도움을 주세요.
|
||||
|
||||
* PR을 적용하기 전에 테스트가 **실패**하는지 확인하세요. 🚨
|
||||
|
||||
* PR을 적용한 후 테스트가 **통과**하는지 확인하세요. ✅
|
||||
|
||||
* 많은 PR에는 테스트가 없습니다. 테스트를 추가하도록 **상기**시켜줄 수도 있고, 직접 테스트를 **제안**할 수도 있습니다. 이는 시간이 많이 소요되는 부분 중 하나이며, 그 부분을 많이 도와줄 수 있습니다.
|
||||
|
||||
* 그리고 시도한 내용을 댓글로 남겨주세요. 그러면 제가 확인했다는 걸 알 수 있습니다. 🤓
|
||||
|
||||
## Pull Request를 만드십시오
|
||||
|
||||
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> 공유할 때.
|
||||
* 해당 섹션의 시작 부분에 링크를 추가해야 합니다.
|
||||
* 당신의 언어로 [문서 번역하는데](contributing.md#translations){.internal-link target=\_blank} 기여할 때.
|
||||
* 다른 사람이 작성한 번역을 검토하는 것도 도울 수 있습니다.
|
||||
* 새로운 문서의 섹션을 제안할 때.
|
||||
* 기존 문제/버그를 수정할 때.
|
||||
* 테스트를 반드시 추가해야 합니다.
|
||||
* 새로운 feature를 추가할 때.
|
||||
* 테스트를 반드시 추가해야 합니다.
|
||||
* 관련 문서가 필요하다면 반드시 추가해야 합니다.
|
||||
|
||||
## FastAPI 유지 관리에 도움 주기
|
||||
|
||||
**FastAPI**의 유지 관리를 도와주세요! 🤓
|
||||
|
||||
할 일이 많고, 그 중 대부분은 **여러분**이 할 수 있습니다.
|
||||
|
||||
지금 할 수 있는 주요 작업은:
|
||||
|
||||
* [GitHub에서 다른 사람들의 질문에 도움 주기](#github_1){.internal-link target=_blank} (위의 섹션을 참조하세요).
|
||||
* [Pull Request 리뷰하기](#pull-requests){.internal-link target=_blank} (위의 섹션을 참조하세요).
|
||||
|
||||
이 두 작업이 **가장 많은 시간을 소모**하는 일입니다. 그것이 FastAPI 유지 관리의 주요 작업입니다.
|
||||
|
||||
이 작업을 도와주신다면, **FastAPI 유지 관리에 도움을 주는 것**이며 그것이 **더 빠르고 더 잘 발전하는 것**을 보장하는 것입니다. 🚀
|
||||
|
||||
## 채팅에 참여하십시오
|
||||
|
||||
👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">디스코드 채팅 서버</a> 👥 에 가입하고 FastAPI 커뮤니티에서 다른 사람들과 어울리세요.
|
||||
|
||||
/// 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} 의 도움을 받을 가능성이 높습니다.
|
||||
|
||||
다른 일반적인 대화에서만 채팅을 사용하십시오.
|
||||
|
||||
///
|
||||
|
||||
### 질문을 위해 채팅을 사용하지 마십시오
|
||||
|
||||
채팅은 더 많은 "자유로운 대화"를 허용하기 때문에, 너무 일반적인 질문이나 대답하기 어려운 질문을 쉽게 질문을 할 수 있으므로, 답변을 받지 못할 수 있습니다.
|
||||
|
||||
GitHub 이슈에서의 템플릿은 올바른 질문을 작성하도록 안내하여 더 쉽게 좋은 답변을 얻거나 질문하기 전에 스스로 문제를 해결할 수도 있습니다. 그리고 GitHub에서는 시간이 조금 걸리더라도 항상 모든 것에 답할 수 있습니다. 채팅 시스템에서는 개인적으로 그렇게 할 수 없습니다. 😅
|
||||
|
||||
채팅 시스템에서의 대화 또한 GitHub에서 처럼 쉽게 검색할 수 없기 때문에 대화 중에 질문과 답변이 손실될 수 있습니다. 그리고 GitHub 이슈에 있는 것만 [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}가 되는 것으로 간주되므로, GitHub 이슈에서 더 많은 관심을 받을 것입니다.
|
||||
|
||||
반면, 채팅 시스템에는 수천 명의 사용자가 있기 때문에, 거의 항상 대화 상대를 찾을 가능성이 높습니다. 😄
|
||||
|
||||
## 개발자 스폰서가 되십시오
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
|
||||
감사합니다! 🚀
|
||||
@@ -0,0 +1,81 @@
|
||||
# 역사, 디자인 그리고 미래
|
||||
|
||||
어느 날, [한 FastAPI 사용자](https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920)가 이렇게 물었습니다:
|
||||
|
||||
> 이 프로젝트의 역사를 알려 주실 수 있나요? 몇 주 만에 멋진 결과를 낸 것 같아요. [...]
|
||||
|
||||
여기서 그 역사에 대해 간단히 설명하겠습니다.
|
||||
|
||||
---
|
||||
|
||||
## 대안
|
||||
|
||||
저는 여러 해 동안 머신러닝, 분산 시스템, 비동기 작업, NoSQL 데이터베이스 같은 복잡한 요구사항을 가진 API를 개발하며 여러 팀을 이끌어 왔습니다.
|
||||
|
||||
이 과정에서 많은 대안을 조사하고, 테스트하며, 사용해야 했습니다. **FastAPI**의 역사는 그 이전에 나왔던 여러 도구의 역사와 밀접하게 연관되어 있습니다.
|
||||
|
||||
[대안](alternatives.md){.internal-link target=_blank} 섹션에서 언급된 것처럼:
|
||||
|
||||
> **FastAPI**는 이전에 나왔던 많은 도구들의 노력 없이는 존재하지 않았을 것입니다.
|
||||
>
|
||||
> 이전에 개발된 여러 도구들이 이 프로젝트에 영감을 주었습니다.
|
||||
>
|
||||
> 저는 오랫동안 새로운 프레임워크를 만드는 것을 피하고자 했습니다. 처음에는 **FastAPI**가 제공하는 기능들을 다양한 프레임워크와 플러그인, 도구들을 조합해 해결하려 했습니다.
|
||||
>
|
||||
> 하지만 결국에는 이 모든 기능을 통합하는 도구가 필요해졌습니다. 이전 도구들로부터 최고의 아이디어들을 모으고, 이를 최적의 방식으로 조합해야만 했습니다. 이는 :term:Python 3.6+ 타입 힌트 <type hints>와 같은, 이전에는 사용할 수 없었던 언어 기능이 가능했기 때문입니다.
|
||||
|
||||
---
|
||||
|
||||
## 조사
|
||||
|
||||
여러 대안을 사용해 보며 다양한 도구에서 배운 점들을 모아 저와 개발팀에게 가장 적합한 방식을 찾았습니다.
|
||||
|
||||
예를 들어, 표준 :term:Python 타입 힌트 <type hints>에 기반하는 것이 이상적이라는 점이 명확했습니다.
|
||||
|
||||
또한, 이미 존재하는 표준을 활용하는 것이 가장 좋은 접근법이라 판단했습니다.
|
||||
|
||||
그래서 **FastAPI**의 코드를 작성하기 전에 몇 달 동안 OpenAPI, JSON Schema, OAuth2 명세를 연구하며 이들의 관계와 겹치는 부분, 차이점을 이해했습니다.
|
||||
|
||||
---
|
||||
|
||||
## 디자인
|
||||
|
||||
그 후, **FastAPI** 사용자가 될 개발자로서 사용하고 싶은 개발자 "API"를 디자인했습니다.
|
||||
|
||||
[Python Developer Survey](https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools)에 따르면 약 80%의 Python 개발자가 PyCharm, VS Code, Jedi 기반 편집기 등에서 개발합니다. 이 과정에서 여러 아이디어를 테스트했습니다.
|
||||
|
||||
대부분의 다른 편집기도 유사하게 동작하기 때문에, **FastAPI**의 이점은 거의 모든 편집기에서 누릴 수 있습니다.
|
||||
|
||||
이 과정을 통해 코드 중복을 최소화하고, 모든 곳에서 자동 완성, 타입 검사, 에러 확인 기능이 제공되는 최적의 방식을 찾아냈습니다.
|
||||
|
||||
이 모든 것은 개발자들에게 최고의 개발 경험을 제공하기 위해 설계되었습니다.
|
||||
|
||||
---
|
||||
|
||||
## 필요조건
|
||||
|
||||
여러 대안을 테스트한 후, [Pydantic](https://docs.pydantic.dev/)을 사용하기로 결정했습니다.
|
||||
|
||||
이후 저는 **Pydantic**이 JSON Schema와 완벽히 호환되도록 개선하고, 다양한 제약 조건 선언을 지원하며, 여러 편집기에서의 자동 완성과 타입 검사 기능을 향상하기 위해 기여했습니다.
|
||||
|
||||
또한, 또 다른 주요 필요조건이었던 [Starlette](https://www.starlette.dev/)에도 기여했습니다.
|
||||
|
||||
---
|
||||
|
||||
## 개발
|
||||
|
||||
**FastAPI**를 개발하기 시작할 즈음에는 대부분의 준비가 이미 완료된 상태였습니다. 설계가 정의되었고, 필요조건과 도구가 준비되었으며, 표준과 명세에 대한 지식도 충분했습니다.
|
||||
|
||||
---
|
||||
|
||||
## 미래
|
||||
|
||||
현시점에서 **FastAPI**가 많은 사람들에게 유용하다는 것이 명백해졌습니다.
|
||||
|
||||
여러 용도에 더 적합한 도구로서 기존 대안보다 선호되고 있습니다.
|
||||
이미 많은 개발자와 팀들이 **FastAPI**에 의존해 프로젝트를 진행 중입니다 (저와 제 팀도 마찬가지입니다).
|
||||
|
||||
하지만 여전히 개선해야 할 점과 추가할 기능들이 많이 남아 있습니다.
|
||||
|
||||
**FastAPI**는 밝은 미래로 나아가고 있습니다.
|
||||
그리고 [여러분의 도움](help-fastapi.md){.internal-link target=_blank}은 큰 힘이 됩니다.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 조건부적인 OpenAPI
|
||||
|
||||
필요한 경우, 설정 및 환경 변수를 사용하여 환경에 따라 조건부로 OpenAPI를 구성하고 완전히 비활성화할 수도 있습니다.
|
||||
|
||||
## 보안, API 및 docs에 대해서
|
||||
|
||||
프로덕션에서, 문서화된 사용자 인터페이스(UI)를 숨기는 것이 API를 보호하는 방법이 *되어서는 안 됩니다*.
|
||||
|
||||
이는 API에 추가적인 보안을 제공하지 않으며, *경로 작업*은 여전히 동일한 위치에서 사용 할 수 있습니다.
|
||||
|
||||
코드에 보안 결함이 있다면, 그 결함은 여전히 존재할 것입니다.
|
||||
|
||||
문서를 숨기는 것은 API와 상호작용하는 방법을 이해하기 어렵게 만들며, 프로덕션에서 디버깅을 더 어렵게 만들 수 있습니다. 이는 단순히 <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">'모호성에 의한 보안'</a>의 한 형태로 간주될 수 있습니다.
|
||||
|
||||
API를 보호하고 싶다면, 예를 들어 다음과 같은 더 나은 방법들이 있습니다:
|
||||
|
||||
* 요청 본문과 응답에 대해 잘 정의된 Pydantic 모델을 사용하도록 하세요.
|
||||
|
||||
* 종속성을 사용하여 필요한 권한과 역할을 구성하세요.
|
||||
|
||||
* 평문 비밀번호를 절대 저장하지 말고, 오직 암호화된 비밀번호만 저장하세요.
|
||||
|
||||
* Passlib과 JWT 토큰과 같은 잘 알려진 암호화 도구들을 구현하고 사용하세요.
|
||||
|
||||
* 필요한 곳에 OAuth2 범위를 사용하여 더 세분화된 권한 제어를 추가하세요.
|
||||
|
||||
* 등등....
|
||||
|
||||
그럼에도 불구하고, 특정 환경(예: 프로덕션)에서 또는 환경 변수의 설정에 따라 API 문서를 비활성화해야 하는 매우 특정한 사용 사례가 있을 수 있습니다.
|
||||
|
||||
## 설정 및 환경변수의 조건부 OpenAPI
|
||||
|
||||
동일한 Pydantic 설정을 사용하여 생성된 OpenAPI 및 문서 UI를 쉽게 구성할 수 있습니다.
|
||||
|
||||
예를 들어:
|
||||
|
||||
{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *}
|
||||
|
||||
여기서 `openapi_url` 설정을 기본값인 `"/openapi.json"`으로 선언합니다.
|
||||
|
||||
그런 뒤, 우리는 `FastAPI` 앱을 만들 때 그것을 사용합니다.
|
||||
|
||||
환경 변수 `OPENAPI_URL`을 빈 문자열로 설정하여 OpenAPI(문서 UI 포함)를 비활성화할 수도 있습니다. 예를 들어:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ OPENAPI_URL= uvicorn main:app
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
그리고 `/openapi.json`, `/docs` 또는 `/redoc`의 URL로 이동하면 `404 Not Found`라는 오류가 다음과 같이 표시됩니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Not Found"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,70 @@
|
||||
# Swagger UI 구성
|
||||
|
||||
추가적인 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI 매개변수</a>를 구성할 수 있습니다.
|
||||
|
||||
구성을 하려면, `FastAPI()` 앱 객체를 생성할 때 또는 `get_swagger_ui_html()` 함수에 `swagger_ui_parameters` 인수를 전달하십시오.
|
||||
|
||||
`swagger_ui_parameters`는 Swagger UI에 직접 전달된 구성을 포함하는 딕셔너리를 받습니다.
|
||||
|
||||
FastAPI는 이 구성을 **JSON** 형식으로 변환하여 JavaScript와 호환되도록 합니다. 이는 Swagger UI에서 필요로 하는 형식입니다.
|
||||
|
||||
## 구문 강조 비활성화
|
||||
|
||||
예를 들어, Swagger UI에서 구문 강조 기능을 비활성화할 수 있습니다.
|
||||
|
||||
설정을 변경하지 않으면, 기본적으로 구문 강조 기능이 활성화되어 있습니다:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image02.png">
|
||||
|
||||
그러나 `syntaxHighlight`를 `False`로 설정하여 구문 강조 기능을 비활성화할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *}
|
||||
|
||||
...그럼 Swagger UI에서 더 이상 구문 강조 기능이 표시되지 않습니다:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image03.png">
|
||||
|
||||
## 테마 변경
|
||||
|
||||
동일한 방식으로 `"syntaxHighlight.theme"` 키를 사용하여 구문 강조 테마를 설정할 수 있습니다 (중간에 점이 포함된 것을 참고하십시오).
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
|
||||
|
||||
이 설정은 구문 강조 색상 테마를 변경합니다:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image04.png">
|
||||
|
||||
## 기본 Swagger UI 매개변수 변경
|
||||
|
||||
FastAPI는 대부분의 사용 사례에 적합한 몇 가지 기본 구성 매개변수를 포함하고 있습니다.
|
||||
|
||||
기본 구성에는 다음이 포함됩니다:
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *}
|
||||
|
||||
`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 일부를 재정의할 수 있습니다.
|
||||
|
||||
예를 들어, `deepLinking`을 비활성화하려면 `swagger_ui_parameters`에 다음 설정을 전달할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
|
||||
|
||||
## 기타 Swagger UI 매개변수
|
||||
|
||||
사용할 수 있는 다른 모든 구성 옵션을 확인하려면, Swagger UI 매개변수에 대한 공식 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">문서</a>를 참조하십시오.
|
||||
|
||||
## JavaScript 전용 설정
|
||||
|
||||
Swagger UI는 **JavaScript 전용** 객체(예: JavaScript 함수)로 다른 구성을 허용하기도 합니다.
|
||||
|
||||
FastAPI는 이러한 JavaScript 전용 `presets` 설정을 포함하고 있습니다:
|
||||
|
||||
```JavaScript
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
]
|
||||
```
|
||||
|
||||
이들은 문자열이 아닌 **JavaScript** 객체이므로 Python 코드에서 직접 전달할 수 없습니다.
|
||||
|
||||
이와 같은 JavaScript 전용 구성을 사용해야 하는 경우, 위의 방법 중 하나를 사용하여 모든 Swagger UI 경로 작업을 재정의하고 필요한 JavaScript를 수동으로 작성할 수 있습니다.
|
||||
@@ -0,0 +1,468 @@
|
||||
# FastAPI
|
||||
|
||||
<style>
|
||||
.md-content .md-typeset h1 { display: none; }
|
||||
</style>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>FastAPI 프레임워크, 고성능, 간편한 학습, 빠른 코드 작성, 준비된 프로덕션</em>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
|
||||
<img src="https://github.com/fastapi/fastapi/actions/workflows/test.yml/badge.svg?event=push&branch=master" alt="Test">
|
||||
</a>
|
||||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/fastapi" target="_blank">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/fastapi" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
**문서**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
|
||||
**소스 코드**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
|
||||
|
||||
---
|
||||
|
||||
FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트에 기초한 Python의 API를 빌드하기 위한 웹 프레임워크입니다.
|
||||
|
||||
주요 특징으로:
|
||||
|
||||
* **빠름**: (Starlette과 Pydantic 덕분에) **NodeJS** 및 **Go**와 대등할 정도로 매우 높은 성능. [사용 가능한 가장 빠른 파이썬 프레임워크 중 하나](#_11).
|
||||
|
||||
* **빠른 코드 작성**: 약 200%에서 300%까지 기능 개발 속도 증가. *
|
||||
* **적은 버그**: 사람(개발자)에 의한 에러 약 40% 감소. *
|
||||
* **직관적**: 훌륭한 편집기 지원. 모든 곳에서 <abbr title="also known as auto-complete, autocompletion, IntelliSense">자동완성</abbr>. 적은 디버깅 시간.
|
||||
* **쉬움**: 쉽게 사용하고 배우도록 설계. 적은 문서 읽기 시간.
|
||||
* **짧음**: 코드 중복 최소화. 각 매개변수 선언의 여러 기능. 적은 버그.
|
||||
* **견고함**: 준비된 프로덕션 용 코드를 얻으십시오. 자동 대화형 문서와 함께.
|
||||
* **표준 기반**: API에 대한 (완전히 호환되는) 개방형 표준 기반: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (이전에 Swagger로 알려졌던) 및 <a href="http://json-schema.org/" class="external-link" target="_blank">JSON 스키마</a>.
|
||||
|
||||
<small>* 내부 개발팀의 프로덕션 애플리케이션을 빌드한 테스트에 근거한 측정</small>
|
||||
|
||||
## 골드 스폰서
|
||||
|
||||
<!-- sponsors -->
|
||||
|
||||
{% if sponsors %}
|
||||
{% for sponsor in sponsors.gold -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor -%}
|
||||
{%- for sponsor in sponsors.silver -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">다른 스폰서</a>
|
||||
|
||||
## 의견들
|
||||
|
||||
"_[...] 저는 요즘 **FastAPI**를 많이 사용하고 있습니다. [...] 사실 우리 팀의 **마이크로소프트 ML 서비스** 전부를 바꿀 계획입니다. 그중 일부는 핵심 **Windows**와 몇몇의 **Office** 제품들이 통합되고 있습니다._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>마이크로소프트</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_**FastAPI** 라이브러리를 채택하여 **예측**을 얻기 위해 쿼리를 실행 할 수 있는 **REST** 서버를 생성했습니다. [Ludwig을 위해]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin 그리고 Sai Sumanth Miryala - <strong>우버</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_**Netflix**는 우리의 오픈 소스 배포판인 **위기 관리** 오케스트레이션 프레임워크를 발표할 수 있어 기쁩니다: 바로 **Dispatch**입니다! [**FastAPI**로 빌드]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>넷플릭스</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_**FastAPI**가 너무 좋아서 구름 위를 걷는듯 합니다. 정말 즐겁습니다!_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> 팟캐스트 호스트</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_솔직히, 당신이 만든 것은 매우 견고하고 세련되어 보입니다. 여러 면에서 **Hug**가 이렇게 되었으면 합니다 - 그걸 만든 누군가를 보는 것은 많은 영감을 줍니다._"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="http://www.hug.rest/" target="_blank">Hug</a> 제작자</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
"_REST API를 만들기 위해 **현대적인 프레임워크**를 찾고 있다면 **FastAPI**를 확인해 보십시오. [...] 빠르고, 쓰기 쉽고, 배우기도 쉽습니다 [...]_"
|
||||
|
||||
"_우리 **API**를 **FastAPI**로 바꿨습니다 [...] 아마 여러분도 좋아하실 것입니다 [...]_"
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> 설립자 - <a href="https://spacy.io" target="_blank">spaCy</a> 제작자</strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## **Typer**, FastAPI의 CLI
|
||||
|
||||
<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>를 확인해 보십시오.
|
||||
|
||||
**Typer**는 FastAPI의 동생입니다. 그리고 **FastAPI의 CLI**가 되기 위해 생겼습니다. ⌨️ 🚀
|
||||
|
||||
## 요구사항
|
||||
|
||||
FastAPI는 거인들의 어깨 위에 서 있습니다:
|
||||
|
||||
* 웹 부분을 위한 <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a>.
|
||||
* 데이터 부분을 위한 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>.
|
||||
|
||||
## 설치
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install fastapi
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
프로덕션을 위해 <a href="http://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> 또는 <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>과 같은 ASGI 서버도 필요할 겁니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "uvicorn[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 예제
|
||||
|
||||
### 만들기
|
||||
|
||||
* `main.py` 파일을 만드십시오:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
<details markdown="1">
|
||||
<summary>또는 <code>async def</code> 사용하기...</summary>
|
||||
|
||||
여러분의 코드가 `async` / `await`을 사용한다면, `async def`를 사용하십시오.
|
||||
|
||||
```Python hl_lines="9 14"
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
**Note**:
|
||||
|
||||
잘 모르겠다면, <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">문서에서 `async`와 `await`</a>에 관한 _"급하세요?"_ 섹션을 확인해 보십시오.
|
||||
|
||||
</details>
|
||||
|
||||
### 실행하기
|
||||
|
||||
서버를 실행하십시오:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
INFO: Started reloader process [28720]
|
||||
INFO: Started server process [28722]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<details markdown="1">
|
||||
<summary><code>uvicorn main:app --reload</code> 명령에 관하여...</summary>
|
||||
|
||||
명령 `uvicorn main:app`은 다음을 나타냅니다:
|
||||
|
||||
* `main`: `main.py` 파일 (파이썬 "모듈").
|
||||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
||||
* `--reload`: 코드가 변경된 후 서버 재시작하기. 개발환경에서만 사용하십시오.
|
||||
|
||||
</details>
|
||||
|
||||
### 확인하기
|
||||
|
||||
브라우저로 <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>를 열어보십시오.
|
||||
|
||||
아래의 JSON 응답을 볼 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
여러분은 벌써 API를 만들었습니다:
|
||||
|
||||
* _경로_ `/` 및 `/items/{item_id}`에서 HTTP 요청 받기.
|
||||
* 두 _경로_ 모두 `GET` <em>연산</em>(HTTP _메소드_ 로 알려진)을 받습니다.
|
||||
* _경로_ `/items/{item_id}`는 _경로 매개변수_ `int`형 이어야 하는 `item_id`를 가지고 있습니다.
|
||||
* _경로_ `/items/{item_id}`는 선택적인 `str`형 이어야 하는 _경로 매개변수_ `q`를 가지고 있습니다.
|
||||
|
||||
### 대화형 API 문서
|
||||
|
||||
이제 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>로 가보십시오.
|
||||
|
||||
자동 대화형 API 문서를 볼 수 있습니다 (<a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> 제공):
|
||||
|
||||

|
||||
|
||||
### 대안 API 문서
|
||||
|
||||
그리고 이제 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>로 가봅시다.
|
||||
|
||||
다른 자동 문서를 볼 수 있습니다(<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 제공):
|
||||
|
||||

|
||||
|
||||
## 예제 심화
|
||||
|
||||
이제 `PUT` 요청에 있는 본문(Body)을 받기 위해 `main.py`를 수정해봅시다.
|
||||
|
||||
Pydantic을 이용해 파이썬 표준 타입으로 본문을 선언합니다.
|
||||
|
||||
```Python hl_lines="4 9 10 11 12 25 26 27"
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
is_offer: Union[bool, None] = None
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
def update_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
서버가 자동으로 리로딩 할 수 있어야 합니다 (위에서 `uvicorn` 명령에 `--reload`을 추가 했기 때문입니다).
|
||||
|
||||
### 대화형 API 문서 업그레이드
|
||||
|
||||
이제 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>로 이동합니다.
|
||||
|
||||
* 대화형 API 문서가 새 본문과 함께 자동으로 업데이트 합니다:
|
||||
|
||||

|
||||
|
||||
* "Try it out" 버튼을 클릭하면, 매개변수를 채울 수 있게 해주고 직접 API와 상호작용 할 수 있습니다:
|
||||
|
||||

|
||||
|
||||
* 그러고 나서 "Execute" 버튼을 누르면, 사용자 인터페이스는 API와 통신하고 매개변수를 전송하며 그 결과를 가져와서 화면에 표시합니다:
|
||||
|
||||

|
||||
|
||||
### 대안 API 문서 업그레이드
|
||||
|
||||
그리고 이제, <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>로 이동합니다.
|
||||
|
||||
* 대안 문서 역시 새 쿼리 매개변수와 본문을 반영합니다:
|
||||
|
||||

|
||||
|
||||
### 요약
|
||||
|
||||
요약하면, 여러분은 매개변수의 타입, 본문 등을 함수 매개변수로서 **한번에** 선언했습니다.
|
||||
|
||||
여러분은 현대 표준 파이썬 타입으로 이를 행했습니다.
|
||||
|
||||
새로운 문법, 특정 라이브러리의 메소드나 클래스 등을 배울 필요가 없습니다.
|
||||
|
||||
그저 표준 **Python** 입니다.
|
||||
|
||||
예를 들어, `int`에 대해선:
|
||||
|
||||
```Python
|
||||
item_id: int
|
||||
```
|
||||
|
||||
또는 좀 더 복잡한 `Item` 모델에 대해선:
|
||||
|
||||
```Python
|
||||
item: Item
|
||||
```
|
||||
|
||||
...그리고 단 하나의 선언으로 여러분이 얻는 것은:
|
||||
|
||||
* 다음을 포함한 편집기 지원:
|
||||
* 자동완성.
|
||||
* 타입 검사.
|
||||
* 데이터 검증:
|
||||
* 데이터가 유효하지 않을 때 자동으로 생성하는 명확한 에러.
|
||||
* 중첩된 JSON 객체에 대한 유효성 검사.
|
||||
* 입력 데이터 <abbr title="다음으로 알려진: 직렬화, 파싱, 마샬링">변환</abbr>: 네트워크에서 파이썬 데이터 및 타입으로 전송. 읽을 수 있는 것들:
|
||||
* JSON.
|
||||
* 경로 매개변수.
|
||||
* 쿼리 매개변수.
|
||||
* 쿠키.
|
||||
* 헤더.
|
||||
* 폼(Forms).
|
||||
* 파일.
|
||||
* 출력 데이터 <abbr title="다음으로 알려진: 직렬화, 파싱, 마샬링">변환</abbr>: 파이썬 데이터 및 타입을 네트워크 데이터로 전환(JSON 형식으로):
|
||||
* 파이썬 타입 변환 (`str`, `int`, `float`, `bool`, `list`, 등).
|
||||
* `datetime` 객체.
|
||||
* `UUID` 객체.
|
||||
* 데이터베이스 모델.
|
||||
* ...더 많은 것들.
|
||||
* 대안가능한 사용자 인터페이스를 2개 포함한 자동 대화형 API 문서:
|
||||
* Swagger UI.
|
||||
* ReDoc.
|
||||
|
||||
---
|
||||
|
||||
이전 코드 예제로 돌아가서, **FastAPI**는 다음처럼 처리합니다:
|
||||
|
||||
* `GET` 및 `PUT` 요청에 `item_id`가 경로에 있는지 검증.
|
||||
* `GET` 및 `PUT` 요청에 `item_id`가 `int` 타입인지 검증.
|
||||
* 그렇지 않다면 클라이언트는 유용하고 명확한 에러를 볼 수 있습니다.
|
||||
* `GET` 요청에 `q`라는 선택적인 쿼리 매개변수가 검사(`http://127.0.0.1:8000/items/foo?q=somequery`처럼).
|
||||
* `q` 매개변수는 `= None`으로 선언되었기 때문에 선택사항입니다.
|
||||
* `None`이 없다면 필수사항입니다(`PUT`의 경우와 마찬가지로).
|
||||
* `/items/{item_id}`으로의 `PUT` 요청은 본문을 JSON으로 읽음:
|
||||
* `name`을 필수 속성으로 갖고 `str` 형인지 검사.
|
||||
* `price`을 필수 속성으로 갖고 `float` 형인지 검사.
|
||||
* 만약 주어진다면, `is_offer`를 선택 속성으로 갖고 `bool` 형인지 검사.
|
||||
* 이 모든 것은 깊이 중첩된 JSON 객체에도 적용됩니다.
|
||||
* JSON을 변환하거나 JSON으로 변환하는 것을 자동화.
|
||||
* 다음에서 사용할 수 있는 모든 것을 OpenAPI로 문서화:
|
||||
* 대화형 문서 시스템.
|
||||
* 여러 언어들에 대한 자동 클라이언트 코드 생성 시스템.
|
||||
* 2개의 대화형 문서 웹 인터페이스를 직접 제공.
|
||||
|
||||
---
|
||||
|
||||
우리는 그저 수박 겉 핥기만 했을 뿐인데 여러분은 벌써 어떻게 작동하는지 알고 있습니다.
|
||||
|
||||
다음 줄을 바꿔보십시오:
|
||||
|
||||
```Python
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
...에서:
|
||||
|
||||
```Python
|
||||
... "item_name": item.name ...
|
||||
```
|
||||
|
||||
...으로:
|
||||
|
||||
```Python
|
||||
... "item_price": item.price ...
|
||||
```
|
||||
|
||||
...그러고 나서 여러분의 편집기가 속성과 타입을 알고 자동 완성하는지 보십시오:
|
||||
|
||||

|
||||
|
||||
더 많은 기능을 포함한 보다 완전한 예제의 경우, <a href="https://fastapi.tiangolo.com/tutorial/">튜토리얼 - 사용자 가이드</a>를 보십시오.
|
||||
|
||||
**스포일러 주의**: 튜토리얼 - 사용자 가이드는:
|
||||
|
||||
* 서로 다른 장소에서 **매개변수** 선언: **헤더**, **쿠키**, **폼 필드** 그리고 **파일**.
|
||||
* `maximum_length` 또는 `regex`처럼 **유효성 제약**하는 방법.
|
||||
* 강력하고 사용하기 쉬운 **<abbr title="컴포넌트, 리소스, 제공자, 서비스, injectables라 알려진">의존성 주입</abbr>** 시스템.
|
||||
* **OAuth2** 지원을 포함한 **JWT tokens** 및 **HTTP Basic**을 갖는 보안과 인증.
|
||||
* (Pydantic 덕분에) **깊은 중첩 JSON 모델**을 선언하는데 더 진보한 (하지만 마찬가지로 쉬운) 기술.
|
||||
* (Starlette 덕분에) 많은 추가 기능:
|
||||
* **웹 소켓**
|
||||
* **GraphQL**
|
||||
* HTTPX 및 `pytest`에 기반한 극히 쉬운 테스트
|
||||
* **CORS**
|
||||
* **쿠키 세션**
|
||||
* ...기타 등등.
|
||||
|
||||
## 성능
|
||||
|
||||
독립된 TechEmpower 벤치마크에서 Uvicorn에서 작동하는 FastAPI 어플리케이션이 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">사용 가능한 가장 빠른 프레임워크 중 하나</a>로 Starlette와 Uvicorn(FastAPI에서 내부적으로 사용)에만 밑돌고 있습니다. (*)
|
||||
|
||||
자세한 내용은 <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">벤치마크</a> 섹션을 보십시오.
|
||||
|
||||
## 선택가능한 의존성
|
||||
|
||||
Pydantic이 사용하는:
|
||||
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - 이메일 유효성 검사.
|
||||
|
||||
Starlette이 사용하는:
|
||||
|
||||
* <a href="https://www.python-httpx.org" target="_blank"><code>HTTPX</code></a> - `TestClient`를 사용하려면 필요.
|
||||
* <a href="http://jinja.pocoo.org" target="_blank"><code>jinja2</code></a> - 기본 템플릿 설정을 사용하려면 필요.
|
||||
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - `request.form()`과 함께 <abbr title="HTTP 요청에서 파이썬 데이터로 가는 문자열 변환">"parsing"</abbr>의 지원을 원하면 필요.
|
||||
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - `SessionMiddleware` 지원을 위해 필요.
|
||||
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요 없을 것입니다).
|
||||
* <a href="https://graphene-python.org/" target="_blank"><code>graphene</code></a> - `GraphQLApp` 지원을 위해 필요.
|
||||
|
||||
FastAPI / Starlette이 사용하는:
|
||||
|
||||
* <a href="http://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - 애플리케이션을 로드하고 제공하는 서버.
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - `ORJSONResponse`을 사용하려면 필요.
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - `UJSONResponse`를 사용하려면 필요.
|
||||
|
||||
`pip install fastapi[all]`를 통해 이 모두를 설치 할 수 있습니다.
|
||||
|
||||
## 라이센스
|
||||
|
||||
이 프로젝트는 MIT 라이센스 조약에 따라 라이센스가 부여됩니다.
|
||||
@@ -0,0 +1,5 @@
|
||||
# 배우기
|
||||
|
||||
여기 **FastAPI**를 배우기 위한 입문 자료와 자습서가 있습니다.
|
||||
|
||||
여러분은 FastAPI를 배우기 위해 **책**, **강의**, **공식 자료** 그리고 추천받은 방법을 고려할 수 있습니다. 😎
|
||||
@@ -0,0 +1,55 @@
|
||||
# OpenAPI 웹훅(Webhooks)
|
||||
|
||||
API **사용자**에게 특정 **이벤트**가 발생할 때 *그들*의 앱(시스템)에 요청을 보내 **알림**을 전달할 수 있다는 것을 알리고 싶은 경우가 있습니다.
|
||||
|
||||
즉, 일반적으로 사용자가 API에 요청을 보내는 것과는 반대로, **API**(또는 앱)가 **사용자의 시스템**(그들의 API나 앱)으로 **요청을 보내는** 상황을 의미합니다.
|
||||
|
||||
이를 흔히 **웹훅(Webhook)**이라고 부릅니다.
|
||||
|
||||
## 웹훅 스텝
|
||||
|
||||
**코드에서** 웹훅으로 보낼 메시지, 즉 요청의 **바디(body)**를 정의하는 것이 일반적인 프로세스입니다.
|
||||
|
||||
앱에서 해당 요청이나 이벤트를 전송할 **시점**을 정의합니다.
|
||||
|
||||
**사용자**는 앱이 해당 요청을 보낼 **URL**을 정의합니다. (예: 웹 대시보드에서 설정)
|
||||
|
||||
웹훅의 URL을 등록하는 방법과 이러한 요청을 실제로 전송하는 코드에 대한 모든 로직은 여러분에게 달려 있습니다. 원하는대로 **고유의 코드**를 작성하면 됩니다.
|
||||
|
||||
## **FastAPI**와 OpenAPI로 웹훅 문서화하기
|
||||
|
||||
**FastAPI**를 사용하여 OpenAPI와 함께 웹훅의 이름, 앱이 보낼 수 있는 HTTP 작업 유형(예: `POST`, `PUT` 등), 그리고 보낼 요청의 **바디**를 정의할 수 있습니다.
|
||||
|
||||
이를 통해 사용자가 **웹훅** 요청을 수신할 **API 구현**을 훨씬 쉽게 할 수 있으며, 경우에 따라 사용자 API 코드의 일부를 자동 생성할 수도 있습니다.
|
||||
|
||||
/// info
|
||||
|
||||
웹훅은 OpenAPI 3.1.0 이상에서 지원되며, FastAPI `0.99.0` 이상 버전에서 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 웹훅이 포함된 앱 만들기
|
||||
|
||||
**FastAPI** 애플리케이션을 만들 때, `webhooks` 속성을 사용하여 *웹훅*을 정의할 수 있습니다. 이는 `@app.webhooks.post()`와 같은 방식으로 *경로(path) 작업*을 정의하는 것과 비슷합니다.
|
||||
|
||||
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
|
||||
|
||||
이렇게 정의한 웹훅은 **OpenAPI** 스키마와 자동 **문서화 UI**에 표시됩니다.
|
||||
|
||||
/// info
|
||||
|
||||
`app.webhooks` 객체는 사실 `APIRouter`일 뿐이며, 여러 파일로 앱을 구성할 때 사용하는 것과 동일한 타입입니다.
|
||||
|
||||
///
|
||||
|
||||
웹훅에서는 실제 **경로(path)** (예: `/items/`)를 선언하지 않는 점에 유의해야 합니다. 여기서 전달하는 텍스트는 **식별자**로, 웹훅의 이름(이벤트 이름)입니다. 예를 들어, `@app.webhooks.post("new-subscription")`에서 웹훅 이름은 `new-subscription`입니다.
|
||||
|
||||
이는 실제 **URL 경로**는 **사용자**가 다른 방법(예: 웹 대시보드)을 통해 지정하도록 기대되기 때문입니다.
|
||||
|
||||
### 문서 확인하기
|
||||
|
||||
이제 앱을 시작하고 <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/openapi-webhooks/image01.png">
|
||||
@@ -0,0 +1,28 @@
|
||||
# Full Stack FastAPI 템플릿
|
||||
|
||||
템플릿은 일반적으로 특정 설정과 함께 제공되지만, 유연하고 커스터마이징이 가능하게 디자인 되었습니다. 이 특성들은 여러분이 프로젝트의 요구사항에 맞춰 수정, 적용을 할 수 있게 해주고, 템플릿이 완벽한 시작점이 되게 해줍니다. 🏁
|
||||
|
||||
많은 초기 설정, 보안, 데이터베이스 및 일부 API 엔드포인트가 이미 준비되어 있으므로, 여러분은 이 템플릿을 (프로젝트를) 시작하는 데 사용할 수 있습니다.
|
||||
|
||||
GitHub 저장소: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI 템플릿</a>
|
||||
|
||||
## Full Stack FastAPI 템플릿 - 기술 스택과 기능들
|
||||
|
||||
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com): Python 백엔드 API.
|
||||
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com): Python SQL 데이터 상호작용을 위한 (ORM).
|
||||
- 🔍 [Pydantic](https://docs.pydantic.dev): FastAPI에 의해 사용되는, 데이터 검증과 설정관리.
|
||||
- 💾 [PostgreSQL](https://www.postgresql.org): SQL 데이터베이스.
|
||||
- 🚀 [React](https://react.dev): 프론트엔드.
|
||||
- 💃 TypeScript, hooks, [Vite](https://vitejs.dev) 및 기타 현대적인 프론트엔드 스택을 사용.
|
||||
- 🎨 [Chakra UI](https://chakra-ui.com): 프론트엔드 컴포넌트.
|
||||
- 🤖 자동으로 생성된 프론트엔드 클라이언트.
|
||||
- 🧪 E2E 테스트를 위한 [Playwright](https://playwright.dev).
|
||||
- 🦇 다크 모드 지원.
|
||||
- 🐋 [Docker Compose](https://www.docker.com): 개발 환경과 프로덕션(운영).
|
||||
- 🔒 기본으로 지원되는 안전한 비밀번호 해싱.
|
||||
- 🔑 JWT 토큰 인증.
|
||||
- 📫 이메일 기반 비밀번호 복구.
|
||||
- ✅ [Pytest]를 이용한 테스트(https://pytest.org).
|
||||
- 📞 [Traefik](https://traefik.io): 리버스 프록시 / 로드 밸런서.
|
||||
- 🚢 Docker Compose를 이용한 배포 지침: 자동 HTTPS 인증서를 처리하기 위한 프론트엔드 Traefik 프록시 설정 방법을 포함.
|
||||
- 🏭 GitHub Actions를 기반으로 CI (지속적인 통합) 및 CD (지속적인 배포).
|
||||
@@ -0,0 +1,313 @@
|
||||
# 파이썬 타입 소개
|
||||
|
||||
파이썬은 선택적으로 "타입 힌트(type hints)"를 지원합니다.
|
||||
|
||||
이러한 **타입 힌트**들은 변수의 <abbr title="예를 들면: str, int, float, bool">타입</abbr>을 선언할 수 있게 해주는 특수한 구문입니다.
|
||||
|
||||
변수의 타입을 지정하면 에디터와 툴이 더 많은 도움을 줄 수 있게 됩니다.
|
||||
|
||||
이 문서는 파이썬 타입 힌트에 대한 **빠른 자습서 / 내용환기** 수준의 문서입니다. 여기서는 **FastAPI**를 쓰기 위한 최소한의 내용만을 다룹니다.
|
||||
|
||||
**FastAPI**는 타입 힌트에 기반을 두고 있으며, 이는 많은 장점과 이익이 있습니다.
|
||||
|
||||
비록 **FastAPI**를 쓰지 않는다고 하더라도, 조금이라도 알아두면 도움이 될 것입니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
파이썬에 능숙하셔서 타입 힌트에 대해 모두 아신다면, 다음 챕터로 건너뛰세요.
|
||||
|
||||
///
|
||||
|
||||
## 동기 부여
|
||||
|
||||
간단한 예제부터 시작해봅시다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py *}
|
||||
|
||||
|
||||
이 프로그램을 실행한 결과값:
|
||||
|
||||
```
|
||||
John Doe
|
||||
```
|
||||
|
||||
함수는 아래와 같이 실행됩니다:
|
||||
|
||||
* `first_name`과 `last_name`를 받습니다.
|
||||
* `title()`로 각 첫 문자를 대문자로 변환시킵니다.
|
||||
* 두 단어를 중간에 공백을 두고 <abbr title="두 개를 하나로 차례차례 이어지게 하다">연결</abbr>합니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py hl[2] *}
|
||||
|
||||
|
||||
### 코드 수정
|
||||
|
||||
이건 매우 간단한 프로그램입니다.
|
||||
|
||||
그런데 처음부터 작성한다고 생각을 해봅시다.
|
||||
|
||||
여러분은 매개변수를 준비했고, 함수를 정의하기 시작했을 겁니다.
|
||||
|
||||
이때 "첫 글자를 대문자로 바꾸는 함수"를 호출해야 합니다.
|
||||
|
||||
`upper`였나? 아니면 `uppercase`? `first_uppercase`? `capitalize`?
|
||||
|
||||
그때 개발자들의 오랜 친구, 에디터 자동완성을 시도해봅니다.
|
||||
|
||||
당신은 `first_name`를 입력한 뒤 점(`.`)을 입력하고 자동완성을 켜기 위해서 `Ctrl+Space`를 눌렀습니다.
|
||||
|
||||
하지만 슬프게도 아무런 도움이 되지 않습니다:
|
||||
|
||||
<img src="/img/python-types/image01.png">
|
||||
|
||||
### 타입 추가하기
|
||||
|
||||
이전 버전에서 한 줄만 수정해봅시다.
|
||||
|
||||
저희는 이 함수의 매개변수 부분:
|
||||
|
||||
```Python
|
||||
first_name, last_name
|
||||
```
|
||||
|
||||
을 아래와 같이 바꿀 겁니다:
|
||||
|
||||
```Python
|
||||
first_name: str, last_name: str
|
||||
```
|
||||
|
||||
이게 다입니다.
|
||||
|
||||
이게 "타입 힌트"입니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial002.py hl[1] *}
|
||||
|
||||
|
||||
타입힌트는 다음과 같이 기본 값을 선언하는 것과는 다릅니다:
|
||||
|
||||
```Python
|
||||
first_name="john", last_name="doe"
|
||||
```
|
||||
|
||||
이는 다른 것입니다.
|
||||
|
||||
등호(`=`) 대신 콜론(`:`)을 쓰고 있습니다.
|
||||
|
||||
일반적으로 타입힌트를 추가한다고 해서 특별하게 어떤 일이 일어나지도 않습니다.
|
||||
|
||||
그렇지만 이제, 다시 함수를 만드는 도중이라고 생각해봅시다. 다만 이번엔 타입 힌트가 있습니다.
|
||||
|
||||
같은 상황에서 `Ctrl+Space`로 자동완성을 작동시키면,
|
||||
|
||||
<img src="/img/python-types/image02.png">
|
||||
|
||||
아래와 같이 "그렇지!"하는 옵션이 나올때까지 스크롤을 내려서 볼 수 있습니다:
|
||||
|
||||
<img src="/img/python-types/image03.png">
|
||||
|
||||
## 더 큰 동기부여
|
||||
|
||||
아래 함수를 보면, 이미 타입 힌트가 적용되어 있는 걸 볼 수 있습니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial003.py hl[1] *}
|
||||
|
||||
|
||||
편집기가 변수의 타입을 알고 있기 때문에, 자동완성 뿐 아니라 에러도 확인할 수 있습니다:
|
||||
|
||||
<img src="/img/python-types/image04.png">
|
||||
|
||||
이제 고쳐야하는 걸 알기 때문에, `age`를 `str(age)`과 같이 문자열로 바꾸게 됩니다:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial004.py hl[2] *}
|
||||
|
||||
|
||||
## 타입 선언
|
||||
|
||||
방금 함수의 매개변수로써 타입 힌트를 선언하는 주요 장소를 보았습니다.
|
||||
|
||||
이 위치는 여러분이 **FastAPI**와 함께 이를 사용하는 주요 장소입니다.
|
||||
|
||||
### Simple 타입
|
||||
|
||||
`str`뿐 아니라 모든 파이썬 표준 타입을 선언할 수 있습니다.
|
||||
|
||||
예를 들면:
|
||||
|
||||
* `int`
|
||||
* `float`
|
||||
* `bool`
|
||||
* `bytes`
|
||||
|
||||
{* ../../docs_src/python_types/tutorial005.py hl[1] *}
|
||||
|
||||
|
||||
### 타입 매개변수를 활용한 Generic(제네릭) 타입
|
||||
|
||||
`dict`, `list`, `set`, `tuple`과 같은 값을 저장할 수 있는 데이터 구조가 있고, 내부의 값은 각자의 타입을 가질 수도 있습니다.
|
||||
|
||||
타입과 내부 타입을 선언하기 위해서는 파이썬 표준 모듈인 `typing`을 이용해야 합니다.
|
||||
|
||||
구체적으로는 아래 타입 힌트를 지원합니다.
|
||||
|
||||
#### `List`
|
||||
|
||||
예를 들면, `str`의 `list`인 변수를 정의해봅시다.
|
||||
|
||||
`typing`에서 `List`(대문자 `L`)를 import 합니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial006.py hl[1] *}
|
||||
|
||||
|
||||
콜론(`:`) 문법을 이용하여 변수를 선언합니다.
|
||||
|
||||
타입으로는 `List`를 넣어줍니다.
|
||||
|
||||
이때 배열은 내부 타입을 포함하는 타입이기 때문에 대괄호 안에 넣어줍니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial006.py hl[4] *}
|
||||
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
대괄호 안의 내부 타입은 "타입 매개변수(type paramters)"라고 합니다.
|
||||
|
||||
이번 예제에서는 `str`이 `List`에 들어간 타입 매개변수 입니다.
|
||||
|
||||
///
|
||||
|
||||
이는 "`items`은 `list`인데, 배열에 들어있는 아이템 각각은 `str`이다"라는 뜻입니다.
|
||||
|
||||
이렇게 함으로써, 에디터는 배열에 들어있는 아이템을 처리할때도 도움을 줄 수 있게 됩니다:
|
||||
|
||||
<img src="/img/python-types/image05.png">
|
||||
|
||||
타입이 없으면 이건 거의 불가능이나 다름 없습니다.
|
||||
|
||||
변수 `item`은 `items`의 개별 요소라는 사실을 알아두세요.
|
||||
|
||||
그리고 에디터는 계속 `str`라는 사실을 알고 도와줍니다.
|
||||
|
||||
#### `Tuple`과 `Set`
|
||||
|
||||
`tuple`과 `set`도 동일하게 선언할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial007.py hl[1,4] *}
|
||||
|
||||
|
||||
이 뜻은 아래와 같습니다:
|
||||
|
||||
* 변수 `items_t`는, 차례대로 `int`, `int`, `str`인 `tuple`이다.
|
||||
* 변수 `items_s`는, 각 아이템이 `bytes`인 `set`이다.
|
||||
|
||||
#### `Dict`
|
||||
|
||||
`dict`를 선언하려면 컴마로 구분된 2개의 파라미터가 필요합니다.
|
||||
|
||||
첫 번째 매개변수는 `dict`의 키(key)이고,
|
||||
|
||||
두 번째 매개변수는 `dict`의 값(value)입니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial008.py hl[1,4] *}
|
||||
|
||||
|
||||
이 뜻은 아래와 같습니다:
|
||||
|
||||
* 변수 `prices`는 `dict`이다:
|
||||
* `dict`의 키(key)는 `str`타입이다. (각 아이템의 이름(name))
|
||||
* `dict`의 값(value)는 `float`타입이다. (각 아이템의 가격(price))
|
||||
|
||||
#### `Optional`
|
||||
|
||||
`str`과 같이 타입을 선언할 때 `Optional`을 쓸 수도 있는데, "선택적(Optional)"이기때문에 `None`도 될 수 있습니다:
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!../../docs_src/python_types/tutorial009.py!}
|
||||
```
|
||||
|
||||
`Optional[str]`을 `str` 대신 쓰게 되면, 특정 값이 실제로는 `None`이 될 수도 있는데 항상 `str`이라고 가정하는 상황에서 에디터가 에러를 찾게 도와줄 수 있습니다.
|
||||
|
||||
#### Generic(제네릭) 타입
|
||||
|
||||
이 타입은 대괄호 안에 매개변수를 가지며, 종류는:
|
||||
|
||||
* `List`
|
||||
* `Tuple`
|
||||
* `Set`
|
||||
* `Dict`
|
||||
* `Optional`
|
||||
* ...등등
|
||||
|
||||
위와 같은 타입은 **Generic(제네릭) 타입** 혹은 **Generics(제네릭스)**라고 불립니다.
|
||||
|
||||
### 타입으로서의 클래스
|
||||
|
||||
변수의 타입으로 클래스를 선언할 수도 있습니다.
|
||||
|
||||
이름(name)을 가진 `Person` 클래스가 있다고 해봅시다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
|
||||
|
||||
|
||||
그렇게 하면 변수를 `Person`이라고 선언할 수 있게 됩니다.
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[6] *}
|
||||
|
||||
|
||||
그리고 역시나 모든 에디터 도움을 받게 되겠죠.
|
||||
|
||||
<img src="/img/python-types/image06.png">
|
||||
|
||||
## Pydantic 모델
|
||||
|
||||
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>은 데이터 검증(Validation)을 위한 파이썬 라이브러리입니다.
|
||||
|
||||
당신은 속성들을 포함한 클래스 형태로 "모양(shape)"을 선언할 수 있습니다.
|
||||
|
||||
그리고 각 속성은 타입을 가지고 있습니다.
|
||||
|
||||
이 클래스를 활용하여서 값을 가지고 있는 인스턴스를 만들게 되면, 필요한 경우에는 적당한 타입으로 변환까지 시키기도 하여 데이터가 포함된 객체를 반환합니다.
|
||||
|
||||
그리고 결과 객체에 대해서는 에디터의 도움을 받을 수 있게 됩니다.
|
||||
|
||||
Pydantic 공식 문서 예시:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial011.py *}
|
||||
|
||||
|
||||
/// info | 정보
|
||||
|
||||
Pydantic<에 대해 더 배우고 싶다면 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">공식 문서</a>를 참고하세요.</a>
|
||||
|
||||
///
|
||||
|
||||
**FastAPI**는 모두 Pydantic을 기반으로 되어 있습니다.
|
||||
|
||||
이 모든 것이 실제로 어떻게 사용되는지에 대해서는 [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank} 에서 더 많이 확인하실 수 있습니다.
|
||||
|
||||
## **FastAPI**에서의 타입 힌트
|
||||
|
||||
**FastAPI**는 여러 부분에서 타입 힌트의 장점을 취하고 있습니다.
|
||||
|
||||
**FastAPI**에서 타입 힌트와 함께 매개변수를 선언하면 장점은:
|
||||
|
||||
* **에디터 도움**.
|
||||
* **타입 확인**.
|
||||
|
||||
...그리고 **FastAPI**는 같은 정의를 아래에도 적용합니다:
|
||||
|
||||
* **요구사항 정의**: 요청 경로 매개변수, 쿼리 매개변수, 헤더, 바디, 의존성 등.
|
||||
* **데이터 변환**: 요청에서 요구한 타입으로.
|
||||
* **데이터 검증**: 각 요청마다:
|
||||
* 데이터가 유효하지 않은 경우에는 **자동으로 에러**를 발생합니다.
|
||||
* OpenAPI를 활용한 **API 문서화**:
|
||||
* 자동으로 상호작용하는 유저 인터페이스에 쓰이게 됩니다.
|
||||
|
||||
위 내용이 다소 추상적일 수도 있지만, 걱정마세요. [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank}에서 전부 확인 가능합니다.
|
||||
|
||||
가장 중요한 건, 표준 파이썬 타입을 한 곳에서(클래스를 더하거나, 데코레이터 사용하는 대신) 사용함으로써 **FastAPI**가 당신을 위해 많은 일을 해준다는 사실이죠.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
만약 모든 자습서를 다 보았음에도 타입에 대해서 더 보고자 방문한 경우에는 <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">`mypy`에서 제공하는 "cheat sheet"</a>이 좋은 자료가 될 겁니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,3 @@
|
||||
# 리소스
|
||||
|
||||
추가 리소스, 외부 링크, 기사 등. ✈️
|
||||
@@ -0,0 +1,19 @@
|
||||
# 고급 보안
|
||||
|
||||
## 추가 기능
|
||||
|
||||
[자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서에서 다룬 내용 외에도 보안 처리를 위한 몇 가지 추가 기능이 있습니다.
|
||||
|
||||
/// tip
|
||||
|
||||
다음 섹션은 **반드시 "고급"** 기능은 아닙니다.
|
||||
|
||||
그리고 여러분의 사용 사례에 따라, 적합한 해결책이 그 중 하나에 있을 가능성이 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 먼저 자습서 읽기
|
||||
|
||||
다음 섹션은 이미 [자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서를 읽었다고 가정합니다.
|
||||
|
||||
이 섹션들은 모두 동일한 개념을 바탕으로 하며, 추가 기능을 제공합니다.
|
||||
@@ -0,0 +1,84 @@
|
||||
# 백그라운드 작업
|
||||
|
||||
FastAPI에서는 응답을 반환한 후에 실행할 백그라운드 작업을 정의할 수 있습니다.
|
||||
|
||||
백그라운드 작업은 클라이언트가 응답을 받기 위해 작업이 완료될 때까지 기다릴 필요가 없기 때문에 요청 후에 발생해야하는 작업에 매우 유용합니다.
|
||||
|
||||
이러한 작업에는 다음이 포함됩니다.
|
||||
|
||||
* 작업을 수행한 후 전송되는 이메일 알림
|
||||
* 이메일 서버에 연결하고 이메일을 전송하는 것은 (몇 초 정도) "느린" 경향이 있으므로, 응답은 즉시 반환하고 이메일 알림은 백그라운드에서 전송하는 게 가능합니다.
|
||||
* 데이터 처리:
|
||||
* 예를 들어 처리에 오랜 시간이 걸리는 데이터를 받았을 때 "Accepted" (HTTP 202)을 반환하고, 백그라운드에서 데이터를 처리할 수 있습니다.
|
||||
|
||||
## `백그라운드 작업` 사용
|
||||
|
||||
먼저 아래와 같이 `BackgroundTasks`를 임포트하고, `BackgroundTasks`를 _경로 작동 함수_ 에서 매개변수로 가져오고 정의합니다.
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
|
||||
|
||||
**FastAPI** 는 `BackgroundTasks` 개체를 생성하고, 매개 변수로 전달합니다.
|
||||
|
||||
## 작업 함수 생성
|
||||
|
||||
백그라운드 작업으로 실행할 함수를 정의합니다.
|
||||
|
||||
이것은 단순히 매개변수를 받을 수 있는 표준 함수일 뿐입니다.
|
||||
|
||||
**FastAPI**는 이것이 `async def` 함수이든, 일반 `def` 함수이든 내부적으로 이를 올바르게 처리합니다.
|
||||
|
||||
이 경우, 아래 작업은 파일에 쓰는 함수입니다. (이메일 보내기 시물레이션)
|
||||
|
||||
그리고 이 작업은 `async`와 `await`를 사용하지 않으므로 일반 `def` 함수로 선언합니다.
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
|
||||
|
||||
## 백그라운드 작업 추가
|
||||
|
||||
_경로 작동 함수_ 내에서 작업 함수를 `.add_task()` 함수 통해 _백그라운드 작업_ 개체에 전달합니다.
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
|
||||
|
||||
`.add_task()` 함수는 다음과 같은 인자를 받습니다 :
|
||||
|
||||
- 백그라운드에서 실행되는 작업 함수 (`write_notification`).
|
||||
- 작업 함수에 순서대로 전달되어야 하는 일련의 인자 (`email`).
|
||||
- 작업 함수에 전달되어야하는 모든 키워드 인자 (`message="some notification"`).
|
||||
|
||||
## 의존성 주입
|
||||
|
||||
`BackgroundTasks`를 의존성 주입 시스템과 함께 사용하면 _경로 작동 함수_, 종속성, 하위 종속성 등 여러 수준에서 BackgroundTasks 유형의 매개변수를 선언할 수 있습니다.
|
||||
|
||||
**FastAPI**는 각 경우에 수행할 작업과 동일한 개체를 내부적으로 재사용하기에, 모든 백그라운드 작업이 함께 병합되고 나중에 백그라운드에서 실행됩니다.
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *}
|
||||
|
||||
이 예제에서는 응답이 반환된 후에 `log.txt` 파일에 메시지가 기록됩니다.
|
||||
|
||||
요청에 쿼리가 있는 경우 백그라운드 작업의 로그에 기록됩니다.
|
||||
|
||||
그리고 _경로 작동 함수_ 에서 생성된 또 다른 백그라운드 작업은 경로 매개 변수를 활용하여 사용하여 메시지를 작성합니다.
|
||||
|
||||
## 기술적 세부사항
|
||||
|
||||
`BackgroundTasks` 클래스는 <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>에서 직접 가져옵니다.
|
||||
|
||||
`BackgroundTasks` 클래스는 FastAPI에서 직접 임포트하거나 포함하기 때문에 실수로 `BackgroundTask` (끝에 `s`가 없음)을 임포트하더라도 starlette.background에서 `BackgroundTask`를 가져오는 것을 방지할 수 있습니다.
|
||||
|
||||
(`BackgroundTask`가 아닌) `BackgroundTasks`를 사용하면, _경로 작동 함수_ 매개변수로 사용할 수 있게 되고 나머지는 **FastAPI**가 대신 처리하도록 할 수 있습니다. 이것은 `Request` 객체를 직접 사용하는 것과 같은 방식입니다.
|
||||
|
||||
FastAPI에서 `BackgroundTask`를 단독으로 사용하는 것은 여전히 가능합니다. 하지만 객체를 코드에서 생성하고, 이 객체를 포함하는 Starlette `Response`를 반환해야 합니다.
|
||||
|
||||
<a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`Starlette의 공식 문서`</a>에서 백그라운드 작업에 대한 자세한 내용을 확인할 수 있습니다.
|
||||
|
||||
## 경고
|
||||
|
||||
만약 무거운 백그라운드 작업을 수행해야하고 동일한 프로세스에서 실행할 필요가 없는 경우 (예: 메모리, 변수 등을 공유할 필요가 없음) <a href="https://docs.celeryq.dev" class="external-link" target="_blank">`Celery`</a>와 같은 큰 도구를 사용하면 도움이 될 수 있습니다.
|
||||
|
||||
RabbitMQ 또는 Redis와 같은 메시지/작업 큐 시스템 보다 복잡한 구성이 필요한 경향이 있지만, 여러 작업 프로세스를 특히 여러 서버의 백그라운드에서 실행할 수 있습니다.
|
||||
|
||||
그러나 동일한 FastAPI 앱에서 변수 및 개체에 접근해야햐는 작은 백그라운드 수행이 필요한 경우 (예 : 알림 이메일 보내기) 간단하게 `BackgroundTasks`를 사용해보세요.
|
||||
|
||||
## 요약
|
||||
|
||||
백그라운드 작업을 추가하기 위해 _경로 작동 함수_ 에 매개변수로 `BackgroundTasks`를 가져오고 사용합니다.
|
||||
@@ -0,0 +1,60 @@
|
||||
# 본문 - 필드
|
||||
|
||||
`Query`, `Path`와 `Body`를 사용해 *경로 작동 함수* 매개변수 내에서 추가적인 검증이나 메타데이터를 선언한 것처럼 Pydantic의 `Field`를 사용하여 모델 내에서 검증과 메타데이터를 선언할 수 있습니다.
|
||||
|
||||
## `Field` 임포트
|
||||
|
||||
먼저 이를 임포트해야 합니다:
|
||||
|
||||
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
`Field`는 다른 것들처럼 (`Query`, `Path`, `Body` 등) `fastapi`에서가 아닌 `pydantic`에서 바로 임포트 되는 점에 주의하세요.
|
||||
|
||||
///
|
||||
|
||||
## 모델 어트리뷰트 선언
|
||||
|
||||
그 다음 모델 어트리뷰트와 함께 `Field`를 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
|
||||
|
||||
`Field`는 `Query`, `Path`와 `Body`와 같은 방식으로 동작하며, 모두 같은 매개변수들 등을 가집니다.
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
실제로 `Query`, `Path`등, 여러분이 앞으로 볼 다른 것들은 공통 클래스인 `Param` 클래스의 서브클래스 객체를 만드는데, 그 자체로 Pydantic의 `FieldInfo` 클래스의 서브클래스입니다.
|
||||
|
||||
그리고 Pydantic의 `Field` 또한 `FieldInfo`의 인스턴스를 반환합니다.
|
||||
|
||||
`Body` 또한 `FieldInfo`의 서브클래스 객체를 직접적으로 반환합니다. 그리고 `Body` 클래스의 서브클래스인 것들도 여러분이 나중에 보게될 것입니다.
|
||||
|
||||
`Query`, `Path`와 그 외 것들을 `fastapi`에서 임포트할 때, 이는 실제로 특별한 클래스를 반환하는 함수인 것을 기억해 주세요.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
주목할 점은 타입, 기본 값 및 `Field`로 이루어진 각 모델 어트리뷰트가 `Path`, `Query`와 `Body`대신 `Field`를 사용하는 *경로 작동 함수*의 매개변수와 같은 구조를 가진다는 점 입니다.
|
||||
|
||||
///
|
||||
|
||||
## 별도 정보 추가
|
||||
|
||||
`Field`, `Query`, `Body`, 그 외 안에 별도 정보를 선언할 수 있습니다. 이는 생성된 JSON 스키마에 포함됩니다.
|
||||
|
||||
여러분이 예제를 선언할 때 나중에 이 공식 문서에서 별도 정보를 추가하는 방법을 배울 것입니다.
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
별도 키가 전달된 `Field` 또한 여러분의 어플리케이션의 OpenAPI 스키마에 나타날 것입니다.
|
||||
이런 키가 OpenAPI 명세서, [the OpenAPI validator](https://validator.swagger.io/)같은 몇몇 OpenAPI 도구들에 포함되지 못할 수 있으며, 여러분이 생성한 스키마와 호환되지 않을 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
모델 어트리뷰트를 위한 추가 검증과 메타데이터 선언하기 위해 Pydantic의 `Field` 를 사용할 수 있습니다.
|
||||
|
||||
또한 추가적인 JSON 스키마 메타데이터를 전달하기 위한 별도의 키워드 인자를 사용할 수 있습니다.
|
||||
@@ -0,0 +1,169 @@
|
||||
# 본문 - 다중 매개변수
|
||||
|
||||
지금부터 `Path`와 `Query`를 어떻게 사용하는지 확인하겠습니다.
|
||||
|
||||
요청 본문 선언에 대한 심화 사용법을 알아보겠습니다.
|
||||
|
||||
## `Path`, `Query` 및 본문 매개변수 혼합
|
||||
|
||||
당연하게 `Path`, `Query` 및 요청 본문 매개변수 선언을 자유롭게 혼합해서 사용할 수 있고, **FastAPI**는 어떤 동작을 할지 압니다.
|
||||
|
||||
또한, 기본 값을 `None`으로 설정해 본문 매개변수를 선택사항으로 선언할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial001.py hl[19:21] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 경우에는 본문으로 부터 가져온 ` item`은 기본값이 `None`이기 때문에, 선택사항이라는 점을 유의해야 합니다.
|
||||
|
||||
///
|
||||
|
||||
## 다중 본문 매개변수
|
||||
|
||||
이전 예제에서 보듯이, *경로 작동*은 아래와 같이 `Item` 속성을 가진 JSON 본문을 예상합니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2
|
||||
}
|
||||
```
|
||||
|
||||
하지만, 다중 본문 매개변수 역시 선언할 수 있습니다. 예. `item`과 `user`:
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *}
|
||||
|
||||
이 경우에, **FastAPI**는 이 함수 안에 한 개 이상의 본문 매개변수(Pydantic 모델인 두 매개변수)가 있다고 알 것입니다.
|
||||
|
||||
그래서, 본문의 매개변수 이름을 키(필드 명)로 사용할 수 있고, 다음과 같은 본문을 예측합니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2
|
||||
},
|
||||
"user": {
|
||||
"username": "dave",
|
||||
"full_name": "Dave Grohl"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이전과 같이 `item`이 선언 되었더라도, 본문 내의 `item` 키가 있을 것이라고 예측합니다.
|
||||
|
||||
///
|
||||
|
||||
FastAPI는 요청을 자동으로 변환해, 매개변수의 `item`과 `user`를 특별한 내용으로 받도록 할 것입니다.
|
||||
|
||||
복합 데이터의 검증을 수행하고 OpenAPI 스키마 및 자동 문서를 문서화합니다.
|
||||
|
||||
## 본문 내의 단일 값
|
||||
|
||||
쿼리 및 경로 매개변수에 대한 추가 데이터를 정의하는 `Query`와 `Path`와 같이, **FastAPI**는 동등한 `Body`를 제공합니다.
|
||||
|
||||
예를 들어 이전의 모델을 확장하면, `item`과 `user`와 동일한 본문에 또 다른 `importance`라는 키를 갖도록 할 수있습니다.
|
||||
|
||||
단일 값을 그대로 선언한다면, **FastAPI**는 쿼리 매개변수로 가정할 것입니다.
|
||||
|
||||
하지만, **FastAPI**의 `Body`를 사용해 다른 본문 키로 처리하도록 제어할 수 있습니다:
|
||||
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial003.py hl[23] *}
|
||||
|
||||
이 경우에는 **FastAPI**는 본문을 이와 같이 예측할 것입니다:
|
||||
|
||||
|
||||
```JSON
|
||||
{
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2
|
||||
},
|
||||
"user": {
|
||||
"username": "dave",
|
||||
"full_name": "Dave Grohl"
|
||||
},
|
||||
"importance": 5
|
||||
}
|
||||
```
|
||||
|
||||
다시 말해, 데이터 타입, 검증, 문서 등을 변환합니다.
|
||||
|
||||
## 다중 본문 매개변수와 쿼리
|
||||
|
||||
당연히, 필요할 때마다 추가적인 쿼리 매개변수를 선언할 수 있고, 이는 본문 매개변수에 추가됩니다.
|
||||
|
||||
기본적으로 단일 값은 쿼리 매개변수로 해석되므로, 명시적으로 `Query`를 추가할 필요가 없고, 아래처럼 할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *}
|
||||
|
||||
이렇게:
|
||||
|
||||
```Python
|
||||
q: Optional[str] = None
|
||||
```
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`Body` 또한 `Query`, `Path` 그리고 이후에 볼 다른 것들처럼 동일한 추가 검증과 메타데이터 매개변수를 갖고 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 단일 본문 매개변수 삽입하기
|
||||
|
||||
Pydantic 모델 `Item`의 `item`을 본문 매개변수로 오직 한개만 갖고있다고 하겠습니다.
|
||||
|
||||
기본적으로 **FastAPI**는 직접 본문으로 예측할 것입니다.
|
||||
|
||||
하지만, 만약 모델 내용에 `item `키를 가진 JSON으로 예측하길 원한다면, 추가적인 본문 매개변수를 선언한 것처럼 `Body`의 특별한 매개변수인 `embed`를 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *}
|
||||
|
||||
아래 처럼:
|
||||
|
||||
```Python
|
||||
item: Item = Body(..., embed=True)
|
||||
```
|
||||
|
||||
이 경우에 **FastAPI**는 본문을 아래 대신에:
|
||||
|
||||
```JSON hl_lines="2"
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2
|
||||
}
|
||||
```
|
||||
|
||||
아래 처럼 예측할 것 입니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"item": {
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 정리
|
||||
|
||||
요청이 단 한개의 본문을 가지고 있더라도, *경로 작동 함수*로 다중 본문 매개변수를 추가할 수 있습니다.
|
||||
|
||||
하지만, **FastAPI**는 이를 처리하고, 함수에 올바른 데이터를 제공하며, *경로 작동*으로 올바른 스키마를 검증하고 문서화 합니다.
|
||||
|
||||
또한, 단일 값을 본문의 일부로 받도록 선언할 수 있습니다.
|
||||
|
||||
그리고 **FastAPI**는 단 한개의 매개변수가 선언 되더라도, 본문 내의 키로 삽입 시킬 수 있습니다.
|
||||
@@ -0,0 +1,230 @@
|
||||
# 본문 - 중첩 모델
|
||||
|
||||
**FastAPI**를 이용하면 (Pydantic 덕분에) 단독으로 깊이 중첩된 모델을 정의, 검증, 문서화하며 사용할 수 있습니다.
|
||||
## 리스트 필드
|
||||
|
||||
어트리뷰트를 서브타입으로 정의할 수 있습니다. 예를 들어 파이썬 `list`는:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *}
|
||||
|
||||
이는 `tags`를 항목 리스트로 만듭니다. 각 항목의 타입을 선언하지 않더라도요.
|
||||
|
||||
## 타입 매개변수가 있는 리스트 필드
|
||||
|
||||
하지만 파이썬은 내부의 타입이나 "타입 매개변수"를 선언할 수 있는 특정 방법이 있습니다:
|
||||
|
||||
### typing의 `List` 임포트
|
||||
|
||||
먼저, 파이썬 표준 `typing` 모듈에서 `List`를 임포트합니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
|
||||
|
||||
### 타입 매개변수로 `List` 선언
|
||||
|
||||
`list`, `dict`, `tuple`과 같은 타입 매개변수(내부 타입)를 갖는 타입을 선언하려면:
|
||||
|
||||
* `typing` 모듈에서 임포트
|
||||
* 대괄호를 사용하여 "타입 매개변수"로 내부 타입 전달: `[` 및 `]`
|
||||
|
||||
```Python
|
||||
from typing import List
|
||||
|
||||
my_list: List[str]
|
||||
```
|
||||
|
||||
이 모든 것은 타입 선언을 위한 표준 파이썬 문법입니다.
|
||||
|
||||
내부 타입을 갖는 모델 어트리뷰트에 대해 동일한 표준 문법을 사용하세요.
|
||||
|
||||
마찬가지로 예제에서 `tags`를 구체적으로 "문자열의 리스트"로 만들 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *}
|
||||
|
||||
## 집합 타입
|
||||
|
||||
그런데 생각해보니 태그는 반복되면 안 되고, 고유한(Unique) 문자열이어야 할 것 같습니다.
|
||||
|
||||
그리고 파이썬은 집합을 위한 특별한 데이터 타입 `set`이 있습니다.
|
||||
|
||||
그렇다면 `Set`을 임포트 하고 `tags`를 `str`의 `set`으로 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *}
|
||||
|
||||
덕분에 중복 데이터가 있는 요청을 수신하더라도 고유한 항목들의 집합으로 변환됩니다.
|
||||
|
||||
그리고 해당 데이터를 출력 할 때마다 소스에 중복이 있더라도 고유한 항목들의 집합으로 출력됩니다.
|
||||
|
||||
또한 그에 따라 주석이 생기고 문서화됩니다.
|
||||
|
||||
## 중첩 모델
|
||||
|
||||
Pydantic 모델의 각 어트리뷰트는 타입을 갖습니다.
|
||||
|
||||
그런데 해당 타입 자체로 또다른 Pydantic 모델의 타입이 될 수 있습니다.
|
||||
|
||||
그러므로 특정한 어트리뷰트의 이름, 타입, 검증을 사용하여 깊게 중첩된 JSON "객체"를 선언할 수 있습니다.
|
||||
|
||||
모든 것이 단독으로 중첩됩니다.
|
||||
|
||||
### 서브모델 정의
|
||||
|
||||
예를 들어, `Image` 모델을 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *}
|
||||
|
||||
### 서브모듈을 타입으로 사용
|
||||
|
||||
그리고 어트리뷰트의 타입으로 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *}
|
||||
|
||||
이는 **FastAPI**가 다음과 유사한 본문을 기대한다는 것을 의미합니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2,
|
||||
"tags": ["rock", "metal", "bar"],
|
||||
"image": {
|
||||
"url": "http://example.com/baz.jpg",
|
||||
"name": "The Foo live"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
다시 한번, **FastAPI**를 사용하여 해당 선언을 함으로써 얻는 것은:
|
||||
|
||||
* 중첩 모델도 편집기 지원(자동완성 등)
|
||||
* 데이터 변환
|
||||
* 데이터 검증
|
||||
* 자동 문서화
|
||||
|
||||
## 특별한 타입과 검증
|
||||
|
||||
`str`, `int`, `float` 등과 같은 단일 타입과는 별개로, `str`을 상속하는 더 복잡한 단일 타입을 사용할 수 있습니다.
|
||||
|
||||
모든 옵션을 보려면, <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Pydantic's exotic types</a> 문서를 확인하세요. 다음 장에서 몇가지 예제를 볼 수 있습니다.
|
||||
|
||||
예를 들어 `Image` 모델 안에 `url` 필드를 `str` 대신 Pydantic의 `HttpUrl`로 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *}
|
||||
|
||||
이 문자열이 유효한 URL인지 검사하고 JSON 스키마/OpenAPI로 문서화 됩니다.
|
||||
|
||||
## 서브모델 리스트를 갖는 어트리뷰트
|
||||
|
||||
`list`, `set` 등의 서브타입으로 Pydantic 모델을 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *}
|
||||
|
||||
아래와 같은 JSON 본문으로 예상(변환, 검증, 문서화 등을)합니다:
|
||||
|
||||
```JSON hl_lines="11"
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "The pretender",
|
||||
"price": 42.0,
|
||||
"tax": 3.2,
|
||||
"tags": [
|
||||
"rock",
|
||||
"metal",
|
||||
"bar"
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"url": "http://example.com/baz.jpg",
|
||||
"name": "The Foo live"
|
||||
},
|
||||
{
|
||||
"url": "http://example.com/dave.jpg",
|
||||
"name": "The Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`images` 키가 어떻게 이미지 객체 리스트를 갖는지 주목하세요.
|
||||
|
||||
///
|
||||
|
||||
## 깊게 중첩된 모델
|
||||
|
||||
단독으로 깊게 중첩된 모델을 정의할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`Offer`가 선택사항 `Image` 리스트를 차례로 갖는 `Item` 리스트를 어떻게 가지고 있는지 주목하세요
|
||||
|
||||
///
|
||||
|
||||
## 순수 리스트의 본문
|
||||
|
||||
예상되는 JSON 본문의 최상위 값이 JSON `array`(파이썬 `list`)면, Pydantic 모델에서와 마찬가지로 함수의 매개변수에서 타입을 선언할 수 있습니다:
|
||||
|
||||
```Python
|
||||
images: List[Image]
|
||||
```
|
||||
|
||||
이를 아래처럼:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *}
|
||||
|
||||
## 어디서나 편집기 지원
|
||||
|
||||
그리고 어디서나 편집기 지원을 받을수 있습니다.
|
||||
|
||||
리스트 내부 항목의 경우에도:
|
||||
|
||||
<img src="/img/tutorial/body-nested-models/image01.png">
|
||||
|
||||
Pydantic 모델 대신에 `dict`를 직접 사용하여 작업할 경우, 이러한 편집기 지원을 받을수 없습니다.
|
||||
|
||||
하지만 수신한 딕셔너리가 자동으로 변환되고 출력도 자동으로 JSON으로 변환되므로 걱정할 필요는 없습니다.
|
||||
|
||||
## 단독 `dict`의 본문
|
||||
|
||||
일부 타입의 키와 다른 타입의 값을 사용하여 `dict`로 본문을 선언할 수 있습니다.
|
||||
|
||||
(Pydantic을 사용한 경우처럼) 유효한 필드/어트리뷰트 이름이 무엇인지 알 필요가 없습니다.
|
||||
|
||||
아직 모르는 키를 받으려는 경우 유용합니다.
|
||||
|
||||
---
|
||||
|
||||
다른 유용한 경우는 다른 타입의 키를 가질 때입니다. 예. `int`.
|
||||
|
||||
여기서 그 경우를 볼 것입니다.
|
||||
|
||||
이 경우, `float` 값을 가진 `int` 키가 있는 모든 `dict`를 받아들입니다:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial009.py hl[15] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
JSON은 오직 `str`형 키만 지원한다는 것을 염두에 두세요.
|
||||
|
||||
하지만 Pydantic은 자동 데이터 변환이 있습니다.
|
||||
|
||||
즉, API 클라이언트가 문자열을 키로 보내더라도 해당 문자열이 순수한 정수를 포함하는한 Pydantic은 이를 변환하고 검증합니다.
|
||||
|
||||
그러므로 `weights`로 받은 `dict`는 실제로 `int` 키와 `float` 값을 가집니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
**FastAPI**를 사용하면 Pydantic 모델이 제공하는 최대 유연성을 확보하면서 코드를 간단하고 짧게, 그리고 우아하게 유지할 수 있습니다.
|
||||
|
||||
물론 아래의 이점도 있습니다:
|
||||
|
||||
* 편집기 지원 (자동완성이 어디서나!)
|
||||
* 데이터 변환 (일명 파싱/직렬화)
|
||||
* 데이터 검증
|
||||
* 스키마 문서화
|
||||
* 자동 문서
|
||||
@@ -0,0 +1,162 @@
|
||||
# 요청 본문
|
||||
|
||||
클라이언트(브라우저라고 해봅시다)로부터 여러분의 API로 데이터를 보내야 할 때, **요청 본문**으로 보냅니다.
|
||||
|
||||
**요청** 본문은 클라이언트에서 API로 보내지는 데이터입니다. **응답** 본문은 API가 클라이언트로 보내는 데이터입니다.
|
||||
|
||||
여러분의 API는 대부분의 경우 **응답** 본문을 보내야 합니다. 하지만 클라이언트는 **요청** 본문을 매 번 보낼 필요가 없습니다.
|
||||
|
||||
**요청** 본문을 선언하기 위해서 모든 강력함과 이점을 갖춘 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 모델을 사용합니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
데이터를 보내기 위해, (좀 더 보편적인) `POST`, `PUT`, `DELETE` 혹은 `PATCH` 중에 하나를 사용하는 것이 좋습니다.
|
||||
|
||||
`GET` 요청에 본문을 담아 보내는 것은 명세서에 정의되지 않은 행동입니다. 그럼에도 불구하고, 이 방식은 아주 복잡한/극한의 사용 상황에서만 FastAPI에 의해 지원됩니다.
|
||||
|
||||
`GET` 요청에 본문을 담는 것은 권장되지 않기에, Swagger UI같은 대화형 문서에서는 `GET` 사용시 담기는 본문에 대한 문서를 표시하지 않으며, 중간에 있는 프록시는 이를 지원하지 않을 수도 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## Pydantic의 `BaseModel` 임포트
|
||||
|
||||
먼저 `pydantic`에서 `BaseModel`를 임포트해야 합니다:
|
||||
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
|
||||
|
||||
## 여러분의 데이터 모델 만들기
|
||||
|
||||
`BaseModel`를 상속받은 클래스로 여러분의 데이터 모델을 선언합니다.
|
||||
|
||||
모든 어트리뷰트에 대해 표준 파이썬 타입을 사용합니다:
|
||||
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
|
||||
|
||||
쿼리 매개변수를 선언할 때와 같이, 모델 어트리뷰트가 기본 값을 가지고 있어도 이는 필수가 아닙니다. 그외에는 필수입니다. 그저 `None`을 사용하여 선택적으로 만들 수 있습니다.
|
||||
|
||||
예를 들면, 위의 이 모델은 JSON "`object`" (혹은 파이썬 `dict`)을 다음과 같이 선언합니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "선택적인 설명란",
|
||||
"price": 45.2,
|
||||
"tax": 3.5
|
||||
}
|
||||
```
|
||||
|
||||
...`description`과 `tax`는 (기본 값이 `None`으로 되어 있어) 선택적이기 때문에, 이 JSON "`object`"는 다음과 같은 상황에서도 유효합니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"name": "Foo",
|
||||
"price": 45.2
|
||||
}
|
||||
```
|
||||
|
||||
## 매개변수로서 선언하기
|
||||
|
||||
여러분의 *경로 작동*에 추가하기 위해, 경로 매개변수 그리고 쿼리 매개변수에서 선언했던 것과 같은 방식으로 선언하면 됩니다.
|
||||
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
|
||||
|
||||
...그리고 만들어낸 모델인 `Item`으로 타입을 선언합니다.
|
||||
|
||||
## 결과
|
||||
|
||||
위에서의 단순한 파이썬 타입 선언으로, **FastAPI**는 다음과 같이 동작합니다:
|
||||
|
||||
* 요청의 본문을 JSON으로 읽어 들입니다.
|
||||
* (필요하다면) 대응되는 타입으로 변환합니다.
|
||||
* 데이터를 검증합니다.
|
||||
* 만약 데이터가 유효하지 않다면, 정확히 어떤 것이 그리고 어디에서 데이터가 잘 못 되었는지 지시하는 친절하고 명료한 에러를 반환할 것입니다.
|
||||
* 매개변수 `item`에 포함된 수신 데이터를 제공합니다.
|
||||
* 함수 내에서 매개변수를 `Item` 타입으로 선언했기 때문에, 모든 어트리뷰트와 그에 대한 타입에 대한 편집기 지원(완성 등)을 또한 받을 수 있습니다.
|
||||
* 여러분의 모델을 위한 <a href="https://json-schema.org" class="external-link" target="_blank">JSON 스키마</a> 정의를 생성합니다. 여러분의 프로젝트에 적합하다면 여러분이 사용하고 싶은 곳 어디에서나 사용할 수 있습니다.
|
||||
* 이러한 스키마는, 생성된 OpenAPI 스키마 일부가 될 것이며, 자동 문서화 <abbr title="사용자 인터페이스">UI</abbr>에 사용됩니다.
|
||||
|
||||
## 자동 문서화
|
||||
|
||||
모델의 JSON 스키마는 생성된 OpenAPI 스키마에 포함되며 대화형 API 문서에 표시됩니다:
|
||||
|
||||
<img src="/img/tutorial/body/image01.png">
|
||||
|
||||
이를 필요로 하는 각각의 *경로 작동*내부의 API 문서에도 사용됩니다:
|
||||
|
||||
<img src="/img/tutorial/body/image02.png">
|
||||
|
||||
## 편집기 지원
|
||||
|
||||
편집기에서, 함수 내에서 타입 힌트와 완성을 어디서나 (만약 Pydantic model 대신에 `dict`을 받을 경우 나타나지 않을 수 있습니다) 받을 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/body/image03.png">
|
||||
|
||||
잘못된 타입 연산에 대한 에러 확인도 받을 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/body/image04.png">
|
||||
|
||||
단순한 우연이 아닙니다. 프레임워크 전체가 이러한 디자인을 중심으로 설계되었습니다.
|
||||
|
||||
그 어떤 실행 전에, 모든 편집기에서 작동할 수 있도록 보장하기 위해 설계 단계에서 혹독하게 테스트되었습니다.
|
||||
|
||||
이를 지원하기 위해 Pydantic 자체에서 몇몇 변경점이 있었습니다.
|
||||
|
||||
이전 스크린샷은 <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>를 찍은 것입니다.
|
||||
|
||||
하지만 똑같은 편집기 지원을 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>에서 받을 수 있거나, 대부분의 다른 편집기에서도 받을 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/body/image05.png">
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
만약 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a>를 편집기로 사용한다면, <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>을 사용할 수 있습니다.
|
||||
|
||||
다음 사항을 포함해 Pydantic 모델에 대한 편집기 지원을 향상시킵니다:
|
||||
|
||||
* 자동 완성
|
||||
* 타입 확인
|
||||
* 리팩토링
|
||||
* 검색
|
||||
* 점검
|
||||
|
||||
///
|
||||
|
||||
## 모델 사용하기
|
||||
|
||||
함수 안에서 모델 객체의 모든 어트리뷰트에 직접 접근 가능합니다:
|
||||
|
||||
{* ../../docs_src/body/tutorial002_py310.py hl[19] *}
|
||||
|
||||
## 요청 본문 + 경로 매개변수
|
||||
|
||||
경로 매개변수와 요청 본문을 동시에 선언할 수 있습니다.
|
||||
|
||||
**FastAPI**는 경로 매개변수와 일치하는 함수 매개변수가 **경로에서 가져와야 한다**는 것을 인지하며, Pydantic 모델로 선언된 그 함수 매개변수는 **요청 본문에서 가져와야 한다**는 것을 인지할 것입니다.
|
||||
|
||||
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
|
||||
|
||||
## 요청 본문 + 경로 + 쿼리 매개변수
|
||||
|
||||
**본문**, **경로** 그리고 **쿼리** 매개변수 모두 동시에 선언할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 각각을 인지하고 데이터를 옳바른 위치에 가져올 것입니다.
|
||||
|
||||
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
|
||||
|
||||
함수 매개변수는 다음을 따라서 인지하게 됩니다:
|
||||
|
||||
* 만약 매개변수가 **경로**에도 선언되어 있다면, 이는 경로 매개변수로 사용될 것입니다.
|
||||
* 만약 매개변수가 (`int`, `float`, `str`, `bool` 등과 같은) **유일한 타입**으로 되어있으면, **쿼리** 매개변수로 해석될 것입니다.
|
||||
* 만약 매개변수가 **Pydantic 모델** 타입으로 선언되어 있으면, 요청 **본문**으로 해석될 것입니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
FastAPI는 `q`의 값이 필요없음을 알게 될 것입니다. 기본 값이 `= None`이기 때문입니다.
|
||||
|
||||
`Union[str, None]`에 있는 `Union`은 FastAPI에 의해 사용된 것이 아니지만, 편집기로 하여금 더 나은 지원과 에러 탐지를 지원할 것입니다.
|
||||
|
||||
///
|
||||
|
||||
## Pydantic없이
|
||||
|
||||
만약 Pydantic 모델을 사용하고 싶지 않다면, **Body** 매개변수를 사용할 수도 있습니다. [Body - 다중 매개변수: 본문에 있는 유일한 값](body-multiple-params.md#_2){.internal-link target=_blank} 문서를 확인하세요.
|
||||
@@ -0,0 +1,76 @@
|
||||
# 쿠키 매개변수 모델
|
||||
|
||||
관련있는 **쿠키**들의 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다. 🍪
|
||||
|
||||
이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😍
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.115.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
동일한 기술이 `Query`, `Cookie`, 그리고 `Header`에 적용됩니다. 😎
|
||||
|
||||
///
|
||||
|
||||
## Pydantic 모델을 사용한 쿠키
|
||||
|
||||
**Pydantic 모델**에 필요한 **쿠키** 매개변수를 선언한 다음, 해당 매개변수를 `Cookie`로 선언합니다:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *}
|
||||
|
||||
**FastAPI**는 요청에서 받은 **쿠키**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
|
||||
|
||||
## 문서 확인하기
|
||||
|
||||
문서 UI `/docs`에서 정의한 쿠키를 볼 수 있습니다:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/cookie-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
/// info | 정보
|
||||
|
||||
명심하세요, 내부적으로 **브라우저는 쿠키를 특별한 방식으로 처리**하기 때문에 **자바스크립트**가 쉽게 쿠키를 건드릴 수 **없습니다**.
|
||||
|
||||
`/docs`에서 **API 문서 UI**로 이동하면 *경로 작업*에 대한 쿠키의 **문서**를 볼 수 있습니다.
|
||||
|
||||
하지만 아무리 **데이터를 입력**하고 "실행(Execute)"을 클릭해도, 문서 UI는 **자바스크립트**로 작동하기 때문에 쿠키는 전송되지 않고, 아무 값도 쓰지 않은 것처럼 **오류** 메시지를 보게 됩니다.
|
||||
|
||||
///
|
||||
|
||||
## 추가 쿠키 금지하기
|
||||
|
||||
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 쿠키를 **제한**할 수 있습니다.
|
||||
|
||||
이제 API는 자신의 <abbr title="농담입니다, 혹시나 해서요. 쿠키 동의와 관련해서 할 수 있는 것은 없지만, 이제 API조차도 잘못된 쿠키를 거부할 수 있다는 점이 재밌습니다. 쿠키 드세요. 🍪">쿠키 동의</abbr>를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
|
||||
|
||||
클라이언트가 **추가 쿠키**를 보내려고 시도하면, **오류** 응답을 받게 됩니다.
|
||||
|
||||
<abbr title="이건 또 다른 농담입니다. 제 말에 귀 기울이지 마세요. 커피랑 쿠키 좀 드세요. ☕">API가 거부</abbr>하는데도 동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들. 🍪
|
||||
|
||||
예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` <abbr title="산타는 쿠키가 부족한 것을 못마땅해합니다. 🎅 알겠습니다, 쿠키 농담은 이제 없습니다.">쿠키가 허용되지 않는다</abbr>는 **오류** 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["cookie", "santa_tracker"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "good-list-please",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
**Pydantic 모델**을 사용하여 **FastAPI**에서 <abbr title="가기 전에 마지막 쿠키를 드세요. 🍪">**쿠키**</abbr>를 선언할 수 있습니다. 😍
|
||||
@@ -0,0 +1,35 @@
|
||||
# 쿠키 매개변수
|
||||
|
||||
쿠키 매개변수를 `Query`와 `Path` 매개변수들과 같은 방식으로 정의할 수 있습니다.
|
||||
|
||||
## `Cookie` 임포트
|
||||
|
||||
먼저 `Cookie`를 임포트합니다:
|
||||
|
||||
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## `Cookie` 매개변수 선언
|
||||
|
||||
그런 다음, `Path`와 `Query`처럼 동일한 구조를 사용하는 쿠키 매개변수를 선언합니다.
|
||||
|
||||
첫 번째 값은 기본값이며, 추가 검증이나 어노테이션 매개변수 모두 전달할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`Cookie`는 `Path` 및 `Query`의 "자매"클래스입니다. 이 역시 동일한 공통 `Param` 클래스를 상속합니다.
|
||||
|
||||
`Query`, `Path`, `Cookie` 그리고 다른 것들은 `fastapi`에서 임포트 할 때, 실제로는 특별한 클래스를 반환하는 함수임을 기억하세요.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
|
||||
쿠키를 선언하기 위해서는 `Cookie`를 사용해야 합니다. 그렇지 않으면 해당 매개변수를 쿼리 매개변수로 해석하기 때문입니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
`Cookie`는 `Query`, `Path`와 동일한 패턴을 사용하여 선언합니다.
|
||||
@@ -0,0 +1,85 @@
|
||||
# 교차 출처 리소스 공유
|
||||
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS 또는 "교차-출처 리소스 공유"</a>란, 브라우저에서 동작하는 프론트엔드가 자바스크립트로 코드로 백엔드와 통신하고, 백엔드는 해당 프론트엔드와 다른 "출처"에 존재하는 상황을 의미합니다.
|
||||
|
||||
## 출처
|
||||
|
||||
출처란 프로토콜(`http` , `https`), 도메인(`myapp.com`, `localhost`, `localhost.tiangolo.com` ), 그리고 포트(`80`, `443`, `8080` )의 조합을 의미합니다.
|
||||
|
||||
따라서, 아래는 모두 상이한 출처입니다:
|
||||
|
||||
* `http://localhost`
|
||||
* `https://localhost`
|
||||
* `http://localhost:8080`
|
||||
|
||||
모두 `localhost` 에 있지만, 서로 다른 프로토콜과 포트를 사용하고 있으므로 다른 "출처"입니다.
|
||||
|
||||
## 단계
|
||||
|
||||
브라우저 내 `http://localhost:8080`에서 동작하는 프론트엔드가 있고, 자바스크립트는 `http://localhost`를 통해 백엔드와 통신한다고 가정해봅시다(포트를 명시하지 않는 경우, 브라우저는 `80` 을 기본 포트로 간주합니다).
|
||||
|
||||
그러면 브라우저는 백엔드에 HTTP `OPTIONS` 요청을 보내고, 백엔드에서 이 다른 출처(`http://localhost:8080`)와의 통신을 허가하는 적절한 헤더를 보내면, 브라우저는 프론트엔드의 자바스크립트가 백엔드에 요청을 보낼 수 있도록 합니다.
|
||||
|
||||
이를 위해, 백엔드는 "허용된 출처(allowed origins)" 목록을 가지고 있어야만 합니다.
|
||||
|
||||
이 경우, 프론트엔드가 제대로 동작하기 위해 `http://localhost:8080`을 목록에 포함해야 합니다.
|
||||
|
||||
## 와일드카드
|
||||
|
||||
모든 출처를 허용하기 위해 목록을 `"*"` ("와일드카드")로 선언하는 것도 가능합니다.
|
||||
|
||||
하지만 이것은 특정한 유형의 통신만을 허용하며, 쿠키 및 액세스 토큰과 사용되는 인증 헤더(Authoriztion header) 등이 포함된 경우와 같이 자격 증명(credentials)이 포함된 통신은 허용되지 않습니다.
|
||||
|
||||
따라서 모든 작업을 의도한대로 실행하기 위해, 허용되는 출처를 명시적으로 지정하는 것이 좋습니다.
|
||||
|
||||
## `CORSMiddleware` 사용
|
||||
|
||||
`CORSMiddleware` 을 사용하여 **FastAPI** 응용 프로그램의 교차 출처 리소스 공유 환경을 설정할 수 있습니다.
|
||||
|
||||
* `CORSMiddleware` 임포트.
|
||||
* 허용되는 출처(문자열 형식)의 리스트 생성.
|
||||
* FastAPI 응용 프로그램에 "미들웨어(middleware)"로 추가.
|
||||
|
||||
백엔드에서 다음의 사항을 허용할지에 대해 설정할 수도 있습니다:
|
||||
|
||||
* 자격증명 (인증 헤더, 쿠키 등).
|
||||
* 특정한 HTTP 메소드(`POST`, `PUT`) 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 메소드.
|
||||
* 특정한 HTTP 헤더 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 헤더.
|
||||
|
||||
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
|
||||
|
||||
`CORSMiddleware` 에서 사용하는 기본 매개변수는 제한적이므로, 브라우저가 교차-도메인 상황에서 특정한 출처, 메소드, 헤더 등을 사용할 수 있도록 하려면 이들을 명시적으로 허용해야 합니다.
|
||||
|
||||
다음의 인자들이 지원됩니다:
|
||||
|
||||
* `allow_origins` - 교차-출처 요청을 보낼 수 있는 출처의 리스트입니다. 예) `['https://example.org', 'https://www.example.org']`. 모든 출처를 허용하기 위해 `['*']` 를 사용할 수 있습니다.
|
||||
* `allow_origin_regex` - 교차-출처 요청을 보낼 수 있는 출처를 정규표현식 문자열로 나타냅니다. `'https://.*\.example\.org'`.
|
||||
* `allow_methods` - 교차-출처 요청을 허용하는 HTTP 메소드의 리스트입니다. 기본값은 `['GET']` 입니다. `['*']` 을 사용하여 모든 표준 메소드들을 허용할 수 있습니다.
|
||||
* `allow_headers` - 교차-출처를 지원하는 HTTP 요청 헤더의 리스트입니다. 기본값은 `[]` 입니다. 모든 헤더들을 허용하기 위해 `['*']` 를 사용할 수 있습니다. `Accept`, `Accept-Language`, `Content-Language` 그리고 `Content-Type` 헤더는 CORS 요청시 언제나 허용됩니다.
|
||||
* `allow_credentials` - 교차-출처 요청시 쿠키 지원 여부를 설정합니다. 기본값은 `False` 입니다. 또한 해당 항목을 허용할 경우 `allow_origins` 는 `['*']` 로 설정할 수 없으며, 출처를 반드시 특정해야 합니다.
|
||||
* `expose_headers` - 브라우저에 접근할 수 있어야 하는 모든 응답 헤더를 가리킵니다. 기본값은 `[]` 입니다.
|
||||
* `max_age` - 브라우저가 CORS 응답을 캐시에 저장하는 최대 시간을 초 단위로 설정합니다. 기본값은 `600` 입니다.
|
||||
|
||||
미들웨어는 두가지 특정한 종류의 HTTP 요청에 응답합니다...
|
||||
|
||||
### CORS 사전 요청
|
||||
|
||||
`Origin` 및 `Access-Control-Request-Method` 헤더와 함께 전송하는 모든 `OPTIONS` 요청입니다.
|
||||
|
||||
이 경우 미들웨어는 들어오는 요청을 가로채 적절한 CORS 헤더와, 정보 제공을 위한 `200` 또는 `400` 응답으로 응답합니다.
|
||||
|
||||
### 단순한 요청
|
||||
|
||||
`Origin` 헤더를 가진 모든 요청. 이 경우 미들웨어는 요청을 정상적으로 전달하지만, 적절한 CORS 헤더를 응답에 포함시킵니다.
|
||||
|
||||
## 더 많은 정보
|
||||
|
||||
<abbr title="교차-출처 리소스 공유">CORS</abbr>에 대한 더 많은 정보를 알고싶다면, <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS 문서</a>를 참고하기 바랍니다.
|
||||
|
||||
/// note | 기술적 세부 사항
|
||||
|
||||
`from starlette.middleware.cors import CORSMiddleware` 역시 사용할 수 있습니다.
|
||||
|
||||
**FastAPI**는 개발자인 당신의 편의를 위해 `fastapi.middleware` 에서 몇가지의 미들웨어를 제공합니다. 하지만 대부분의 미들웨어가 Stralette으로부터 직접 제공됩니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,113 @@
|
||||
# 디버깅
|
||||
|
||||
예를 들면 Visual Studio Code 또는 PyCharm을 사용하여 편집기에서 디버거를 연결할 수 있습니다.
|
||||
|
||||
## `uvicorn` 호출
|
||||
|
||||
FastAPI 애플리케이션에서 `uvicorn`을 직접 임포트하여 실행합니다
|
||||
|
||||
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
|
||||
|
||||
### `__name__ == "__main__"` 에 대하여
|
||||
|
||||
`__name__ == "__main__"`의 주요 목적은 다음과 같이 파일이 호출될 때 실행되는 일부 코드를 갖는 것입니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python myapp.py
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
그러나 다음과 같이 다른 파일을 가져올 때는 호출되지 않습니다.
|
||||
|
||||
```Python
|
||||
from myapp import app
|
||||
```
|
||||
|
||||
#### 추가 세부사항
|
||||
|
||||
파일 이름이 `myapp.py`라고 가정해 보겠습니다.
|
||||
|
||||
다음과 같이 실행하면
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python myapp.py
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Python에 의해 자동으로 생성된 파일의 내부 변수 `__name__`은 문자열 `"__main__"`을 값으로 갖게 됩니다.
|
||||
|
||||
따라서 섹션
|
||||
|
||||
```Python
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
이 실행됩니다.
|
||||
|
||||
---
|
||||
|
||||
해당 모듈(파일)을 가져오면 이런 일이 발생하지 않습니다
|
||||
|
||||
그래서 다음과 같은 다른 파일 `importer.py`가 있는 경우:
|
||||
|
||||
```Python
|
||||
from myapp import app
|
||||
|
||||
# Some more code
|
||||
```
|
||||
|
||||
이 경우 `myapp.py` 내부의 자동 변수에는 값이 `"__main__"`인 변수 `__name__`이 없습니다.
|
||||
|
||||
따라서 다음 행
|
||||
|
||||
```Python
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
은 실행되지 않습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
자세한 내용은 <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">공식 Python 문서</a>를 확인하세요
|
||||
|
||||
///
|
||||
|
||||
## 디버거로 코드 실행
|
||||
|
||||
코드에서 직접 Uvicorn 서버를 실행하고 있기 때문에 디버거에서 직접 Python 프로그램(FastAPI 애플리케이션)을 호출할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
예를 들어 Visual Studio Code에서 다음을 수행할 수 있습니다.
|
||||
|
||||
* "Debug" 패널로 이동합니다.
|
||||
* "Add configuration...".
|
||||
* "Python"을 선택합니다.
|
||||
* "`Python: Current File (Integrated Terminal)`" 옵션으로 디버거를 실행합니다.
|
||||
|
||||
그런 다음 **FastAPI** 코드로 서버를 시작하고 중단점 등에서 중지합니다.
|
||||
|
||||
다음과 같이 표시됩니다.
|
||||
|
||||
<img src="/img/tutorial/debugging/image01.png">
|
||||
|
||||
---
|
||||
|
||||
Pycharm을 사용하는 경우 다음을 수행할 수 있습니다
|
||||
|
||||
* "Run" 메뉴를 엽니다
|
||||
* "Debug..." 옵션을 선택합니다.
|
||||
* 그러면 상황에 맞는 메뉴가 나타납니다.
|
||||
* 디버그할 파일을 선택합니다(이 경우 `main.py`).
|
||||
|
||||
그런 다음 **FastAPI** 코드로 서버를 시작하고 중단점 등에서 중지합니다.
|
||||
|
||||
다음과 같이 표시됩니다.
|
||||
|
||||
<img src="/img/tutorial/debugging/image02.png">
|
||||
@@ -0,0 +1,177 @@
|
||||
# 의존성으로서의 클래스
|
||||
|
||||
**의존성 주입** 시스템에 대해 자세히 살펴보기 전에 이전 예제를 업그레이드 해보겠습니다.
|
||||
|
||||
## 이전 예제의 `딕셔너리`
|
||||
|
||||
이전 예제에서, 우리는 의존성(의존 가능한) 함수에서 `딕셔너리`객체를 반환하고 있었습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001.py hl[9] *}
|
||||
|
||||
우리는 *경로 작동 함수*의 매개변수 `commons`에서 `딕셔너리` 객체를 얻습니다.
|
||||
|
||||
그리고 우리는 에디터들이 `딕셔너리` 객체의 키나 밸류의 자료형을 알 수 없기 때문에 자동 완성과 같은 기능을 제공해 줄 수 없다는 것을 알고 있습니다.
|
||||
|
||||
더 나은 방법이 있을 것 같습니다...
|
||||
|
||||
## 의존성으로 사용 가능한 것
|
||||
|
||||
지금까지 함수로 선언된 의존성을 봐왔습니다.
|
||||
|
||||
아마도 더 일반적이기는 하겠지만 의존성을 선언하는 유일한 방법은 아닙니다.
|
||||
|
||||
핵심 요소는 의존성이 "호출 가능"해야 한다는 것입니다
|
||||
|
||||
파이썬에서의 "**호출 가능**"은 파이썬이 함수처럼 "호출"할 수 있는 모든 것입니다.
|
||||
|
||||
따라서, 만약 당신이 `something`(함수가 아닐 수도 있음) 객체를 가지고 있고,
|
||||
|
||||
```Python
|
||||
something()
|
||||
```
|
||||
|
||||
또는
|
||||
|
||||
```Python
|
||||
something(some_argument, some_keyword_argument="foo")
|
||||
```
|
||||
|
||||
상기와 같은 방식으로 "호출(실행)" 할 수 있다면 "호출 가능"이 됩니다.
|
||||
|
||||
## 의존성으로서의 클래스
|
||||
|
||||
파이썬 클래스의 인스턴스를 생성하기 위해 사용하는 것과 동일한 문법을 사용한다는 걸 알 수 있습니다.
|
||||
|
||||
예를 들어:
|
||||
|
||||
```Python
|
||||
class Cat:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
|
||||
fluffy = Cat(name="Mr Fluffy")
|
||||
```
|
||||
|
||||
이 경우에 `fluffy`는 클래스 `Cat`의 인스턴스입니다. 그리고 우리는 `fluffy`를 만들기 위해서 `Cat`을 "호출"했습니다.
|
||||
|
||||
따라서, 파이썬 클래스는 **호출 가능**합니다.
|
||||
|
||||
그래서 **FastAPI**에서는 파이썬 클래스를 의존성으로 사용할 수 있습니다.
|
||||
|
||||
FastAPI가 실질적으로 확인하는 것은 "호출 가능성"(함수, 클래스 또는 다른 모든 것)과 정의된 매개변수들입니다.
|
||||
|
||||
"호출 가능"한 것을 의존성으로서 **FastAPI**에 전달하면, 그 "호출 가능"한 것의 매개변수들을 분석한 후 이를 *경로 작동 함수*를 위한 매개변수와 동일한 방식으로 처리합니다. 하위-의존성 또한 같은 방식으로 처리합니다.
|
||||
|
||||
매개변수가 없는 "호출 가능"한 것 역시 매개변수가 없는 *경로 작동 함수*와 동일한 방식으로 적용됩니다.
|
||||
|
||||
그래서, 우리는 위 예제에서의 `common_paramenters` 의존성을 클래스 `CommonQueryParams`로 바꿀 수 있습니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002.py hl[11:15] *}
|
||||
|
||||
클래스의 인스턴스를 생성하는 데 사용되는 `__init__` 메서드에 주목하기 바랍니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002.py hl[12] *}
|
||||
|
||||
...이전 `common_parameters`와 동일한 매개변수를 가집니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001.py hl[9] *}
|
||||
|
||||
이 매개변수들은 **FastAPI**가 의존성을 "해결"하기 위해 사용할 것입니다
|
||||
|
||||
함수와 클래스 두 가지 방식 모두, 아래 요소를 갖습니다:
|
||||
|
||||
* `문자열`이면서 선택사항인 쿼리 매개변수 `q`.
|
||||
* 기본값이 `0`이면서 `정수형`인 쿼리 매개변수 `skip`
|
||||
* 기본값이 `100`이면서 `정수형`인 쿼리 매개변수 `limit`
|
||||
|
||||
두 가지 방식 모두, 데이터는 변환, 검증되고 OpenAPI 스키마에 문서화됩니다.
|
||||
|
||||
## 사용해봅시다!
|
||||
|
||||
이제 아래의 클래스를 이용해서 의존성을 정의할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002.py hl[19] *}
|
||||
|
||||
**FastAPI**는 `CommonQueryParams` 클래스를 호출합니다. 이것은 해당 클래스의 "인스턴스"를 생성하고 그 인스턴스는 함수의 매개변수 `commons`로 전달됩니다.
|
||||
|
||||
## 타입 힌팅 vs `Depends`
|
||||
|
||||
위 코드에서 `CommonQueryParams`를 두 번 작성한 방식에 주목하십시오:
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
마지막 `CommonQueryParams` 변수를 보면:
|
||||
|
||||
```Python
|
||||
... = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
... **FastAPI**가 실제로 어떤 것이 의존성인지 알기 위해서 사용하는 방법입니다.
|
||||
FastAPI는 선언된 매개변수들을 추출할 것이고 실제로 이 변수들을 호출할 것입니다.
|
||||
|
||||
---
|
||||
|
||||
이 경우에, 첫번째 `CommonQueryParams` 변수를 보면:
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams ...
|
||||
```
|
||||
|
||||
... **FastAPI**는 `CommonQueryParams` 변수에 어떠한 특별한 의미도 부여하지 않습니다. FastAPI는 이 변수를 데이터 변환, 검증 등에 활용하지 않습니다. (활용하려면 `= Depends(CommonQueryParams)`를 사용해야 합니다.)
|
||||
|
||||
사실 아래와 같이 작성해도 무관합니다:
|
||||
|
||||
```Python
|
||||
commons = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
..전체적인 코드는 아래와 같습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial003.py hl[19] *}
|
||||
|
||||
그러나 자료형을 선언하면 에디터가 매개변수 `commons`로 전달될 것이 무엇인지 알게 되고, 이를 통해 코드 완성, 자료형 확인 등에 도움이 될 수 있으므로 권장됩니다.
|
||||
|
||||
<!-- <img src="/img/tutorial/dependencies/image02.png"> -->
|
||||
|
||||
## 코드 단축
|
||||
|
||||
그러나 여기 `CommonQueryParams`를 두 번이나 작성하는, 코드 반복이 있다는 것을 알 수 있습니다:
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
**FastAPI**는 *특히* 의존성이 **FastAPI**가 클래스 자체의 인스턴스를 생성하기 위해 "호출"하는 클래스인 경우, 조금 더 쉬운 방법을 제공합니다.
|
||||
|
||||
이러한 특정한 경우에는 아래처럼 사용할 수 있습니다:
|
||||
|
||||
이렇게 쓰는 것 대신:
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
...이렇게 쓸 수 있습니다.:
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends()
|
||||
```
|
||||
|
||||
의존성을 매개변수의 타입으로 선언하는 경우 `Depends(CommonQueryParams)`처럼 클래스 이름 전체를 *다시* 작성하는 대신, 매개변수를 넣지 않은 `Depends()`의 형태로 사용할 수 있습니다.
|
||||
|
||||
아래에 같은 예제가 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial004.py hl[19] *}
|
||||
|
||||
...이렇게 코드를 단축하여도 **FastAPI**는 무엇을 해야하는지 알고 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
만약 이것이 도움이 되기보다 더 헷갈리게 만든다면, 잊어버리십시오. 이것이 반드시 필요한 것은 아닙니다.
|
||||
|
||||
이것은 단지 손쉬운 방법일 뿐입니다. 왜냐하면 **FastAPI**는 코드 반복을 최소화할 수 있는 방법을 고민하기 때문입니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,69 @@
|
||||
# 경로 작동 데코레이터에서의 의존성
|
||||
|
||||
몇몇 경우에는, *경로 작동 함수* 안에서 의존성의 반환 값이 필요하지 않습니다.
|
||||
|
||||
또는 의존성이 값을 반환하지 않습니다.
|
||||
|
||||
그러나 여전히 실행/해결될 필요가 있습니다.
|
||||
|
||||
그런 경우에, `Depends`를 사용하여 *경로 작동 함수*의 매개변수로 선언하는 것보다 *경로 작동 데코레이터*에 `dependencies`의 `list`를 추가할 수 있습니다.
|
||||
|
||||
## *경로 작동 데코레이터*에 `dependencies` 추가하기
|
||||
|
||||
*경로 작동 데코레이터*는 `dependencies`라는 선택적인 인자를 받습니다.
|
||||
|
||||
`Depends()`로 된 `list`이어야합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *}
|
||||
|
||||
이러한 의존성들은 기존 의존성들과 같은 방식으로 실행/해결됩니다. 그러나 값은 (무엇이든 반환한다면) *경로 작동 함수*에 제공되지 않습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
일부 편집기에서는 사용되지 않는 함수 매개변수를 검사하고 오류로 표시합니다.
|
||||
|
||||
*경로 작동 데코레이터*에서 `dependencies`를 사용하면 편집기/도구 오류를 피하며 실행되도록 할 수 있습니다.
|
||||
|
||||
또한 코드에서 사용되지 않는 매개변수를 보고 불필요하다고 생각할 수 있는 새로운 개발자의 혼란을 방지하는데 도움이 될 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
|
||||
이 예시에서 `X-Key`와 `X-Token`이라는 커스텀 헤더를 만들어 사용했습니다.
|
||||
|
||||
그러나 실제로 보안을 구현할 때는 통합된 [보안 유틸리티 (다음 챕터)](../security/index.md){.internal-link target=_blank}를 사용하는 것이 더 많은 이점을 얻을 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 의존성 오류와 값 반환하기
|
||||
|
||||
평소에 사용하던대로 같은 의존성 *함수*를 사용할 수 있습니다.
|
||||
|
||||
### 의존성 요구사항
|
||||
|
||||
(헤더같은) 요청 요구사항이나 하위-의존성을 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
|
||||
|
||||
### 오류 발생시키기
|
||||
|
||||
다음 의존성은 기존 의존성과 동일하게 예외를 `raise`를 일으킬 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
|
||||
|
||||
### 값 반환하기
|
||||
|
||||
값을 반환하거나, 그러지 않을 수 있으며 값은 사용되지 않습니다.
|
||||
|
||||
그래서 이미 다른 곳에서 사용된 (값을 반환하는) 일반적인 의존성을 재사용할 수 있고, 비록 값은 사용되지 않지만 의존성은 실행될 것입니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
|
||||
|
||||
## *경로 작동* 모음에 대한 의존성
|
||||
|
||||
나중에 여러 파일을 가지고 있을 수 있는 더 큰 애플리케이션을 구조화하는 법([더 큰 애플리케이션 - 여러 파일들](../../tutorial/bigger-applications.md){.internal-link target=_blank})을 읽을 때, *경로 작동* 모음에 대한 단일 `dependencies` 매개변수를 선언하는 법에 대해서 배우게 될 것입니다.
|
||||
|
||||
## 전역 의존성
|
||||
|
||||
다음으로 각 *경로 작동*에 적용되도록 `FastAPI` 애플리케이션 전체에 의존성을 추가하는 법을 볼 것입니다.
|
||||
@@ -0,0 +1,275 @@
|
||||
# yield를 사용하는 의존성
|
||||
|
||||
FastAPI는 <abbr title='때로는 "종료 코드", "정리 코드", "종료 처리 코드", "닫기 코드", "컨텍스트 관리자 종료 코드" 등으로도 불립니다'>작업 완료 후 추가 단계를 수행하는</abbr> 의존성을 지원합니다.
|
||||
|
||||
이를 구현하려면 `return` 대신 `yield`를 사용하고, 추가로 실행할 단계 (코드)를 그 뒤에 작성하세요.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
각 의존성마다 `yield`는 한 번만 사용해야 합니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
다음과 함께 사용할 수 있는 모든 함수:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> 또는
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
는 **FastAPI**의 의존성으로 사용할 수 있습니다.
|
||||
|
||||
사실, FastAPI는 내부적으로 이 두 데코레이터를 사용합니다.
|
||||
|
||||
///
|
||||
|
||||
## `yield`를 사용하는 데이터베이스 의존성
|
||||
|
||||
예를 들어, 이 기능을 사용하면 데이터베이스 세션을 생성하고 작업이 끝난 후에 세션을 종료할 수 있습니다.
|
||||
|
||||
응답을 생성하기 전에는 `yield`문을 포함하여 그 이전의 코드만이 실행됩니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
|
||||
|
||||
yield된 값은 *경로 작업* 및 다른 의존성들에 주입되는 값 입니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
|
||||
|
||||
`yield`문 다음의 코드는 응답을 생성한 후 보내기 전에 실행됩니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`async` 함수와 일반 함수 모두 사용할 수 있습니다.
|
||||
|
||||
**FastAPI**는 일반 의존성과 마찬가지로 각각의 함수를 올바르게 처리할 것입니다.
|
||||
|
||||
///
|
||||
|
||||
## `yield`와 `try`를 사용하는 의존성
|
||||
|
||||
`yield`를 사용하는 의존성에서 `try` 블록을 사용한다면, 의존성을 사용하는 도중 발생한 모든 예외를 받을 수 있습니다.
|
||||
|
||||
예를 들어, 다른 의존성이나 *경로 작업*의 중간에 데이터베이스 트랜잭션 "롤백"이 발생하거나 다른 오류가 발생한다면, 해당 예외를 의존성에서 받을 수 있습니다.
|
||||
|
||||
따라서, 의존성 내에서 `except SomeException`을 사용하여 특정 예외를 처리할 수 있습니다.
|
||||
|
||||
마찬가지로, `finally`를 사용하여 예외 발생 여부와 관계 없이 종료 단계까 실행되도록 할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
|
||||
|
||||
## `yield`를 사용하는 하위 의존성
|
||||
|
||||
모든 크기와 형태의 하위 의존성과 하위 의존성의 "트리"도 가질 수 있으며, 이들 모두가 `yield`를 사용할 수 있습니다.
|
||||
|
||||
**FastAPI**는 `yield`를 사용하는 각 의존성의 "종료 코드"가 올바른 순서로 실행되도록 보장합니다.
|
||||
|
||||
예를 들어, `dependency_c`는 `dependency_b`에 의존할 수 있고, `dependency_b`는 `dependency_a`에 의존할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
|
||||
|
||||
이들 모두는 `yield`를 사용할 수 있습니다.
|
||||
|
||||
이 경우 `dependency_c`는 종료 코드를 실행하기 위해, `dependency_b`의 값 (여기서는 `dep_b`로 명명)이 여전히 사용 가능해야 합니다.
|
||||
|
||||
그리고, `dependency_b`는 종료 코드를 위해 `dependency_a`의 값 (여기서는 `dep_a`로 명명) 이 사용 가능해야 합니다.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
|
||||
|
||||
같은 방식으로, `yield`를 사용하는 의존성과 `return`을 사용하는 의존성을 함께 사용할 수 있으며, 이들 중 일부가 다른 것들에 의존할 수 있습니다.
|
||||
|
||||
그리고 `yield`를 사용하는 다른 여러 의존성을 필요로 하는 단일 의존성을 가질 수도 있습니다.
|
||||
|
||||
원하는 의존성을 원하는 대로 조합할 수 있습니다.
|
||||
|
||||
**FastAPI**는 모든 것이 올바른 순서로 실행되도록 보장합니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
파이썬의 <a href=“https://docs.python.org/3/library/contextlib.html” class=“external-link” target=“_blank”>Context Managers</a> 덕분에 이 기능이 작동합니다.
|
||||
|
||||
**FastAPI**는 이를 내부적으로 컨텍스트 관리자를 사용하여 구현합니다.
|
||||
|
||||
///
|
||||
|
||||
## `yield`와 `HTTPException`를 사용하는 의존성
|
||||
|
||||
`yield`와 `try` 블록이 있는 의존성을 사용하여 예외를 처리할 수 있다는 것을 알게 되었습니다.
|
||||
|
||||
같은 방식으로, `yield` 이후의 종료 코드에서 `HTTPException`이나 유사한 예외를 발생시킬 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이는 다소 고급 기술이며, 대부분의 경우 경로 연산 함수 등 나머지 애플리케이션 코드 내부에서 예외 (`HTTPException` 포함)를 발생시킬 수 있으므로 실제로는 필요하지 않을 것입니다.
|
||||
|
||||
하지만 필요한 경우 사용할 수 있습니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
|
||||
|
||||
예외를 처리하고(또는 추가로 다른 `HTTPException`을 발생시키기 위해) 사용할 수 있는 또 다른 방법은 [사용자 정의 예외 처리기](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}를 생성하는 것 입니다.
|
||||
|
||||
## `yield`와 `except`를 사용하는 의존성
|
||||
|
||||
`yield`를 사용하는 의존성에서 `except`를 사용하여 예외를 포착하고 예외를 다시 발생시키지 않거나 (또는 새 예외를 발생시키지 않으면), FastAPI는 해당 예외가 발생했는지 알 수 없습니다. 이는 일반적인 Python 방식과 동일합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
|
||||
|
||||
이 경우, `HTTPException`이나 유사한 예외를 발생시키지 않기 때문에 클라이언트는 HTTP 500 Internal Server Error 응답을 보게 되지만, 서버는 어떤 오류가 발생했는지에 대한 **로그**나 다른 표시를 전혀 가지지 않게 됩니다. 😱
|
||||
|
||||
### `yield`와 `except`를 사용하는 의존성에서 항상 `raise` 하기
|
||||
|
||||
`yield`가 있는 의존성에서 예외를 잡았을 때는 `HTTPException`이나 유사한 예외를 새로 발생시키지 않는 한, 반드시 원래의 예외를 다시 발생시켜야 합니다.
|
||||
|
||||
`raise`를 사용하여 동일한 예외를 다시 발생시킬 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
|
||||
|
||||
이제 클라이언트는 동일한 *HTTP 500 Internal Server Error* 오류 응답을 받게 되지만, 서버 로그에는 사용자 정의 예외인 `InternalError"가 기록됩니다. 😎
|
||||
|
||||
## `yield`를 사용하는 의존성의 실행 순서
|
||||
|
||||
실행 순서는 아래 다이어그램과 거의 비슷합니다. 시간은 위에서 아래로 흐릅니다. 그리고 각 열은 상호 작용하거나 코드를 실행하는 부분 중 하나입니다.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant client as Client
|
||||
participant handler as Exception handler
|
||||
participant dep as Dep with yield
|
||||
participant operation as Path Operation
|
||||
participant tasks as Background tasks
|
||||
|
||||
Note over client,operation: Can raise exceptions, including HTTPException
|
||||
client ->> dep: Start request
|
||||
Note over dep: Run code up to yield
|
||||
opt raise Exception
|
||||
dep -->> handler: Raise Exception
|
||||
handler -->> client: HTTP error response
|
||||
end
|
||||
dep ->> operation: Run dependency, e.g. DB session
|
||||
opt raise
|
||||
operation -->> dep: Raise Exception (e.g. HTTPException)
|
||||
opt handle
|
||||
dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
|
||||
end
|
||||
handler -->> client: HTTP error response
|
||||
end
|
||||
|
||||
operation ->> client: Return response to client
|
||||
Note over client,operation: Response is already sent, can't change it anymore
|
||||
opt Tasks
|
||||
operation -->> tasks: Send background tasks
|
||||
end
|
||||
opt Raise other exception
|
||||
tasks -->> tasks: Handle exceptions in the background task code
|
||||
end
|
||||
```
|
||||
|
||||
/// info | 정보
|
||||
|
||||
클라이언트에 **하나의 응답** 만 전송됩니다. 이는 오류 응답 중 하나일 수도 있고,*경로 작업*에서 생성된 응답일 수도 있습니다.
|
||||
|
||||
이러한 응답 중 하나가 전송된 후에는 다른 응답을 보낼 수 없습니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 다이어그램은 `HTTPException`을 보여주지만, `yield`를 사용하는 의존성에서 처리한 예외나 [사용자 정의 예외처리기](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.를 사용하여 처리한 다른 예외도 발생시킬 수 있습니다.
|
||||
|
||||
어떤 예외가 발생하든, `HTTPException`을 포함하여 yield를 사용하는 의존성으로 전달됩니다. 대부분의 경우 예외를 다시 발생시키거나 새로운 예외를 발생시켜야 합니다.
|
||||
|
||||
///
|
||||
|
||||
## `yield`, `HTTPException`, `except` 및 백그라운드 작업을 사용하는 의존성
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
이러한 기술적 세부 사항은 대부분 필요하지 않으므로 이 섹션을 건너뛰고 아래에서 계속 진행해도 됩니다.
|
||||
|
||||
이러한 세부 정보는 주로 FastAPI 0.106.0 이전 버전에서 `yield`가 있는 의존성의 리소스를 백그라운드 작업에서 사용했던 경우메 유용합니다.
|
||||
|
||||
///
|
||||
|
||||
### `yield`와 `except`를 사용하는 의존성, 기술 세부사항
|
||||
|
||||
FastAPI 0.110.0 이전에는 `yield`가 포함된 의존성을 사용한 후 해당 의존성에서 `except`가 포함된 예외를 캡처하고 다시 예외를 발생시키지 않으면 예외가 자동으로 예외 핸들러 또는 내부 서버 오류 핸들러로 발생/전달되었습니다.
|
||||
|
||||
이는 처리기 없이 전달된 예외(내부 서버 오류)에서 처리되지 않은 메모리 소비를 수정하고 일반 파이썬 코드의 동작과 일치하도록 하기 위해 0.110.0 버전에서 변경되었습니다.
|
||||
|
||||
### 백그라운드 작업과 `yield`를 사용하는 의존성, 기술 세부사항
|
||||
|
||||
FastAPI 0.106.0 이전에는 `yield` 이후에 예외를 발생시키는 것이 불가능했습니다. `yield`가 있는 의존성 종료 코드는 응답이 전송된 이후에 실행되었기 때문에, [예외 처리기](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}가 이미 실행된 상태였습니다.
|
||||
|
||||
이는 주로 백그라운드 작업 내에서 의존성에서 "yield된" 동일한 객체를 사용할 수 있도록 하기 위해 이런 방식으로 설계되었습니다. 종료 코드는 백그라운드 작업이 완료된 후에 실행되었기 때문입니다
|
||||
|
||||
하지만 이렇게 하면 리소스를 불필요하게 양보한 의존성(예: 데이터베이스 연결)에서 보유하면서 응답이 네트워크를 통해 이동할 때까지 기다리는 것을 의미하기 때문에 FastAPI 0.106.0에서 변경되었습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
또한 백그라운드 작업은 일반적으로 자체 리소스(예: 자체 데이터베이스 연결)를 사용하여 별도로 처리해야 하는 독립적인 로직 집합입니다.
|
||||
|
||||
따라서 이렇게 하면 코드가 더 깔끔해집니다.
|
||||
|
||||
///
|
||||
|
||||
만약 이전에 이러한 동작에 의존했다면, 이제는 백그라운드 작업 내부에서 백그라운드 작업을 위한 리소스를 생성하고, `yield`가 있는 의존성의 리소스에 의존하지 않는 데이터만 내부적으로 사용해야합니다.
|
||||
|
||||
예를 들어, 동일한 데이터베이스 세션을 사용하는 대신, 백그라운드 작업 내부에서 새로운 데이터베이스 세션을 생성하고 이 새로운 세션을 사용하여 데이터베이스에서 객체를 가져와야 합니다. 그리고 데이터베이스 객체를 백그라운드 작업 함수의 매개변수로 직접 전달하는 대신, 해당 객체의 ID를 전달한 다음 백그라운드 작업 함수 내부에서 객체를 다시 가져와야 합니다
|
||||
|
||||
## 컨텍스트 관리자
|
||||
|
||||
### "컨텍스트 관리자"란?
|
||||
|
||||
"컨텍스트 관리자"는 Python에서 `with` 문에서 사용할 수 있는 모든 객체를 의미합니다.
|
||||
|
||||
예를 들어, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank"> `with`를 사용하여 파일을 읽을 수 있습니다</a>:
|
||||
|
||||
```Python
|
||||
with open("./somefile.txt") as f:
|
||||
contents = f.read()
|
||||
print(contents)
|
||||
```
|
||||
|
||||
내부적으로 `open("./somefile.txt")` 는 "컨텍스트 관리자(Context Manager)"라고 불리는 객체를 생성합니다.
|
||||
|
||||
`with` 블록이 끝나면, 예외가 발생했더라도 파일을 닫도록 보장합니다.
|
||||
|
||||
`yield`가 있는 의존성을 생성하면 **FastAPI**는 내부적으로 이를 위한 컨텍스트 매니저를 생성하고 다른 관련 도구들과 결합합니다.
|
||||
|
||||
### `yield`를 사용하는 의존성에서 컨텍스트 관리자 사용하기
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
이것은 어느 정도 "고급" 개념입니다.
|
||||
|
||||
**FastAPI**를 처음 시작하는 경우 지금은 이 부분을 건너뛰어도 좋습니다.
|
||||
|
||||
///
|
||||
|
||||
Python에서는 다음을 통해 컨텍스트 관리자를 생성할 수 있습니다. <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank"> 두 가지 메서드가 있는 클래스를 생성합니다: `__enter__()` and `__exit__()`</a>.
|
||||
|
||||
**FastAPI**의 `yield`가 있는 의존성 내에서
|
||||
`with` 또는 `async with`문을 사용하여 이들을 활용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
컨텍스트 관리자를 생성하는 또 다른 방법은 다음과 같습니다:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> 또는
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
이들은 단일 `yield`가 있는 함수를 꾸미는 데 사용합니다.
|
||||
|
||||
이것이 **FastAPI**가 `yield`가 있는 의존성을 위해 내부적으로 사용하는 방식입니다.
|
||||
|
||||
하지만 FastAPI 의존성에는 이러한 데코레이터를 사용할 필요가 없습니다(그리고 사용해서도 안됩니다).
|
||||
|
||||
FastAPI가 내부적으로 이를 처리해 줄 것입니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,15 @@
|
||||
# 전역 의존성
|
||||
|
||||
몇몇 애플리케이션에서는 애플리케이션 전체에 의존성을 추가하고 싶을 수 있습니다.
|
||||
|
||||
[*경로 작동 데코레이터*에 `dependencies` 추가하기](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}와 유사한 방법으로 `FastAPI` 애플리케이션에 그것들을 추가할 수 있습니다.
|
||||
|
||||
그런 경우에, 애플리케이션의 모든 *경로 작동*에 적용될 것입니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *}
|
||||
|
||||
그리고 [*경로 작동 데코레이터*에 `dependencies` 추가하기](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}에 대한 아이디어는 여전히 적용되지만 여기에서는 앱에 있는 모든 *경로 작동*에 적용됩니다.
|
||||
|
||||
## *경로 작동* 모음에 대한 의존성
|
||||
|
||||
이후에 여러 파일들을 가지는 더 큰 애플리케이션을 구조화하는 법([더 큰 애플리케이션 - 여러 파일들](../../tutorial/bigger-applications.md){.internal-link target=_blank})을 읽을 때, *경로 작동* 모음에 대한 단일 `dependencies` 매개변수를 선언하는 법에 대해서 배우게 될 것입니다.
|
||||
@@ -0,0 +1,250 @@
|
||||
# 의존성
|
||||
|
||||
**FastAPI**는 아주 강력하지만 직관적인 **<abbr title="컴포넌트, 자원, 제공자, 서비스, 인젝터블로 알려져 있습니다">의존성 주입</abbr>** 시스템을 가지고 있습니다.
|
||||
|
||||
이는 사용하기 아주 쉽게 설계했으며, 어느 개발자나 다른 컴포넌트와 **FastAPI**를 쉽게 통합할 수 있도록 만들었습니다.
|
||||
|
||||
## "의존성 주입"은 무엇입니까?
|
||||
|
||||
**"의존성 주입"**은 프로그래밍에서 여러분의 코드(이 경우, 경로 작동 함수)가 작동하고 사용하는 데 필요로 하는 것, 즉 "의존성"을 선언할 수 있는 방법을 의미합니다.
|
||||
|
||||
그 후에, 시스템(이 경우 FastAPI)은 여러분의 코드가 요구하는 의존성을 제공하기 위해 필요한 모든 작업을 처리합니다.(의존성을 "주입"합니다)
|
||||
|
||||
이는 여러분이 다음과 같은 사항을 필요로 할 때 매우 유용합니다:
|
||||
|
||||
* 공용된 로직을 가졌을 경우 (같은 코드 로직이 계속 반복되는 경우).
|
||||
* 데이터베이스 연결을 공유하는 경우.
|
||||
* 보안, 인증, 역할 요구 사항 등을 강제하는 경우.
|
||||
* 그리고 많은 다른 사항...
|
||||
|
||||
이 모든 사항을 할 때 코드 반복을 최소화합니다.
|
||||
|
||||
## 첫번째 단계
|
||||
|
||||
아주 간단한 예제를 봅시다. 너무 간단할 것이기에 지금 당장은 유용하지 않을 수 있습니다.
|
||||
|
||||
하지만 이를 통해 **의존성 주입** 시스템이 어떻게 작동하는지에 중점을 둘 것입니다.
|
||||
|
||||
### 의존성 혹은 "디펜더블" 만들기
|
||||
|
||||
의존성에 집중해 봅시다.
|
||||
|
||||
*경로 작동 함수*가 가질 수 있는 모든 매개변수를 갖는 단순한 함수입니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
|
||||
|
||||
이게 다입니다.
|
||||
|
||||
**단 두 줄입니다**.
|
||||
|
||||
그리고, 이 함수는 여러분의 모든 *경로 작동 함수*가 가지고 있는 것과 같은 형태와 구조를 가지고 있습니다.
|
||||
|
||||
여러분은 이를 "데코레이터"가 없는 (`@app.get("/some-path")`가 없는) *경로 작동 함수*라고 생각할 수 있습니다.
|
||||
|
||||
그리고 여러분이 원하는 무엇이든 반환할 수 있습니다.
|
||||
|
||||
이 경우, 이 의존성은 다음과 같은 경우를 기대합니다:
|
||||
|
||||
* 선택적인 쿼리 매개변수 `q`, `str`을 자료형으로 가집니다.
|
||||
* 선택적인 쿼리 매개변수 `skip`, `int`를 자료형으로 가지며 기본 값은 `0`입니다.
|
||||
* 선택적인 쿼리 매개변수 `limit`,`int`를 자료형으로 가지며 기본 값은 `100`입니다.
|
||||
|
||||
그 후 위의 값을 포함한 `dict` 자료형으로 반환할 뿐입니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
FastAPI는 0.95.0 버전부터 `Annotated`에 대한 지원을 (그리고 이를 사용하기 권장합니다) 추가했습니다.
|
||||
|
||||
옛날 버전을 가지고 있는 경우, `Annotated`를 사용하려 하면 에러를 맞이하게 될 것입니다.
|
||||
|
||||
`Annotated`를 사용하기 전에 최소 0.95.1로 [FastAPI 버전 업그레이드](../../deployment/versions.md#fastapi_2){.internal-link target=_blank}를 확실하게 하세요.
|
||||
|
||||
///
|
||||
|
||||
### `Depends` 불러오기
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
### "의존자"에 의존성 명시하기
|
||||
|
||||
*경로 작동 함수*의 매개변수로 `Body`, `Query` 등을 사용하는 방식과 같이 새로운 매개변수로 `Depends`를 사용합니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
|
||||
|
||||
비록 `Body`, `Query` 등을 사용하는 것과 같은 방식으로 여러분의 함수의 매개변수에 있는 `Depends`를 사용하지만, `Depends`는 약간 다르게 작동합니다.
|
||||
|
||||
`Depends`에 단일 매개변수만 전달했습니다.
|
||||
|
||||
이 매개변수는 함수같은 것이어야 합니다.
|
||||
|
||||
여러분은 직접 **호출하지 않았습니다** (끝에 괄호를 치지 않았습니다), 단지 `Depends()`에 매개변수로 넘겨 줬을 뿐입니다.
|
||||
|
||||
그리고 그 함수는 *경로 작동 함수*가 작동하는 것과 같은 방식으로 매개변수를 받습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
여러분은 다음 장에서 함수를 제외하고서, "다른 것들"이 어떻게 의존성으로 사용되는지 알게 될 것입니다.
|
||||
|
||||
///
|
||||
|
||||
새로운 요청이 도착할 때마다, **FastAPI**는 다음을 처리합니다:
|
||||
|
||||
* 올바른 매개변수를 가진 의존성("디펜더블") 함수를 호출합니다.
|
||||
* 함수에서 결과를 받아옵니다.
|
||||
* *경로 작동 함수*에 있는 매개변수에 그 결과를 할당합니다
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
common_parameters(["common_parameters"])
|
||||
read_items["/items/"]
|
||||
read_users["/users/"]
|
||||
|
||||
common_parameters --> read_items
|
||||
common_parameters --> read_users
|
||||
```
|
||||
|
||||
이렇게 하면 공용 코드를 한번만 적어도 되며, **FastAPI**는 *경로 작동*을 위해 이에 대한 호출을 처리합니다.
|
||||
|
||||
/// check | 확인
|
||||
|
||||
특별한 클래스를 만들지 않아도 되며, 이러한 것 혹은 비슷한 종류를 **FastAPI**에 "등록"하기 위해 어떤 곳에 넘겨주지 않아도 됩니다.
|
||||
|
||||
단순히 `Depends`에 넘겨주기만 하면 되며, **FastAPI**는 나머지를 어찌할지 알고 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## `Annotated`인 의존성 공유하기
|
||||
|
||||
위의 예제에서 몇몇 작은 **코드 중복**이 있다는 것을 보았을 겁니다.
|
||||
|
||||
`common_parameters()`의존을 사용해야 한다면, 타입 명시와 `Depends()`와 함께 전체 매개변수를 적어야 합니다:
|
||||
|
||||
```Python
|
||||
commons: Annotated[dict, Depends(common_parameters)]
|
||||
```
|
||||
|
||||
하지만 `Annotated`를 사용하고 있기에, `Annotated` 값을 변수에 저장하고 여러 장소에서 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이는 그저 표준 파이썬이고 "type alias"라고 부르며 사실 **FastAPI**에 국한되는 것은 아닙니다.
|
||||
|
||||
하지만, `Annotated`를 포함하여, **FastAPI**가 파이썬 표준을 기반으로 하고 있기에, 이를 여러분의 코드 트릭으로 사용할 수 있습니다. 😎
|
||||
|
||||
///
|
||||
|
||||
이 의존성은 계속해서 예상한대로 작동할 것이며, **제일 좋은 부분**은 **타입 정보가 보존된다는 것입니다**. 즉 여러분의 편집기가 **자동 완성**, **인라인 에러** 등을 계속해서 제공할 수 있다는 것입니다. `mypy`같은 다른 도구도 마찬가지입니다.
|
||||
|
||||
이는 특히 **많은 *경로 작동***에서 **같은 의존성**을 계속해서 사용하는 **거대 코드 기반**안에서 사용하면 유용할 것입니다.
|
||||
|
||||
## `async`하게, 혹은 `async`하지 않게
|
||||
|
||||
의존성이 (*경로 작동 함수*에서 처럼 똑같이) **FastAPI**에 의해 호출될 수 있으며, 함수를 정의할 때 동일한 규칙이 적용됩니다.
|
||||
|
||||
`async def`을 사용하거나 혹은 일반적인 `def`를 사용할 수 있습니다.
|
||||
|
||||
그리고 일반적인 `def` *경로 작동 함수* 안에 `async def`로 의존성을 선언할 수 있으며, `async def` *경로 작동 함수* 안에 `def`로 의존성을 선언하는 등의 방법이 있습니다.
|
||||
|
||||
아무 문제 없습니다. **FastAPI**는 무엇을 할지 알고 있습니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
잘 모르시겠다면, [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} 문서에서 `async`와 `await`에 대해 확인할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## OpenAPI와 통합
|
||||
|
||||
모든 요청 선언, 검증과 의존성(및 하위 의존성)에 대한 요구 사항은 동일한 OpenAPI 스키마에 통합됩니다.
|
||||
|
||||
따라서 대화형 문서에 이러한 의존성에 대한 모든 정보 역시 포함하고 있습니다:
|
||||
|
||||
<img src="/img/tutorial/dependencies/image01.png">
|
||||
|
||||
## 간단한 사용법
|
||||
|
||||
이를 보면, *경로 작동 함수*는 *경로*와 *작동*이 매칭되면 언제든지 사용되도록 정의되었으며, **FastAPI**는 올바른 매개변수를 가진 함수를 호출하고 해당 요청에서 데이터를 추출합니다.
|
||||
|
||||
사실, 모든 (혹은 대부분의) 웹 프레임워크는 이와 같은 방식으로 작동합니다.
|
||||
|
||||
여러분은 이러한 함수들을 절대 직접 호출하지 않습니다. 프레임워크(이 경우 **FastAPI**)에 의해 호출됩니다.
|
||||
|
||||
의존성 주입 시스템과 함께라면 **FastAPI**에게 여러분의 *경로 작동 함수*가 실행되기 전에 실행되어야 하는 무언가에 여러분의 *경로 작동 함수* 또한 "의존"하고 있음을 알릴 수 있으며, **FastAPI**는 이를 실행하고 결과를 "주입"할 것입니다.
|
||||
|
||||
"의존성 주입"이라는 동일한 아이디어에 대한 다른 일반적인 용어는 다음과 같습니다:
|
||||
|
||||
* 리소스
|
||||
* 제공자
|
||||
* 서비스
|
||||
* 인젝터블
|
||||
* 컴포넌트
|
||||
|
||||
## **FastAPI** 플러그인
|
||||
|
||||
통합과 "플러그인"은 **의존성 주입** 시스템을 사용하여 구축할 수 있습니다. 하지만 실제로 **"플러그인"을 만들 필요는 없습니다**, 왜냐하면 의존성을 사용함으로써 여러분의 *경로 작동 함수*에 통합과 상호 작용을 무한대로 선언할 수 있기 때문입니다.
|
||||
|
||||
그리고 "말 그대로", 그저 필요로 하는 파이썬 패키지를 임포트하고 단 몇 줄의 코드로 여러분의 API 함수와 통합함으로써, 의존성을 아주 간단하고 직관적인 방법으로 만들 수 있습니다.
|
||||
|
||||
관계형 및 NoSQL 데이터베이스, 보안 등, 이에 대한 예시를 다음 장에서 볼 수 있습니다.
|
||||
|
||||
## **FastAPI** 호환성
|
||||
|
||||
의존성 주입 시스템의 단순함은 **FastAPI**를 다음과 같은 요소들과 호환할 수 있게 합니다:
|
||||
|
||||
* 모든 관계형 데이터베이스
|
||||
* NoSQL 데이터베이스
|
||||
* 외부 패키지
|
||||
* 외부 API
|
||||
* 인증 및 권한 부여 시스템
|
||||
* API 사용 모니터링 시스템
|
||||
* 응답 데이터 주입 시스템
|
||||
* 기타 등등.
|
||||
|
||||
## 간편하고 강력하다
|
||||
|
||||
계층적인 의존성 주입 시스템은 정의하고 사용하기 쉽지만, 여전히 매우 강력합니다.
|
||||
|
||||
여러분은 스스로를 의존하는 의존성을 정의할 수 있습니다.
|
||||
|
||||
끝에는, 계층적인 나무로 된 의존성이 만들어지며, 그리고 **의존성 주입** 시스템은 (하위 의존성도 마찬가지로) 이러한 의존성들을 처리하고 각 단계마다 결과를 제공합니다(주입합니다).
|
||||
|
||||
예를 들면, 여러분이 4개의 API 엔드포인트(*경로 작동*)를 가지고 있다고 해봅시다:
|
||||
|
||||
* `/items/public/`
|
||||
* `/items/private/`
|
||||
* `/users/{user_id}/activate`
|
||||
* `/items/pro/`
|
||||
|
||||
그 다음 각각에 대해 그저 의존성과 하위 의존성을 사용하여 다른 권한 요구 사항을 추가할 수 있을 겁니다:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
current_user(["current_user"])
|
||||
active_user(["active_user"])
|
||||
admin_user(["admin_user"])
|
||||
paying_user(["paying_user"])
|
||||
|
||||
public["/items/public/"]
|
||||
private["/items/private/"]
|
||||
activate_user["/users/{user_id}/activate"]
|
||||
pro_items["/items/pro/"]
|
||||
|
||||
current_user --> active_user
|
||||
active_user --> admin_user
|
||||
active_user --> paying_user
|
||||
|
||||
current_user --> public
|
||||
active_user --> private
|
||||
admin_user --> activate_user
|
||||
paying_user --> pro_items
|
||||
```
|
||||
|
||||
## **OpenAPI**와의 통합
|
||||
|
||||
이 모든 의존성은 각각의 요구사항을 선언하는 동시에, *경로 작동*에 매개변수, 검증 등을 추가합니다.
|
||||
|
||||
**FastAPI**는 이 모든 것을 OpenAPI 스키마에 추가할 것이며, 이를 통해 대화형 문서 시스템에 나타날 것입니다.
|
||||
@@ -0,0 +1,35 @@
|
||||
# JSON 호환 가능 인코더
|
||||
|
||||
데이터 유형(예: Pydantic 모델)을 JSON과 호환된 형태로 반환해야 하는 경우가 있습니다. (예: `dict`, `list` 등)
|
||||
|
||||
예를 들면, 데이터베이스에 저장해야하는 경우입니다.
|
||||
|
||||
이를 위해, **FastAPI** 에서는 `jsonable_encoder()` 함수를 제공합니다.
|
||||
|
||||
## `jsonable_encoder` 사용
|
||||
|
||||
JSON 호환 가능 데이터만 수신하는 `fake_db` 데이터베이스가 존재한다고 가정하겠습니다.
|
||||
|
||||
예를 들면, `datetime` 객체는 JSON과 호환되는 데이터가 아니므로 이 데이터는 받아들여지지 않습니다.
|
||||
|
||||
따라서 `datetime` 객체는 <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO format</a> 데이터를 포함하는 `str`로 변환되어야 합니다.
|
||||
|
||||
같은 방식으로 이 데이터베이스는 Pydantic 모델(속성이 있는 객체)을 받지 않고, `dict` 만을 받습니다.
|
||||
|
||||
이를 위해 `jsonable_encoder` 를 사용할 수 있습니다.
|
||||
|
||||
Pydantic 모델과 같은 객체를 받고 JSON 호환 가능한 버전으로 반환합니다:
|
||||
|
||||
{* ../../docs_src/encoder/tutorial001.py hl[5,22] *}
|
||||
|
||||
이 예시는 Pydantic 모델을 `dict`로, `datetime` 형식을 `str`로 변환합니다.
|
||||
|
||||
이렇게 호출한 결과는 파이썬 표준인 <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>로 인코딩 할 수 있습니다.
|
||||
|
||||
길이가 긴 문자열 형태의 JSON 형식(문자열)의 데이터가 들어있는 상황에서는 `str`로 반환하지 않습니다. JSON과 모두 호환되는 값과 하위 값이 있는 Python 표준 데이터 구조 (예: `dict`)를 반환합니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
실제로 `jsonable_encoder`는 **FastAPI** 에서 내부적으로 데이터를 변환하는 데 사용하지만, 다른 많은 곳에서도 이는 유용합니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,62 @@
|
||||
# 추가 데이터 자료형
|
||||
|
||||
지금까지 일반적인 데이터 자료형을 사용했습니다. 예를 들면 다음과 같습니다:
|
||||
|
||||
* `int`
|
||||
* `float`
|
||||
* `str`
|
||||
* `bool`
|
||||
|
||||
하지만 더 복잡한 데이터 자료형 또한 사용할 수 있습니다.
|
||||
|
||||
그리고 지금까지와 같은 기능들을 여전히 사용할 수 있습니다.
|
||||
|
||||
* 훌륭한 편집기 지원.
|
||||
* 들어오는 요청의 데이터 변환.
|
||||
* 응답 데이터의 데이터 변환.
|
||||
* 데이터 검증.
|
||||
* 자동 어노테이션과 문서화.
|
||||
|
||||
## 다른 데이터 자료형
|
||||
|
||||
아래의 추가적인 데이터 자료형을 사용할 수 있습니다:
|
||||
|
||||
* `UUID`:
|
||||
* 표준 "범용 고유 식별자"로, 많은 데이터베이스와 시스템에서 ID로 사용됩니다.
|
||||
* 요청과 응답에서 `str`로 표현됩니다.
|
||||
* `datetime.datetime`:
|
||||
* 파이썬의 `datetime.datetime`.
|
||||
* 요청과 응답에서 `2008-09-15T15:53:00+05:00`와 같은 ISO 8601 형식의 `str`로 표현됩니다.
|
||||
* `datetime.date`:
|
||||
* 파이썬의 `datetime.date`.
|
||||
* 요청과 응답에서 `2008-09-15`와 같은 ISO 8601 형식의 `str`로 표현됩니다.
|
||||
* `datetime.time`:
|
||||
* 파이썬의 `datetime.time`.
|
||||
* 요청과 응답에서 `14:23:55.003`와 같은 ISO 8601 형식의 `str`로 표현됩니다.
|
||||
* `datetime.timedelta`:
|
||||
* 파이썬의 `datetime.timedelta`.
|
||||
* 요청과 응답에서 전체 초(seconds)의 `float`로 표현됩니다.
|
||||
* Pydantic은 "ISO 8601 시차 인코딩"으로 표현하는 것 또한 허용합니다. <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">더 많은 정보는 이 문서에서 확인하십시오.</a>.
|
||||
* `frozenset`:
|
||||
* 요청과 응답에서 `set`와 동일하게 취급됩니다:
|
||||
* 요청 시, 리스트를 읽어 중복을 제거하고 `set`로 변환합니다.
|
||||
* 응답 시, `set`는 `list`로 변환됩니다.
|
||||
* 생성된 스키마는 (JSON 스키마의 `uniqueItems`를 이용해) `set`의 값이 고유함을 명시합니다.
|
||||
* `bytes`:
|
||||
* 표준 파이썬의 `bytes`.
|
||||
* 요청과 응답에서 `str`로 취급됩니다.
|
||||
* 생성된 스키마는 이것이 `binary` "형식"의 `str`임을 명시합니다.
|
||||
* `Decimal`:
|
||||
* 표준 파이썬의 `Decimal`.
|
||||
* 요청과 응답에서 `float`와 동일하게 다뤄집니다.
|
||||
* 여기에서 모든 유효한 pydantic 데이터 자료형을 확인할 수 있습니다: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">Pydantic 데이터 자료형</a>.
|
||||
|
||||
## 예시
|
||||
|
||||
위의 몇몇 자료형을 매개변수로 사용하는 *경로 작동* 예시입니다.
|
||||
|
||||
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *}
|
||||
|
||||
함수 안의 매개변수가 그들만의 데이터 자료형을 가지고 있으며, 예를 들어, 다음과 같이 날짜를 조작할 수 있음을 참고하십시오:
|
||||
|
||||
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *}
|
||||
@@ -0,0 +1,223 @@
|
||||
# 추가 모델
|
||||
|
||||
지난 예제에 이어서, 연관된 모델을 여러개 갖는 것은 흔한 일입니다.
|
||||
|
||||
특히 사용자 모델의 경우에 그러한데, 왜냐하면:
|
||||
|
||||
* **입력 모델** 은 비밀번호를 가져야 합니다.
|
||||
* **출력 모델** 은 비밀번호를 가지면 안됩니다.
|
||||
* **데이터베이스 모델** 은 해시처리된 비밀번호를 가질 것입니다.
|
||||
|
||||
/// danger | 위험
|
||||
|
||||
절대 사용자의 비밀번호를 평문으로 저장하지 마세요. 항상 이후에 검증 가능한 "안전한 해시(secure hash)"로 저장하세요.
|
||||
|
||||
만약 이게 무엇인지 모르겠다면, [security chapters](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.에서 비밀번호 해시에 대해 배울 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 다중 모델
|
||||
|
||||
아래는 비밀번호 필드와 해당 필드가 사용되는 위치를 포함하여, 각 모델들이 어떤 형태를 가질 수 있는지 전반적인 예시입니다:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
|
||||
|
||||
|
||||
/// info | 정보
|
||||
|
||||
Pydantic v1에서는 해당 메서드가 `.dict()`로 불렸으며, Pydantic v2에서는 `.model_dump()`로 이름이 변경되었습니다. `.dict()`는 여전히 지원되지만 더 이상 권장되지 않습니다.
|
||||
|
||||
여기에서 사용하는 예제는 Pydantic v1과의 호환성을 위해 `.dict()`를 사용하지만, Pydantic v2를 사용할 수 있다면 `.model_dump()`를 사용하는 것이 좋습니다.
|
||||
|
||||
///
|
||||
|
||||
### `**user_in.dict()` 에 대하여
|
||||
|
||||
#### Pydantic의 `.dict()`
|
||||
|
||||
`user_in`은 Pydantic 모델 클래스인 `UserIn`입니다.
|
||||
|
||||
Pydantic 모델은 모델 데이터를 포함한 `dict`를 반환하는 `.dict()` 메서드를 제공합니다.
|
||||
|
||||
따라서, 다음과 같이 Pydantic 객체 `user_in`을 생성할 수 있습니다:
|
||||
|
||||
```Python
|
||||
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
|
||||
```
|
||||
|
||||
그 다음, 다음과 같이 호출합니다:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
```
|
||||
|
||||
이제 변수 `user_dict`에 데이터가 포함된 `dict`를 가지게 됩니다(이는 Pydantic 모델 객체가 아닌 `dict`입니다).
|
||||
|
||||
그리고 다음과 같이 호출하면:
|
||||
|
||||
```Python
|
||||
print(user_dict)
|
||||
```
|
||||
|
||||
Python의 `dict`가 다음과 같이 출력됩니다:
|
||||
|
||||
```Python
|
||||
{
|
||||
'username': 'john',
|
||||
'password': 'secret',
|
||||
'email': 'john.doe@example.com',
|
||||
'full_name': None,
|
||||
}
|
||||
```
|
||||
|
||||
#### `dict` 언패킹(Unpacking)
|
||||
|
||||
`user_dict`와 같은 `dict`를 함수(또는 클래스)에 `**user_dict`로 전달하면, Python은 이를 "언팩(unpack)"합니다. 이 과정에서 `user_dict`의 키와 값을 각각 키-값 인자로 직접 전달합니다.
|
||||
|
||||
따라서, 위에서 생성한 `user_dict`를 사용하여 다음과 같이 작성하면:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
다음과 같은 결과를 생성합니다:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username="john",
|
||||
password="secret",
|
||||
email="john.doe@example.com",
|
||||
full_name=None,
|
||||
)
|
||||
```
|
||||
|
||||
혹은 더 정확히 말하자면, `user_dict`를 직접 사용하는 것은, 나중에 어떤 값이 추가되더라도 아래와 동일한 효과를 냅니다:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
password = user_dict["password"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
)
|
||||
```
|
||||
|
||||
#### 다른 모델 데이터로 새 Pydantic 모델 생성
|
||||
|
||||
위의 예제에서 `user_in.dict()`로부터 `user_dict`를 생성한 것처럼, 아래 코드는:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
다음과 동일합니다:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict())
|
||||
```
|
||||
|
||||
...왜냐하면 `user_in.dict()`는 `dict`이며, 이를 `**`로 Python이 "언팩(unpack)"하도록 하여 `UserInDB`에 전달하기 때문입니다.
|
||||
|
||||
따라서, 다른 Pydantic 모델의 데이터를 사용하여 새로운 Pydantic 모델을 생성할 수 있습니다.
|
||||
|
||||
#### `dict` 언패킹(Unpacking)과 추가 키워드
|
||||
|
||||
그리고 다음과 같이 추가 키워드 인자 `hashed_password=hashed_password`를 추가하면:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict(), hashed_password=hashed_password)
|
||||
```
|
||||
|
||||
다음과 같은 결과를 생성합니다:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
password = user_dict["password"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
hashed_password = hashed_password,
|
||||
)
|
||||
```
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
추가적으로 제공된 함수 `fake_password_hasher`와 `fake_save_user`는 데이터 흐름을 시연하기 위한 예제일 뿐이며, 실제 보안을 제공하지 않습니다.
|
||||
|
||||
///
|
||||
|
||||
## 중복 줄이기
|
||||
|
||||
코드 중복을 줄이는 것은 **FastAPI**의 핵심 아이디어 중 하나입니다.
|
||||
|
||||
코드 중복은 버그, 보안 문제, 코드 비동기화 문제(한 곳은 업데이트되었지만 다른 곳은 업데이트되지 않는 문제) 등의 가능성을 증가시킵니다.
|
||||
|
||||
그리고 이 모델들은 많은 데이터를 공유하면서 속성 이름과 타입을 중복하고 있습니다.
|
||||
|
||||
더 나은 방법이 있습니다.
|
||||
|
||||
`UserBase` 모델을 선언하여 다른 모델들의 기본(base)으로 사용할 수 있습니다. 그런 다음 이 모델을 상속받아 속성과 타입 선언(유형 선언, 검증 등)을 상속하는 서브클래스를 만들 수 있습니다.
|
||||
|
||||
모든 데이터 변환, 검증, 문서화 등은 정상적으로 작동할 것입니다.
|
||||
|
||||
이렇게 하면 각 모델 간의 차이점만 선언할 수 있습니다(평문 `password`가 있는 경우, `hashed_password`만 있는 경우, 혹은 비밀번호가 없는 경우):
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
|
||||
|
||||
## `Union` 또는 `anyOf`
|
||||
|
||||
두 가지 이상의 타입을 포함하는 `Union`으로 응답을 선언할 수 있습니다. 이는 응답이 그 중 하나의 타입일 수 있음을 의미합니다.
|
||||
|
||||
OpenAPI에서는 이를 `anyOf`로 정의합니다.
|
||||
|
||||
이를 위해 표준 Python 타입 힌트인 <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>을 사용할 수 있습니다:
|
||||
|
||||
/// note | 참고
|
||||
|
||||
<a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>을 정의할때는 더 구체적인 타입을 먼저 포함하고, 덜 구체적인 타입을 그 뒤에 나열해야합니다. 아래 예제에서는 `Union[PlaneItem, CarItem]` 를 보면, 더 구체적인 `PlaneItem`이 `CarItem`보다 앞에 위치합니다.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
|
||||
|
||||
|
||||
### Python 3.10에서 `Union`
|
||||
|
||||
위의 예제에서는 `response_model` 인자 값으로 `Union[PlaneItem, CarItem]`을 전달합니다.
|
||||
|
||||
이 경우, 이를 **타입 어노테이션(type annotation)** 이 아닌 **인자 값(argument value)** 으로 전달하고 있기 때문에 Python 3.10에서도 `Union`을 사용해야 합니다.
|
||||
|
||||
만약 타입 어노테이션에 사용한다면, 다음과 같이 수직 막대(|)를 사용할 수 있습니다:
|
||||
|
||||
```Python
|
||||
some_variable: PlaneItem | CarItem
|
||||
```
|
||||
|
||||
하지만 이를 `response_model=PlaneItem | CarItem`과 같이 할당하면 에러가 발생합니다. 이는 Python이 이를 타입 어노테이션으로 해석하지 않고, `PlaneItem`과 `CarItem` 사이의 **잘못된 연산(invalid operation)**을 시도하기 때문입니다
|
||||
|
||||
## 모델 리스트
|
||||
|
||||
마찬가지로, 객체 리스트 형태의 응답을 선언할 수도 있습니다.
|
||||
|
||||
이를 위해 표준 Python의 `typing.List`를 사용하세요(또는 Python 3.9 이상에서는 단순히 `list`를 사용할 수 있습니다):
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
|
||||
|
||||
|
||||
## 임의의 `dict` 응답
|
||||
|
||||
Pydantic 모델을 사용하지 않고, 키와 값의 타입만 선언하여 평범한 임의의 `dict`로 응답을 선언할 수도 있습니다.
|
||||
|
||||
이는 Pydantic 모델에 필요한 유효한 필드/속성 이름을 사전에 알 수 없는 경우에 유용합니다.
|
||||
|
||||
이 경우, `typing.Dict`를 사용할 수 있습니다(또는 Python 3.9 이상에서는 단순히 `dict`를 사용할 수 있습니다):
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
|
||||
|
||||
|
||||
## 요약
|
||||
|
||||
여러 Pydantic 모델을 사용하고, 각 경우에 맞게 자유롭게 상속하세요.
|
||||
|
||||
엔터티가 서로 다른 "상태"를 가져야 하는 경우, 엔터티당 단일 데이터 모델을 사용할 필요는 없습니다. 예를 들어, 사용자 "엔터티"가 `password`, `password_hash`, 또는 비밀번호가 없는 상태를 포함할 수 있는 경우처럼 말입니다.
|
||||
@@ -0,0 +1,335 @@
|
||||
# 첫걸음
|
||||
|
||||
가장 단순한 FastAPI 파일은 다음과 같이 보일 것입니다:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py *}
|
||||
|
||||
위 코드를 `main.py`에 복사합니다.
|
||||
|
||||
라이브 서버를 실행합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
<span style="color: green;">INFO</span>: Started reloader process [28720]
|
||||
<span style="color: green;">INFO</span>: Started server process [28722]
|
||||
<span style="color: green;">INFO</span>: Waiting for application startup.
|
||||
<span style="color: green;">INFO</span>: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// note | 참고
|
||||
|
||||
`uvicorn main:app` 명령은 다음을 의미합니다:
|
||||
|
||||
* `main`: 파일 `main.py` (파이썬 "모듈").
|
||||
* `app`: `main.py` 내부의 `app = FastAPI()` 줄에서 생성한 오브젝트.
|
||||
* `--reload`: 코드 변경 시 자동으로 서버 재시작. 개발 시에만 사용.
|
||||
|
||||
///
|
||||
|
||||
출력되는 줄들 중에는 아래와 같은 내용이 있습니다:
|
||||
|
||||
```hl_lines="4"
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
해당 줄은 로컬에서 앱이 서비스되는 URL을 보여줍니다.
|
||||
|
||||
### 확인하기
|
||||
|
||||
브라우저로 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>를 여세요.
|
||||
|
||||
아래와 같은 JSON 응답을 볼 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
### 대화형 API 문서
|
||||
|
||||
이제 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>로 가봅니다.
|
||||
|
||||
자동 대화형 API 문서를 볼 수 있습니다 (<a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> 제공):
|
||||
|
||||

|
||||
|
||||
### 대안 API 문서
|
||||
|
||||
그리고 이제, <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>로 가봅니다.
|
||||
|
||||
대안 자동 문서를 볼 수 있습니다 (<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 제공):
|
||||
|
||||

|
||||
|
||||
### OpenAPI
|
||||
|
||||
**FastAPI**는 API를 정의하기 위한 **OpenAPI** 표준을 사용하여 여러분의 모든 API를 이용해 "스키마"를 생성합니다.
|
||||
|
||||
#### "스키마"
|
||||
|
||||
"스키마"는 무언가의 정의 또는 설명입니다. 이를 구현하는 코드가 아니라 추상적인 설명일 뿐입니다.
|
||||
|
||||
#### API "스키마"
|
||||
|
||||
<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a>는 API의 스키마를 어떻게 정의하는지 지시하는 규격입니다.
|
||||
|
||||
이 스키마 정의는 API 경로, 가능한 매개변수 등을 포함합니다.
|
||||
|
||||
#### 데이터 "스키마"
|
||||
|
||||
"스키마"라는 용어는 JSON처럼 어떤 데이터의 형태를 나타낼 수도 있습니다.
|
||||
|
||||
이러한 경우 JSON 속성, 가지고 있는 데이터 타입 등을 뜻합니다.
|
||||
|
||||
#### OpenAPI와 JSON 스키마
|
||||
|
||||
OpenAPI는 당신의 API에 대한 API 스키마를 정의합니다. 또한 이 스키마는 JSON 데이터 스키마의 표준인 **JSON 스키마**를 사용하여 당신의 API가 보내고 받는 데이터의 정의(또는 "스키마")를 포함합니다.
|
||||
|
||||
#### `openapi.json` 확인
|
||||
|
||||
FastAPI는 자동으로 API의 설명과 함께 JSON (스키마)를 생성합니다.
|
||||
|
||||
가공되지 않은 OpenAPI 스키마가 어떻게 생겼는지 궁금하다면, 여기에서 직접 볼 수 있습니다: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
|
||||
|
||||
다음과 같이 시작하는 JSON을 확인할 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
||||
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### OpenAPI의 용도
|
||||
|
||||
OpenAPI 스키마는 포함된 두 개의 대화형 문서 시스템을 제공합니다.
|
||||
|
||||
그리고 OpenAPI의 모든 것을 기반으로 하는 수십 가지 대안이 있습니다. **FastAPI**로 빌드한 애플리케이션에 이러한 대안을 쉽게 추가 할 수 있습니다.
|
||||
|
||||
API와 통신하는 클라이언트(프론트엔드, 모바일, IoT 애플리케이션 등)를 위해 코드를 자동으로 생성하는 데도 사용할 수 있습니다.
|
||||
|
||||
## 단계별 요약
|
||||
|
||||
### 1 단계: `FastAPI` 임포트
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
|
||||
|
||||
`FastAPI`는 당신의 API를 위한 모든 기능을 제공하는 파이썬 클래스입니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`FastAPI`는 `Starlette`를 직접 상속하는 클래스입니다.
|
||||
|
||||
`FastAPI`로 <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a>의 모든 기능을 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
### 2 단계: `FastAPI` "인스턴스" 생성
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
|
||||
|
||||
여기에서 `app` 변수는 `FastAPI` 클래스의 "인스턴스"가 됩니다.
|
||||
|
||||
이것은 당신의 모든 API를 생성하기 위한 상호작용의 주요 지점이 될 것입니다.
|
||||
|
||||
이 `app`은 다음 명령에서 `uvicorn`이 참조하고 있는 것과 동일합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
아래처럼 앱을 만든다면:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial002.py hl[3] *}
|
||||
|
||||
이를 `main.py` 파일에 넣고, `uvicorn`을 아래처럼 호출해야 합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:my_awesome_api --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### 3 단계: *경로 작동* 생성
|
||||
|
||||
#### 경로
|
||||
|
||||
여기서 "경로"는 첫 번째 `/`부터 시작하는 URL의 뒷부분을 의미합니다.
|
||||
|
||||
그러므로 아래와 같은 URL에서:
|
||||
|
||||
```
|
||||
https://example.com/items/foo
|
||||
```
|
||||
|
||||
...경로는 다음과 같습니다:
|
||||
|
||||
```
|
||||
/items/foo
|
||||
```
|
||||
|
||||
/// info | 정보
|
||||
|
||||
"경로"는 일반적으로 "엔드포인트" 또는 "라우트"라고도 불립니다.
|
||||
|
||||
///
|
||||
|
||||
API를 설계할 때 "경로"는 "관심사"와 "리소스"를 분리하기 위한 주요한 방법입니다.
|
||||
|
||||
#### 작동
|
||||
|
||||
"작동(Operation)"은 HTTP "메소드" 중 하나를 나타냅니다.
|
||||
|
||||
다음 중 하나이며:
|
||||
|
||||
* `POST`
|
||||
* `GET`
|
||||
* `PUT`
|
||||
* `DELETE`
|
||||
|
||||
...흔히 사용되지 않는 것들도 있습니다:
|
||||
|
||||
* `OPTIONS`
|
||||
* `HEAD`
|
||||
* `PATCH`
|
||||
* `TRACE`
|
||||
|
||||
HTTP 프로토콜에서는 이러한 "메소드"를 하나(또는 이상) 사용하여 각 경로와 통신할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
API를 설계할 때 일반적으로 특정 행동을 수행하기 위해 특정 HTTP 메소드를 사용합니다.
|
||||
|
||||
일반적으로 다음과 같습니다:
|
||||
|
||||
* `POST`: 데이터를 생성하기 위해.
|
||||
* `GET`: 데이터를 읽기 위해.
|
||||
* `PUT`: 데이터를 수정하기 위해.
|
||||
* `DELETE`: 데이터를 삭제하기 위해.
|
||||
|
||||
그래서 OpenAPI에서는 각 HTTP 메소드들을 "작동"이라 부릅니다.
|
||||
|
||||
우리 역시 이제부터 메소드를 "**작동**"이라고 부를 것입니다.
|
||||
|
||||
#### *경로 작동 데코레이터* 정의
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
|
||||
|
||||
`@app.get("/")`은 **FastAPI**에게 바로 아래에 있는 함수가 다음으로 이동하는 요청을 처리한다는 것을 알려줍니다.
|
||||
|
||||
* 경로 `/`
|
||||
* <abbr title="HTTP GET 메소드"><code>get</code> 작동</abbr> 사용
|
||||
|
||||
/// info | `@decorator` 정보
|
||||
|
||||
이 `@something` 문법은 파이썬에서 "데코레이터"라 부릅니다.
|
||||
|
||||
마치 예쁜 장식용(Decorative) 모자처럼(개인적으로 이 용어가 여기서 유래한 것 같습니다) 함수 맨 위에 놓습니다.
|
||||
|
||||
"데코레이터"는 아래 있는 함수를 받아 그것으로 무언가를 합니다.
|
||||
|
||||
우리의 경우, 이 데코레이터는 **FastAPI**에게 아래 함수가 **경로** `/`의 `get` **작동**에 해당한다고 알려줍니다.
|
||||
|
||||
이것이 "**경로 작동 데코레이터**"입니다.
|
||||
|
||||
///
|
||||
|
||||
다른 작동도 사용할 수 있습니다:
|
||||
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
* `@app.delete()`
|
||||
|
||||
흔히 사용되지 않는 것들도 있습니다:
|
||||
|
||||
* `@app.options()`
|
||||
* `@app.head()`
|
||||
* `@app.patch()`
|
||||
* `@app.trace()`
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
각 작동(HTTP 메소드)을 원하는 대로 사용해도 됩니다.
|
||||
|
||||
**FastAPI**는 특정 의미를 강제하지 않습니다.
|
||||
|
||||
여기서 정보는 지침서일뿐 강제사항이 아닙니다.
|
||||
|
||||
예를 들어 GraphQL을 사용하는 경우, 일반적으로 `POST` 작동만 사용하여 모든 행동을 수행합니다.
|
||||
|
||||
///
|
||||
|
||||
### 4 단계: **경로 작동 함수** 정의
|
||||
|
||||
다음은 우리의 "**경로 작동 함수**"입니다:
|
||||
|
||||
* **경로**: 는 `/`입니다.
|
||||
* **작동**: 은 `get`입니다.
|
||||
* **함수**: 는 "데코레이터" 아래에 있는 함수입니다 (`@app.get("/")` 아래).
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
|
||||
|
||||
이것은 파이썬 함수입니다.
|
||||
|
||||
URL "`/`"에 대한 `GET` 작동을 사용하는 요청을 받을 때마다 **FastAPI**에 의해 호출됩니다.
|
||||
|
||||
위의 예시에서 이 함수는 `async`(비동기) 함수입니다.
|
||||
|
||||
---
|
||||
|
||||
`async def`을 이용하는 대신 일반 함수로 정의할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
차이점을 모르겠다면 [Async: *"바쁘신 경우"*](../async.md#_1){.internal-link target=_blank}을 확인하세요.
|
||||
|
||||
///
|
||||
|
||||
### 5 단계: 콘텐츠 반환
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
|
||||
|
||||
`dict`, `list`, 단일값을 가진 `str`, `int` 등을 반환할 수 있습니다.
|
||||
|
||||
Pydantic 모델을 반환할 수도 있습니다(나중에 더 자세히 살펴봅니다).
|
||||
|
||||
JSON으로 자동 변환되는 객체들과 모델들(ORM 등을 포함해서)이 많이 있습니다. 가장 마음에 드는 것을 사용하십시오, 이미 지원되고 있을 것입니다.
|
||||
|
||||
## 요약
|
||||
|
||||
* `FastAPI` 임포트.
|
||||
* `app` 인스턴스 생성.
|
||||
* (`@app.get("/")`처럼) **경로 작동 데코레이터** 작성.
|
||||
* (위에 있는 `def root(): ...`처럼) **경로 작동 함수** 작성.
|
||||
* (`uvicorn main:app --reload`처럼) 개발 서버 실행.
|
||||
@@ -0,0 +1,56 @@
|
||||
# 헤더 매개변수 모델
|
||||
|
||||
관련 있는 **헤더 매개변수** 그룹이 있는 경우, **Pydantic 모델**을 생성하여 선언할 수 있습니다.
|
||||
|
||||
이를 통해 **여러 위치**에서 **모델을 재사용** 할 수 있고 모든 매개변수에 대한 유효성 검사 및 메타데이터를 한 번에 선언할 수도 있습니다. 😎
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.115.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Pydantic 모델을 사용한 헤더 매개변수
|
||||
|
||||
**Pydantic 모델**에 필요한 **헤더 매개변수**를 선언한 다음, 해당 매개변수를 `Header`로 선언합니다:
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
|
||||
|
||||
**FastAPI**는 요청에서 받은 **헤더**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
|
||||
|
||||
## 문서 확인하기
|
||||
|
||||
문서 UI `/docs`에서 필요한 헤더를 볼 수 있습니다:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/header-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 추가 헤더 금지하기
|
||||
|
||||
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 헤더를 **제한**할 수 있습니다.
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
클라이언트가 **추가 헤더**를 보내려고 시도하면, **오류** 응답을 받게 됩니다.
|
||||
|
||||
예를 들어, 클라이언트가 `plumbus` 값으로 `tool` 헤더를 보내려고 하면, 클라이언트는 헤더 매개변수 `tool`이 허용 되지 않는다는 **오류** 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["header", "tool"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "plumbus",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
**Pydantic 모델**을 사용하여 **FastAPI**에서 **헤더**를 선언할 수 있습니다. 😎
|
||||
@@ -0,0 +1,91 @@
|
||||
# 헤더 매개변수
|
||||
|
||||
헤더 매개변수를 `Query`, `Path` 그리고 `Cookie` 매개변수들과 같은 방식으로 정의할 수 있습니다.
|
||||
|
||||
## `Header` 임포트
|
||||
|
||||
먼저 `Header`를 임포트합니다:
|
||||
|
||||
{* ../../docs_src/header_params/tutorial001.py hl[3] *}
|
||||
|
||||
## `Header` 매개변수 선언
|
||||
|
||||
`Path`, `Query` 그리고 `Cookie`를 사용한 동일한 구조를 이용하여 헤더 매개변수를 선언합니다.
|
||||
|
||||
첫 번째 값은 기본값이며, 추가 검증이나 어노테이션 매개변수 모두 전달할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/header_params/tutorial001.py hl[9] *}
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`Header`는 `Path`, `Query` 및 `Cookie`의 "자매"클래스입니다. 이 역시 동일한 공통 `Param` 클래스를 상속합니다.
|
||||
|
||||
`Query`, `Path`, `Header` 그리고 다른 것들을 `fastapi`에서 임포트 할 때, 이들은 실제로 특별한 클래스를 반환하는 함수임을 기억하세요.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
|
||||
헤더를 선언하기 위해서 `Header`를 사용해야 합니다. 그렇지 않으면 해당 매개변수를 쿼리 매개변수로 해석하기 때문입니다.
|
||||
|
||||
///
|
||||
|
||||
## 자동 변환
|
||||
|
||||
`Header`는 `Path`, `Query` 그리고 `Cookie`가 제공하는 것 외에 기능이 조금 더 있습니다.
|
||||
|
||||
대부분의 표준 헤더는 "마이너스 기호" (`-`)라고도 하는 "하이픈" 문자로 구분됩니다.
|
||||
|
||||
그러나 파이썬에서 `user-agent`와 같은 형태의 변수는 유효하지 않습니다.
|
||||
|
||||
따라서 `Header`는 기본적으로 매개변수 이름을 언더스코어(`_`)에서 하이픈(`-`)으로 변환하여 헤더를 추출하고 기록합니다.
|
||||
|
||||
또한 HTTP 헤더는 대소문자를 구분하지 않으므로 "snake_case"로 알려진 표준 파이썬 스타일로 선언할 수 있습니다.
|
||||
|
||||
따라서, `User_Agent` 등과 같이 첫 문자를 대문자화할 필요없이 파이썬 코드에서처럼 `user_agent`로 사용합니다.
|
||||
|
||||
만약 언더스코어를 하이픈으로 자동 변환을 비활성화해야 할 어떤 이유가 있다면, `Header`의 `convert_underscores` 매개변수를 `False`로 설정하십시오:
|
||||
|
||||
{* ../../docs_src/header_params/tutorial002.py hl[10] *}
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
`convert_underscore`를 `False`로 설정하기 전에, 어떤 HTTP 프록시들과 서버들은 언더스코어가 포함된 헤더 사용을 허락하지 않는다는 것을 명심하십시오.
|
||||
|
||||
///
|
||||
|
||||
## 중복 헤더
|
||||
|
||||
중복 헤더들을 수신할 수 있습니다. 즉, 다중값을 갖는 동일한 헤더를 뜻합니다.
|
||||
|
||||
타입 정의에서 리스트를 사용하여 이러한 케이스를 정의할 수 있습니다.
|
||||
|
||||
중복 헤더의 모든 값을 파이썬 `list`로 수신합니다.
|
||||
|
||||
예를 들어, 두 번 이상 나타날 수 있는 `X-Token`헤더를 선언하려면, 다음과 같이 작성합니다:
|
||||
|
||||
{* ../../docs_src/header_params/tutorial003.py hl[9] *}
|
||||
|
||||
다음과 같은 두 개의 HTTP 헤더를 전송하여 해당 *경로* 와 통신할 경우:
|
||||
|
||||
```
|
||||
X-Token: foo
|
||||
X-Token: bar
|
||||
```
|
||||
|
||||
응답은 다음과 같습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"X-Token values": [
|
||||
"bar",
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
`Header`는 `Query`, `Path`, `Cookie`와 동일한 패턴을 사용하여 선언합니다.
|
||||
|
||||
변수의 언더스코어를 걱정하지 마십시오, **FastAPI**가 변수를 변환할 것입니다.
|
||||
@@ -0,0 +1,84 @@
|
||||
# 자습서 - 사용자 안내서
|
||||
|
||||
이 자습서는 단계별로 **FastAPI**의 대부분의 기능에 대해 설명합니다.
|
||||
|
||||
각 섹션은 이전 섹션에 기반하는 순차적인 구조로 작성되었지만, 각 주제로 구분되어 있기 때문에 필요에 따라 특정 섹션으로 바로 이동하여 필요한 내용을 바로 확인할 수 있습니다.
|
||||
|
||||
또한 향후에도 참조 자료로 쓰일 수 있도록 작성되었습니다.
|
||||
|
||||
그러므로 필요할 때에 다시 돌아와서 원하는 것을 정확히 찾을 수 있습니다.
|
||||
|
||||
## 코드 실행하기
|
||||
|
||||
모든 코드 블록은 복사하여 바로 사용할 수 있습니다(실제로 테스트된 파이썬 파일입니다).
|
||||
|
||||
예제를 실행하려면 코드를 `main.py` 파일에 복사하고 다음을 사용하여 `uvicorn`을 시작합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
<span style="color: green;">INFO</span>: Started reloader process [28720]
|
||||
<span style="color: green;">INFO</span>: Started server process [28722]
|
||||
<span style="color: green;">INFO</span>: Waiting for application startup.
|
||||
<span style="color: green;">INFO</span>: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
코드를 작성하거나 복사, 편집할 때, 로컬 환경에서 실행하는 것을 **강력히 권장**합니다.
|
||||
|
||||
로컬 편집기에서 사용한다면, 모든 타입 검사와 자동완성 등 작성해야 하는 코드가 얼마나 적은지 보면서 FastAPI의 이점을 비로소 경험할 수 있습니다.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## FastAPI 설치
|
||||
|
||||
첫 번째 단계는 FastAPI를 설치하는 것입니다.
|
||||
|
||||
자습시에는 모든 선택적인 의존성 및 기능을 함께 설치하는 것을 추천합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[all]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
...이는 코드를 실행하는 서버로 사용할 수 있는 `uvicorn` 또한 포함하고 있습니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
부분적으로 설치할 수도 있습니다.
|
||||
|
||||
애플리케이션을 운영 환경에 배포하려는 경우 다음과 같이 합니다:
|
||||
|
||||
```
|
||||
pip install fastapi
|
||||
```
|
||||
|
||||
추가로 서버 역할을 하는 `uvicorn`을 설치합니다:
|
||||
|
||||
```
|
||||
pip install uvicorn
|
||||
```
|
||||
|
||||
사용하려는 각 선택적인 의존성에 대해서도 동일합니다.
|
||||
|
||||
///
|
||||
|
||||
## 고급 사용자 안내서
|
||||
|
||||
이 **자습서 - 사용자 안내서** 다음에 읽을 수 있는 **고급 사용자 안내서**도 있습니다.
|
||||
|
||||
**고급 사용자 안내서**는 현재 문서를 기반으로 하고, 동일한 개념을 사용하며, 추가적인 기능들에 대해 설명합니다.
|
||||
|
||||
하지만 (지금 읽고 있는) **자습서 - 사용자 안내서**를 먼저 읽는 것을 권장합니다.
|
||||
|
||||
**자습서 - 사용자 안내서**만으로도 완전한 애플리케이션을 구축할 수 있도록 작성되었으며, 필요에 따라 **고급 사용자 안내서**의 추가적인 아이디어를 적용하여 다양한 방식으로 확장할 수 있습니다.
|
||||
@@ -0,0 +1,118 @@
|
||||
# 메타데이터 및 문서화 URL
|
||||
|
||||
**FastAPI** 응용 프로그램에서 다양한 메타데이터 구성을 사용자 맞춤 설정할 수 있습니다.
|
||||
|
||||
## API에 대한 메타데이터
|
||||
|
||||
OpenAPI 명세 및 자동화된 API 문서 UI에 사용되는 다음 필드를 설정할 수 있습니다:
|
||||
|
||||
| 매개변수 | 타입 | 설명 |
|
||||
|----------|------|-------|
|
||||
| `title` | `str` | API의 제목입니다. |
|
||||
| `summary` | `str` | API에 대한 짧은 요약입니다. <small>OpenAPI 3.1.0, FastAPI 0.99.0부터 사용 가능</small> |
|
||||
| `description` | `str` | API에 대한 짧은 설명입니다. 마크다운을 사용할 수 있습니다. |
|
||||
| `version` | `string` | API의 버전입니다. OpenAPI의 버전이 아닌, 여러분의 애플리케이션의 버전을 나타냅니다. 예: `2.5.0` |
|
||||
| `terms_of_service` | `str` | API 이용 약관의 URL입니다. 제공하는 경우 URL 형식이어야 합니다. |
|
||||
| `contact` | `dict` | 노출된 API에 대한 연락처 정보입니다. 여러 필드를 포함할 수 있습니다. <details><summary><code>contact</code> 필드</summary><table><thead><tr><th>매개변수</th><th>타입</th><th>설명</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>연락처 인물/조직의 식별명입니다.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>연락처 정보가 담긴 URL입니다. URL 형식이어야 합니다.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>연락처 인물/조직의 이메일 주소입니다. 이메일 주소 형식이어야 합니다.</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | 노출된 API의 라이선스 정보입니다. 여러 필드를 포함할 수 있습니다. <details><summary><code>license_info</code> 필드</summary><table><thead><tr><th>매개변수</th><th>타입</th><th>설명</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>필수</strong> (<code>license_info</code>가 설정된 경우). API에 사용된 라이선스 이름입니다.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>API에 대한 <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> 라이선스 표현입니다. <code>identifier</code> 필드는 <code>url</code> 필드와 상호 배타적입니다. <small>OpenAPI 3.1.0, FastAPI 0.99.0부터 사용 가능</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>API에 사용된 라이선스의 URL입니다. URL 형식이어야 합니다.</td></tr></tbody></table></details> |
|
||||
|
||||
다음과 같이 설정할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001.py hl[3:16,19:32] *}
|
||||
|
||||
/// tip
|
||||
|
||||
`description` 필드에 마크다운을 사용할 수 있으며, 출력에서 렌더링됩니다.
|
||||
|
||||
///
|
||||
|
||||
이 구성을 사용하면 문서 자동화(로 생성된) API 문서는 다음과 같이 보입니다:
|
||||
|
||||
<img src="/img/tutorial/metadata/image01.png">
|
||||
|
||||
## 라이선스 식별자
|
||||
|
||||
OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `identifier`를 URL 대신 설정할 수 있습니다.
|
||||
|
||||
예:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
|
||||
|
||||
## 태그에 대한 메타데이터
|
||||
|
||||
`openapi_tags` 매개변수를 사용하여 경로 작동을 그룹화하는 데 사용되는 태그에 추가 메타데이터를 추가할 수 있습니다.
|
||||
|
||||
리스트는 각 태그에 대해 하나의 딕셔너리를 포함해야 합니다.
|
||||
|
||||
각 딕셔너리에는 다음이 포함될 수 있습니다:
|
||||
|
||||
* `name` (**필수**): `tags` 매개변수에서 *경로 작동*과 `APIRouter`에 사용된 태그 이름과 동일한 `str`입니다.
|
||||
* `description`: 태그에 대한 간단한 설명을 담은 `str`입니다. 마크다운을 사용할 수 있으며 문서 UI에 표시됩니다.
|
||||
* `externalDocs`: 외부 문서를 설명하는 `dict`이며:
|
||||
* `description`: 외부 문서에 대한 간단한 설명을 담은 `str`입니다.
|
||||
* `url` (**필수**): 외부 문서의 URL을 담은 `str`입니다.
|
||||
|
||||
### 태그에 대한 메타데이터 생성
|
||||
|
||||
`users` 및 `items`에 대한 태그 예시와 함께 메타데이터를 생성하고 이를 `openapi_tags` 매개변수로 전달해 보겠습니다:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
|
||||
|
||||
설명 안에 마크다운을 사용할 수 있습니다. 예를 들어 "login"은 굵게(**login**) 표시되고, "fancy"는 기울임꼴(_fancy_)로 표시됩니다.
|
||||
|
||||
/// tip
|
||||
|
||||
사용 중인 모든 태그에 메타데이터를 추가할 필요는 없습니다.
|
||||
|
||||
///
|
||||
|
||||
### 태그 사용
|
||||
|
||||
`tags` 매개변수를 *경로 작동* 및 `APIRouter`와 함께 사용하여 태그에 할당할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
|
||||
|
||||
/// info
|
||||
|
||||
태그에 대한 자세한 내용은 [경로 작동 구성](path-operation-configuration.md#tags){.internal-link target=_blank}에서 읽어보세요.
|
||||
|
||||
///
|
||||
|
||||
### 문서 확인
|
||||
|
||||
이제 문서를 확인하면 모든 추가 메타데이터가 표시됩니다:
|
||||
|
||||
<img src="/img/tutorial/metadata/image02.png">
|
||||
|
||||
### 태그 순서
|
||||
|
||||
각 태그 메타데이터 딕셔너리의 순서는 문서 UI에 표시되는 순서를 정의합니다.
|
||||
|
||||
예를 들어, 알파벳 순서상 `users`는 `items` 뒤에 오지만, 우리는 `users` 메타데이터를 리스트의 첫 번째 딕셔너리로 추가했기 때문에 먼저 표시됩니다.
|
||||
|
||||
## OpenAPI URL
|
||||
|
||||
OpenAPI 구조는 기본적으로 `/openapi.json`에서 제공됩니다.
|
||||
|
||||
`openapi_url` 매개변수를 통해 이를 설정할 수 있습니다.
|
||||
|
||||
예를 들어, 이를 `/api/v1/openapi.json`에 제공하도록 설정하려면:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
|
||||
|
||||
OpenAPI 구조를 완전히 비활성화하려면 `openapi_url=None`으로 설정할 수 있으며, 이를 사용하여 문서화 사용자 인터페이스도 비활성화됩니다.
|
||||
|
||||
## 문서화 URL
|
||||
|
||||
포함된 두 가지 문서화 사용자 인터페이스를 설정할 수 있습니다:
|
||||
|
||||
* **Swagger UI**: `/docs`에서 제공됩니다.
|
||||
* `docs_url` 매개변수로 URL을 설정할 수 있습니다.
|
||||
* `docs_url=None`으로 설정하여 비활성화할 수 있습니다.
|
||||
* **ReDoc**: `/redoc`에서 제공됩니다.
|
||||
* `redoc_url` 매개변수로 URL을 설정할 수 있습니다.
|
||||
* `redoc_url=None`으로 설정하여 비활성화할 수 있습니다.
|
||||
|
||||
예를 들어, Swagger UI를 `/documentation`에서 제공하고 ReDoc을 비활성화하려면:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial003.py hl[3] *}
|
||||
@@ -0,0 +1,66 @@
|
||||
# 미들웨어
|
||||
|
||||
미들웨어를 **FastAPI** 응용 프로그램에 추가할 수 있습니다.
|
||||
|
||||
"미들웨어"는 특정 *경로 작동*에 의해 처리되기 전, 모든 **요청**에 대해서 동작하는 함수입니다. 또한 모든 **응답**이 반환되기 전에도 동일하게 동작합니다.
|
||||
|
||||
* 미들웨어는 응용 프로그램으로 오는 **요청**를 가져옵니다.
|
||||
* **요청** 또는 다른 필요한 코드를 실행 시킬 수 있습니다.
|
||||
* **요청**을 응용 프로그램의 *경로 작동*으로 전달하여 처리합니다.
|
||||
* 애플리케이션의 *경로 작업*에서 생성한 **응답**를 받습니다.
|
||||
* **응답** 또는 다른 필요한 코드를 실행시키는 동작을 할 수 있습니다.
|
||||
* **응답**를 반환합니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
만약 `yield`를 사용한 의존성을 가지고 있다면, 미들웨어가 실행되고 난 후에 exit이 실행됩니다.
|
||||
|
||||
만약 (나중에 문서에서 다룰) 백그라운드 작업이 있다면, 모든 미들웨어가 실행되고 *난 후에* 실행됩니다.
|
||||
|
||||
///
|
||||
|
||||
## 미들웨어 만들기
|
||||
|
||||
미들웨어를 작성하기 위해서 함수 상단에 `@app.middleware("http")` 데코레이터를 사용할 수 있습니다.
|
||||
|
||||
미들웨어 함수는 다음 항목들을 받습니다:
|
||||
|
||||
* `request`.
|
||||
* `request`를 매개변수로 받는 `call_next` 함수.
|
||||
* 이 함수는 `request`를 해당하는 *경로 작업*으로 전달합니다.
|
||||
* 그런 다음, *경로 작업*에 의해 생성된 `response` 를 반환합니다.
|
||||
* `response`를 반환하기 전에 추가로 `response`를 수정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
사용자 정의 헤더는 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">'X-' 접두사를 사용</a>하여 추가할 수 있습니다.
|
||||
|
||||
그러나 만약 클라이언트의 브라우저에서 볼 수 있는 사용자 정의 헤더를 가지고 있다면, 그것들을 CORS 설정([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})에 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette CORS 문서</a>에 명시된 `expose_headers` 매개변수를 이용하여 헤더들을 추가하여야합니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
`from starlette.requests import request`를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 개발자에게 편의를 위해 이를 제공합니다. 그러나 Starlette에서 직접 파생되었습니다.
|
||||
|
||||
///
|
||||
|
||||
### `response`의 전과 후
|
||||
|
||||
*경로 작동*을 받기 전 `request`와 함께 작동할 수 있는 코드를 추가할 수 있습니다.
|
||||
|
||||
그리고 `response` 또한 생성된 후 반환되기 전에 코드를 추가 할 수 있습니다.
|
||||
|
||||
예를 들어, 요청을 수행하고 응답을 생성하는데 까지 걸린 시간 값을 가지고 있는 `X-Process-Time` 같은 사용자 정의 헤더를 추가할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
|
||||
|
||||
## 다른 미들웨어
|
||||
|
||||
미들웨어에 대한 더 많은 정보는 [숙련된 사용자 안내서: 향상된 미들웨어](../advanced/middleware.md){.internal-link target=\_blank}에서 확인할 수 있습니다.
|
||||
|
||||
다음 부분에서 미들웨어와 함께 <abbr title="교차-출처 리소스 공유">CORS</abbr>를 어떻게 다루는지에 대해 확인할 것입니다.
|
||||
@@ -0,0 +1,97 @@
|
||||
# 경로 작동 설정
|
||||
|
||||
*경로 작동 데코레이터*를 설정하기 위해서 전달할수 있는 몇 가지 매개변수가 있습니다.
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
아래 매개변수들은 *경로 작동 함수*가 아닌 *경로 작동 데코레이터*에 직접 전달된다는 사실을 기억하십시오.
|
||||
|
||||
///
|
||||
|
||||
## 응답 상태 코드
|
||||
|
||||
*경로 작동*의 응답에 사용될 (HTTP) `status_code`를 정의할수 있습니다.
|
||||
|
||||
`404`와 같은 `int`형 코드를 직접 전달할수 있습니다.
|
||||
|
||||
하지만 각 코드의 의미를 모른다면, `status`에 있는 단축 상수들을 사용할수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *}
|
||||
|
||||
각 상태 코드들은 응답에 사용되며, OpenAPI 스키마에 추가됩니다.
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
다음과 같이 임포트하셔도 좋습니다. `from starlette import status`.
|
||||
|
||||
**FastAPI**는 개발자 여러분의 편의를 위해서 `starlette.status`와 동일한 `fastapi.status`를 제공합니다. 하지만 Starlette에서 직접 온 것입니다.
|
||||
|
||||
///
|
||||
|
||||
## 태그
|
||||
|
||||
(보통 단일 `str`인) `str`로 구성된 `list`와 함께 매개변수 `tags`를 전달하여, `경로 작동`에 태그를 추가할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *}
|
||||
|
||||
전달된 태그들은 OpenAPI의 스키마에 추가되며, 자동 문서 인터페이스에서 사용됩니다:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image01.png">
|
||||
|
||||
## 요약과 기술
|
||||
|
||||
`summary`와 `description`을 추가할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *}
|
||||
|
||||
## 독스트링으로 만든 기술
|
||||
|
||||
설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 작동* 기술을 함수 <abbr title="함수안에 있는 첫번째 표현식으로, 문서로 사용될 여러 줄에 걸친 (변수에 할당되지 않은) 문자열"> 독스트링</abbr> 에 선언할 수 있습니다, 이를 **FastAPI**가 독스트링으로부터 읽습니다.
|
||||
|
||||
<a href="https://ko.wikipedia.org/wiki/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4" class="external-link" target="_blank">마크다운</a> 문법으로 독스트링을 작성할 수 있습니다, 작성된 마크다운 형식의 독스트링은 (마크다운의 들여쓰기를 고려하여) 올바르게 화면에 출력됩니다.
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *}
|
||||
|
||||
이는 대화형 문서에서 사용됩니다:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image02.png">
|
||||
|
||||
## 응답 기술
|
||||
|
||||
`response_description` 매개변수로 응답에 관한 설명을 명시할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`response_description`은 구체적으로 응답을 지칭하며, `description`은 일반적인 *경로 작동*을 지칭합니다.
|
||||
|
||||
///
|
||||
|
||||
/// check | 확인
|
||||
|
||||
OpenAPI는 각 *경로 작동*이 응답에 관한 설명을 요구할 것을 명시합니다.
|
||||
|
||||
따라서, 응답에 관한 설명이 없을경우, **FastAPI**가 자동으로 "성공 응답" 중 하나를 생성합니다.
|
||||
|
||||
///
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image03.png">
|
||||
|
||||
## 단일 *경로 작동* 지원중단
|
||||
|
||||
단일 *경로 작동*을 없애지 않고 <abbr title="구식, 사용하지 않는것이 권장됨">지원중단</abbr>을 해야한다면, `deprecated` 매개변수를 전달하면 됩니다.
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
|
||||
|
||||
대화형 문서에 지원중단이라고 표시됩니다.
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image04.png">
|
||||
|
||||
지원중단된 경우와 지원중단 되지 않은 경우에 대한 *경로 작동*이 어떻게 보이는 지 확인하십시오.
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image05.png">
|
||||
|
||||
## 정리
|
||||
|
||||
*경로 작동 데코레이터*에 매개변수(들)를 전달함으로 *경로 작동*을 설정하고 메타데이터를 추가할수 있습니다.
|
||||
@@ -0,0 +1,117 @@
|
||||
# 경로 매개변수와 숫자 검증
|
||||
|
||||
`Query`를 사용하여 쿼리 매개변수에 더 많은 검증과 메타데이터를 선언하는 방법과 동일하게 `Path`를 사용하여 경로 매개변수에 검증과 메타데이터를 같은 타입으로 선언할 수 있습니다.
|
||||
|
||||
## 경로 임포트
|
||||
|
||||
먼저 `fastapi`에서 `Path`를 임포트합니다:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[3] *}
|
||||
|
||||
## 메타데이터 선언
|
||||
|
||||
`Query`에 동일한 매개변수를 선언할 수 있습니다.
|
||||
|
||||
예를 들어, `title` 메타데이터 값을 경로 매개변수 `item_id`에 선언하려면 다음과 같이 입력할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
경로 매개변수는 경로의 일부여야 하므로 언제나 필수적입니다.
|
||||
|
||||
즉, `...`로 선언해서 필수임을 나타내는게 좋습니다.
|
||||
|
||||
그럼에도 `None`으로 선언하거나 기본값을 지정할지라도 아무 영향을 끼치지 않으며 언제나 필수입니다.
|
||||
|
||||
///
|
||||
|
||||
## 필요한 경우 매개변수 정렬하기
|
||||
|
||||
`str` 형인 쿼리 매개변수 `q`를 필수로 선언하고 싶다고 해봅시다.
|
||||
|
||||
해당 매개변수에 대해 아무런 선언을 할 필요가 없으므로 `Query`를 정말로 써야할 필요는 없습니다.
|
||||
|
||||
하지만 `item_id` 경로 매개변수는 여전히 `Path`를 사용해야 합니다.
|
||||
|
||||
파이썬은 "기본값"이 없는 값 앞에 "기본값"이 있는 값을 입력하면 불평합니다.
|
||||
|
||||
그러나 매개변수들을 재정렬함으로써 기본값(쿼리 매개변수 `q`)이 없는 값을 처음 부분에 위치 할 수 있습니다.
|
||||
|
||||
**FastAPI**에서는 중요하지 않습니다. 이름, 타입 그리고 선언구(`Query`, `Path` 등)로 매개변수를 감지하며 순서는 신경 쓰지 않습니다.
|
||||
|
||||
따라서 함수를 다음과 같이 선언 할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
|
||||
|
||||
## 필요한 경우 매개변수 정렬하기, 트릭
|
||||
|
||||
`Query`나 아무런 기본값으로도 `q` 경로 매개변수를 선언하고 싶지 않지만 `Path`를 사용하여 경로 매개변수를 `item_id` 다른 순서로 선언하고 싶다면, 파이썬은 이를 위한 작고 특별한 문법이 있습니다.
|
||||
|
||||
`*`를 함수의 첫 번째 매개변수로 전달하세요.
|
||||
|
||||
파이썬은 `*`으로 아무런 행동도 하지 않지만, 따르는 매개변수들은 <abbr title="유래: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>로도 알려진 키워드 인자(키-값 쌍)여야 함을 인지합니다. 기본값을 가지고 있지 않더라도 그렇습니다.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
|
||||
|
||||
## 숫자 검증: 크거나 같음
|
||||
|
||||
`Query`와 `Path`(나중에 볼 다른 것들도)를 사용하여 문자열 뿐만 아니라 숫자의 제약을 선언할 수 있습니다.
|
||||
|
||||
여기서 `ge=1`인 경우, `item_id`는 `1`보다 "크거나(`g`reater) 같은(`e`qual)" 정수형 숫자여야 합니다.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
|
||||
|
||||
## 숫자 검증: 크거나 같음 및 작거나 같음
|
||||
|
||||
동일하게 적용됩니다:
|
||||
|
||||
* `gt`: 크거나(`g`reater `t`han)
|
||||
* `le`: 작거나 같은(`l`ess than or `e`qual)
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *}
|
||||
|
||||
## 숫자 검증: 부동소수, 크거나 및 작거나
|
||||
|
||||
숫자 검증은 `float` 값에도 동작합니다.
|
||||
|
||||
여기에서 <abbr title="greater than or equal"><code>ge</code></abbr>뿐만 아니라 <abbr title="greater than"><code>gt</code></abbr>를 선언 할 수있는 것이 중요해집니다. 예를 들어 필요한 경우, 값이 `1`보다 작더라도 반드시 `0`보다 커야합니다.
|
||||
|
||||
즉, `0.5`는 유효한 값입니다. 그러나 `0.0` 또는 `0`은 그렇지 않습니다.
|
||||
|
||||
<abbr title="less than"><code>lt</code></abbr> 역시 마찬가지입니다.
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *}
|
||||
|
||||
## 요약
|
||||
|
||||
`Query`, `Path`(아직 보지 못한 다른 것들도)를 사용하면 [쿼리 매개변수와 문자열 검증](query-params-str-validations.md){.internal-link target=_blank}에서와 마찬가지로 메타데이터와 문자열 검증을 선언할 수 있습니다.
|
||||
|
||||
그리고 숫자 검증 또한 선언할 수 있습니다:
|
||||
|
||||
* `gt`: 크거나(`g`reater `t`han)
|
||||
* `ge`: 크거나 같은(`g`reater than or `e`qual)
|
||||
* `lt`: 작거나(`l`ess `t`han)
|
||||
* `le`: 작거나 같은(`l`ess than or `e`qual)
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`Query`, `Path`, 그리고 나중에게 보게될 것들은 (여러분이 사용할 필요가 없는) 공통 `Param` 클래스의 서브 클래스입니다.
|
||||
|
||||
그리고 이들 모두는 여태까지 본 추가 검증과 메타데이터의 동일한 모든 매개변수를 공유합니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`fastapi`에서 `Query`, `Path` 등을 임포트 할 때, 이것들은 실제로 함수입니다.
|
||||
|
||||
호출되면 동일한 이름의 클래스의 인스턴스를 반환합니다.
|
||||
|
||||
즉, 함수인 `Query`를 임포트한 겁니다. 그리고 호출하면 `Query`라는 이름을 가진 클래스의 인스턴스를 반환합니다.
|
||||
|
||||
편집기에서 타입에 대한 오류를 표시하지 않도록 하기 위해 (클래스를 직접 사용하는 대신) 이러한 함수들이 있습니다.
|
||||
|
||||
이렇게 하면 오류를 무시하기 위한 사용자 설정을 추가하지 않고도 일반 편집기와 코딩 도구를 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,250 @@
|
||||
# 경로 매개변수
|
||||
|
||||
파이썬의 포맷 문자열 리터럴에서 사용되는 문법을 이용하여 경로 "매개변수" 또는 "변수"를 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
|
||||
|
||||
경로 매개변수 `item_id`의 값은 함수의 `item_id` 인자로 전달됩니다.
|
||||
|
||||
그래서 이 예제를 실행하고 <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>로 이동하면, 다음 응답을 볼 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{"item_id":"foo"}
|
||||
```
|
||||
|
||||
## 타입이 있는 매개변수
|
||||
|
||||
파이썬 표준 타입 어노테이션을 사용하여 함수에 있는 경로 매개변수의 타입을 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
|
||||
|
||||
위의 예시에서, `item_id`는 `int`로 선언되었습니다.
|
||||
|
||||
/// check | 확인
|
||||
|
||||
이 기능은 함수 내에서 오류 검사, 자동완성 등의 편집기 기능을 활용할 수 있게 해줍니다.
|
||||
|
||||
///
|
||||
|
||||
## 데이터 <abbr title="다음으로도 알려져 있습니다: 직렬화, 파싱, 마샬링">변환</abbr>
|
||||
|
||||
이 예제를 실행하고 <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>을 열면, 다음 응답을 볼 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{"item_id":3}
|
||||
```
|
||||
|
||||
/// check | 확인
|
||||
|
||||
함수가 받은(반환도 하는) 값은 문자열 `"3"`이 아니라 파이썬 `int` 형인 `3`입니다.
|
||||
|
||||
즉, 타입 선언을 하면 **FastAPI**는 자동으로 요청을 <abbr title="HTTP 요청에서 전달되는 문자열을 파이썬 데이터로 변환">"파싱"</abbr>합니다.
|
||||
|
||||
///
|
||||
|
||||
## 데이터 검증
|
||||
|
||||
하지만 브라우저에서 <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>로 이동하면, HTTP 오류가 잘 뜨는 것을 확인할 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"path",
|
||||
"item_id"
|
||||
],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
경로 매개변수 `item_id`는 `int`가 아닌 `"foo"` 값이기 때문입니다.
|
||||
|
||||
`int`가 아닌 `float`을 전달하는 경우에도 동일한 오류가 나타납니다: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
|
||||
|
||||
/// check | 확인
|
||||
|
||||
즉, 파이썬 타입 선언을 하면 **FastAPI**는 데이터 검증을 합니다.
|
||||
|
||||
오류에는 정확히 어느 지점에서 검증을 통과하지 못했는지 명시됩니다.
|
||||
|
||||
이는 API와 상호 작용하는 코드를 개발하고 디버깅하는 데 매우 유용합니다.
|
||||
|
||||
///
|
||||
|
||||
## 문서화
|
||||
|
||||
그리고 브라우저에서 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>를 열면, 다음과 같이 자동 대화식 API 문서를 볼 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/path-params/image01.png">
|
||||
|
||||
/// check | 확인
|
||||
|
||||
그저 파이썬 타입 선언을 하기만 하면 **FastAPI**는 자동 대화형 API 문서(Swagger UI)를 제공합니다.
|
||||
|
||||
경로 매개변수가 정수형으로 명시된 것을 확인할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 표준 기반의 이점, 대체 문서
|
||||
|
||||
그리고 생성된 스키마는 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a> 표준에서 나온 것이기 때문에 호환되는 도구가 많이 있습니다.
|
||||
|
||||
이 덕분에 **FastAPI**는 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>로 접속할 수 있는 (ReDoc을 사용하는) 대체 API 문서를 제공합니다:
|
||||
|
||||
<img src="/img/tutorial/path-params/image02.png">
|
||||
|
||||
이와 마찬가지로 다양한 언어에 대한 코드 생성 도구를 포함하여 여러 호환되는 도구가 있습니다.
|
||||
|
||||
## Pydantic
|
||||
|
||||
모든 데이터 검증은 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>에 의해 내부적으로 수행되므로 이로 인한 이점을 모두 얻을 수 있습니다. 여러분은 관리를 잘 받고 있음을 느낄 수 있습니다.
|
||||
|
||||
`str`, `float`, `bool`, 그리고 다른 여러 복잡한 데이터 타입 선언을 할 수 있습니다.
|
||||
|
||||
이 중 몇 가지는 자습서의 다음 장에 설명되어 있습니다.
|
||||
|
||||
## 순서 문제
|
||||
|
||||
*경로 작동*을 만들때 고정 경로를 갖고 있는 상황들을 맞닥뜨릴 수 있습니다.
|
||||
|
||||
`/users/me`처럼, 현재 사용자의 데이터를 가져온다고 합시다.
|
||||
|
||||
사용자 ID를 이용해 특정 사용자의 정보를 가져오는 경로 `/users/{user_id}`도 있습니다.
|
||||
|
||||
*경로 작동*은 순차적으로 실행되기 때문에 `/users/{user_id}` 이전에 `/users/me`를 먼저 선언해야 합니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
|
||||
|
||||
그렇지 않으면 `/users/{user_id}`는 `/users/me` 요청 또한 매개변수 `user_id`의 값이 `"me"`인 것으로 "생각하게" 됩니다.
|
||||
|
||||
## 사전정의 값
|
||||
|
||||
만약 *경로 매개변수*를 받는 *경로 작동*이 있지만, *경로 매개변수*로 가능한 값들을 미리 정의하고 싶다면 파이썬 표준 <abbr title="열거형(Enumeration)">`Enum`</abbr>을 사용할 수 있습니다.
|
||||
|
||||
### `Enum` 클래스 생성
|
||||
|
||||
`Enum`을 임포트하고 `str`과 `Enum`을 상속하는 서브 클래스를 만듭니다.
|
||||
|
||||
`str`을 상속함으로써 API 문서는 값이 `string` 형이어야 하는 것을 알게 되고 이는 문서에 제대로 표시됩니다.
|
||||
|
||||
가능한 값들에 해당하는 고정된 값의 클래스 어트리뷰트들을 만듭니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">열거형(또는 enums)</a>은 파이썬 버전 3.4 이후로 사용 가능합니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 기계 학습 <abbr title="기술적으로 정확히는 딥 러닝 모델 구조">모델</abbr>들의 이름입니다.
|
||||
|
||||
///
|
||||
|
||||
### *경로 매개변수* 선언
|
||||
|
||||
생성한 열거형 클래스(`ModelName`)를 사용하는 타입 어노테이션으로 *경로 매개변수*를 만듭니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
|
||||
|
||||
### 문서 확인
|
||||
|
||||
*경로 매개변수*에 사용할 수 있는 값은 미리 정의되어 있으므로 대화형 문서에서 잘 표시됩니다:
|
||||
|
||||
<img src="/img/tutorial/path-params/image03.png">
|
||||
|
||||
### 파이썬 *열거형*으로 작업하기
|
||||
|
||||
*경로 매개변수*의 값은 *열거형 멤버*가 됩니다.
|
||||
|
||||
#### *열거형 멤버* 비교
|
||||
|
||||
열거형 `ModelName`의 *열거형 멤버*를 비교할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
|
||||
|
||||
#### *열거형 값* 가져오기
|
||||
|
||||
`model_name.value` 또는 일반적으로 `your_enum_member.value`를 이용하여 실제 값(위 예시의 경우 `str`)을 가져올 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[20] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`ModelName.lenet.value`로도 값 `"lenet"`에 접근할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
#### *열거형 멤버* 반환
|
||||
|
||||
*경로 작동*에서 *열거형 멤버*를 반환할 수 있습니다. 이는 중첩 JSON 본문(예: `dict`)내의 값으로도 가능합니다.
|
||||
|
||||
클라이언트에 반환하기 전에 해당 값(이 경우 문자열)으로 변환됩니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
|
||||
|
||||
클라이언트는 아래의 JSON 응답을 얻습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"model_name": "alexnet",
|
||||
"message": "Deep Learning FTW!"
|
||||
}
|
||||
```
|
||||
|
||||
## 경로를 포함하는 경로 매개변수
|
||||
|
||||
경로를 포함하는 *경로 작동* `/files/{file_path}`이 있다고 해봅시다.
|
||||
|
||||
그런데 이 경우 `file_path` 자체가 `home/johndoe/myfile.txt`와 같은 경로를 포함해야 합니다.
|
||||
|
||||
이때 해당 파일의 URL은 다음처럼 됩니다: `/files/home/johndoe/myfile.txt`.
|
||||
|
||||
### OpenAPI 지원
|
||||
|
||||
테스트와 정의가 어려운 시나리오로 이어질 수 있으므로 OpenAPI는 *경로*를 포함하는 *경로 매개변수*를 내부에 선언하는 방법을 지원하지 않습니다.
|
||||
|
||||
그럼에도 Starlette의 내부 도구중 하나를 사용하여 **FastAPI**에서는 이가 가능합니다.
|
||||
|
||||
문서에 매개변수에 경로가 포함되어야 한다는 정보가 명시되지는 않지만 여전히 작동합니다.
|
||||
|
||||
### 경로 변환기
|
||||
|
||||
Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으로써 *path*를 포함하는 *경로 매개변수*를 선언할 수 있습니다:
|
||||
|
||||
```
|
||||
/files/{file_path:path}
|
||||
```
|
||||
|
||||
이러한 경우 매개변수의 이름은 `file_path`이며, 마지막 부분 `:path`는 매개변수가 *경로*와 일치해야 함을 명시합니다.
|
||||
|
||||
따라서 다음과 같이 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial004.py hl[6] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
매개변수가 가져야 하는 값이 `/home/johndoe/myfile.txt`와 같이 슬래시로 시작(`/`)해야 할 수 있습니다.
|
||||
|
||||
이 경우 URL은: `/files//home/johndoe/myfile.txt`이며 `files`과 `home` 사이에 이중 슬래시(`//`)가 생깁니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
**FastAPI**를 이용하면 짧고 직관적인 표준 파이썬 타입 선언을 사용하여 다음을 얻을 수 있습니다:
|
||||
|
||||
* 편집기 지원: 오류 검사, 자동완성 등
|
||||
* 데이터 "<abbr title="HTTP 요청에서 전달되는 문자열을 파이썬 데이터로 변환">파싱</abbr>"
|
||||
* 데이터 검증
|
||||
* API 주석(Annotation)과 자동 문서
|
||||
|
||||
단 한번의 선언만으로 위 사항들을 모두 선언할 수 있습니다.
|
||||
|
||||
이는 대체 프레임워크와 비교했을 때 (엄청나게 빠른 성능 외에도) **FastAPI**의 주요한 장점일 것입니다.
|
||||
@@ -0,0 +1,68 @@
|
||||
# 쿼리 매개변수 모델
|
||||
|
||||
연관된 쿼리 **매개변수** 그룹이 있다면 **Pydantic 모델** 을 사용해 선언할 수 있습니다.
|
||||
|
||||
이렇게 하면 **여러 곳**에서 **모델을 재사용**할 수 있을 뿐만 아니라, 매개변수에 대한 검증 및 메타데이터도 한 번에 선언할 수 있습니다. 😎
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.115.0`부터 제공됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
## 쿼리 매개변수와 Pydantic 모델
|
||||
|
||||
필요한 **쿼리 매개변수**를 **Pydantic 모델** 안에 선언한 다음, 모델을 `Query`로 선언합니다.
|
||||
|
||||
{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *}
|
||||
|
||||
**FastAPI**는 요청의 **쿼리 매개변수**에서 **각 필드**의 데이터를 **추출**해 정의한 Pydantic 모델로 제공합니다.
|
||||
|
||||
## 문서 확인하기
|
||||
|
||||
`/docs` 경로의 API 문서에서 매개변수를 확인할 수 있습니다.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/query-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 추가 쿼리 매개변수 금지
|
||||
|
||||
몇몇의 특이한 경우에 (흔치 않지만), 허용할 쿼리 매개변수를 **제한**해야할 수 있습니다.
|
||||
|
||||
Pydantic 모델 설정에서 `extra` 필드를 `forbid` 로 설정할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
만약 클라이언트가 쿼리 매개변수로 **추가적인** 데이터를 보내려고 하면, 클라이언트는 **에러** 응답을 받게 됩니다.
|
||||
|
||||
예를 들어, 아래와 같이 만약 클라이언트가 `tool` 쿼리 매개변수에 `plumbus` 라는 값을 추가해서 보내려고 하면,
|
||||
|
||||
```http
|
||||
https://example.com/items/?limit=10&tool=plumbus
|
||||
```
|
||||
|
||||
클라이언트는 쿼리 매개변수 `tool` 이 허용되지 않는다는 **에러** 응답을 받게 됩니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["query", "tool"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "plumbus"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
**FastAPI** 에서 **쿼리 매개변수** 를 선언할 때 **Pydantic 모델** 을 사용할 수 있습니다. 😎
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
스포일러 경고: Pydantic 모델을 쿠키와 헤더에도 적용할 수 있습니다. 이에 대해서는 이후 튜토리얼에서 다룰 예정입니다. 🤫
|
||||
|
||||
///
|
||||
@@ -0,0 +1,296 @@
|
||||
# 쿼리 매개변수와 문자열 검증
|
||||
|
||||
**FastAPI**를 사용하면 매개변수에 대한 추가 정보 및 검증을 선언할 수 있습니다.
|
||||
|
||||
이 응용 프로그램을 예로 들어보겠습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *}
|
||||
|
||||
쿼리 매개변수 `q`는 `Optional[str]` 자료형입니다. 즉, `str` 자료형이지만 `None` 역시 될 수 있음을 뜻하고, 실제로 기본값은 `None`이기 때문에 FastAPI는 이 매개변수가 필수가 아니라는 것을 압니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
FastAPI는 `q`의 기본값이 `= None`이기 때문에 필수가 아님을 압니다.
|
||||
|
||||
`Optional[str]`에 있는 `Optional`은 FastAPI가 사용하는게 아니지만, 편집기에게 더 나은 지원과 오류 탐지를 제공하게 해줍니다.
|
||||
|
||||
///
|
||||
|
||||
## 추가 검증
|
||||
|
||||
`q`가 선택적이지만 값이 주어질 때마다 **값이 50 글자를 초과하지 않게** 강제하려 합니다.
|
||||
|
||||
### `Query` 임포트
|
||||
|
||||
이를 위해 먼저 `fastapi`에서 `Query`를 임포트합니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *}
|
||||
|
||||
## 기본값으로 `Query` 사용
|
||||
|
||||
이제 `Query`를 매개변수의 기본값으로 사용하여 `max_length` 매개변수를 50으로 설정합니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *}
|
||||
|
||||
기본값 `None`을 `Query(None)`으로 바꿔야 하므로, `Query`의 첫 번째 매개변수는 기본값을 정의하는 것과 같은 목적으로 사용됩니다.
|
||||
|
||||
그러므로:
|
||||
|
||||
```Python
|
||||
q: Optional[str] = Query(None)
|
||||
```
|
||||
|
||||
...위 코드는 아래와 동일하게 매개변수를 선택적으로 만듭니다:
|
||||
|
||||
```Python
|
||||
q: Optional[str] = None
|
||||
```
|
||||
|
||||
하지만 명시적으로 쿼리 매개변수를 선언합니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
FastAPI는 다음 부분에 관심이 있습니다:
|
||||
|
||||
```Python
|
||||
= None
|
||||
```
|
||||
|
||||
또는:
|
||||
|
||||
```Python
|
||||
= Query(None)
|
||||
```
|
||||
|
||||
그리고 `None`을 사용하여 쿼라 매개변수가 필수적이지 않다는 것을 파악합니다.
|
||||
|
||||
`Optional` 부분은 편집기에게 더 나은 지원을 제공하기 위해서만 사용됩니다.
|
||||
|
||||
///
|
||||
|
||||
또한 `Query`로 더 많은 매개변수를 전달할 수 있습니다. 지금의 경우 문자열에 적용되는 `max_length` 매개변수입니다:
|
||||
|
||||
```Python
|
||||
q: str = Query(None, max_length=50)
|
||||
```
|
||||
|
||||
이는 데이터를 검증할 것이고, 데이터가 유효하지 않다면 명백한 오류를 보여주며, OpenAPI 스키마 *경로 작동*에 매개변수를 문서화 합니다.
|
||||
|
||||
## 검증 추가
|
||||
|
||||
매개변수 `min_length` 또한 추가할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial003.py hl[9] *}
|
||||
|
||||
## 정규식 추가
|
||||
|
||||
매개변수와 일치해야 하는 <abbr title="정규표현식(regular expression), regex 또는 regexp는 문자열 조회 패턴을 정의하는 문자들의 순열입니다">정규표현식</abbr>을 정의할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial004.py hl[10] *}
|
||||
|
||||
이 특정 정규표현식은 전달 받은 매개변수 값을 검사합니다:
|
||||
|
||||
* `^`: 이전에 문자가 없고 뒤따르는 문자로 시작합니다.
|
||||
* `fixedquery`: 정확히 `fixedquery` 값을 갖습니다.
|
||||
* `$`: 여기서 끝나고 `fixedquery` 이후로 아무 문자도 갖지 않습니다.
|
||||
|
||||
**"정규표현식"** 개념에 대해 상실감을 느꼈다면 걱정하지 않아도 됩니다. 많은 사람에게 어려운 주제입니다. 아직은 정규표현식 없이도 많은 작업들을 할 수 있습니다.
|
||||
|
||||
하지만 언제든지 가서 배울수 있고, **FastAPI**에서 직접 사용할 수 있다는 사실을 알고 있어야 합니다.
|
||||
|
||||
## 기본값
|
||||
|
||||
기본값으로 사용하는 첫 번째 인자로 `None`을 전달하듯이, 다른 값을 전달할 수 있습니다.
|
||||
|
||||
`min_length`가 `3`이고, 기본값이 `"fixedquery"`인 쿼리 매개변수 `q`를 선언해봅시다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
기본값을 갖는 것만으로 매개변수는 선택적이 됩니다.
|
||||
|
||||
///
|
||||
|
||||
## 필수로 만들기
|
||||
|
||||
더 많은 검증이나 메타데이터를 선언할 필요가 없는 경우, 다음과 같이 기본값을 선언하지 않고 쿼리 매개변수 `q`를 필수로 만들 수 있습니다:
|
||||
|
||||
```Python
|
||||
q: str
|
||||
```
|
||||
|
||||
아래 대신:
|
||||
|
||||
```Python
|
||||
q: Optional[str] = None
|
||||
```
|
||||
|
||||
그러나 이제 다음과 같이 `Query`로 선언합니다:
|
||||
|
||||
```Python
|
||||
q: Optional[str] = Query(None, min_length=3)
|
||||
```
|
||||
|
||||
그래서 `Query`를 필수값으로 만들어야 할 때면, 첫 번째 인자로 `...`를 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
이전에 `...`를 본적이 없다면: 특별한 단일값으로, <a href="https://docs.python.org/3/library/constants.html#Ellipsis" class="external-link" target="_blank">파이썬의 일부이며 "Ellipsis"라 부릅니다</a>.
|
||||
|
||||
///
|
||||
|
||||
이렇게 하면 **FastAPI**가 이 매개변수는 필수임을 알 수 있습니다.
|
||||
|
||||
## 쿼리 매개변수 리스트 / 다중값
|
||||
|
||||
쿼리 매개변수를 `Query`와 함께 명시적으로 선언할 때, 값들의 리스트나 다른 방법으로 여러 값을 받도록 선언 할 수도 있습니다.
|
||||
|
||||
예를 들어, URL에서 여러번 나오는 `q` 쿼리 매개변수를 선언하려면 다음과 같이 작성할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *}
|
||||
|
||||
아래와 같은 URL을 사용합니다:
|
||||
|
||||
```
|
||||
http://localhost:8000/items/?q=foo&q=bar
|
||||
```
|
||||
|
||||
여러 `q` *쿼리 매개변수* 값들을 (`foo` 및 `bar`) 파이썬 `list`로 *경로 작동 함수* 내 *함수 매개변수* `q`로 전달 받습니다.
|
||||
|
||||
따라서 해당 URL에 대한 응답은 다음과 같습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"q": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
위의 예와 같이 `list` 자료형으로 쿼리 매개변수를 선언하려면 `Query`를 명시적으로 사용해야 합니다. 그렇지 않으면 요청 본문으로 해석됩니다.
|
||||
|
||||
///
|
||||
|
||||
대화형 API 문서는 여러 값을 허용하도록 수정 됩니다:
|
||||
|
||||
<img src="/img/tutorial/query-params-str-validations/image02.png">
|
||||
|
||||
### 쿼리 매개변수 리스트 / 기본값을 사용하는 다중값
|
||||
|
||||
그리고 제공된 값이 없으면 기본 `list` 값을 정의할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *}
|
||||
|
||||
아래로 이동한다면:
|
||||
|
||||
```
|
||||
http://localhost:8000/items/
|
||||
```
|
||||
|
||||
`q`의 기본값은: `["foo", "bar"]`이며 응답은 다음이 됩니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"q": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `list` 사용하기
|
||||
|
||||
`List[str]` 대신 `list`를 직접 사용할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 경우 FastAPI는 리스트의 내용을 검사하지 않음을 명심하기 바랍니다.
|
||||
|
||||
예를 들어, `List[int]`는 리스트 내용이 정수인지 검사(및 문서화)합니다. 하지만 `list` 단독일 경우는 아닙니다.
|
||||
|
||||
///
|
||||
|
||||
## 더 많은 메타데이터 선언
|
||||
|
||||
매개변수에 대한 정보를 추가할 수 있습니다.
|
||||
|
||||
해당 정보는 생성된 OpenAPI에 포함되고 문서 사용자 인터페이스 및 외부 도구에서 사용됩니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
도구에 따라 OpenAPI 지원 수준이 다를 수 있음을 명심하기 바랍니다.
|
||||
|
||||
일부는 아직 선언된 추가 정보를 모두 표시하지 않을 수 있지만, 대부분의 경우 누락된 기능은 이미 개발 계획이 있습니다.
|
||||
|
||||
///
|
||||
|
||||
`title`을 추가할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *}
|
||||
|
||||
그리고 `description`도 추가할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *}
|
||||
|
||||
## 별칭 매개변수
|
||||
|
||||
매개변수가 `item-query`이길 원한다고 가정해 봅시다.
|
||||
|
||||
마치 다음과 같습니다:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?item-query=foobaritems
|
||||
```
|
||||
|
||||
그러나 `item-query`은 유효한 파이썬 변수 이름이 아닙니다.
|
||||
|
||||
가장 가까운 것은 `item_query`일 겁니다.
|
||||
|
||||
하지만 정확히`item-query`이길 원합니다...
|
||||
|
||||
이럴 경우 `alias`를 선언할 수 있으며, 해당 별칭은 매개변수 값을 찾는 데 사용됩니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *}
|
||||
|
||||
## 매개변수 사용하지 않게 하기
|
||||
|
||||
이제는 더이상 이 매개변수를 마음에 들어하지 않는다고 가정해 봅시다.
|
||||
|
||||
이 매개변수를 사용하는 클라이언트가 있기 때문에 한동안은 남겨둬야 하지만, <abbr title="구식이며, 사용하지 않는 것을 추천">사용되지 않는다(deprecated)</abbr>고 확실하게 문서에서 보여주고 싶습니다.
|
||||
|
||||
그렇다면 `deprecated=True` 매개변수를 `Query`로 전달합니다:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *}
|
||||
|
||||
문서가 아래와 같이 보일겁니다:
|
||||
|
||||
<img src="/img/tutorial/query-params-str-validations/image01.png">
|
||||
|
||||
## 요약
|
||||
|
||||
매개변수에 검증과 메타데이터를 추가 선언할 수 있습니다.
|
||||
|
||||
제네릭 검증과 메타데이터:
|
||||
|
||||
* `alias`
|
||||
* `title`
|
||||
* `description`
|
||||
* `deprecated`
|
||||
|
||||
특정 문자열 검증:
|
||||
|
||||
* `min_length`
|
||||
* `max_length`
|
||||
* `regex`
|
||||
|
||||
예제에서 `str` 값의 검증을 어떻게 추가하는지 살펴보았습니다.
|
||||
|
||||
숫자와 같은 다른 자료형에 대한 검증을 어떻게 선언하는지 확인하려면 다음 장을 확인하기 바랍니다.
|
||||
@@ -0,0 +1,195 @@
|
||||
# 쿼리 매개변수
|
||||
|
||||
경로 매개변수의 일부가 아닌 다른 함수 매개변수를 선언하면 "쿼리" 매개변수로 자동 해석합니다.
|
||||
|
||||
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
|
||||
|
||||
쿼리는 URL에서 `?` 후에 나오고 `&`으로 구분되는 키-값 쌍의 집합입니다.
|
||||
|
||||
예를 들어, 아래 URL에서:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?skip=0&limit=10
|
||||
```
|
||||
|
||||
...쿼리 매개변수는:
|
||||
|
||||
* `skip`: 값 `0`을 가집니다.
|
||||
* `limit`: 값 `10`을 가집니다.
|
||||
|
||||
URL의 일부이므로 "자연스럽게" 문자열입니다.
|
||||
|
||||
하지만 파이썬 타입과 함께 선언할 경우(위 예에서 `int`), 해당 타입으로 변환 및 검증됩니다.
|
||||
|
||||
경로 매개변수에 적용된 동일한 프로세스가 쿼리 매개변수에도 적용됩니다:
|
||||
|
||||
* (당연히) 편집기 지원
|
||||
* 데이터 <abbr title="HTTP 요청에서 전달되는 문자열을 파이썬 데이터로 변환">"파싱"</abbr>
|
||||
* 데이터 검증
|
||||
* 자동 문서화
|
||||
|
||||
## 기본값
|
||||
|
||||
쿼리 매개변수는 경로에서 고정된 부분이 아니기 때문에 선택적일 수 있고 기본값을 가질 수 있습니다.
|
||||
|
||||
위 예에서 `skip=0`과 `limit=10`은 기본값을 갖고 있습니다.
|
||||
|
||||
그러므로 URL로 이동하는 것은:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/
|
||||
```
|
||||
|
||||
아래로 이동하는 것과 같습니다:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?skip=0&limit=10
|
||||
```
|
||||
|
||||
하지만 가령 아래로 이동한 경우:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?skip=20
|
||||
```
|
||||
|
||||
함수의 매개변수 값은 아래가 됩니다:
|
||||
|
||||
* `skip=20`: URL에서 지정했기 때문입니다
|
||||
* `limit=10`: 기본값이기 때문입니다
|
||||
|
||||
## 선택적 매개변수
|
||||
|
||||
같은 방법으로 기본값을 `None`으로 설정하여 선택적 매개변수를 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial002.py hl[9] *}
|
||||
|
||||
이 경우 함수 매개변수 `q`는 선택적이며 기본값으로 `None` 값이 됩니다.
|
||||
|
||||
/// check | 확인
|
||||
|
||||
**FastAPI**는 `item_id`가 경로 매개변수이고 `q`는 경로 매개변수가 아닌 쿼리 매개변수라는 것을 알 정도로 충분히 똑똑합니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 참고
|
||||
|
||||
FastAPI는 `q`가 `= None`이므로 선택적이라는 것을 인지합니다.
|
||||
|
||||
`Union[str, None]`에 있는 `Union`은 FastAPI(FastAPI는 `str` 부분만 사용합니다)가 사용하는게 아니지만, `Union[str, None]`은 편집기에게 코드에서 오류를 찾아낼 수 있게 도와줍니다.
|
||||
|
||||
///
|
||||
|
||||
## 쿼리 매개변수 형변환
|
||||
|
||||
`bool` 형으로 선언할 수도 있고, 아래처럼 변환됩니다:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial003.py hl[9] *}
|
||||
|
||||
이 경우, 아래로 이동하면:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/foo?short=1
|
||||
```
|
||||
|
||||
또는
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/foo?short=True
|
||||
```
|
||||
|
||||
또는
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/foo?short=true
|
||||
```
|
||||
|
||||
또는
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/foo?short=on
|
||||
```
|
||||
|
||||
또는
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/foo?short=yes
|
||||
```
|
||||
|
||||
또는 다른 어떤 변형(대문자, 첫글자만 대문자 등)이더라도 함수는 매개변수 `bool`형을 가진 `short`의 값이 `True`임을 압니다. 그렇지 않은 경우 `False`입니다.
|
||||
|
||||
|
||||
## 여러 경로/쿼리 매개변수
|
||||
|
||||
여러 경로 매개변수와 쿼리 매개변수를 동시에 선언할 수 있으며 **FastAPI**는 어느 것이 무엇인지 알고 있습니다.
|
||||
|
||||
그리고 특정 순서로 선언할 필요가 없습니다.
|
||||
|
||||
매개변수들은 이름으로 감지됩니다:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial004.py hl[8,10] *}
|
||||
|
||||
## 필수 쿼리 매개변수
|
||||
|
||||
경로가 아닌 매개변수에 대한 기본값을 선언할 때(지금은 쿼리 매개변수만 보았습니다), 해당 매개변수는 필수적(Required)이지 않았습니다.
|
||||
|
||||
특정값을 추가하지 않고 선택적으로 만들기 위해선 기본값을 `None`으로 설정하면 됩니다.
|
||||
|
||||
그러나 쿼리 매개변수를 필수로 만들려면 단순히 기본값을 선언하지 않으면 됩니다:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
|
||||
|
||||
여기 쿼리 매개변수 `needy`는 `str`형인 필수 쿼리 매개변수입니다.
|
||||
|
||||
브라우저에서 아래와 같은 URL을 연다면:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/foo-item
|
||||
```
|
||||
|
||||
...필수 매개변수 `needy`를 넣지 않았기 때문에 아래와 같은 오류를 보게 됩니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"query",
|
||||
"needy"
|
||||
],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`needy`는 필수 매개변수이므로 URL에 반드시 설정해줘야 합니다:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
|
||||
```
|
||||
|
||||
...아래처럼 작동합니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"item_id": "foo-item",
|
||||
"needy": "sooooneedy"
|
||||
}
|
||||
```
|
||||
|
||||
그리고 물론, 일부 매개변수는 필수로, 다른 일부는 기본값을, 또 다른 일부는 선택적으로 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial006.py hl[10] *}
|
||||
|
||||
위 예시에서는 3가지 쿼리 매개변수가 있습니다:
|
||||
|
||||
* `needy`, 필수적인 `str`.
|
||||
* `skip`, 기본값이 `0`인 `int`.
|
||||
* `limit`, 선택적인 `int`.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
[경로 매개변수](path-params.md#_8){.internal-link target=_blank}와 마찬가지로 `Enum`을 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,163 @@
|
||||
# 파일 요청
|
||||
|
||||
`File`을 사용하여 클라이언트가 업로드할 파일들을 정의할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
업로드된 파일을 전달받기 위해 먼저 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>를 설치해야합니다.
|
||||
|
||||
예시) `pip install python-multipart`.
|
||||
|
||||
업로드된 파일들은 "폼 데이터"의 형태로 전송되기 때문에 이 작업이 필요합니다.
|
||||
|
||||
///
|
||||
|
||||
## `File` 임포트
|
||||
|
||||
`fastapi` 에서 `File` 과 `UploadFile` 을 임포트 합니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001.py hl[1] *}
|
||||
|
||||
## `File` 매개변수 정의
|
||||
|
||||
`Body` 및 `Form` 과 동일한 방식으로 파일의 매개변수를 생성합니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001.py hl[7] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`File` 은 `Form` 으로부터 직접 상속된 클래스입니다.
|
||||
|
||||
하지만 `fastapi`로부터 `Query`, `Path`, `File` 등을 임포트 할 때, 이것들은 특별한 클래스들을 반환하는 함수라는 것을 기억하기 바랍니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본문(JSON) 매개변수로 해석되는 것을 방지하기 위해 `File` 을 사용해야합니다.
|
||||
|
||||
///
|
||||
|
||||
파일들은 "폼 데이터"의 형태로 업로드 됩니다.
|
||||
|
||||
*경로 작동 함수*의 매개변수를 `bytes` 로 선언하는 경우 **FastAPI**는 파일을 읽고 `bytes` 형태의 내용을 전달합니다.
|
||||
|
||||
이것은 전체 내용이 메모리에 저장된다는 것을 의미한다는 걸 염두하기 바랍니다. 이는 작은 크기의 파일들에 적합합니다.
|
||||
|
||||
어떤 경우에는 `UploadFile` 을 사용하는 것이 더 유리합니다.
|
||||
|
||||
## `File` 매개변수와 `UploadFile`
|
||||
|
||||
`File` 매개변수를 `UploadFile` 타입으로 정의합니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001.py hl[12] *}
|
||||
|
||||
`UploadFile` 을 사용하는 것은 `bytes` 과 비교해 다음과 같은 장점이 있습니다:
|
||||
|
||||
* "스풀 파일"을 사용합니다.
|
||||
* 최대 크기 제한까지만 메모리에 저장되며, 이를 초과하는 경우 디스크에 저장됩니다.
|
||||
* 따라서 이미지, 동영상, 큰 이진코드와 같은 대용량 파일들을 많은 메모리를 소모하지 않고 처리하기에 적합합니다.
|
||||
* 업로드 된 파일의 메타데이터를 얻을 수 있습니다.
|
||||
* <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` 인터페이스를 갖고 있습니다.
|
||||
* file-like object를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 파이썬 <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> 객체를 반환합니다.
|
||||
|
||||
### `UploadFile`
|
||||
|
||||
`UploadFile` 은 다음과 같은 어트리뷰트가 있습니다:
|
||||
|
||||
* `filename` : 문자열(`str`)로 된 업로드된 파일의 파일명입니다 (예: `myimage.jpg`).
|
||||
* `content_type` : 문자열(`str`)로 된 파일 형식(MIME type / media type)입니다 (예: `image/jpeg`).
|
||||
* `file` : <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">파일류</a> 객체)입니다. 이것은 "파일류" 객체를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 실질적인 파이썬 파일입니다.
|
||||
|
||||
`UploadFile` 에는 다음의 `async` 메소드들이 있습니다. 이들은 내부적인 `SpooledTemporaryFile` 을 사용하여 해당하는 파일 메소드를 호출합니다.
|
||||
|
||||
* `write(data)`: `data`(`str` 또는 `bytes`)를 파일에 작성합니다.
|
||||
* `read(size)`: 파일의 바이트 및 글자의 `size`(`int`)를 읽습니다.
|
||||
* `seek(offset)`: 파일 내 `offset`(`int`) 위치의 바이트로 이동합니다.
|
||||
* 예) `await myfile.seek(0)` 를 사용하면 파일의 시작부분으로 이동합니다.
|
||||
* `await myfile.read()` 를 사용한 후 내용을 다시 읽을 때 유용합니다.
|
||||
* `close()`: 파일을 닫습니다.
|
||||
|
||||
상기 모든 메소드들이 `async` 메소드이기 때문에 “await”을 사용하여야 합니다.
|
||||
|
||||
예를들어, `async` *경로 작동 함수*의 내부에서 다음과 같은 방식으로 내용을 가져올 수 있습니다:
|
||||
|
||||
```Python
|
||||
contents = await myfile.read()
|
||||
```
|
||||
|
||||
만약 일반적인 `def` *경로 작동 함수*의 내부라면, 다음과 같이 `UploadFile.file` 에 직접 접근할 수 있습니다:
|
||||
|
||||
```Python
|
||||
contents = myfile.file.read()
|
||||
```
|
||||
|
||||
/// note | "`async` 기술적 세부사항"
|
||||
|
||||
`async` 메소드들을 사용할 때 **FastAPI**는 스레드풀에서 파일 메소드들을 실행하고 그들을 기다립니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | Starlette 기술적 세부사항
|
||||
|
||||
**FastAPI**의 `UploadFile` 은 **Starlette**의 `UploadFile` 을 직접적으로 상속받지만, **Pydantic** 및 FastAPI의 다른 부분들과의 호환성을 위해 필요한 부분들이 추가되었습니다.
|
||||
|
||||
///
|
||||
|
||||
## "폼 데이터"란
|
||||
|
||||
HTML의 폼들(`<form></form>`)이 서버에 데이터를 전송하는 방식은 대개 데이터에 JSON과는 다른 "특별한" 인코딩을 사용합니다.
|
||||
|
||||
**FastAPI**는 JSON 대신 올바른 위치에서 데이터를 읽을 수 있도록 합니다.
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
폼의 데이터는 파일이 포함되지 않은 경우 일반적으로 "미디어 유형" `application/x-www-form-urlencoded` 을 사용해 인코딩 됩니다.
|
||||
|
||||
하지만 파일이 포함된 경우, `multipart/form-data`로 인코딩됩니다. `File`을 사용하였다면, **FastAPI**는 본문의 적합한 부분에서 파일을 가져와야 한다는 것을 인지합니다.
|
||||
|
||||
인코딩과 폼 필드에 대해 더 알고싶다면, <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> 를 참고하기 바랍니다,.
|
||||
|
||||
///
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
다수의 `File` 과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json` 가 아닌 `multipart/form-data` 로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다.
|
||||
|
||||
이는 **FastAPI**의 한계가 아니라, HTTP 프로토콜에 의한 것입니다.
|
||||
|
||||
///
|
||||
|
||||
## 다중 파일 업로드
|
||||
|
||||
여러 파일을 동시에 업로드 할 수 있습니다.
|
||||
|
||||
그들은 "폼 데이터"를 사용하여 전송된 동일한 "폼 필드"에 연결됩니다.
|
||||
|
||||
이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial002.py hl[10,15] *}
|
||||
|
||||
선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
2019년 4월 14일부터 Swagger UI가 하나의 폼 필드로 다수의 파일을 업로드하는 것을 지원하지 않습니다. 더 많은 정보를 원하면, <a href="https://github.com/swagger-api/swagger-ui/issues/4276" class="external-link" target="_blank">#4276</a>과 <a href="https://github.com/swagger-api/swagger-ui/issues/3641" class="external-link" target="_blank">#3641</a>을 참고하세요.
|
||||
|
||||
그럼에도, **FastAPI**는 표준 Open API를 사용해 이미 호환이 가능합니다.
|
||||
|
||||
따라서 Swagger UI 또는 기타 그 외의 OpenAPI를 지원하는 툴이 다중 파일 업로드를 지원하는 경우, 이들은 **FastAPI**와 호환됩니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
`from starlette.responses import HTMLResponse` 역시 사용할 수 있습니다.
|
||||
|
||||
**FastAPI**는 개발자의 편의를 위해 `fastapi.responses` 와 동일한 `starlette.responses` 도 제공합니다. 하지만 대부분의 응답들은 Starlette로부터 직접 제공됩니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
폼 데이터로써 입력 매개변수로 업로드할 파일을 선언할 경우 `File` 을 사용하기 바랍니다.
|
||||
@@ -0,0 +1,78 @@
|
||||
# 폼 모델
|
||||
|
||||
FastAPI에서 **Pydantic 모델**을 이용하여 **폼 필드**를 선언할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
폼(Form)을 사용하려면, 먼저 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>를 설치하세요.
|
||||
|
||||
[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 아래와 같이 설치할 수 있습니다:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.113.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Pydantic 모델을 사용한 폼
|
||||
|
||||
**폼 필드**로 받고 싶은 필드를 **Pydantic 모델**로 선언한 다음, 매개변수를 `Form`으로 선언하면 됩니다:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
|
||||
|
||||
**FastAPI**는 요청에서 받은 **폼 데이터**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다.
|
||||
|
||||
## 문서 확인하기
|
||||
|
||||
문서 UI `/docs`에서 확인할 수 있습니다:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/request-form-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 추가 폼 필드 금지하기
|
||||
|
||||
일부 특별한 사용 사례(흔하지는 않겠지만)에서는 Pydantic 모델에서 정의한 폼 필드를 **제한**하길 원할 수도 있습니다. 그리고 **추가** 필드를 **금지**할 수도 있습니다.
|
||||
|
||||
/// note | 참고
|
||||
|
||||
이 기능은 FastAPI 버전 `0.114.0` 이후부터 지원됩니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
|
||||
|
||||
클라이언트가 추가 데이터를 보내려고 하면 **오류** 응답을 받게 됩니다.
|
||||
|
||||
예를 들어, 클라이언트가 폼 필드를 보내려고 하면:
|
||||
|
||||
* `username`: `Rick`
|
||||
* `password`: `Portal Gun`
|
||||
* `extra`: `Mr. Poopybutthole`
|
||||
|
||||
`extra` 필드가 허용되지 않는다는 오류 응답을 받게 됩니다:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "Mr. Poopybutthole"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
Pydantic 모델을 사용하여 FastAPI에서 폼 필드를 선언할 수 있습니다. 😎
|
||||
@@ -0,0 +1,37 @@
|
||||
# 폼 및 파일 요청
|
||||
|
||||
`File` 과 `Form` 을 사용하여 파일과 폼을 함께 정의할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
파일과 폼 데이터를 함께, 또는 각각 업로드하기 위해 먼저 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>를 설치해야합니다.
|
||||
|
||||
예 ) `pip install python-multipart`.
|
||||
|
||||
///
|
||||
|
||||
## `File` 및 `Form` 업로드
|
||||
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *}
|
||||
|
||||
## `File` 및 `Form` 매개변수 정의
|
||||
|
||||
`Body` 및 `Query`와 동일한 방식으로 파일과 폼의 매개변수를 생성합니다:
|
||||
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *}
|
||||
|
||||
파일과 폼 필드는 폼 데이터 형식으로 업로드되어 파일과 폼 필드로 전달됩니다.
|
||||
|
||||
어떤 파일들은 `bytes`로, 또 어떤 파일들은 `UploadFile`로 선언할 수 있습니다.
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
다수의 `File`과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json`가 아닌 `multipart/form-data`로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다.
|
||||
|
||||
이는 **FastAPI**의 한계가 아니라, HTTP 프로토콜에 의한 것입니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
하나의 요청으로 데이터와 파일들을 받아야 할 경우 `File`과 `Form`을 함께 사용하기 바랍니다.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 폼 데이터
|
||||
|
||||
JSON 대신 폼 필드를 받아야 하는 경우 `Form`을 사용할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
폼을 사용하려면, 먼저 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>를 설치하세요.
|
||||
|
||||
[가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, 아래와 같이 설치할 수 있습니다:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## `Form` 임포트하기
|
||||
|
||||
`fastapi`에서 `Form`을 임포트합니다:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## `Form` 매개변수 정의하기
|
||||
|
||||
`Body` 또는 `Query`와 동일한 방식으로 폼 매개변수를 만듭니다:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
|
||||
|
||||
예를 들어, OAuth2 사양을 사용할 수 있는 방법 중 하나("패스워드 플로우"라고 함)로 `username`과 `password`를 폼 필드로 보내야 합니다.
|
||||
|
||||
<abbr title="specification">사양</abbr>에서는 필드 이름이 `username` 및 `password`로 정확하게 명명되어야 하고, JSON이 아닌 폼 필드로 전송해야 합니다.
|
||||
|
||||
`Form`을 사용하면 유효성 검사, 예제, 별칭(예: `username` 대신 `user-name`) 등을 포함하여 `Body`(및 `Query`, `Path`, `Cookie`)와 동일한 구성을 선언할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`Form`은 `Body`에서 직접 상속되는 클래스입니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
폼 본문을 선언할 때, 폼이 없으면 매개변수가 쿼리 매개변수나 본문(JSON) 매개변수로 해석(interpret)되기 때문에 `Form`을 명시적으로 사용해야 합니다.
|
||||
|
||||
///
|
||||
|
||||
## "폼 필드"에 대해
|
||||
|
||||
HTML 폼(`<form></form>`)이 데이터를 서버로 보내는 방식은 일반적으로 해당 데이터에 대해 "특수" 인코딩을 사용하며, 이는 JSON과 다릅니다.
|
||||
|
||||
**FastAPI**는 JSON 대신 올바른 위치에서 해당 데이터를 읽습니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
폼의 데이터는 일반적으로 "미디어 유형(media type)" `application/x-www-form-urlencoded`를 사용하여 인코딩합니다.
|
||||
|
||||
그러나 폼에 파일이 포함된 경우, `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</a> 웹 문서를 참조하세요.
|
||||
|
||||
///
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
*경로 작업*에서 여러 `Form` 매개변수를 선언할 수 있지만, JSON으로 수신할 것으로 예상되는 `Body` 필드와 함께 선언할 수 없습니다. 요청 본문은 `application/json` 대신에 `application/x-www-form-urlencoded`를 사용하여 인코딩되기 때문입니다.
|
||||
|
||||
이는 **FastAPI**의 제한 사항이 아니며 HTTP 프로토콜의 일부입니다.
|
||||
|
||||
///
|
||||
|
||||
## 요약
|
||||
|
||||
폼 데이터 입력 매개변수를 선언하려면 `Form`을 사용하세요.
|
||||
@@ -0,0 +1,214 @@
|
||||
# 응답 모델
|
||||
|
||||
어떤 *경로 작동*이든 매개변수 `response_model`를 사용하여 응답을 위한 모델을 선언할 수 있습니다:
|
||||
|
||||
* `@app.get()`
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
* `@app.delete()`
|
||||
* 기타.
|
||||
|
||||
{* ../../docs_src/response_model/tutorial001.py hl[17] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
`response_model`은 "데코레이터" 메소드(`get`, `post`, 등)의 매개변수입니다. 모든 매개변수들과 본문(body)처럼 *경로 작동 함수*가 아닙니다.
|
||||
|
||||
///
|
||||
|
||||
Pydantic 모델 어트리뷰트를 선언한 것과 동일한 타입을 수신하므로 Pydantic 모델이 될 수 있지만, `List[Item]`과 같이 Pydantic 모델들의 `list`일 수도 있습니다.
|
||||
|
||||
FastAPI는 이 `response_model`를 사용하여:
|
||||
|
||||
* 출력 데이터를 타입 선언으로 변환.
|
||||
* 데이터 검증.
|
||||
* OpenAPI *경로 작동*의 응답에 JSON 스키마 추가.
|
||||
* 자동 생성 문서 시스템에 사용.
|
||||
|
||||
하지만 가장 중요한 것은:
|
||||
|
||||
* 해당 모델의 출력 데이터 제한. 이것이 얼마나 중요한지 아래에서 볼 것입니다.
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
응답 모델은 함수의 타입 어노테이션 대신 이 매개변수로 선언하는데, 경로 함수가 실제 응답 모델을 반환하지 않고 `dict`, 데이터베이스 객체나 기타 다른 모델을 `response_model`을 사용하여 필드 제한과 직렬화를 수행하고 반환할 수 있기 때문입니다
|
||||
|
||||
///
|
||||
|
||||
## 동일한 입력 데이터 반환
|
||||
|
||||
여기서 우리는 평문 비밀번호를 포함하는 `UserIn` 모델을 선언합니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial002.py hl[9,11] *}
|
||||
|
||||
그리고 이 모델을 사용하여 입력을 선언하고 같은 모델로 출력을 선언합니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial002.py hl[17:18] *}
|
||||
|
||||
이제 브라우저가 비밀번호로 사용자를 만들 때마다 API는 응답으로 동일한 비밀번호를 반환합니다.
|
||||
|
||||
이 경우, 사용자가 스스로 비밀번호를 발신했기 때문에 문제가 되지 않을 수 있습니다.
|
||||
|
||||
그러나 동일한 모델을 다른 *경로 작동*에서 사용할 경우, 모든 클라이언트에게 사용자의 비밀번호를 발신할 수 있습니다.
|
||||
|
||||
/// danger | 위험
|
||||
|
||||
절대로 사용자의 평문 비밀번호를 저장하거나 응답으로 발신하지 마십시오.
|
||||
|
||||
///
|
||||
|
||||
## 출력 모델 추가
|
||||
|
||||
대신 평문 비밀번호로 입력 모델을 만들고 해당 비밀번호 없이 출력 모델을 만들 수 있습니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *}
|
||||
|
||||
여기서 *경로 작동 함수*가 비밀번호를 포함하는 동일한 입력 사용자를 반환할지라도:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003.py hl[24] *}
|
||||
|
||||
...`response_model`을 `UserOut` 모델로 선언했기 때문에 비밀번호를 포함하지 않습니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003.py hl[22] *}
|
||||
|
||||
따라서 **FastAPI**는 출력 모델에서 선언하지 않은 모든 데이터를 (Pydantic을 사용하여) 필터링합니다.
|
||||
|
||||
## 문서에서 보기
|
||||
|
||||
자동 생성 문서를 보면 입력 모델과 출력 모델이 각자의 JSON 스키마를 가지고 있음을 확인할 수 있습니다:
|
||||
|
||||
<img src="/img/tutorial/response-model/image01.png">
|
||||
|
||||
그리고 두 모델 모두 대화형 API 문서에 사용됩니다:
|
||||
|
||||
<img src="/img/tutorial/response-model/image02.png">
|
||||
|
||||
## 응답 모델 인코딩 매개변수
|
||||
|
||||
응답 모델은 아래와 같이 기본값을 가질 수 있습니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *}
|
||||
|
||||
* `description: Optional[str] = None`은 기본값으로 `None`을 갖습니다.
|
||||
* `tax: float = 10.5`는 기본값으로 `10.5`를 갖습니다.
|
||||
* `tags: List[str] = []` 빈 리스트의 기본값으로: `[]`.
|
||||
|
||||
그러나 실제로 저장되지 않았을 경우 결과에서 값을 생략하고 싶을 수 있습니다.
|
||||
|
||||
예를 들어, NoSQL 데이터베이스에 많은 선택적 속성이 있는 모델이 있지만, 기본값으로 가득 찬 매우 긴 JSON 응답을 보내고 싶지 않습니다.
|
||||
|
||||
### `response_model_exclude_unset` 매개변수 사용
|
||||
|
||||
*경로 작동 데코레이터* 매개변수를 `response_model_exclude_unset=True`로 설정 할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial004.py hl[24] *}
|
||||
|
||||
이러한 기본값은 응답에 포함되지 않고 실제로 설정된 값만 포함됩니다.
|
||||
|
||||
따라서 해당 *경로 작동*에 ID가 `foo`인 항목(items)을 요청으로 보내면 (기본값을 제외한) 응답은 다음과 같습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"name": "Foo",
|
||||
"price": 50.2
|
||||
}
|
||||
```
|
||||
|
||||
/// info | 정보
|
||||
|
||||
FastAPI는 이를 위해 Pydantic 모델의 `.dict()`의 <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank"> `exclude_unset` 매개변수</a>를 사용합니다.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
|
||||
아래 또한 사용할 수 있습니다:
|
||||
|
||||
* `response_model_exclude_defaults=True`
|
||||
* `response_model_exclude_none=True`
|
||||
|
||||
<a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">Pydantic 문서</a>에서 `exclude_defaults` 및 `exclude_none`에 대해 설명한 대로 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
#### 기본값이 있는 필드를 갖는 값의 데이터
|
||||
|
||||
하지만 모델의 필드가 기본값이 있어도 ID가 `bar`인 항목(items)처럼 데이터가 값을 갖는다면:
|
||||
|
||||
```Python hl_lines="3 5"
|
||||
{
|
||||
"name": "Bar",
|
||||
"description": "The bartenders",
|
||||
"price": 62,
|
||||
"tax": 20.2
|
||||
}
|
||||
```
|
||||
|
||||
응답에 해당 값들이 포함됩니다.
|
||||
|
||||
#### 기본값과 동일한 값을 갖는 데이터
|
||||
|
||||
If the data has the same values as the default ones, like the item with ID `baz`:
|
||||
ID가 `baz`인 항목(items)처럼 기본값과 동일한 값을 갖는다면:
|
||||
|
||||
```Python hl_lines="3 5-6"
|
||||
{
|
||||
"name": "Baz",
|
||||
"description": None,
|
||||
"price": 50.2,
|
||||
"tax": 10.5,
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
|
||||
`description`, `tax` 그리고 `tags`가 기본값과 같더라도 (기본값에서 가져오는 대신) 값들이 명시적으로 설정되었다는 것을 인지할 정도로 FastAPI는 충분히 똑똑합니다(사실, Pydantic이 충분히 똑똑합니다).
|
||||
|
||||
따라서 JSON 스키마에 포함됩니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`None` 뿐만 아니라 다른 어떤 것도 기본값이 될 수 있습니다.
|
||||
|
||||
리스트(`[]`), `float`인 `10.5` 등이 될 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
### `response_model_include` 및 `response_model_exclude`
|
||||
|
||||
*경로 작동 데코레이터* 매개변수 `response_model_include` 및 `response_model_exclude`를 사용할 수 있습니다.
|
||||
|
||||
이들은 포함(나머지 생략)하거나 제외(나머지 포함) 할 어트리뷰트의 이름과 `str`의 `set`을 받습니다.
|
||||
|
||||
Pydantic 모델이 하나만 있고 출력에서 일부 데이터를 제거하려는 경우 빠른 지름길로 사용할 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
하지만 이러한 매개변수 대신 여러 클래스를 사용하여 위 아이디어를 사용하는 것을 추천합니다.
|
||||
|
||||
이는 일부 어트리뷰트를 생략하기 위해 `response_model_include` 또는 `response_model_exclude`를 사용하더라도 앱의 OpenAPI(및 문서)가 생성한 JSON 스키마가 여전히 전체 모델에 대한 스키마이기 때문입니다.
|
||||
|
||||
비슷하게 작동하는 `response_model_by_alias` 역시 마찬가지로 적용됩니다.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/response_model/tutorial005.py hl[31,37] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
문법 `{"name", "description"}`은 두 값을 갖는 `set`을 만듭니다.
|
||||
|
||||
이는 `set(["name", "description"])`과 동일합니다.
|
||||
|
||||
///
|
||||
|
||||
#### `set` 대신 `list` 사용하기
|
||||
|
||||
`list` 또는 `tuple` 대신 `set`을 사용하는 법을 잊었더라도, FastAPI는 `set`으로 변환하고 정상적으로 작동합니다:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial006.py hl[31,37] *}
|
||||
|
||||
## 요약
|
||||
|
||||
응답 모델을 정의하고 개인정보가 필터되는 것을 보장하기 위해 *경로 작동 데코레이터*의 매개변수 `response_model`을 사용하세요.
|
||||
|
||||
명시적으로 설정된 값만 반환하려면 `response_model_exclude_unset`을 사용하세요.
|
||||
@@ -0,0 +1,101 @@
|
||||
# 응답 상태 코드
|
||||
|
||||
응답 모델과 같은 방법으로, 어떤 *경로 작동*이든 `status_code` 매개변수를 사용하여 응답에 대한 HTTP 상태 코드를 선언할 수 있습니다.
|
||||
|
||||
* `@app.get()`
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
* `@app.delete()`
|
||||
* 기타
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
|
||||
|
||||
/// note | 참고
|
||||
|
||||
`status_code` 는 "데코레이터" 메소드(`get`, `post` 등)의 매개변수입니다. 모든 매개변수들과 본문처럼 *경로 작동 함수*가 아닙니다.
|
||||
|
||||
///
|
||||
|
||||
`status_code` 매개변수는 HTTP 상태 코드를 숫자로 입력받습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`status_code` 는 파이썬의 `http.HTTPStatus` 와 같은 `IntEnum` 을 입력받을 수도 있습니다.
|
||||
|
||||
///
|
||||
|
||||
`status_code` 매개변수는:
|
||||
|
||||
* 응답에서 해당 상태 코드를 반환합니다.
|
||||
* 상태 코드를 OpenAPI 스키마(및 사용자 인터페이스)에 문서화 합니다.
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/response-status-code/image01.png">
|
||||
|
||||
/// note | 참고
|
||||
|
||||
어떤 응답 코드들은 해당 응답에 본문이 없다는 것을 의미하기도 합니다 (다음 항목 참고).
|
||||
|
||||
이에 따라 FastAPI는 응답 본문이 없음을 명시하는 OpenAPI를 생성합니다.
|
||||
|
||||
///
|
||||
|
||||
## HTTP 상태 코드에 대하여
|
||||
|
||||
/// note | 참고
|
||||
|
||||
만약 HTTP 상태 코드에 대하여 이미 알고있다면, 다음 항목으로 넘어가십시오.
|
||||
|
||||
///
|
||||
|
||||
HTTP는 세자리의 숫자 상태 코드를 응답의 일부로 전송합니다.
|
||||
|
||||
이 상태 코드들은 각자를 식별할 수 있도록 지정된 이름이 있으나, 중요한 것은 숫자 코드입니다.
|
||||
|
||||
요약하자면:
|
||||
|
||||
* `1xx` 상태 코드는 "정보"용입니다. 이들은 직접적으로는 잘 사용되지는 않습니다. 이 상태 코드를 갖는 응답들은 본문을 가질 수 없습니다.
|
||||
* **`2xx`** 상태 코드는 "성공적인" 응답을 위해 사용됩니다. 가장 많이 사용되는 유형입니다.
|
||||
* `200` 은 디폴트 상태 코드로, 모든 것이 "성공적임"을 의미합니다.
|
||||
* 다른 예로는 `201` "생성됨"이 있습니다. 일반적으로 데이터베이스에 새로운 레코드를 생성한 후 사용합니다.
|
||||
* 단, `204` "내용 없음"은 특별한 경우입니다. 이것은 클라이언트에게 반환할 내용이 없는 경우 사용합니다. 따라서 응답은 본문을 가질 수 없습니다.
|
||||
* **`3xx`** 상태 코드는 "리다이렉션"용입니다. 본문을 가질 수 없는 `304` "수정되지 않음"을 제외하고, 이 상태 코드를 갖는 응답에는 본문이 있을 수도, 없을 수도 있습니다.
|
||||
* **`4xx`** 상태 코드는 "클라이언트 오류" 응답을 위해 사용됩니다. 이것은 아마 가장 많이 사용하게 될 두번째 유형입니다.
|
||||
* 일례로 `404` 는 "찾을 수 없음" 응답을 위해 사용합니다.
|
||||
* 일반적인 클라이언트 오류의 경우 `400` 을 사용할 수 있습니다.
|
||||
* `5xx` 상태 코드는 서버 오류에 사용됩니다. 이것들을 직접 사용할 일은 거의 없습니다. 응용 프로그램 코드나 서버의 일부에서 문제가 발생하면 자동으로 이들 상태 코드 중 하나를 반환합니다.
|
||||
|
||||
/// 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> 를 확인하십시오.
|
||||
|
||||
///
|
||||
|
||||
## 이름을 기억하는 쉬운 방법
|
||||
|
||||
상기 예시 참고:
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
|
||||
|
||||
`201` 은 "생성됨"를 의미하는 상태 코드입니다.
|
||||
|
||||
하지만 모든 상태 코드들이 무엇을 의미하는지 외울 필요는 없습니다.
|
||||
|
||||
`fastapi.status` 의 편의 변수를 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
|
||||
|
||||
이것은 단순히 작업을 편리하게 하기 위한 것으로, HTTP 상태 코드와 동일한 번호를 갖고있지만, 이를 사용하면 편집기의 자동완성 기능을 사용할 수 있습니다:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/response-status-code/image02.png">
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
`from starlette import status` 역시 사용할 수 있습니다.
|
||||
|
||||
**FastAPI**는 개발자인 여러분의 편의를 위해 `fastapi.status` 와 동일한 `starlette.status` 도 제공합니다. 하지만 이것은 Starlette로부터 직접 제공됩니다.
|
||||
|
||||
///
|
||||
|
||||
## 기본값 변경
|
||||
|
||||
추후 여기서 선언하는 기본 상태 코드가 아닌 다른 상태 코드를 반환하는 방법을 [숙련된 사용자 지침서](https://fastapi.tiangolo.com/ko/advanced/response-change-status-code/){.internal-link target=_blank}에서 확인할 수 있습니다.
|
||||
@@ -0,0 +1,224 @@
|
||||
# 요청 예제 데이터 선언
|
||||
|
||||
여러분의 앱이 받을 수 있는 데이터 예제를 선언할 수 있습니다.
|
||||
|
||||
여기 이를 위한 몇가지 방식이 있습니다.
|
||||
|
||||
## Pydantic 모델 속 추가 JSON 스키마 데이터
|
||||
|
||||
생성된 JSON 스키마에 추가될 Pydantic 모델을 위한 `examples`을 선언할 수 있습니다.
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
|
||||
|
||||
////
|
||||
|
||||
추가 정보는 있는 그대로 해당 모델의 **JSON 스키마** 결과에 추가되고, API 문서에서 사용합니다.
|
||||
|
||||
//// tab | Pydantic v2
|
||||
|
||||
Pydantic 버전 2에서 <a href="https://docs.pydantic.dev/latest/usage/model_config/" class="external-link" target="_blank">Pydantic 공식 문서: Model Config</a>에 나와 있는 것처럼 `dict`를 받는 `model_config` 어트리뷰트를 사용할 것입니다.
|
||||
|
||||
`"json_schema_extra"`를 생성된 JSON 스키마에서 보여주고 싶은 별도의 데이터와 `examples`를 포함하는 `dict`으로 설정할 수 있습니다.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Pydantic v1
|
||||
|
||||
Pydantic v1에서 <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic 공식 문서: Schema customization</a>에서 설명하는 것처럼, 내부 클래스인 `Config`와 `schema_extra`를 사용할 것입니다.
|
||||
|
||||
`schema_extra`를 생성된 JSON 스키마에서 보여주고 싶은 별도의 데이터와 `examples`를 포함하는 `dict`으로 설정할 수 있습니다.
|
||||
|
||||
////
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
JSON 스키마를 확장하고 여러분의 별도의 자체 데이터를 추가하기 위해 같은 기술을 사용할 수 있습니다.
|
||||
|
||||
예를 들면, 프론트엔드 사용자 인터페이스에 메타데이터를 추가하는 등에 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
|
||||
(FastAPI 0.99.0부터 쓰이기 시작한) OpenAPI 3.1.0은 **JSON 스키마** 표준의 일부인 `examples`에 대한 지원을 추가했습니다.
|
||||
|
||||
그 전에는, 하나의 예제만 가능한 `example` 키워드만 지원했습니다. 이는 아직 OpenAPI 3.1.0에서 지원하지만, 지원이 종료될 것이며 JSON 스키마 표준에 포함되지 않습니다. 그렇기에 `example`을 `examples`으로 이전하는 것을 추천합니다. 🤓
|
||||
|
||||
이 문서 끝에 더 많은 읽을거리가 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## `Field` 추가 인자
|
||||
|
||||
Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 선언할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
|
||||
|
||||
## JSON Schema에서의 `examples` - OpenAPI
|
||||
|
||||
이들 중에서 사용합니다:
|
||||
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
* `Header()`
|
||||
* `Cookie()`
|
||||
* `Body()`
|
||||
* `Form()`
|
||||
* `File()`
|
||||
|
||||
**OpenAPI**의 **JSON 스키마**에 추가될 부가적인 정보를 포함한 `examples` 모음을 선언할 수 있습니다.
|
||||
|
||||
### `examples`를 포함한 `Body`
|
||||
|
||||
여기, `Body()`에 예상되는 예제 데이터 하나를 포함한 `examples`를 넘겼습니다:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
|
||||
|
||||
### 문서 UI 예시
|
||||
|
||||
위의 어느 방법과 함께라면 `/docs`에서 다음과 같이 보일 것입니다:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image01.png">
|
||||
|
||||
### 다중 `examples`를 포함한 `Body`
|
||||
|
||||
물론 여러 `examples`를 넘길 수 있습니다:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
|
||||
|
||||
이와 같이 하면 이 예제는 그 본문 데이터를 위한 내부 **JSON 스키마**의 일부가 될 것입니다.
|
||||
|
||||
그럼에도 불구하고, 지금 <abbr title="2023-08-26">이 문서를 작성하는 시간</abbr>에, 문서 UI를 보여주는 역할을 맡은 Swagger UI는 **JSON 스키마** 속 데이터를 위한 여러 예제의 표현을 지원하지 않습니다. 하지만 해결 방안을 밑에서 읽어보세요.
|
||||
|
||||
### OpenAPI-특화 `examples`
|
||||
|
||||
**JSON 스키마**가 `examples`를 지원하기 전 부터, OpenAPI는 `examples`이라 불리는 다른 필드를 지원해 왔습니다.
|
||||
|
||||
이 **OpenAPI-특화** `examples`는 OpenAPI 명세서의 다른 구역으로 들어갑니다. 각 JSON 스키마 내부가 아니라 **각 *경로 작동* 세부 정보**에 포함됩니다.
|
||||
|
||||
그리고 Swagger UI는 이 특정한 `examples` 필드를 한동안 지원했습니다. 그래서, 이를 다른 **문서 UI에 있는 예제**를 **표시**하기 위해 사용할 수 있습니다.
|
||||
|
||||
이 OpenAPI-특화 필드인 `examples`의 형태는 (`list`대신에) **다중 예제**가 포함된 `dict`이며, 각각의 별도 정보 또한 **OpenAPI**에 추가될 것입니다.
|
||||
|
||||
이는 OpenAPI에 포함된 JSON 스키마 안으로 포함되지 않으며, *경로 작동*에 직접적으로 포함됩니다.
|
||||
|
||||
### `openapi_examples` 매개변수 사용하기
|
||||
|
||||
다음 예시 속에 OpenAPI-특화 `examples`를 FastAPI 안에서 매개변수 `openapi_examples` 매개변수와 함께 선언할 수 있습니다:
|
||||
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
* `Header()`
|
||||
* `Cookie()`
|
||||
* `Body()`
|
||||
* `Form()`
|
||||
* `File()`
|
||||
|
||||
`dict`의 키가 또 다른 `dict`인 각 예제와 값을 구별합니다.
|
||||
|
||||
각각의 특정 `examples` 속 `dict` 예제는 다음을 포함할 수 있습니다:
|
||||
|
||||
* `summary`: 예제에 대한 짧은 설명문.
|
||||
* `description`: 마크다운 텍스트를 포함할 수 있는 긴 설명문.
|
||||
* `value`: 실제로 보여지는 예시, 예를 들면 `dict`.
|
||||
* `externalValue`: `value`의 대안이며 예제를 가르키는 URL. 비록 `value`처럼 많은 도구를 지원하지 못할 수 있습니다.
|
||||
|
||||
이를 다음과 같이 사용할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
|
||||
|
||||
### 문서 UI에서의 OpenAPI 예시
|
||||
|
||||
`Body()`에 추가된 `openapi_examples`를 포함한 `/docs`는 다음과 같이 보일 것입니다:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image02.png">
|
||||
|
||||
## 기술적 세부 사항
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이미 **FastAPI**의 **0.99.0 혹은 그 이상** 버전을 사용하고 있다면, 이 세부 사항을 **스킵**해도 상관 없을 것입니다.
|
||||
|
||||
세부 사항은 OpenAPI 3.1.0이 사용가능하기 전, 예전 버전과 더 관련있습니다.
|
||||
|
||||
간략한 OpenAPI와 JSON 스키마 **역사 강의**로 생각할 수 있습니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
/// warning | 경고
|
||||
|
||||
표준 **JSON 스키마**와 **OpenAPI**에 대한 아주 기술적인 세부사항입니다.
|
||||
|
||||
만약 위의 생각이 작동한다면, 그것으로 충분하며 이 세부 사항은 필요없을 것이니, 마음 편하게 스킵하셔도 됩니다.
|
||||
|
||||
///
|
||||
|
||||
OpenAPI 3.1.0 전에 OpenAPI는 오래된 **JSON 스키마**의 수정된 버전을 사용했습니다.
|
||||
|
||||
JSON 스키마는 `examples`를 가지고 있지 않았고, 따라서 OpenAPI는 그들만의 `example` 필드를 수정된 버전에 추가했습니다.
|
||||
|
||||
OpenAPI는 또한 `example`과 `examples` 필드를 명세서의 다른 부분에 추가했습니다:
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`(명세서에 있는) Parameter Object`</a>는 FastAPI의 다음 기능에서 쓰였습니다:
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
* `Header()`
|
||||
* `Cookie()`
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">(명세서에 있는)`Media Type Object`속 `content`에 있는 `Request Body Object`</a>는 FastAPI의 다음 기능에서 쓰였습니다:
|
||||
* `Body()`
|
||||
* `File()`
|
||||
* `Form()`
|
||||
|
||||
/// info | 정보
|
||||
|
||||
이 예전 OpenAPI-특화 `examples` 매개변수는 이제 FastAPI `0.103.0`부터 `openapi_examples`입니다.
|
||||
|
||||
///
|
||||
|
||||
### JSON 스키마의 `examples` 필드
|
||||
|
||||
하지만, 후에 JSON 스키마는 <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>필드를 명세서의 새 버전에 추가했습니다.
|
||||
|
||||
그리고 새로운 OpenAPI 3.1.0은 이 새로운 `examples` 필드가 포함된 최신 버전 (JSON 스키마 2020-12)을 기반으로 했습니다.
|
||||
|
||||
이제 새로운 `examples` 필드는 이전의 단일 (그리고 커스텀) `example` 필드보다 우선되며, `example`은 사용하지 않는 것이 좋습니다.
|
||||
|
||||
JSON 스키마의 새로운 `examples` 필드는 예제 속 **단순한 `list`**이며, (위에서 상술한 것처럼) OpenAPI의 다른 곳에 존재하는 dict으로 된 추가적인 메타데이터가 아닙니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
더 쉽고 새로운 JSON 스키마와의 통합과 함께 OpenAPI 3.1.0가 배포되었지만, 잠시동안 자동 문서 생성을 제공하는 도구인 Swagger UI는 OpenAPI 3.1.0을 지원하지 않았습니다 (5.0.0 버전부터 지원합니다 🎉).
|
||||
|
||||
이로인해, FastAPI 0.99.0 이전 버전은 아직 OpenAPI 3.1.0 보다 낮은 버전을 사용했습니다.
|
||||
|
||||
///
|
||||
|
||||
### Pydantic과 FastAPI `examples`
|
||||
|
||||
`examples`를 Pydantic 모델 속에 추가할 때, `schema_extra` 혹은 `Field(examples=["something"])`를 사용하면 Pydantic 모델의 **JSON 스키마**에 해당 예시가 추가됩니다.
|
||||
|
||||
그리고 Pydantic 모델의 **JSON 스키마**는 API의 **OpenAPI**에 포함되고, 그 후 문서 UI 속에서 사용됩니다.
|
||||
|
||||
FastAPI 0.99.0 이전 버전에서 (0.99.0 이상 버전은 새로운 OpenAPI 3.1.0을 사용합니다), `example` 혹은 `examples`를 다른 유틸리티(`Query()`, `Body()` 등)와 함께 사용했을 때, 저러한 예시는 데이터를 설명하는 JSON 스키마에 추가되지 않으며 (심지어 OpenAPI의 자체 JSON 스키마에도 포함되지 않습니다), OpenAPI의 *경로 작동* 선언에 직접적으로 추가됩니다 (JSON 스키마를 사용하는 OpenAPI 부분 외에도).
|
||||
|
||||
하지만 지금은 FastAPI 0.99.0 및 이후 버전에서는 JSON 스키마 2020-12를 사용하는 OpenAPI 3.1.0과 Swagger UI 5.0.0 및 이후 버전을 사용하며, 모든 것이 더 일관성을 띄고 예시는 JSON 스키마에 포함됩니다.
|
||||
|
||||
### Swagger UI와 OpenAPI-특화 `examples`
|
||||
|
||||
현재 (2023-08-26), Swagger UI가 다중 JSON 스키마 예시를 지원하지 않으며, 사용자는 다중 예시를 문서에 표시하는 방법이 없었습니다.
|
||||
|
||||
이를 해결하기 위해, FastAPI `0.103.0`은 새로운 매개변수인 `openapi_examples`를 포함하는 예전 **OpenAPI-특화** `examples` 필드를 선언하기 위한 **지원을 추가**했습니다. 🤓
|
||||
|
||||
### 요약
|
||||
|
||||
저는 역사를 그다지 좋아하는 편이 아니라고 말하고는 했지만... "기술 역사" 강의를 가르치는 지금의 저를 보세요.
|
||||
|
||||
요약하자면 **FastAPI 0.99.0 혹은 그 이상의 버전**으로 업그레이드하는 것은 많은 것들이 더 **쉽고, 일관적이며 직관적이게** 되며, 여러분은 이 모든 역사적 세부 사항을 알 필요가 없습니다. 😎
|
||||
@@ -0,0 +1,105 @@
|
||||
# 현재 사용자 가져오기
|
||||
|
||||
이전 장에서 (의존성 주입 시스템을 기반으로 한)보안 시스템은 *경로 작동 함수*에서 `str`로 `token`을 제공했습니다:
|
||||
|
||||
{* ../../docs_src/security/tutorial001.py hl[10] *}
|
||||
|
||||
그러나 아직도 유용하지 않습니다.
|
||||
|
||||
현재 사용자를 제공하도록 합시다.
|
||||
|
||||
## 유저 모델 생성하기
|
||||
|
||||
먼저 Pydantic 유저 모델을 만들어 보겠습니다.
|
||||
|
||||
Pydantic을 사용하여 본문을 선언하는 것과 같은 방식으로 다른 곳에서 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[5,12:16] *}
|
||||
|
||||
## `get_current_user` 의존성 생성하기
|
||||
|
||||
의존성 `get_current_user`를 만들어 봅시다.
|
||||
|
||||
의존성이 하위 의존성을 가질 수 있다는 것을 기억하십니까?
|
||||
|
||||
`get_current_user`는 이전에 생성한 것과 동일한 `oauth2_scheme`과 종속성을 갖게 됩니다.
|
||||
|
||||
이전에 *경로 작동*에서 직접 수행했던 것과 동일하게 새 종속성 `get_current_user`는 하위 종속성 `oauth2_scheme`에서 `str`로 `token`을 수신합니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[25] *}
|
||||
|
||||
## 유저 가져오기
|
||||
|
||||
`get_current_user`는 토큰을 `str`로 취하고 Pydantic `User` 모델을 반환하는 우리가 만든 (가짜) 유틸리티 함수를 사용합니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *}
|
||||
|
||||
## 현재 유저 주입하기
|
||||
|
||||
이제 *경로 작동*에서 `get_current_user`와 동일한 `Depends`를 사용할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[31] *}
|
||||
|
||||
Pydantic 모델인 `User`로 `current_user`의 타입을 선언하는 것을 알아야 합니다.
|
||||
|
||||
이것은 모든 완료 및 타입 검사를 통해 함수 내부에서 우리를 도울 것입니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
요청 본문도 Pydantic 모델로 선언된다는 것을 기억할 것입니다.
|
||||
|
||||
여기서 **FastAPI**는 `Depends`를 사용하고 있기 때문에 혼동되지 않습니다.
|
||||
|
||||
///
|
||||
|
||||
/// check | 확인
|
||||
|
||||
이 의존성 시스템이 설계된 방식은 모두 `User` 모델을 반환하는 다양한 의존성(다른 "의존적인")을 가질 수 있도록 합니다.
|
||||
|
||||
해당 타입의 데이터를 반환할 수 있는 의존성이 하나만 있는 것으로 제한되지 않습니다.
|
||||
|
||||
///
|
||||
|
||||
## 다른 모델
|
||||
|
||||
이제 *경로 작동 함수*에서 현재 사용자를 직접 가져올 수 있으며 `Depends`를 사용하여 **의존성 주입** 수준에서 보안 메커니즘을 처리할 수 있습니다.
|
||||
|
||||
그리고 보안 요구 사항에 대한 모든 모델 또는 데이터를 사용할 수 있습니다(이 경우 Pydantic 모델 `User`).
|
||||
|
||||
그러나 일부 특정 데이터 모델, 클래스 또는 타입을 사용하도록 제한되지 않습니다.
|
||||
|
||||
모델에 `id`와 `email`이 있고 `username`이 없길 원하십니까? 맞습니다. 이들은 동일한 도구를 사용할 수 있습니다.
|
||||
|
||||
`str`만 갖고 싶습니까? 아니면 그냥 `dict`를 갖고 싶습니까? 아니면 데이터베이스 클래스 모델 인스턴스를 직접 갖고 싶습니까? 그들은 모두 같은 방식으로 작동합니다.
|
||||
|
||||
실제로 애플리케이션에 로그인하는 사용자가 없지만 액세스 토큰만 있는 로봇, 봇 또는 기타 시스템이 있습니까? 다시 말하지만 모두 동일하게 작동합니다.
|
||||
|
||||
애플리케이션에 필요한 모든 종류의 모델, 모든 종류의 클래스, 모든 종류의 데이터베이스를 사용하십시오. **FastAPI**는 의존성 주입 시스템을 다루었습니다.
|
||||
|
||||
## 코드 사이즈
|
||||
|
||||
이 예는 장황해 보일 수 있습니다. 동일한 파일에서 보안, 데이터 모델, 유틸리티 기능 및 *경로 작동*을 혼합하고 있음을 염두에 두십시오.
|
||||
|
||||
그러나 이게 키포인트입니다.
|
||||
|
||||
보안과 종속성 주입 항목을 한 번만 작성하면 됩니다.
|
||||
|
||||
그리고 원하는 만큼 복잡하게 만들 수 있습니다. 그래도 유연성과 함께 한 곳에 한 번에 작성할 수 있습니다.
|
||||
|
||||
그러나 동일한 보안 시스템을 사용하여 수천 개의 엔드포인트(*경로 작동*)를 가질 수 있습니다.
|
||||
|
||||
그리고 그들 모두(또는 원하는 부분)는 이러한 의존성 또는 생성한 다른 의존성을 재사용하는 이점을 얻을 수 있습니다.
|
||||
|
||||
그리고 이 수천 개의 *경로 작동*은 모두 3줄 정도로 줄일 수 있습니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[30:32] *}
|
||||
|
||||
## 요약
|
||||
|
||||
이제 *경로 작동 함수*에서 현재 사용자를 직접 가져올 수 있습니다.
|
||||
|
||||
우리는 이미 이들 사이에 있습니다.
|
||||
|
||||
사용자/클라이언트가 실제로 `username`과 `password`를 보내려면 *경로 작동*을 추가하기만 하면 됩니다.
|
||||
|
||||
다음 장을 확인해 봅시다.
|
||||
@@ -0,0 +1,273 @@
|
||||
# 패스워드 해싱을 이용한 OAuth2, JWT 토큰을 사용하는 Bearer 인증
|
||||
|
||||
모든 보안 흐름을 구성했으므로, 이제 <abbr title="JSON Web Tokens">JWT</abbr> 토큰과 패스워드 해싱을 사용해 애플리케이션을 안전하게 만들 것입니다.
|
||||
|
||||
이 코드는 실제로 애플리케이션에서 패스워드를 해싱하여 DB에 저장하는 등의 작업에 활용할 수 있습니다.
|
||||
|
||||
이전 장에 이어서 시작해 봅시다.
|
||||
|
||||
## JWT
|
||||
|
||||
JWT 는 "JSON Web Tokens" 을 의미합니다.
|
||||
|
||||
JSON 객체를 공백이 없는 긴 문자열로 인코딩하는 표준이며, 다음과 같은 형태입니다:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
```
|
||||
|
||||
JWT는 암호화되지 않아 누구든지 토큰에서 정보를 복원할 수 있습니다.
|
||||
|
||||
하지만 JWT는 서명되어 있습니다. 그래서 자신이 발급한 토큰을 받았을 때, 실제로 자신이 발급한게 맞는지 검증할 수 있습니다.
|
||||
|
||||
만료 기간이 일주일인 토큰을 발행했다고 가정해 봅시다. 다음 날 사용자가 토큰을 가져왔을 때, 그 사용자가 시스템에 여전히 로그인되어 있다는 것을 알 수 있습니다.
|
||||
|
||||
일주일 뒤에는 토큰이 만료될 것이고, 사용자는 인가되지 않아 새 토큰을 받기 위해 다시 로그인해야 할 것입니다. 만약 사용자(또는 제3자)가 토큰을 수정하거나 만료일을 변경하면, 서명이 일치하지 않기 때문에 알아챌 수 있을 것입니다.
|
||||
|
||||
만약 JWT 토큰을 다뤄보고, 작동 방식도 알아보고 싶다면 <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a> 을 확인하십시오.
|
||||
|
||||
## `PyJWT` 설치
|
||||
|
||||
파이썬으로 JWT 토큰을 생성하고 검증하려면 `PyJWT` 를 설치해야 합니다.
|
||||
|
||||
[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 `pyjwt` 를 설치하십시오:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pyjwt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info | 참고
|
||||
|
||||
RSA나 ECDSA 같은 전자 서명 알고리즘을 사용하려면, `pyjwt[crypto]`라는 암호화 라이브러리 의존성을 설치해야 합니다.
|
||||
|
||||
더 자세한 내용은 <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT 설치</a> 에서 확인할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 패스워드 해싱
|
||||
|
||||
"해싱(Hashing)"은 어떤 내용(여기서는 패스워드)을 해석할 수 없는 일련의 바이트 집합(단순 문자열)으로 변환하는 것을 의미합니다.
|
||||
|
||||
동일한 내용(똑같은 패스워드)을 해싱하면 동일한 문자열을 얻습니다.
|
||||
|
||||
하지만 그 문자열을 다시 패스워드로 되돌릴 수는 없습니다.
|
||||
|
||||
### 패스워드를 해싱하는 이유
|
||||
|
||||
데이터베이스를 탈취당하더라도, 침입자는 사용자의 평문 패스워드 대신 해시 값만 얻을 수 있습니다.
|
||||
|
||||
따라서 침입자는 훔친 사용자 패스워드를 다른 시스템에서 활용할 수 없습니다. (대다수 사용자가 여러 시스템에서 동일한 패스워드를 사용하기 때문에 평문 패스워드가 유출되면 위험합니다.)
|
||||
|
||||
## `passlib` 설치
|
||||
|
||||
PassLib는 패스워드 해시를 다루는 훌륭한 파이썬 패키지입니다.
|
||||
|
||||
많은 안전한 해시 알고리즘과 도구들을 지원합니다.
|
||||
|
||||
추천하는 알고리즘은 "Bcrypt"입니다.
|
||||
|
||||
[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 PassLib와 Bcrypt를 설치하십시오:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "passlib[bcrypt]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`passlib`를 사용하여, **Django**, **Flask** 의 보안 플러그인이나 다른 도구로 생성한 패스워드를 읽을 수 있도록 설정할 수도 있습니다.
|
||||
|
||||
예를 들자면, FastAPI 애플리케이션과 Django 애플리케이션이 같은 데이터베이스에서 데이터를 공유할 수 있습니다. 또는 같은 데이터베이스를 사용하여 Django 애플리케이션을 점진적으로 마이그레이션 할 수도 있습니다.
|
||||
|
||||
그리고 사용자는 FastAPI 애플리케이션과 Django 애플리케이션에 동시에 로그인할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 패스워드의 해시와 검증
|
||||
|
||||
필요한 도구를 `passlib`에서 임포트합니다.
|
||||
|
||||
PassLib "컨텍스트(context)"를 생성합니다. 이것은 패스워드를 해싱하고 검증하는데 사용합니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
PassLib 컨텍스트는 다양한 해싱 알고리즘을 사용할 수 있는 기능을 제공하며, 더 이상 사용이 권장되지 않는 오래된 해싱 알고리즘을 검증하는 기능도 포함되어 있습니다.
|
||||
|
||||
예를 들어, 다른 시스템(Django 같은)에서 생성한 패스워드를 읽고 검증할 수 있으며, 새로운 패스워드를 Bcrypt 같은 다른 알고리즘으로 해싱할 수도 있습니다.
|
||||
|
||||
그리고 동시에 그런 모든 알고리즘과 호환성을 유지합니다.
|
||||
|
||||
///
|
||||
|
||||
사용자로부터 받은 패스워드를 해싱하는 유틸리티 함수를 생성합니다.
|
||||
|
||||
그리고 받은 패스워드가 저장된 해시와 일치하는지 검증하는 또 다른 유틸리티 함수도 생성합니다.
|
||||
|
||||
그리고 사용자를 인증하고 반환하는 또 다른 함수도 생성합니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
|
||||
|
||||
/// note
|
||||
|
||||
새로운 (가짜) 데이터베이스 `fake_users_db`를 확인하면, 해시 처리된 패스워드가 어떻게 생겼는지 볼 수 있습니다: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
|
||||
|
||||
///
|
||||
|
||||
## JWT 토큰 처리
|
||||
|
||||
설치된 모듈을 임포트 합니다.
|
||||
|
||||
JWT 토큰 서명에 사용될 임의의 비밀키를 생성합니다.
|
||||
|
||||
안전한 임의의 비밀키를 생성하려면 다음 명령어를 사용하십시오:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ openssl rand -hex 32
|
||||
|
||||
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
그리고 생성한 비밀키를 복사해 변수 `SECRET_KEY`에 대입합니다. (이 예제의 변수 값을 그대로 사용하지 마십시오.)
|
||||
|
||||
JWT 토큰을 서명하는 데 사용될 알고리즘을 위한 변수 `ALGORITHM` 을 생성하고 `"HS256"` 으로 설정합니다.
|
||||
|
||||
토큰 만료 기간을 위한 변수를 생성합니다.
|
||||
|
||||
응답을 위한 토큰 엔드포인트에 사용될 Pydantic 모델을 정의합니다.
|
||||
|
||||
새 액세스 토큰을 생성하기 위한 유틸리티 함수를 생성합니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
|
||||
|
||||
## 의존성 수정
|
||||
|
||||
`get_current_user` 함수를 이전과 동일한 토큰을 받도록 수정하되, 이번에는 JWT 토큰을 사용하도록 합니다.
|
||||
|
||||
받은 토큰을 디코딩하여 검증한 후 현재 사용자를 반환합니다.
|
||||
|
||||
토큰이 유효하지 않다면 HTTP 오류를 반환합니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
|
||||
|
||||
## `/token` 경로 작업 수정
|
||||
|
||||
토큰의 만료 시각을 설정하기 위해 `timedelta` 를 생성합니다.
|
||||
|
||||
실제 JWT 액세스 토큰을 생성하여 반환합니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
|
||||
|
||||
### JWT "주체(subject)" `sub`에 대한 기술 세부 사항
|
||||
|
||||
JWT 명세에 따르면 토큰의 주체를 포함하는 `sub`라는 키가 있습니다.
|
||||
|
||||
사용 여부는 선택사항이지만, 사용자의 식별 정보를 저장할 수 있으므로 여기서는 이를 사용합니다.
|
||||
|
||||
JWT는 사용자를 식별하고 사용자가 API를 직접 사용할 수 있도록 허용하는 것 외에도 다른 용도로 사용될 수도 있습니다.
|
||||
|
||||
예를 들어 "자동차"나 "블로그 게시물"을 식별하는 데 사용할 수 있습니다.
|
||||
|
||||
그리고 "자동차를 운전하다"나 "블로그 게시물을 수정하다"처럼 해당 엔터티에 대한 권한을 추가할 수 있습니다.
|
||||
|
||||
그 후 이 JWT 토큰을 사용자(또는 봇)에게 제공하면, 그들은 계정을 따로 만들 필요 없이 API가 생성한 JWT 토큰만으로 작업(자동차 운전 또는 블로그 게시물 편집)을 수행할 수 있습니다.
|
||||
|
||||
이러한 개념을 활용하면 JWT는 훨씬 더 복잡한 시나리오에도 사용할 수 있습니다.
|
||||
|
||||
이 경우 여러 엔터티가 동일한 ID를 가질 수 있습니다. 예를 들어 foo라는 ID를 가진 사용자, 자동차, 블로그 게시물이 있을 수 있습니다.
|
||||
|
||||
그래서 ID 충돌을 방지하기 위해, 사용자의 JWT 토큰을 생성할 때 접두사로 `sub` 키를 추가할 수 있습니다. 예를 들어 `username:` 을 붙이는 방식입니다. 이 예제에서는 `sub` 값이 `username:johndoe`이 될 수 있습니다.
|
||||
|
||||
가장 중요한 점은 `sub` 키는 전체 애플리케이션에서 고유한 식별자가 되어야 하며 문자열이어야 한다는 점입니다.
|
||||
|
||||
## 확인해봅시다
|
||||
|
||||
서버를 실행하고 문서로 이동하십시오: <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/image07.png">
|
||||
|
||||
이전과 같은 방법으로 애플리케이션에 인증하십시오.
|
||||
|
||||
다음 인증 정보를 사용하십시오:
|
||||
|
||||
Username: `johndoe`
|
||||
Password: `secret`
|
||||
|
||||
/// check
|
||||
|
||||
코드 어디에도 평문 패스워드 "`secret`" 이 없다는 점에 유의하십시오. 해시된 버전만 있습니다.
|
||||
|
||||
///
|
||||
|
||||
<img src="/img/tutorial/security/image08.png">
|
||||
|
||||
`/users/me/` 를 호출하면 다음과 같은 응답을 얻을 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image09.png">
|
||||
|
||||
개발자 도구를 열어보면 전송된 데이터에 토큰만 포함된 것을 확인할 수 있습니다. 패스워드는 사용자를 인증하고 액세스 토큰을 받기 위한 첫 번째 요청에만 전송되며, 이후에는 전송되지 않습니다:
|
||||
|
||||
<img src="/img/tutorial/security/image10.png">
|
||||
|
||||
/// note
|
||||
|
||||
`Bearer `로 시작하는 `Authorization` 헤더에 주목하십시오.
|
||||
|
||||
///
|
||||
|
||||
## `scopes` 의 고급 사용법
|
||||
|
||||
OAuth2는 "스코프(scopes)" 라는 개념을 갖고 있습니다.
|
||||
|
||||
이를 사용하여 JWT 토큰에 특정 권한 집합을 추가할 수 있습니다.
|
||||
|
||||
그 후 이 토큰을 사용자에게 직접 제공하거나 제3자에게 제공하여, 특정 제한사항 하에있는 API와 통신하도록 할 수 있습니다.
|
||||
|
||||
**FastAPI** 에서의 사용 방법과 통합 방식은 **심화 사용자 안내서** 에서 자세히 배울 수 있습니다.
|
||||
|
||||
## 요약
|
||||
|
||||
지금까지 살펴본 내용을 바탕으로, OAuth2와 JWT 같은 표준을 사용하여 안전한 **FastAPI** 애플리케이션을 만들 수 있습니다.
|
||||
|
||||
거의 모든 프레임워크에서 보안 처리는 상당히 복잡한 주제입니다.
|
||||
|
||||
이를 단순화하는 많은 패키지는 데이터 모델, 데이터베이스, 사용 가능한 기능들에 대해 여러 제약이 있습니다. 그리고 지나치게 단순화하는 일부 패키지들은 심각한 보안 결함을 가질 수도 있습니다.
|
||||
|
||||
---
|
||||
|
||||
**FastAPI** 는 어떤 데이터베이스, 데이터 모델, 도구도 강요하지 않습니다.
|
||||
|
||||
프로젝트에 가장 적합한 것을 선택할 수 있는 유연성을 제공합니다.
|
||||
|
||||
그리고 `passlib` 와 `PyJWT` 처럼 잘 관리되고 널리 사용되는 패키지들을 바로 사용할 수 있습니다. **FastAPI** 는 외부 패키지 통합을 위해 복잡한 메커니즘이 필요하지 않기 때문입니다.
|
||||
|
||||
그러나 유연성, 견고성, 보안성을 해치지 않으면서 과정을 단순화할 수 있는 도구들을 제공합니다.
|
||||
|
||||
그리고 OAuth2와 같은 표준 프로토콜을 비교적 간단한 방법으로 구현하고 사용할 수 있습니다.
|
||||
|
||||
더 세분화된 권한 체계를 위해 OAuth2의 "스코프"를 사용하는 방법은 **심화 사용자 안내서**에서 더 자세히 배울 수 있습니다. OAuth2의 스코프는 제3자 애플리케이션이 사용자를 대신해 그들의 API와 상호작용하도록 권한을 부여하기 위해, Facebook, Google, GitHub, Microsoft, X (Twitter) 등의 많은 대형 인증 제공업체들이 사용하는 메커니즘입니다.
|
||||
@@ -0,0 +1,295 @@
|
||||
# 패스워드와 Bearer를 이용한 간단한 OAuth2
|
||||
|
||||
이제 이전 장에서 빌드하고 누락된 부분을 추가하여 완전한 보안 흐름을 갖도록 하겠습니다.
|
||||
|
||||
## `username`와 `password` 얻기
|
||||
|
||||
**FastAPI** 보안 유틸리티를 사용하여 `username` 및 `password`를 가져올 것입니다.
|
||||
|
||||
OAuth2는 (우리가 사용하고 있는) "패스워드 플로우"을 사용할 때 클라이언트/유저가 `username` 및 `password` 필드를 폼 데이터로 보내야 함을 지정합니다.
|
||||
|
||||
그리고 사양에는 필드의 이름을 그렇게 지정해야 한다고 나와 있습니다. 따라서 `user-name` 또는 `email`은 작동하지 않습니다.
|
||||
|
||||
하지만 걱정하지 않아도 됩니다. 프런트엔드에서 최종 사용자에게 원하는 대로 표시할 수 있습니다.
|
||||
|
||||
그리고 데이터베이스 모델은 원하는 다른 이름을 사용할 수 있습니다.
|
||||
|
||||
그러나 로그인 *경로 작동*의 경우 사양과 호환되도록 이러한 이름을 사용해야 합니다(예를 들어 통합 API 문서 시스템을 사용할 수 있어야 합니다).
|
||||
|
||||
사양에는 또한 `username`과 `password`가 폼 데이터로 전송되어야 한다고 명시되어 있습니다(따라서 여기에는 JSON이 없습니다).
|
||||
|
||||
### `scope`
|
||||
|
||||
사양에는 클라이언트가 다른 폼 필드 "`scope`"를 보낼 수 있다고 나와 있습니다.
|
||||
|
||||
폼 필드 이름은 `scope`(단수형)이지만 실제로는 공백으로 구분된 "범위"가 있는 긴 문자열입니다.
|
||||
|
||||
각 "범위"는 공백이 없는 문자열입니다.
|
||||
|
||||
일반적으로 특정 보안 권한을 선언하는 데 사용됩니다. 다음을 봅시다:
|
||||
|
||||
* `users:read` 또는 `users:write`는 일반적인 예시입니다.
|
||||
* `instagram_basic`은 페이스북/인스타그램에서 사용합니다.
|
||||
* `https://www.googleapis.com/auth/drive`는 Google에서 사용합니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
OAuth2에서 "범위"는 필요한 특정 권한을 선언하는 문자열입니다.
|
||||
|
||||
`:`과 같은 다른 문자가 있는지 또는 URL인지는 중요하지 않습니다.
|
||||
|
||||
이러한 세부 사항은 구현에 따라 다릅니다.
|
||||
|
||||
OAuth2의 경우 문자열일 뿐입니다.
|
||||
|
||||
///
|
||||
|
||||
## `username`과 `password`를 가져오는 코드
|
||||
|
||||
이제 **FastAPI**에서 제공하는 유틸리티를 사용하여 이를 처리해 보겠습니다.
|
||||
|
||||
### `OAuth2PasswordRequestForm`
|
||||
|
||||
먼저 `OAuth2PasswordRequestForm`을 가져와 `/token`에 대한 *경로 작동*에서 `Depends`의 의존성으로 사용합니다.
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[4,76] *}
|
||||
|
||||
`OAuth2PasswordRequestForm`은 다음을 사용하여 폼 본문을 선언하는 클래스 의존성입니다:
|
||||
|
||||
* `username`.
|
||||
* `password`.
|
||||
* `scope`는 선택적인 필드로 공백으로 구분된 문자열로 구성된 큰 문자열입니다.
|
||||
* `grant_type`(선택적으로 사용).
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
OAuth2 사양은 실제로 `password`라는 고정 값이 있는 `grant_type` 필드를 *요구*하지만 `OAuth2PasswordRequestForm`은 이를 강요하지 않습니다.
|
||||
|
||||
사용해야 한다면 `OAuth2PasswordRequestForm` 대신 `OAuth2PasswordRequestFormStrict`를 사용하면 됩니다.
|
||||
|
||||
///
|
||||
|
||||
* `client_id`(선택적으로 사용) (예제에서는 필요하지 않습니다).
|
||||
* `client_secret`(선택적으로 사용) (예제에서는 필요하지 않습니다).
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`OAuth2PasswordRequestForm`은 `OAuth2PasswordBearer`와 같이 **FastAPI**에 대한 특수 클래스가 아닙니다.
|
||||
|
||||
`OAuth2PasswordBearer`는 **FastAPI**가 보안 체계임을 알도록 합니다. 그래서 OpenAPI에 그렇게 추가됩니다.
|
||||
|
||||
그러나 `OAuth2PasswordRequestForm`은 직접 작성하거나 `Form` 매개변수를 직접 선언할 수 있는 클래스 의존성일 뿐입니다.
|
||||
|
||||
그러나 일반적인 사용 사례이므로 더 쉽게 하기 위해 **FastAPI**에서 직접 제공합니다.
|
||||
|
||||
///
|
||||
|
||||
### 폼 데이터 사용하기
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
종속성 클래스 `OAuth2PasswordRequestForm`의 인스턴스에는 공백으로 구분된 긴 문자열이 있는 `scope` 속성이 없고 대신 전송된 각 범위에 대한 실제 문자열 목록이 있는 `scopes` 속성이 있습니다.
|
||||
|
||||
이 예제에서는 `scopes`를 사용하지 않지만 필요한 경우, 기능이 있습니다.
|
||||
|
||||
///
|
||||
|
||||
이제 폼 필드의 `username`을 사용하여 (가짜) 데이터베이스에서 유저 데이터를 가져옵니다.
|
||||
|
||||
해당 사용자가 없으면 "잘못된 사용자 이름 또는 패스워드"라는 오류가 반환됩니다.
|
||||
|
||||
오류의 경우 `HTTPException` 예외를 사용합니다:
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[3,77:79] *}
|
||||
|
||||
### 패스워드 확인하기
|
||||
|
||||
이 시점에서 데이터베이스의 사용자 데이터 형식을 확인했지만 암호를 확인하지 않았습니다.
|
||||
|
||||
먼저 데이터를 Pydantic `UserInDB` 모델에 넣겠습니다.
|
||||
|
||||
일반 텍스트 암호를 저장하면 안 되니 (가짜) 암호 해싱 시스템을 사용합니다.
|
||||
|
||||
두 패스워드가 일치하지 않으면 동일한 오류가 반환됩니다.
|
||||
|
||||
#### 패스워드 해싱
|
||||
|
||||
"해싱"은 일부 콘텐츠(이 경우 패스워드)를 횡설수설하는 것처럼 보이는 일련의 바이트(문자열)로 변환하는 것을 의미합니다.
|
||||
|
||||
정확히 동일한 콘텐츠(정확히 동일한 패스워드)를 전달할 때마다 정확히 동일한 횡설수설이 발생합니다.
|
||||
|
||||
그러나 횡설수설에서 암호로 다시 변환할 수는 없습니다.
|
||||
|
||||
##### 패스워드 해싱을 사용해야 하는 이유
|
||||
|
||||
데이터베이스가 유출된 경우 해커는 사용자의 일반 텍스트 암호가 아니라 해시만 갖게 됩니다.
|
||||
|
||||
따라서 해커는 다른 시스템에서 동일한 암호를 사용하려고 시도할 수 없습니다(많은 사용자가 모든 곳에서 동일한 암호를 사용하므로 이는 위험할 수 있습니다).
|
||||
|
||||
//// tab | 파이썬 3.7 이상
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[80:83] *}
|
||||
|
||||
////
|
||||
|
||||
{* ../../docs_src/security/tutorial003_py310.py hl[78:81] *}
|
||||
|
||||
#### `**user_dict`에 대해
|
||||
|
||||
`UserInDB(**user_dict)`는 다음을 의미한다:
|
||||
|
||||
*`user_dict`의 키와 값을 다음과 같은 키-값 인수로 직접 전달합니다:*
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
disabled = user_dict["disabled"],
|
||||
hashed_password = user_dict["hashed_password"],
|
||||
)
|
||||
```
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`**user_dict`에 대한 자세한 설명은 [**추가 모델** 문서](../extra-models.md#about-user_indict){.internal-link target=_blank}를 다시 읽어봅시다.
|
||||
|
||||
///
|
||||
|
||||
## 토큰 반환하기
|
||||
|
||||
`token` 엔드포인트의 응답은 JSON 객체여야 합니다.
|
||||
|
||||
`token_type`이 있어야 합니다. 여기서는 "Bearer" 토큰을 사용하므로 토큰 유형은 "`bearer`"여야 합니다.
|
||||
|
||||
그리고 액세스 토큰을 포함하는 문자열과 함께 `access_token`이 있어야 합니다.
|
||||
|
||||
이 간단한 예제에서는 완전히 안전하지 않고, 동일한 `username`을 토큰으로 반환합니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
다음 장에서는 패스워드 해싱 및 <abbr title="JSON Web Tokens">JWT</abbr> 토큰을 사용하여 실제 보안 구현을 볼 수 있습니다.
|
||||
|
||||
하지만 지금은 필요한 세부 정보에 집중하겠습니다.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[85] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
사양에 따라 이 예제와 동일하게 `access_token` 및 `token_type`이 포함된 JSON을 반환해야 합니다.
|
||||
|
||||
이는 코드에서 직접 수행해야 하며 해당 JSON 키를 사용해야 합니다.
|
||||
|
||||
사양을 준수하기 위해 스스로 올바르게 수행하기 위해 거의 유일하게 기억해야 하는 것입니다.
|
||||
|
||||
나머지는 **FastAPI**가 처리합니다.
|
||||
|
||||
///
|
||||
|
||||
## 의존성 업데이트하기
|
||||
|
||||
이제 의존성을 업데이트를 할 겁니다.
|
||||
|
||||
이 사용자가 활성화되어 있는 *경우에만* `current_user`를 가져올 겁니다.
|
||||
|
||||
따라서 `get_current_user`를 의존성으로 사용하는 추가 종속성 `get_current_active_user`를 만듭니다.
|
||||
|
||||
이러한 의존성 모두, 사용자가 존재하지 않거나 비활성인 경우 HTTP 오류를 반환합니다.
|
||||
|
||||
따라서 엔드포인트에서는 사용자가 존재하고 올바르게 인증되었으며 활성 상태인 경우에만 사용자를 얻습니다:
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *}
|
||||
|
||||
/// info | 정보
|
||||
|
||||
여기서 반환하는 값이 `Bearer`인 추가 헤더 `WWW-Authenticate`도 사양의 일부입니다.
|
||||
|
||||
모든 HTTP(오류) 상태 코드 401 "UNAUTHORIZED"는 `WWW-Authenticate` 헤더도 반환해야 합니다.
|
||||
|
||||
베어러 토큰의 경우(지금의 경우) 해당 헤더의 값은 `Bearer`여야 합니다.
|
||||
|
||||
실제로 추가 헤더를 건너뛸 수 있으며 여전히 작동합니다.
|
||||
|
||||
그러나 여기에서는 사양을 준수하도록 제공됩니다.
|
||||
|
||||
또한 이를 예상하고 (현재 또는 미래에) 사용하는 도구가 있을 수 있으며, 현재 또는 미래에 자신 혹은 자신의 유저들에게 유용할 것입니다.
|
||||
|
||||
그것이 표준의 이점입니다 ...
|
||||
|
||||
///
|
||||
|
||||
## 확인하기
|
||||
|
||||
대화형 문서 열기: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
### 인증하기
|
||||
|
||||
"Authorize" 버튼을 눌러봅시다.
|
||||
|
||||
자격 증명을 사용합니다.
|
||||
|
||||
유저명: `johndoe`
|
||||
|
||||
패스워드: `secret`
|
||||
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
시스템에서 인증하면 다음과 같이 표시됩니다:
|
||||
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### 자신의 유저 데이터 가져오기
|
||||
|
||||
이제 `/users/me` 경로에 `GET` 작업을 진행합시다.
|
||||
|
||||
다음과 같은 사용자 데이터를 얻을 수 있습니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false,
|
||||
"hashed_password": "fakehashedsecret"
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
잠금 아이콘을 클릭하고 로그아웃한 다음 동일한 작업을 다시 시도하면 다음과 같은 HTTP 401 오류가 발생합니다.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
### 비활성된 유저
|
||||
|
||||
이제 비활성된 사용자로 시도하고, 인증해봅시다:
|
||||
|
||||
유저명: `alice`
|
||||
|
||||
패스워드: `secret2`
|
||||
|
||||
그리고 `/users/me` 경로와 함께 `GET` 작업을 사용해 봅시다.
|
||||
|
||||
다음과 같은 "Inactive user" 오류가 발생합니다:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Inactive user"
|
||||
}
|
||||
```
|
||||
|
||||
## 요약
|
||||
|
||||
이제 API에 대한 `username` 및 `password`를 기반으로 완전한 보안 시스템을 구현할 수 있는 도구가 있습니다.
|
||||
|
||||
이러한 도구를 사용하여 보안 시스템을 모든 데이터베이스 및 모든 사용자 또는 데이터 모델과 호환되도록 만들 수 있습니다.
|
||||
|
||||
유일한 오점은 아직 실제로 "안전"하지 않다는 것입니다.
|
||||
|
||||
다음 장에서는 안전한 패스워드 해싱 라이브러리와 <abbr title="JSON Web Tokens">JWT</abbr> 토큰을 사용하는 방법을 살펴보겠습니다.
|
||||
@@ -0,0 +1,360 @@
|
||||
# SQL (관계형) 데이터베이스
|
||||
|
||||
**FastAPI**에서 SQL(관계형) 데이터베이스 사용은 필수가 아닙니다. 여러분이 원하는 **어떤 데이터베이스든** 사용할 수 있습니다.
|
||||
|
||||
여기서는 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>을 사용하는 예제를 살펴보겠습니다.
|
||||
|
||||
**SQLModel**은 <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a>와 Pydantic을 기반으로 구축되었습니다.SQLModel은 **SQL 데이터베이스**를 사용하는 FastAPI 애플리케이션에 완벽히 어울리도록 **FastAPI**의 제작자가 설계한 도구입니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
다른 SQL 또는 NoSQL 데이터베이스 라이브러리를 사용할 수도 있습니다 (일부는 <abbr title="객체 관계 매퍼(Object Relational Mapper), SQL 테이블을 나타내는 클래스를 제공하고 테이블의 행을 인스턴스로 표현하는 라이브러리를 지칭하는 용어">"ORM"</abbr>이라고도 불립니다), FastAPI는 특정 라이브러리의 사용을 강요하지 않습니다. 😎
|
||||
|
||||
///
|
||||
|
||||
SQLModel은 SQLAlchemy를 기반으로 하므로, SQLAlchemy에서 **지원하는 모든 데이터베이스**를 손쉽게 사용할 수 있습니다(SQLModel에서도 동일하게 지원됩니다). 예를 들면:
|
||||
|
||||
* PostgreSQL
|
||||
* MySQL
|
||||
* SQLite
|
||||
* Oracle
|
||||
* Microsoft SQL Server 등.
|
||||
|
||||
이 예제에서는 **SQLite**를 사용합니다. SQLite는 단일 파일을 사용하고 파이썬에서 기본적으로 지원하기 때문입니다. 따라서 이 예제를 그대로 복사하여 실행할 수 있습니다.
|
||||
|
||||
나중에 실제 프로덕션 애플리케이션에서는 **PostgreSQL**과 같은 데이터베이스 서버를 사용하는 것이 좋습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
**FastAPI**와 **PostgreSQL**를 포함하여 프론트엔드와 다양한 도구를 제공하는 공식 프로젝트 생성기가 있습니다: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
|
||||
|
||||
///
|
||||
|
||||
이 튜토리얼은 매우 간단하고 짧습니다. 데이터베이스 기본 개념, SQL, 또는 더 복잡한 기능에 대해 배우고 싶다면, <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel 문서</a>를 참고하세요.
|
||||
|
||||
## `SQLModel` 설치하기
|
||||
|
||||
먼저, [가상 환경](../virtual-environments.md){.internal-link target=_blank}을 생성하고 활성화한 다음, `sqlmodel`을 설치하세요:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install sqlmodel
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 단일 모델로 애플리케이션 생성하기
|
||||
|
||||
우선 단일 **SQLModel** 모델을 사용하여 애플리케이션의 가장 간단한 첫 번째 버전을 생성해보겠습니다.
|
||||
|
||||
이후 **다중 모델**을 추가하여 보안과 유연성을 강화할 것입니다. 🤓
|
||||
|
||||
### 모델 생성하기
|
||||
|
||||
`SQLModel`을 가져오고 데이터베이스 모델을 생성합니다:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}
|
||||
|
||||
`Hero` 클래스는 Pydantic 모델과 매우 유사합니다 (실제로 내부적으로 *Pydantic 모델이기도 합니다*).
|
||||
|
||||
몇 가지 차이점이 있습니다:
|
||||
|
||||
* `table=True`는 SQLModel에 이 모델이 *테이블 모델*이며, 단순한 데이터 모델이 아니라 SQL 데이터베이스의 **테이블**을 나타낸다는 것을 알려줍니다. (다른 일반적인 Pydantic 클래스처럼) 단순한 *데이터 모델*이 아닙니다.
|
||||
|
||||
* `Field(primary_key=True)`는 SQLModel에 `id`가 SQL 데이터베이스의 **기본 키**임을 알려줍니다 (SQL 기본 키에 대한 자세한 내용은 SQLModel 문서를 참고하세요).
|
||||
|
||||
`int | None` 유형으로 설정하면, SQLModel은 해당 열이 SQL 데이터베이스에서 `INTEGER` 유형이며 `NULLABLE` 값이어야 한다는 것을 알 수 있습니다.
|
||||
|
||||
* `Field(index=True)`는 SQLModel에 해당 열에 대해 **SQL 인덱스**를 생성하도록 지시합니다. 이를 통해 데이터베이스에서 이 열으로 필터링된 데이터를 읽을 때 더 빠르게 조회할 수 있습니다.
|
||||
|
||||
SQLModel은 `str`으로 선언된 항목이 SQL 데이터베이스에서 `TEXT` (또는 데이터베이스에 따라 `VARCHAR`) 유형의 열로 저장된다는 것을 인식합니다.
|
||||
|
||||
### 엔진 생성하기
|
||||
|
||||
SQLModel의 `engine` (내부적으로는 SQLAlchemy `engine`)은 데이터베이스에 대한 **연결을 유지**하는 역할을 합니다.
|
||||
|
||||
**하나의 단일 engine 객체**를 통해 코드 전체에서 동일한 데이터베이스에 연결할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
|
||||
|
||||
`check_same_thread=False`를 사용하면 FastAPI에서 여러 스레드에서 동일한 SQLite 데이터베이스를 사용할 수 있습니다. 이는 **하나의 단일 요청**이 **여러 스레드**를 사용할 수 있기 때문에 필요합니다(예: 의존성에서 사용되는 경우).
|
||||
|
||||
걱정하지 마세요. 코드가 구조화된 방식으로 인해, 이후에 **각 요청마다 단일 SQLModel *세션*을 사용**하도록 보장할 것입니다. 실제로 그것이 `check_same_thread`가 하려는 것입니다.
|
||||
|
||||
### 테이블 생성하기
|
||||
|
||||
그 다음 `SQLModel.metadata.create_all(engine)`을 사용하여 모든 *테이블 모델*의 **테이블을 생성**하는 함수를 추가합니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
|
||||
|
||||
### 세션 의존성 생성하기
|
||||
|
||||
**`Session`**은 **메모리에 객체**를 저장하고 데이터에 필요한 모든 변경 사항을 추적한 후, **`engine`을 통해** 데이터베이스와 통신합니다.
|
||||
|
||||
`yield`를 사용해 FastAPI의 **의존성**을 생성하여 각 요청마다 새로운 `Session`을 제공합니다. 이는 요청당 하나의 세션만 사용되도록 보장합니다. 🤓
|
||||
|
||||
그런 다음 이 의존성을 사용하는 코드를 간소화하기 위해 `Annotated` 의존성 `SessionDep`을 생성합니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
|
||||
|
||||
### 시작 시 데이터베이스 테이블 생성하기
|
||||
|
||||
애플리케이션 시작 시 데이터베이스 테이블을 생성합니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}
|
||||
|
||||
여기서는 애플리케이션 시작 이벤트 시 테이블을 생성합니다.
|
||||
|
||||
프로덕션 환경에서는 애플리케이션을 시작하기 전에 실행되는 마이그레이션 스크립트를 사용할 가능성이 높습니다. 🤓
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
SQLModel은 Alembic을 감싸는 마이그레이션 유틸리티를 제공할 예정입니다. 하지만 현재 <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a>을 직접 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
### Hero 생성하기
|
||||
|
||||
각 SQLModel 모델은 Pydantic 모델이기도 하므로, Pydantic 모델을 사용할 수 있는 **타입 어노테이**션에서 동일하게 사용할 수 있습니다.
|
||||
|
||||
예를 들어, 파라미터를 `Hero` 타입으로 선언하면 **JSON 본문**에서 값을 읽어옵니다.
|
||||
|
||||
마찬가지로, 함수의 **반환 타입**으로 선언하면 해당 데이터의 구조가 자동으로 생성되는 API 문서의 UI에 나타납니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
|
||||
|
||||
</details>
|
||||
|
||||
여기서 `SessionDep` 의존성 (즉, `Session`)을 사용하여 새로운 `Hero`를 `Session` 인스턴스에 추가하고, 데이터베이스에 변경 사항을 커밋하고, `hero` 데이터의 최신 상태를 갱신한 다음 이를 반환합니다.
|
||||
|
||||
### Heroes 조회하기
|
||||
|
||||
`select()`를 사용하여 데이터베이스에서 `Hero`를 **조회**할 수 있습니다. 결과에 페이지네이션을 적용하기 위해 `limit`와 `offset`을 포함할 수 있습니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
|
||||
|
||||
### 단일 Hero 조회하기
|
||||
|
||||
단일 `Hero`를 **조회**할 수도 있습니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
|
||||
|
||||
### Hero 삭제하기
|
||||
|
||||
`Hero`를 **삭제**하는 것도 가능합니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
|
||||
|
||||
### 애플리케이션 실행하기
|
||||
|
||||
애플리케이션을 실행하려면 다음 명령을 사용합니다:
|
||||
|
||||
<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>
|
||||
|
||||
그런 다음 `/docs` UI로 이동하면, **FastAPI**가 해당 **model들**을 사용하여 API **문서를 생성**하는 것으르 확인할 수 있습니다. 또한 이 모델들은 데이터를 직렬화하고 검증하는 데에도 사용됩니다.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image01.png">
|
||||
</div>
|
||||
|
||||
## 여러 모델로 애플리케이션 업데이트
|
||||
|
||||
이제 애플리케이션을 약간 **리팩토링**하여 **보안**과 **유연성**을 개선해 보겠습니다.
|
||||
|
||||
이전 애플리케이션의 UI를 보면, 지금까지는 클라이언트가 생성할 `Hero`의 `id`를 직접 지정할 수 있다는 것을 알 수 있습니다. 😱
|
||||
|
||||
이는 허용되어선 안 됩니다. 클라이언트가 이미 데이터베이스에 저장된 `id`를 덮어쓸 위험이 있기 때문입니다. `id`는 **백엔드** 또는 **데이터베이스**가 결정해야 하며, **클라이언트**가 결정해서는 안 됩니다.
|
||||
|
||||
또한 hero의 `secret_name`을 생성하긴 했지만, 지금까지는 이 값을 어디에서나 반환하고 있습니다. 이는 그다지 **비밀스럽지** 않습니다... 😅
|
||||
|
||||
이러한 문제를 해결하기 위해 몇 가지 **추가 모델**을 추가할 것입니다. 바로 여기서 SQLModel이 빛을 발하게 됩니다. ✨
|
||||
|
||||
### 여러 모델 생성하기
|
||||
|
||||
**SQLModel**에서 `table=True`가 설정된 모델 클래스는 **테이블 모델**입니다.
|
||||
|
||||
`table=True`가 없는 모델 클래스는 **데이터 모델**로, 이는 실제로 몇 가지 추가 기능이 포함된 Pydantic 모델에 불과합니다. 🤓
|
||||
|
||||
SQLModel을 사용하면 **상속**을 통해 모든 경우에 필드를 **중복 선언하지 않아도** 됩니다.
|
||||
|
||||
#### `HeroBase` - 기본 클래스
|
||||
|
||||
모든 모델에서 **공유되는 필드**를 가진 `HeroBase` 모델을 시작해 봅시다:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
|
||||
|
||||
#### `Hero` - *테이블 모델*
|
||||
|
||||
다음으로 실제 *테이블 모델*인 `Hero`를 생성합니다. 이 모델은 다른 모델에는 항상 포함되는 건 아닌 **추가 필드**를 포함합니다:
|
||||
|
||||
* `id`
|
||||
* `secret_name`
|
||||
|
||||
`Hero`는 `HeroBase`를 상속하므로 `HeroBase`에 선언된 필드도 포함합니다. 따라서 `Hero`는 다음 **필드들도** 가지게 됩니다:
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
|
||||
|
||||
#### `HeroPublic` - 공개 *데이터 모델*
|
||||
|
||||
다음으로 `HeroPublic` 모델을 생성합니다. 이 모델은 API 클라이언트에 **반환**되는 모델입니다.
|
||||
|
||||
`HeroPublic`은 `HeroBase`와 동일한 필드를 가지며, `secret_name`은 포함하지 않습니다.
|
||||
|
||||
마침내 우리의 heroes의 정체가 보호됩니다! 🥷
|
||||
|
||||
또한 `id: int`를 다시 선언합니다. 이를 통해, API 클라이언트와 **계약**을 맺어 `id`가 항상 존재하며 항상 `int` 타입이라는 것을 보장합니다(`None`이 될 수 없습니다).
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
반환 모델이 값이 항상 존재하고 항상 `int`(`None`이 아님)를 보장하는 것은 API 클라이언트에게 매우 유용합니다. 이를 통해 API와 통신하는 개발자가 훨씬 더 간단한 코드를 작성할 수 있습니다.
|
||||
|
||||
또한 **자동으로 생성된 클라이언트**는 더 단순한 인터페이스를 제공하므로, API와 소통하는 개발자들이 훨씬 수월하게 작업할 수 있습니다. 😎
|
||||
|
||||
///
|
||||
|
||||
`HeroPublic`의 모든 필드는 `HeroBase`와 동일하며, `id`는 `int`로 선언됩니다(`None`이 아님):
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
|
||||
|
||||
#### `HeroCreate` - hero 생성용 *데이터 모델*
|
||||
|
||||
이제 `HeroCreate` 모델을 생성합니다. 이 모델은 클라이언트로부터 받은 데이터를 **검증**하는 역할을 합니다.
|
||||
|
||||
`HeroCreate`는 `HeroBase와` 동일한 필드를 가지며, 추가로 `secret_name을` 포함합니다.
|
||||
|
||||
클라이언트가 **새 hero을 생성**할 때 `secret_name`을 보내고, 이는 데이터베이스에 저장되지만, 해당 비밀 이름은 API를 통해 클라이언트에게 반환되지 않습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 방식은 **비밀번호**를 처리하는 방법과 동일합니다. 비밀번호를 받지만, 이를 API에서 반환하지는 않습니다.
|
||||
|
||||
비밀번호 값을 저장하기 전에 **해싱**하여 저장하고, **평문으로 저장하지 마십시오**.
|
||||
|
||||
///
|
||||
|
||||
`HeroCreate`의 필드는 다음과 같습니다:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
|
||||
|
||||
#### `HeroUpdate` - hero 수정용 *데이터 모델*
|
||||
|
||||
이전 애플리케이션에서는 **hero를 수정**할 방법이 없었지만, 이제 **다중 모델**을 통해 수정 기능을 추가할 수 있습니다. 🎉
|
||||
|
||||
`HeroUpdate` *데이터 모델*은 약간 특별한데, 새 hero을 생성할 때 필요한 **모든 동일한 필드**를 가지지만, 모든 필드가 **선택적**(기본값이 있음)입니다. 이렇게 하면 hero을 수정할 때 수정하려는 필드만 보낼 수 있습니다.
|
||||
|
||||
모든 **필드가 변경되기** 때문에(타입이 `None`을 포함하고, 기본값이 `None`으로 설정됨), 모든 필드를 **다시 선언**해야 합니다.
|
||||
|
||||
엄밀히 말하면 `HeroBase`를 상속할 필요는 없습니다. 모든 필드를 다시 선언하기 때문입니다. 일관성을 위해 상속을 유지하긴 했지만, 필수는 아닙니다. 이는 개인적인 취향의 문제입니다. 🤷
|
||||
|
||||
`HeroUpdate`의 필드는 다음과 같습니다:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
|
||||
|
||||
### `HeroCreate`로 생성하고 `HeroPublic` 반환하기
|
||||
|
||||
이제 **다중 모델**을 사용하므로 애플리케이션의 관련 부분을 업데이트할 수 있습니다.
|
||||
|
||||
요청에서 `HeroCreate` *데이터 모델*을 받아 이를 기반으로 `Hero` *테이블 모델*을 생성합니다.
|
||||
|
||||
이 새 *테이블 모델* `Hero`는 클라이언트에서 보낸 필드를 가지며, 데이터베이스에서 생성된 `id`도 포함합니다.
|
||||
|
||||
그런 다음 함수를 통해 동일한 *테이블 모델* `Hero`를 반환합니다. 하지만 `response_model`로 `HeroPublic` *데이터 모델*을 선언했기 때문에, **FastAPI**는 `HeroPublic`을 사용하여 데이터를 검증하고 직렬화합니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이제 **반환 타입 주석** `-> HeroPublic` 대신 `response_model=HeroPublic`을 사용합니다. 반환하는 값이 실제로 `HeroPublic`이 *아니기* 때문입니다.
|
||||
|
||||
만약 `-> HeroPublic`으로 선언했다면, 에디터와 린터에서 반환값이 `HeroPublic`이 아니라 `Hero`라고 경고했을 것입니다. 이는 적절한 경고입니다.
|
||||
|
||||
`response_model`에 선언함으로써 **FastAPI**가 이를 처리하도록 하고, 타입 어노테이션과 에디터 및 다른 도구의 도움에는 영향을 미치지 않도록 설정합니다.
|
||||
|
||||
///
|
||||
|
||||
### `HeroPublic`으로 Heroes 조회하기
|
||||
|
||||
이전과 동일하게 `Hero`를 **조회**할 수 있습니다. 이번에도 `response_model=list[HeroPublic]`을 사용하여 데이터가 올바르게 검증되고 직렬화되도록 보장합니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
|
||||
|
||||
### `HeroPublic`으로 단일 Hero 조회하기
|
||||
|
||||
단일 hero을 **조회**할 수도 있습니다:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
|
||||
|
||||
### `HeroUpdate`로 Hero 수정하기
|
||||
|
||||
**hero를 수정**할 수도 있습니다. 이를 위해 HTTP `PATCH` 작업을 사용합니다.
|
||||
|
||||
코드에서는 클라이언트가 보낸 데이터를 딕셔너리 형태(`dict`)로 가져옵니다. 이는 **클라이언트가 보낸 데이터만 포함**하며, 기본값으로 들어가는 값은 제외합니다. 이를 위해 `exclude_unset=True`를 사용합니다. 이것이 주요 핵심입니다. 🪄
|
||||
|
||||
그런 다음, `hero_db.sqlmodel_update(hero_data)`를 사용하여 `hero_data`의 데이터를 `hero_db`에 업데이트합니다.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
|
||||
|
||||
### Hero 다시 삭제하기
|
||||
|
||||
hero **삭제**는 이전과 거의 동일합니다.
|
||||
|
||||
이번에는 모든 것을 리팩토링하고 싶은 욕구를 만족시키지 못할 것 같습니다. 😅
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
|
||||
|
||||
### 애플리케이션 다시 실행하기
|
||||
|
||||
다음 명령을 사용해 애플리케이션을 다시 실행할 수 있습니다:
|
||||
|
||||
<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>
|
||||
|
||||
`/docs` API UI로 이동하면 업데이트된 것을 확인할 수 있습니다. 클라이언트가 영웅을 생성할 때 `id`를 제공할 필요가 없게 되는 등의 변화도 보입니다.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image02.png">
|
||||
</div>
|
||||
|
||||
## 요약
|
||||
|
||||
<a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a>을 사용하여 SQL 데이터베이스와 상호작용하고, *데이터 모델* 및 *테이블 모델*로 코드를 간소화할 수 있습니다.
|
||||
|
||||
더 많은 내용을 배우고 싶다면, **SQLModel** 문서를 참고하세요. <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">SQLModel을 **FastAPI**와 함께 사용하는 것에 대한 더 긴 미니 튜토리얼</a>도 제공합니다. 🚀
|
||||
@@ -0,0 +1,41 @@
|
||||
# 정적 파일
|
||||
|
||||
'StaticFiles'를 사용하여 디렉토리에서 정적 파일을 자동으로 제공할 수 있습니다.
|
||||
|
||||
## `StaticFiles` 사용
|
||||
|
||||
* `StaticFiles` 임포트합니다.
|
||||
* 특정 경로에 `StaticFiles()` 인스턴스를 "마운트" 합니다.
|
||||
|
||||
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
|
||||
|
||||
/// note | 기술적 세부사항
|
||||
|
||||
`from starlette.staticfiles import StaticFiles` 를 사용할 수도 있습니다.
|
||||
|
||||
**FastAPI**는 단지 개발자인, 당신에게 편의를 제공하기 위해 `fastapi.static files` 와 동일한 `starlett.static files`를 제공합니다. 하지만 사실 이것은 Starlett에서 직접 온 것입니다.
|
||||
|
||||
///
|
||||
|
||||
### "마운팅" 이란
|
||||
|
||||
"마운팅"은 특정 경로에 완전히 "독립적인" 애플리케이션을 추가하는 것을 의미하는데, 그 후 모든 하위 경로에 대해서도 적용됩니다.
|
||||
|
||||
마운트된 응용 프로그램은 완전히 독립적이기 때문에 `APIRouter`를 사용하는 것과는 다릅니다. OpenAPI 및 응용 프로그램의 문서는 마운트된 응용 프로그램 등에서 어떤 것도 포함하지 않습니다.
|
||||
|
||||
자세한 내용은 **숙련된 사용자 안내서**에서 확인할 수 있습니다.
|
||||
|
||||
## 세부사항
|
||||
|
||||
첫 번째 `"/static"`은 이 "하위 응용 프로그램"이 "마운트"될 하위 경로를 가리킵니다. 따라서 `"/static"`으로 시작하는 모든 경로는 `"/static"`으로 처리됩니다.
|
||||
|
||||
`'directory="static"`은 정적 파일이 들어 있는 디렉토리의 이름을 나타냅니다.
|
||||
|
||||
`name="static"`은 **FastAPI**에서 내부적으로 사용할 수 있는 이름을 제공합니다.
|
||||
|
||||
이 모든 매개변수는 "`static`"과 다를 수 있으며, 사용자 응용 프로그램의 요구 사항 및 구체적인 세부 정보에 따라 매개변수를 조정할 수 있습니다.
|
||||
|
||||
|
||||
## 추가 정보
|
||||
|
||||
자세한 내용과 선택 사항을 보려면 <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette의 정적 파일에 관한 문서</a>를 확인하십시오.
|
||||
@@ -0,0 +1,243 @@
|
||||
# 테스팅
|
||||
|
||||
<a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a> 덕분에 **FastAPI** 를 테스트하는 일은 쉽고 즐거운 일이 되었습니다.
|
||||
|
||||
Starlette는 <a href="https://www.python-httpx.org\" class="external-link" target="_blank">HTTPX</a>를 기반으로 하며, 이는 Requests를 기반으로 설계되었기 때문에 매우 친숙하고 직관적입니다.
|
||||
|
||||
이를 사용하면 FastAPI에서 <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>를 직접 사용할 수 있습니다.
|
||||
|
||||
## `TestClient` 사용하기
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`TestClient` 사용하려면, 우선 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a> 를 설치해야 합니다.
|
||||
|
||||
[virtual environment](../virtual-environments.md){.internal-link target=_blank} 를 만들고, 활성화 시킨 뒤에 설치하세요. 예시:
|
||||
|
||||
```console
|
||||
$ pip install httpx
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
`TestClient` 를 임포트하세요.
|
||||
|
||||
**FastAPI** 어플리케이션을 전달하여 `TestClient` 를 만드세요.
|
||||
|
||||
이름이 `test_` 로 시작하는 함수를 만드세요(`pytest` 의 표준적인 관례입니다).
|
||||
|
||||
`httpx` 를 사용하는 것과 같은 방식으로 `TestClient` 객체를 사용하세요.
|
||||
|
||||
표준적인 파이썬 문법을 이용하여 확인이 필요한 곳에 간단한 `assert` 문장을 작성하세요(역시 표준적인 `pytest` 관례입니다).
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
테스트를 위한 함수는 `async def` 가 아니라 `def` 로 작성됨에 주의하세요.
|
||||
|
||||
그리고 클라이언트에 대한 호출도 `await` 를 사용하지 않는 일반 호출입니다.
|
||||
|
||||
이렇게 하여 복잡한 과정 없이 `pytest` 를 직접적으로 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
/// note | 기술 세부사항
|
||||
|
||||
`from starlette.testclient import TestClient` 역시 사용할 수 있습니다.
|
||||
|
||||
**FastAPI** 는 개발자의 편의를 위해 `starlette.testclient` 를 `fastapi.testclient` 로도 제공할 뿐입니다. 이는 단지 `Starlette` 에서 직접 가져오는지의 차이일 뿐입니다.
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
FastAPI 애플리케이션에 요청을 보내는 것 외에도 테스트에서 `async` 함수를 호출하고 싶다면 (예: 비동기 데이터베이스 함수), 심화 튜토리얼의 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 를 참조하세요.
|
||||
|
||||
///
|
||||
|
||||
## 테스트 분리하기
|
||||
|
||||
실제 애플리케이션에서는 테스트를 별도의 파일로 나누는 경우가 많습니다.
|
||||
|
||||
|
||||
그리고 **FastAPI** 애플리케이션도 여러 파일이나 모듈 등으로 구성될 수 있습니다.
|
||||
|
||||
### **FastAPI** app 파일
|
||||
|
||||
[Bigger Applications](bigger-applications.md){.internal-link target=_blank} 에 묘사된 파일 구조를 가지고 있는 것으로 가정해봅시다.
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ └── main.py
|
||||
```
|
||||
|
||||
`main.py` 파일 안에 **FastAPI** app 을 만들었습니다:
|
||||
|
||||
{* ../../docs_src/app_testing/main.py *}
|
||||
|
||||
### 테스트 파일
|
||||
|
||||
테스트를 위해 `test_main.py` 라는 파일을 생성할 수 있습니다. 이 파일은 동일한 Python 패키지(즉, `__init__.py` 파일이 있는 동일한 디렉터리)에 위치할 수 있습니다.
|
||||
|
||||
``` hl_lines="5"
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
파일들이 동일한 패키지에 위치해 있으므로, 상대 참조를 사용하여 `main` 에서 `app` 객체를 임포트 해올 수 있습니다.
|
||||
|
||||
{* ../../docs_src/app_testing/test_main.py hl[3] *}
|
||||
|
||||
|
||||
...그리고 이전에 작성했던 것과 같은 테스트 코드를 작성할 수 있습니다.
|
||||
|
||||
## 테스트: 확장된 예시
|
||||
|
||||
이제 위의 예시를 확장하고 더 많은 세부 사항을 추가하여 다양한 부분을 어떻게 테스트하는지 살펴보겠습니다.
|
||||
|
||||
### 확장된 FastAPI 애플리케이션 파일
|
||||
|
||||
이전과 같은 파일 구조를 계속 사용해 보겠습니다.
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
이제 **FastAPI** 앱이 있는 `main.py` 파일에 몇 가지 다른 **경로 작업** 이 추가된 경우를 생각해봅시다.
|
||||
|
||||
단일 오류를 반환할 수 있는 `GET` 작업이 있습니다.
|
||||
|
||||
여러 다른 오류를 반환할 수 있는 `POST` 작업이 있습니다.
|
||||
|
||||
두 *경로 작업* 모두 `X-Token` 헤더를 요구합니다.
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ non-Annotated
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
될 수 있으면 `Annotated` 버전 사용을 권장합나다.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
될 수 있으면 `Annotated` 버전 사용을 권장합나다.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
### 확장된 테스트 파일
|
||||
|
||||
이제는 `test_main.py` 를 확장된 테스트들로 수정할 수 있습니다:
|
||||
|
||||
{* ../../docs_src/app_testing/app_b/test_main.py *}
|
||||
|
||||
|
||||
클라이언트가 요청에 정보를 전달해야 하는데 방법을 모르겠다면, `httpx`에서 해당 작업을 수행하는 방법을 검색(Google)하거나, `requests`에서의 방법을 검색해보세요. HTTPX는 Requests의 디자인을 기반으로 설계되었습니다.
|
||||
|
||||
그 후, 테스트에서도 동일하게 적용하면 됩니다.
|
||||
|
||||
예시:
|
||||
|
||||
* *경로* 혹은 *쿼리* 매개변수를 전달하려면, URL 자체에 추가한다.
|
||||
* JSON 본문을 전달하려면, 파이썬 객체 (예를들면 `dict`) 를 `json` 파라미터로 전달한다.
|
||||
* JSON 대신 *폼 데이터* 를 보내야한다면, `data` 파라미터를 대신 전달한다.
|
||||
* *헤더* 를 전달하려면, `headers` 파라미터에 `dict` 를 전달한다.
|
||||
* *쿠키* 를 전달하려면, `cookies` 파라미터에 `dict` 를 전달한다.
|
||||
|
||||
백엔드로 데이터를 어떻게 보내는지 정보를 더 얻으려면 (`httpx` 혹은 `TestClient` 를 이용해서) <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX documentation</a> 를 확인하세요.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
`TestClient` 는 Pydantic 모델이 아니라 JSON 으로 변환될 수 있는 데이터를 받습니다.
|
||||
|
||||
만약 테스트중 Pydantic 모델을 어플리케이션으로에 보내고 싶다면, [JSON 호환 가능 인코더](encoder.md){.internal-link target=_blank} 에 설명되어 있는 `jsonable_encoder` 를 사용할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
## 실행하기
|
||||
|
||||
테스트 코드를 작성하고, `pytest` 를 설치해야합니다.
|
||||
|
||||
[virtual environment](../virtual-environments.md){.internal-link target=_blank} 를 만들고, 활성화 시킨 뒤에 설치하세요. 예시:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`pytest` 파일과 테스트를 자동으로 감지하고 실행한 다음, 결과를 보고할 것입니다.
|
||||
|
||||
테스트를 다음 명령어로 실행하세요.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pytest
|
||||
|
||||
================ test session starts ================
|
||||
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
|
||||
rootdir: /home/user/code/superawesome-cli/app
|
||||
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
|
||||
collected 6 items
|
||||
|
||||
---> 100%
|
||||
|
||||
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span>
|
||||
|
||||
<span style="color: green;">================= 1 passed in 0.03s =================</span>
|
||||
```
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,846 @@
|
||||
# 가상 환경
|
||||
|
||||
Python 프로젝트를 작업할 때는 **가상 환경** (또는 이와 유사한 도구)을 사용하는 것이 좋습니다. 각 프로젝트 마다 설치하는 패키지를 분리하여 관리할 수 있습니다.
|
||||
|
||||
/// info | 정보
|
||||
|
||||
이미 가상 환경에 대해 잘 알고 있다면, 이 섹션은 건너 뛰어도 괜찮습니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
**가상 환경(Virtual Environment)** 은 **환경 변수(Environment Variable)** 와 다릅니다.
|
||||
|
||||
**환경 변수**는 시스템에 존재하며, 프로그램이 사용할 수 있는 변수입니다.
|
||||
|
||||
**가상 환경**은 몇몇 파일로 구성된 하나의 디렉터리입니다.
|
||||
|
||||
///
|
||||
|
||||
/// info | 정보
|
||||
|
||||
이 페이지에서는 **가상 환경**의 사용 방법과 작동 방식을 설명합니다.
|
||||
|
||||
만약 **모든 것을 관리해주는 도구** (Python 설치까지 포함)를 사용하고 싶다면 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>를 사용해보세요.
|
||||
|
||||
///
|
||||
|
||||
## 프로젝트 생성
|
||||
|
||||
먼저, 프로젝트를 위한 디렉터리를 하나 생성합니다.
|
||||
|
||||
보통 사용자 홈 디렉터리 안에 `code`라는 디렉터리를 만들고, 그 안에 프로젝트마다 하나씩 디렉터리를 만들어 관리합니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 홈 디렉터리로 이동
|
||||
$ cd
|
||||
// 모든 코드 프로젝트를 위한 디렉터리 생성
|
||||
$ mkdir code
|
||||
// code 디렉터리로 이동
|
||||
$ cd code
|
||||
// 이번 프로젝트를 위한 디렉터리 생성
|
||||
$ mkdir awesome-project
|
||||
// 해당 프로젝트 디렉터리로 이동
|
||||
$ cd awesome-project
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 가상 환경 생성
|
||||
|
||||
Python 프로젝트를 **처음 시작할 때**, 가상 환경을 **<abbr title="다른 방법들도 있지만, 이건 간단한 가이드라인입니다">프로젝트 내부</abbr>**에 생성합니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 작업은 **프로젝트를 처음 설정할 때 한번만** 해주면 됩니다. 이후 작업할 때 반복할 필요는 없습니다.
|
||||
|
||||
///
|
||||
|
||||
//// tab | `venv`
|
||||
|
||||
Python 표준 라이브러리에 포함된 venv 모듈을 사용해 가상 환경을 생성할 수 있습니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m venv .venv
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// details | 명령어 상세 설명
|
||||
|
||||
* `python`: `python` 프로그램을 실행합니다.
|
||||
* `-m`: 특정 모듈을 스크립트처럼 실행합니다. 대상 모듈을 바로 뒤에 지정합니다.
|
||||
* `venv`: Python 표준 라이브러리에 포함된 `venv` 모듈을 실행합니다.
|
||||
* `.venv`: 가상 환경을 `.venv` 디렉터리에 생성합니다.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>가 설치되어 있다면, uv를 통해 가상 환경을 생성할 수 있습니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv venv
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`uv`는 기본적으로 `.venv` 디렉터리에 가상 환경을 생성합니다.
|
||||
|
||||
별도로 디렉터리 이름을 추가 인자로 넘겨 주면 경로를 지정 할 수 있습니다.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
해당 명령어는 `.venv` 디렉터리에 새로운 가상 환경을 생성합니다.
|
||||
|
||||
/// details | `.venv` 또는 다른 이름
|
||||
|
||||
가상 환경을 다른 디렉터리에 생성할 수도 있지만, 관례적으로 `.venv` 디렉터리 이름을 사용합니다.
|
||||
|
||||
///
|
||||
|
||||
## 가상 환경 활성화
|
||||
|
||||
이후 실행하는 Python 명령어와 패키지 설치가 가상 환경을 따르도록, 가상 환경을 활성화하세요.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
**터미널을 새로 열고** 프로젝트 작업을 시작할 때는, **항상 이 작업을** 해주세요.
|
||||
|
||||
///
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/bin/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ .venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
Windows에서 Bash(예: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)를 사용하는 경우:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
가상 환경에 새로운 패키지를 설치할 때마다, 해당 환경을 다시 활성화하세요.
|
||||
|
||||
이렇게 하면 해당 패키지로 설치된 **터미널(<abbr title="command line interface">CLI</abbr>) 프로그램**을 사용할 때, 전역에 설치된 다른 버전이 아니라, 가상 환경 안에 설치된 정확한 버전을 사용합니다.
|
||||
|
||||
///
|
||||
|
||||
## 가상 환경이 활성화 여부 확인
|
||||
|
||||
가상 환경이 활성화되었는지 확인합니다. (이전 명령어가 제대로 작동했는지 확인합니다).
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 단계는 **선택 사항**이지만, 모든 것이 예상대로 작동하고 있는지, 그리고 의도한 가상 환경이 활성화 되었는 지 **확인**하는 좋은 방법입니다.
|
||||
|
||||
///
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ which python
|
||||
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv/bin/python` 경로로 표시된다면 성공입니다. 🎉
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ Get-Command python
|
||||
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv\bin\python` 경로로 표시된다면 성공입니다. 🎉
|
||||
|
||||
////
|
||||
|
||||
## pip 업그레이드
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용한다면, `pip` 대신 `uv`로 패키지를 설치하게 되므로 `pip`을 업그레이드할 필요가 없습니다. 😎
|
||||
|
||||
///
|
||||
|
||||
`pip`을 사용하여 패키지를 설치하는 경우 (Python 표준 라이브러리에 포함되어 있습니다), **최신 버전으로 업그레이드**하는 것이 좋습니다.
|
||||
|
||||
패키지 설치 중 발생하는 다양하고 특이한 에러들은 `pip` 업그레이드로 쉽게 해결되는 경우가 많습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 작업은 보통 가상 환경을 생성한 **직후 한 번만** 하면 됩니다.
|
||||
|
||||
///
|
||||
|
||||
가상 환경이 활성화된 상태인지 확인한 후(앞서 설명한 명령어 사용), 아래 명령어를 실행하세요:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m pip install --upgrade pip
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## `.gitignore` 추가하기
|
||||
|
||||
**Git**을 사용하고 있다면 (사용하는 것이 좋습니다), `.gitignore` 파일을 추가해서 `.venv` 디렉터리 전체를 Git에서 제외하세요.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용해 가상 환경을 생성했다면, 이미 이 작업이 자동으로 처리되어 있으므로 이 단계는 건너뛰어도 됩니다. 😎
|
||||
|
||||
///
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 작업도 마찬가지로, 가상 환경을 생성한 **직후 한 번만** 하면 됩니다.
|
||||
|
||||
///
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ echo "*" > .venv/.gitignore
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// details | 명령어 상세 설명
|
||||
|
||||
* `echo "*"`: 터미널에 `*` 텍스트를 "출력"합니다 (다음 설명에서 조금 바뀝니다)
|
||||
* `>`: 왼쪽 명령어의 출력 내용을 터미널에 출력하지 않고, 오른쪽에 지정된 파일로 **기록(write)** 하라는 의미입니다.
|
||||
* `.gitignore`: 출력된 텍스트가 기록될 파일 이름입니다.
|
||||
|
||||
그리고 Git에서 `*`는 "모든 것"을 의미합니다. 따라서 `.venv` 디렉터리 안의 모든 것을 무시하게 됩니다.
|
||||
|
||||
이 명령어는 다음과 같은 내용을 가진 `.gitignore` 파일을 생성합니다:
|
||||
|
||||
|
||||
```gitignore
|
||||
*
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## 패키지 설치
|
||||
|
||||
가상 환경을 활성화한 후, 그 안에 필요한 패키지들을 설치할 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
프로젝트에서 필요한 패키지를 설치하거나 업그레이드할 때는 이 작업을 **한 번만** 하면 됩니다.
|
||||
|
||||
만약 특정 패키지의 버전을 업그레이드하거나, 새로운 패키지를 추가할 필요가 생기면 **다시 이 작업을 반복**하면 됩니다.
|
||||
|
||||
///
|
||||
|
||||
### 패키지 직접 설치
|
||||
|
||||
급하게 작업하거나, 프로젝트에 필요한 패키지 목록을 따로 파일로 관리하고 싶지 않은 경우, 패키지를 직접 설치할 수도 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
패키지 이름과 버전 정보를 파일에 정리해두는 것(예: `requirements.txt` 또는 `pyproject.toml`)은 (매우) 좋은 생각입니다.
|
||||
|
||||
///
|
||||
|
||||
//// tab | `pip`
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용하는 경우:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
### `requirements.txt`에서 설치
|
||||
|
||||
`requirements.txt` 파일이 있다면, 그 안에 명시된 패키지들을 한 번에 설치할 수 있습니다.
|
||||
|
||||
//// tab | `pip`
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uv`
|
||||
|
||||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용하는 경우:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uv pip install -r requirements.txt
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
/// details | `requirements.txt`
|
||||
|
||||
다음은 몇 가지 패키지를 포함한 `requirements.txt`의 예시입니다:
|
||||
|
||||
```requirements.txt
|
||||
fastapi[standard]==0.113.0
|
||||
pydantic==2.8.0
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## 프로그램 실행
|
||||
|
||||
가상 환경을 활성화한 후에는 프로그램을 실행할 수 있습니다. 이때 해당 가상 환경에 설치된 Python과 패키지들이 사용됩니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py
|
||||
|
||||
Hello World
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 에디터 설정
|
||||
|
||||
에디터를 사용할 경우, 앞서 만든 가상 환경을 사용하도록 설정하는 것이 좋습니다. (대부분의 에디터는 자동으로 감지하기도 합니다.)
|
||||
이렇게 하면 자동 완성 기능이나 코드 내 오류 표시 기능을 제대로 사용할 수 있습니다.
|
||||
|
||||
예시:
|
||||
|
||||
* <a href="https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment" class="external-link" target="_blank">VS Code</a>
|
||||
* <a href="https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html" class="external-link" target="_blank">PyCharm</a>
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
이 설정은 보통 가상 환경을 **처음 만들었을 때 한 번만** 해주면 됩니다.
|
||||
|
||||
///
|
||||
|
||||
## 가상 환경 비활성화
|
||||
|
||||
프로젝트 작업이 끝났다면, 가상 환경을 **비활성화**할 수 있습니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ deactivate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
이렇게 하면 이후에 `python` 명령어를 실행했을 때, 가상 환경의 Python이나 그 안에 설치된 패키지들을 사용하지 않게 됩니다.
|
||||
|
||||
## 이제 작업할 준비가 되었습니다
|
||||
|
||||
이제 프로젝트 작업을 시작할 준비가 완료되었습니다.
|
||||
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
위 내용을 더 깊이 이해하고 싶으신가요?
|
||||
|
||||
그렇다면 계속 읽어 주세요. 👇🤓
|
||||
|
||||
///
|
||||
|
||||
## 가상 환경을 왜 사용하는가
|
||||
|
||||
FastAPI를 사용하려면 먼저 <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>을 설치해야 합니다.
|
||||
|
||||
그 후에는 FastAPI와 함께 사용할 **기타 패키지들**을 **설치**해야 합니다.
|
||||
|
||||
패키지를 설치할 때 보통 Python에 기본 포함된 `pip` 명령어(또는 유사한 도구)를 사용합니다.
|
||||
|
||||
하지만 `pip`을 그냥 직접 사용하면, 해당 패키지들은 **전역 Python 환경**(시스템 전체에 설치된 Python)에 설치됩니다.
|
||||
|
||||
### 문제점
|
||||
|
||||
그렇다면, 전역 Python 환경에 패키지를 설치하면 어떤 문제가 발생할까요?
|
||||
|
||||
어느 시점이 되면, **서로 다른 패키지들**에 의존하는 여러 개의 프로그램을 작성하게 될 것입니다. 그리고 이들 중 일부는 **같은 패키지의 서로 다른 버전**을 필요로 할 수 있습니다. 😱
|
||||
|
||||
예를 들어, `마법사의 돌(philosophers-stone)` 프로젝트를 만들었다고 가정해봅시다. 이 프로그램은 `해리 포터(harry)`라는 패키지의 `v1` 버전을 **의존**합니다. 따라서 `harry`를 설치해야 합니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
stone(philosophers-stone) -->|requires| harry-1[harry v1]
|
||||
```
|
||||
|
||||
그런데 나중에 `아즈카반의 죄수(prisoner-of-azkaban)`이라는 또 다른 프로젝트를 만들게 되었고, 이 프로젝트도 역시 `harry` 패키지를 사용합니다. 그런데 이 프로젝트는 `harry`의 `v3` 버전이 필요합니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
|
||||
```
|
||||
|
||||
하지만 이제 문제가 생깁니다. 로컬 가상 환경 대신에 전역 환경에 패키지를 설치하게 되면, 어떤 버전의 `harry`를 설치할지를 선택해야 하기 때문입니다.
|
||||
|
||||
예를 들어, `마법사의 돌(philosophers-stone)`을 실행하고 싶다면 먼저 `harry` `v1` 버전을 다음과 같이 설치 해야 합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==1"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
그러면 결국 전역 Python 환경에는 `harry` `v1`버전이 설치된 상태가 됩니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[global env]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) -->|requires| harry-1
|
||||
end
|
||||
```
|
||||
|
||||
하지만 이제 `아즈카반의 죄수(prisoner-of-azkaban)`을 실행하고 싶다면, `harry` `v1`버전을 제거하고 `harry` `v3`버전을 설치해야 합니다. (또는 단순히 `v3`버전을 설치하는 것만으로도 기존의 `v1`버전이 자동으로 제거됩니다.)
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==3"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
그렇게 하면 이제 전역 Python 환경에는 `harry` `v3`버전이 설치된 상태가 됩니다.
|
||||
|
||||
그리고 다시 `마법사의 돌(philosophers-stone)`을 실행하려고 하면, **작동하지** 않을 수 있습니다. 왜냐하면 이 프로그램은 `harry` `v1`버전을 필요로 하기 때문입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[global env]
|
||||
harry-1[<strike>harry v1</strike>]
|
||||
style harry-1 fill:#ccc,stroke-dasharray: 5 5
|
||||
harry-3[harry v3]
|
||||
end
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) -.-x|⛔️| harry-1
|
||||
end
|
||||
subgraph azkaban-project[prisoner-of-azkaban project]
|
||||
azkaban(prisoner-of-azkaban) --> |requires| harry-3
|
||||
end
|
||||
```
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
Python 패키지들은 **새 버전**에서 **호환성 문제(breaking changes)**가 발생하지 않도록 최대한 노력하는 것이 일반적입니다. 하지만 그래도 안전하게 작업하려면, 테스트를 실행해보면서 새 버전을 의도적으로 설치하는 것이 좋습니다.
|
||||
|
||||
///
|
||||
|
||||
이제, 이런 일이 여러분의 **모든 프로젝트**가 사용하는 **수많은 패키지들**에서 동시에 발생한다고 상상해보세요. 이는 매우 관리하기 어려우며, 결국 **서로 호환되지 않는 버전**의 패키지로 프로젝트를 실행하게 될 가능성이 높고, 그로 인해 어떤 문제가 왜 발생하는지 알 수 없게 될 수 있습니다.
|
||||
|
||||
또한 사용하는 운영체제(Linux, Windows, macOS 등)에 따라 Python이 **미리 설치되어 있을 수도** 있습니다. 이런 경우에는 운영체제의 동작에 필요한 특정 버전의 패키지들이 함께 설치되어 있을 수 있습니다. 이 상태에서 전역 Python 환경에 임의의 패키지를 설치하면, 운영체제에 포함된 프로그램 일부가 **깨질 위험**도 있습니다.
|
||||
|
||||
## 패키지들은 어디에 설치되는가
|
||||
|
||||
Python을 설치하면, 컴퓨터에 여러 디렉터리와 파일들이 생성됩니다.
|
||||
|
||||
이 중 일부 디렉터리는 사용자가 설치한 패키지들을 보관하는 역할을 합니다.
|
||||
|
||||
예를 들어, 아래 명령어를 실행하면:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 지금 실행하지 않아도 됩니다, 그냥 예제일 뿐이에요 🤓
|
||||
$ pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
해당 명령어는 FastAPI 코드를 포함한 압축 파일을 다운로드합니다. 이 파일은 보통 <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>에서 받아옵니다.
|
||||
|
||||
또한 FastAPI가 의존하는 다른 패키지들도 함께 **다운로드**됩니다.
|
||||
|
||||
그리고 그 모든 파일들을 **압축 해제**한 뒤, 컴퓨터의 특정 디렉터리에 저장합니다.
|
||||
|
||||
기본적으로 이 파일들은 Python이 설치된 디렉터리 안, 즉 **전역 환경**에 내의 디렉터리에 저장됩니다.
|
||||
|
||||
## 가상 환경이란
|
||||
|
||||
전역 환경에 모든 패키지를 설치하면서 발생하는 문제에 대한 해결책은, 작업하는 **각 프로젝트마다 가상 환경**을 사용하는 것입니다.
|
||||
|
||||
가상 환경은 전역 환경과 매우 유사한 하나의 **디렉터리**이며, 그 안에 해당 프로젝트를 위한 패키지들을 설치할 수 있습니다.
|
||||
|
||||
이렇게 하면 각 프로젝트는 자체적인 가상 환경(`.venv` 디렉터리)을 가지게 되며, 그 안에 해당 프로젝트 전용 패키지들을 보유하게 됩니다.
|
||||
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) --->|requires| harry-1
|
||||
subgraph venv1[.venv]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
end
|
||||
subgraph azkaban-project[prisoner-of-azkaban project]
|
||||
azkaban(prisoner-of-azkaban) --->|requires| harry-3
|
||||
subgraph venv2[.venv]
|
||||
harry-3[harry v3]
|
||||
end
|
||||
end
|
||||
stone-project ~~~ azkaban-project
|
||||
```
|
||||
|
||||
## 가상 환경 활성화 의미
|
||||
|
||||
가상 환경을 활성화한다는 것은, 예를 들어 다음과 같은 명령어를 실행하는 것을 의미합니다:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/bin/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ .venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows Bash
|
||||
|
||||
Windows에서 Bash(예: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)를 사용하는 경우:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
이 명령어는 이후에 실행될 명령어에서 사용될 [환경 변수](environment-variables.md){.internal-link target=_blank} 몇 개를 생성하거나 수정합니다.
|
||||
|
||||
이 변수들 중 하나가 바로 `PATH` 변수입니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
`PATH` 환경 변수에 대해 더 알고 싶다면 [환경 변수 문서의 PATH 환경 변수 섹션](environment-variables.md#path-environment-variable){.internal-link target=_blank}을 참고하세요.
|
||||
|
||||
///
|
||||
|
||||
가상 환경을 활성화하면, 가상 환경의 경로인 `.venv/bin` (Linux와 macOS) 또는 `.venv\Scripts`(Windows)를 `PATH` 환경 변수에 추가됩니다.
|
||||
|
||||
예를 들어, 가상 환경을 활성화하기 전의 `PATH` 변수는 다음과 같았다고 가정해봅시다:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
시스템은 다음 경로들에서 프로그램을 찾게 됩니다:
|
||||
|
||||
* `/usr/bin`
|
||||
* `/bin`
|
||||
* `/usr/sbin`
|
||||
* `/sbin`
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Windows\System32
|
||||
```
|
||||
|
||||
시스템은 다음 경로들에서 프로그램을 찾게 됩니다:
|
||||
|
||||
* `C:\Windows\System32`
|
||||
|
||||
////
|
||||
|
||||
가상 환경을 활성화한 후에는, `PATH` 변수는 다음과 같은 형태가 됩니다:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다:
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin
|
||||
```
|
||||
|
||||
그 후에 다른 디렉터리들을 탐색합니다.
|
||||
|
||||
따라서 터미널에 `python`을 입력하면, 시스템은 다음 위치에 있는 Python 프로그램을 찾게 됩니다:
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
그리고 해당 Python을 사용하게 됩니다.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
|
||||
```
|
||||
|
||||
시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다:
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts
|
||||
```
|
||||
|
||||
그 후에 다른 디렉터리들을 탐색합니다.
|
||||
|
||||
따라서 터미널에 `python`을 입력하면, 시스템은 다음 경로에 있는 Python 프로그램을 찾게 됩니다:
|
||||
|
||||
```plaintext
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
그리고 해당 Python을 사용하게 됩니다.
|
||||
|
||||
////
|
||||
|
||||
중요한 세부 사항 중 하나는, 가상 환경의 경로가 `PATH` 변수의 가장 **앞**에 추가된다는 점입니다. 시스템은 사용 가능한 다른 Python들보다 **먼저** 이 경로를 찾습니다. 그래서 터미널에서 `python`을 실행하면, 전역 환경의 Python이 아닌 **가상 환경에 있는** Python이 사용됩니다. (예: 전역 환경에 설치된 `python`이 있더라도 그보다 우선합니다.)
|
||||
|
||||
가상 환경을 활성화하면 이 외에도 몇 가지 다른 것들이 변경되지만, 이는 그중에서도 가장 중요한 변화 중 하나입니다.
|
||||
|
||||
## 가상 환경 확인하기
|
||||
|
||||
가상 환경이 활성화 되었는지 확인하려면, 아래 명령어를 사용할 수 있습니다:
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ which python
|
||||
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ Get-Command python
|
||||
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
즉, 현재 사용되는 `python` 프로그램은 **가상 환경 내부에 있는 것**입니다.
|
||||
|
||||
Linux와 macOS에서는 `which`, Windows PowerShell에서는 `Get-Command` 명령어를 사용합니다.
|
||||
|
||||
이 명령어는 `PATH` 환경 변수에 지정된 경로들을 **순서대로 탐색**하면서 `python`이라는 이름의 프로그램을 찾습니다.
|
||||
찾는 즉시, 해당 프로그램의 **경로를 출력**합니다.
|
||||
|
||||
중요한 점은 터미널에서 `python`을 실행했을 때, 실제로 실행되는 "`python`"이 어떤 것인지 정확히 알 수 있다는 것입니다.
|
||||
|
||||
따라서 현재 올바른 가상 환경에 있는지 확인할 수 있습니다.
|
||||
|
||||
/// tip | 팁
|
||||
|
||||
하나의 가상 환경을 활성화한 뒤, 해당 Python을 가진 상태에서 **또 다른 프로젝트**로 이동하는 것은 흔히 발생합니다.
|
||||
|
||||
하지만 이때 이전 프로젝트의 가상 환경에 있는 **잘못된 Python 실행 파일**을 사용하게 되어 새 프로젝트가 **정상 작동하지 않을 수 있습니다.**
|
||||
|
||||
그래서 현재 어떤 `python`이 사용되고 있는지 확인할 수 있는 능력은 매우 유용합니다. 🤓
|
||||
|
||||
///
|
||||
|
||||
## 가상 환경을 비활성화하는 이유
|
||||
|
||||
예를 들어 `마법사의 돌(philosophers-stone)`이라는 프로젝트에서 작업 중이라고 해보겠습니다. 이때 해당 **가상 환경을 활성화**하고, 필요한 패키지를 설치하며 작업을 진행합니다.
|
||||
|
||||
그런데 이제는 **다른 프로젝트**인 `아즈카반의 죄수(prisoner-of-azkaban)`을 작업하고 싶어졌습니다.
|
||||
|
||||
그래서 그 프로젝트 디렉터리로 이동합니다:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
만약 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화하지 않았다면, 터미널에서 `python`을 실행할 때 여전히 `마법사의 돌(philosophers-stone)` 가상 환경의 Python을 사용하게 됩니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
$ python main.py
|
||||
|
||||
// sirius를 임포트하는 데 실패했습니다. 설치되어 있지 않아요 😱
|
||||
Traceback (most recent call last):
|
||||
File "main.py", line 1, in <module>
|
||||
import sirius
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
하지만 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화한 다음, `아즈카반의 죄수(prisoner-of-azkaban)` 프로젝트의 가상 환경을 활성화하면, 이제 `python` 명령어는 `아즈카반의 죄수(prisoner-of-azkaban)` 가상 환경의 Python을 사용하게 됩니다.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
|
||||
// 이전 디렉터리에 있을 필요 없이, 어디서든 가상 환경을 비활성화할 수 있습니다. 다른 프로젝트 디렉터리로 이동한 후에도 괜찮아요 😎
|
||||
$ deactivate
|
||||
|
||||
// prisoner-of-azkaban/.venv 가상 환경을 활성화합니다 🚀
|
||||
$ source .venv/bin/activate
|
||||
|
||||
// 이제 python을 실행하면, 이 가상 환경에 설치된 sirius 패키지를 찾게 됩니다 ✨
|
||||
$ python main.py
|
||||
|
||||
못된 짓을 꾸미고 있음을 엄숙히 맹세합니다.🧙
|
||||
ImportError는 이제 없습니다. 🐺
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 대안들
|
||||
|
||||
이 문서는 여러분이 Python 프로젝트를 시작하고, **그 내부에서** 어떻게 돌아가는지 알려주는 간단한 가이드입니다.
|
||||
|
||||
가상 환경, 패키지 의존성(Requirements), 프로젝트를 관리하는 방법에는 이 외에도 다양한 **대안**들이 존재합니다.
|
||||
|
||||
만약 준비가 되었다면, **프로젝트 전체**, 패키지 의존성, 가상 환경 등을 통합적으로 **관리**할 수 있는 도구를 써보는 것도 좋습니다. 그럴 때 추천하는 도구가 바로 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>입니다.
|
||||
|
||||
`uv`는 다양한 기능을 지원합니다:
|
||||
|
||||
* 다양한 버전의 **Python 설치**
|
||||
* 각 프로젝트 별 **가상 환경 관리**
|
||||
* **패키지 설치**
|
||||
* 프로젝트의 **의존성과 버전** 관리
|
||||
* 설치된 패키지들과 그 버전을 **정확히 고정(lock)**해서,개발 환경과 운영 환경이 완전히 동일하게 작동할 수 있도록 보장
|
||||
* 이 외에도 다양한 기능을 지원
|
||||
|
||||
## 결론
|
||||
|
||||
여기까지 모두 읽고 이해했다면, 이제 많은 개발자들보다 가상 환경을 **훨씬 더 깊이 있게 이해**하게 되셨습니다. 🤓
|
||||
|
||||
이런 세부적인 내용을 알고 있으면, 언젠가 복잡해 보이는 문제를 디버깅할 때 분명히 큰 도움이 될 것입니다. 이제는 **이 모든 것들이 내부에서 어떻게 작동하는지** 알고 있기 때문입니다. 😎
|
||||
@@ -0,0 +1 @@
|
||||
INHERIT: ../en/mkdocs.yml
|
||||
Reference in New Issue
Block a user