90 changed files with 5007 additions and 2295 deletions
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,179 @@ |
|||||
|
# 고급 의존성 |
||||
|
|
||||
|
## 매개변수화된 의존성 |
||||
|
|
||||
|
지금까지 본 모든 의존성은 고정된 함수 또는 클래스입니다. |
||||
|
|
||||
|
하지만 여러 개의 함수나 클래스를 선언하지 않고도 의존성에 매개변수를 설정해야 하는 경우가 있을 수 있습니다. |
||||
|
|
||||
|
예를 들어, `q` 쿼리 매개변수가 특정 고정된 내용을 포함하고 있는지 확인하는 의존성을 원한다고 가정해 봅시다. |
||||
|
|
||||
|
이때 해당 고정된 내용을 매개변수화할 수 있길 바랍니다. |
||||
|
|
||||
|
## "호출 가능한" 인스턴스 |
||||
|
|
||||
|
Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법이 있습니다. |
||||
|
|
||||
|
클래스 자체(이미 호출 가능함)가 아니라 해당 클래스의 인스턴스에 대해 호출 가능하게 하는 것입니다. |
||||
|
|
||||
|
이를 위해 `__call__` 메서드를 선언합니다: |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="12" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="11" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | 참고 |
||||
|
|
||||
|
가능하다면 `Annotated` 버전을 사용하는 것이 좋습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="10" |
||||
|
{!> ../../docs_src/dependencies/tutorial011.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
이 경우, **FastAPI**는 추가 매개변수와 하위 의존성을 확인하기 위해 `__call__`을 사용하게 되며, |
||||
|
나중에 *경로 연산 함수*에서 매개변수에 값을 전달할 때 이를 호출하게 됩니다. |
||||
|
|
||||
|
## 인스턴스 매개변수화하기 |
||||
|
|
||||
|
이제 `__init__`을 사용하여 의존성을 "매개변수화"할 수 있는 인스턴스의 매개변수를 선언할 수 있습니다: |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="9" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="8" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | 참고 |
||||
|
|
||||
|
가능하다면 `Annotated` 버전을 사용하는 것이 좋습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="7" |
||||
|
{!> ../../docs_src/dependencies/tutorial011.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
이 경우, **FastAPI**는 `__init__`에 전혀 관여하지 않으며, 우리는 이 메서드를 코드에서 직접 사용하게 됩니다. |
||||
|
|
||||
|
## 인스턴스 생성하기 |
||||
|
|
||||
|
다음과 같이 이 클래스의 인스턴스를 생성할 수 있습니다: |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="18" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="17" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | 참고 |
||||
|
|
||||
|
가능하다면 `Annotated` 버전을 사용하는 것이 좋습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="16" |
||||
|
{!> ../../docs_src/dependencies/tutorial011.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
이렇게 하면 `checker.fixed_content` 속성에 `"bar"`라는 값을 담아 의존성을 "매개변수화"할 수 있습니다. |
||||
|
|
||||
|
## 인스턴스를 의존성으로 사용하기 |
||||
|
|
||||
|
그런 다음, `Depends(FixedContentQueryChecker)` 대신 `Depends(checker)`에서 이 `checker` 인스턴스를 사용할 수 있으며, |
||||
|
클래스 자체가 아닌 인스턴스 `checker`가 의존성이 됩니다. |
||||
|
|
||||
|
의존성을 해결할 때 **FastAPI**는 이 `checker`를 다음과 같이 호출합니다: |
||||
|
|
||||
|
```Python |
||||
|
checker(q="somequery") |
||||
|
``` |
||||
|
|
||||
|
...그리고 이때 반환되는 값을 *경로 연산 함수*의 `fixed_content_included` 매개변수로 전달합니다: |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="22" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="21" |
||||
|
{!> ../../docs_src/dependencies/tutorial011_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | 참고 |
||||
|
|
||||
|
가능하다면 `Annotated` 버전을 사용하는 것이 좋습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="20" |
||||
|
{!> ../../docs_src/dependencies/tutorial011.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
/// tip | 참고 |
||||
|
|
||||
|
이 모든 과정이 복잡하게 느껴질 수 있습니다. 그리고 지금은 이 방법이 얼마나 유용한지 명확하지 않을 수도 있습니다. |
||||
|
|
||||
|
이 예시는 의도적으로 간단하게 만들었지만, 전체 구조가 어떻게 작동하는지 보여줍니다. |
||||
|
|
||||
|
보안 관련 장에서는 이와 같은 방식으로 구현된 편의 함수들이 있습니다. |
||||
|
|
||||
|
이 모든 과정을 이해했다면, 이러한 보안 도구들이 내부적으로 어떻게 작동하는지 이미 파악한 것입니다. |
||||
|
|
||||
|
/// |
@ -0,0 +1,33 @@ |
|||||
|
# 응답 - 상태 코드 변경 |
||||
|
|
||||
|
기본 [응답 상태 코드 설정](../tutorial/response-status-code.md){.internal-link target=_blank}이 가능하다는 걸 이미 알고 계실 겁니다. |
||||
|
|
||||
|
하지만 경우에 따라 기본 설정과 다른 상태 코드를 반환해야 할 때가 있습니다. |
||||
|
|
||||
|
## 사용 예 |
||||
|
|
||||
|
예를 들어 기본적으로 HTTP 상태 코드 "OK" `200`을 반환하고 싶다고 가정해 봅시다. |
||||
|
|
||||
|
하지만 데이터가 존재하지 않으면 이를 새로 생성하고, HTTP 상태 코드 "CREATED" `201`을 반환하고자 할 때가 있을 수 있습니다. |
||||
|
|
||||
|
이때도 여전히 `response_model`을 사용하여 반환하는 데이터를 필터링하고 변환하고 싶을 수 있습니다. |
||||
|
|
||||
|
이런 경우에는 `Response` 파라미터를 사용할 수 있습니다. |
||||
|
|
||||
|
## `Response` 파라미터 사용하기 |
||||
|
|
||||
|
*경로 작동 함수*에 `Response` 타입의 파라미터를 선언할 수 있습니다. (쿠키와 헤더에 대해 선언하는 것과 유사하게) |
||||
|
|
||||
|
그리고 이 *임시* 응답 객체에서 `status_code`를 설정할 수 있습니다. |
||||
|
|
||||
|
```Python hl_lines="1 9 12" |
||||
|
{!../../docs_src/response_change_status_code/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
그리고 평소처럼 원하는 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다. |
||||
|
|
||||
|
`response_model`을 선언했다면 반환된 객체는 여전히 필터링되고 변환됩니다. |
||||
|
|
||||
|
**FastAPI**는 이 *임시* 응답 객체에서 상태 코드(쿠키와 헤더 포함)를 추출하여, `response_model`로 필터링된 반환 값을 최종 응답에 넣습니다. |
||||
|
|
||||
|
또한, 의존성에서도 `Response` 파라미터를 선언하고 그 안에서 상태 코드를 설정할 수 있습니다. 단, 마지막으로 설정된 상태 코드가 우선 적용된다는 점을 유의하세요. |
@ -0,0 +1,53 @@ |
|||||
|
# 응답 쿠키 |
||||
|
|
||||
|
## `Response` 매개변수 사용하기 |
||||
|
|
||||
|
*경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다. |
||||
|
|
||||
|
그런 다음 해당 *임시* 응답 객체에서 쿠키를 설정할 수 있습니다. |
||||
|
|
||||
|
```Python hl_lines="1 8-9" |
||||
|
{!../../docs_src/response_cookies/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
그런 다음 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다. |
||||
|
|
||||
|
그리고 `response_model`을 선언했다면 반환한 객체를 거르고 변환하는 데 여전히 사용됩니다. |
||||
|
|
||||
|
**FastAPI**는 그 *임시* 응답에서 쿠키(또한 헤더 및 상태 코드)를 추출하고, 반환된 값이 포함된 최종 응답에 이를 넣습니다. 이 값은 `response_model`로 걸러지게 됩니다. |
||||
|
|
||||
|
또한 의존관계에서 `Response` 매개변수를 선언하고, 해당 의존성에서 쿠키(및 헤더)를 설정할 수도 있습니다. |
||||
|
|
||||
|
## `Response`를 직접 반환하기 |
||||
|
|
||||
|
코드에서 `Response`를 직접 반환할 때도 쿠키를 생성할 수 있습니다. |
||||
|
|
||||
|
이를 위해 [Response를 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성할 수 있습니다. |
||||
|
|
||||
|
그런 다음 쿠키를 설정하고 반환하면 됩니다: |
||||
|
```Python hl_lines="1 18" |
||||
|
{!../../docs_src/response_directly/tutorial002.py!} |
||||
|
``` |
||||
|
/// 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.io/responses/#set-cookie" class="external-link" target="_blank">Starlette 문서</a>에서 확인할 수 있습니다. |
@ -0,0 +1,67 @@ |
|||||
|
# 응답을 직접 반환하기 |
||||
|
|
||||
|
**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`를 사용하여 변환할 수 있습니다: |
||||
|
|
||||
|
```Python hl_lines="6-7 21-22" |
||||
|
{!../../docs_src/response_directly/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
/// 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`에 넣어 반환할 수 있습니다: |
||||
|
|
||||
|
```Python hl_lines="1 18" |
||||
|
{!../../docs_src/response_directly/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
## 참고 사항 |
||||
|
`Response`를 직접 반환할 때, 그 데이터는 자동으로 유효성 검사되거나, 변환(직렬화)되거나, 문서화되지 않습니다. |
||||
|
|
||||
|
그러나 [OpenAPI에서 추가 응답](additional-responses.md){.internal-link target=_blank}에서 설명된 대로 문서화할 수 있습니다. |
||||
|
|
||||
|
이후 단락에서 자동 데이터 변환, 문서화 등을 사용하면서 사용자 정의 `Response`를 선언하는 방법을 확인할 수 있습니다. |
@ -0,0 +1,45 @@ |
|||||
|
# 응답 헤더 |
||||
|
|
||||
|
## `Response` 매개변수 사용하기 |
||||
|
|
||||
|
여러분은 *경로 작동 함수*에서 `Response` 타입의 매개변수를 선언할 수 있습니다 (쿠키와 같이 사용할 수 있습니다). |
||||
|
|
||||
|
그런 다음, 여러분은 해당 *임시* 응답 객체에서 헤더를 설정할 수 있습니다. |
||||
|
|
||||
|
```Python hl_lines="1 7-8" |
||||
|
{!../../docs_src/response_headers/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
그 후, 일반적으로 사용하듯이 필요한 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다. |
||||
|
|
||||
|
`response_model`을 선언한 경우, 반환한 객체를 필터링하고 변환하는 데 여전히 사용됩니다. |
||||
|
|
||||
|
**FastAPI**는 해당 *임시* 응답에서 헤더(쿠키와 상태 코드도 포함)를 추출하여, 여러분이 반환한 값을 포함하는 최종 응답에 `response_model`로 필터링된 값을 넣습니다. |
||||
|
|
||||
|
또한, 종속성에서 `Response` 매개변수를 선언하고 그 안에서 헤더(및 쿠키)를 설정할 수 있습니다. |
||||
|
|
||||
|
## `Response` 직접 반환하기 |
||||
|
|
||||
|
`Response`를 직접 반환할 때에도 헤더를 추가할 수 있습니다. |
||||
|
|
||||
|
[응답을 직접 반환하기](response-directly.md){.internal-link target=_blank}에서 설명한 대로 응답을 생성하고, 헤더를 추가 매개변수로 전달하세요. |
||||
|
|
||||
|
```Python hl_lines="10-12" |
||||
|
{!../../docs_src/response_headers/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
/// 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.io/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette의 CORS 설명서</a>에 문서화된 대로 설정할 수 있습니다. |
@ -0,0 +1,7 @@ |
|||||
|
# 이벤트 테스트: 시작 - 종료 |
||||
|
|
||||
|
테스트에서 이벤트 핸들러(`startup` 및 `shutdown`)를 실행해야 하는 경우, `with` 문과 함께 `TestClient`를 사용할 수 있습니다. |
||||
|
|
||||
|
```Python hl_lines="9-12 20-24" |
||||
|
{!../../docs_src/app_testing/tutorial003.py!} |
||||
|
``` |
@ -0,0 +1,58 @@ |
|||||
|
# `Request` 직접 사용하기 |
||||
|
|
||||
|
지금까지 요청에서 필요한 부분을 각 타입으로 선언하여 사용해 왔습니다. |
||||
|
|
||||
|
다음과 같은 곳에서 데이터를 가져왔습니다: |
||||
|
|
||||
|
* 경로의 파라미터로부터. |
||||
|
* 헤더. |
||||
|
* 쿠키. |
||||
|
* 기타 등등. |
||||
|
|
||||
|
이렇게 함으로써, **FastAPI**는 데이터를 검증하고 변환하며, API에 대한 문서를 자동화로 생성합니다. |
||||
|
|
||||
|
하지만 `Request` 객체에 직접 접근해야 하는 상황이 있을 수 있습니다. |
||||
|
|
||||
|
## `Request` 객체에 대한 세부 사항 |
||||
|
|
||||
|
**FastAPI**는 실제로 내부에 **Starlette**을 사용하며, 그 위에 여러 도구를 덧붙인 구조입니다. 따라서 여러분이 필요할 때 Starlette의 <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">`Request`</a> 객체를 직접 사용할 수 있습니다. |
||||
|
|
||||
|
`Request` 객체에서 데이터를 직접 가져오는 경우(예: 본문을 읽기)에는 FastAPI가 해당 데이터를 검증하거나 변환하지 않으며, 문서화(OpenAPI를 통한 문서 자동화(로 생성된) API 사용자 인터페이스)도 되지 않습니다. |
||||
|
|
||||
|
그러나 다른 매개변수(예: Pydantic 모델을 사용한 본문)는 여전히 검증, 변환, 주석 추가 등이 이루어집니다. |
||||
|
|
||||
|
하지만 특정한 경우에는 `Request` 객체에 직접 접근하는 것이 유용할 수 있습니다. |
||||
|
|
||||
|
## `Request` 객체를 직접 사용하기 |
||||
|
|
||||
|
여러분이 클라이언트의 IP 주소/호스트 정보를 *경로 작동 함수* 내부에서 가져와야 한다고 가정해 보겠습니다. |
||||
|
|
||||
|
이를 위해서는 요청에 직접 접근해야 합니다. |
||||
|
|
||||
|
```Python hl_lines="1 7-8" |
||||
|
{!../../docs_src/using_request_directly/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
*경로 작동 함수* 매개변수를 `Request` 타입으로 선언하면 **FastAPI**가 해당 매개변수에 `Request` 객체를 전달하는 것을 알게 됩니다. |
||||
|
|
||||
|
/// tip | 팁 |
||||
|
|
||||
|
이 경우, 요청 매개변수와 함께 경로 매개변수를 선언한 것을 볼 수 있습니다. |
||||
|
|
||||
|
따라서, 경로 매개변수는 추출되고 검증되며 지정된 타입으로 변환되고 OpenAPI로 주석이 추가됩니다. |
||||
|
|
||||
|
이와 같은 방식으로, 다른 매개변수들을 평소처럼 선언하면서, 부가적으로 `Request`도 가져올 수 있습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## `Request` 설명서 |
||||
|
|
||||
|
여러분은 `Request` 객체에 대한 더 자세한 내용을 <a href="https://www.starlette.io/requests/" class="external-link" target="_blank">공식 Starlette 설명서 사이트</a>에서 읽어볼 수 있습니다. |
||||
|
|
||||
|
/// note | 기술 세부사항 |
||||
|
|
||||
|
`from starlette.requests import Request`를 사용할 수도 있습니다. |
||||
|
|
||||
|
**FastAPI**는 여러분(개발자)를 위한 편의를 위해 이를 직접 제공하지만, 실제로는 Starlette에서 가져온 것입니다. |
||||
|
|
||||
|
/// |
@ -0,0 +1,37 @@ |
|||||
|
# 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) 응용 프로그램을 미들웨어로 포장합니다. |
||||
|
|
||||
|
그 후, 해당 경로에 마운트합니다. |
||||
|
|
||||
|
```Python hl_lines="2-3 23" |
||||
|
{!../../docs_src/wsgi/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
## 확인하기 |
||||
|
|
||||
|
이제 `/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,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,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.org" 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,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.io/)에도 기여했습니다. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 개발 |
||||
|
|
||||
|
**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,19 @@ |
|||||
|
# 고급 보안 |
||||
|
|
||||
|
## 추가 기능 |
||||
|
|
||||
|
[자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서에서 다룬 내용 외에도 보안 처리를 위한 몇 가지 추가 기능이 있습니다. |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
다음 섹션은 **반드시 "고급"** 기능은 아닙니다. |
||||
|
|
||||
|
그리고 여러분의 사용 사례에 따라, 적합한 해결책이 그 중 하나에 있을 가능성이 있습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## 먼저 자습서 읽기 |
||||
|
|
||||
|
다음 섹션은 이미 [자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서를 읽었다고 가정합니다. |
||||
|
|
||||
|
이 섹션들은 모두 동일한 개념을 바탕으로 하며, 추가 기능을 제공합니다. |
@ -0,0 +1,131 @@ |
|||||
|
|
||||
|
# 메타데이터 및 문서화 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> | |
||||
|
|
||||
|
다음과 같이 설정할 수 있습니다: |
||||
|
|
||||
|
```Python hl_lines="3-16 19-32" |
||||
|
{!../../docs_src/metadata/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
`description` 필드에 마크다운을 사용할 수 있으며, 출력에서 렌더링됩니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
이 구성을 사용하면 문서 자동화(로 생성된) API 문서는 다음과 같이 보입니다: |
||||
|
|
||||
|
<img src="/img/tutorial/metadata/image01.png"> |
||||
|
|
||||
|
## 라이선스 식별자 |
||||
|
|
||||
|
OpenAPI 3.1.0 및 FastAPI 0.99.0부터 `license_info`에 `identifier`를 URL 대신 설정할 수 있습니다. |
||||
|
|
||||
|
예: |
||||
|
|
||||
|
```Python hl_lines="31" |
||||
|
{!../../docs_src/metadata/tutorial001_1.py!} |
||||
|
``` |
||||
|
|
||||
|
## 태그에 대한 메타데이터 |
||||
|
|
||||
|
`openapi_tags` 매개변수를 사용하여 경로 작동을 그룹화하는 데 사용되는 태그에 추가 메타데이터를 추가할 수 있습니다. |
||||
|
|
||||
|
리스트는 각 태그에 대해 하나의 딕셔너리를 포함해야 합니다. |
||||
|
|
||||
|
각 딕셔너리에는 다음이 포함될 수 있습니다: |
||||
|
|
||||
|
* `name` (**필수**): `tags` 매개변수에서 *경로 작동*과 `APIRouter`에 사용된 태그 이름과 동일한 `str`입니다. |
||||
|
* `description`: 태그에 대한 간단한 설명을 담은 `str`입니다. 마크다운을 사용할 수 있으며 문서 UI에 표시됩니다. |
||||
|
* `externalDocs`: 외부 문서를 설명하는 `dict`이며: |
||||
|
* `description`: 외부 문서에 대한 간단한 설명을 담은 `str`입니다. |
||||
|
* `url` (**필수**): 외부 문서의 URL을 담은 `str`입니다. |
||||
|
|
||||
|
### 태그에 대한 메타데이터 생성 |
||||
|
|
||||
|
`users` 및 `items`에 대한 태그 예시와 함께 메타데이터를 생성하고 이를 `openapi_tags` 매개변수로 전달해 보겠습니다: |
||||
|
|
||||
|
```Python hl_lines="3-16 18" |
||||
|
{!../../docs_src/metadata/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
설명 안에 마크다운을 사용할 수 있습니다. 예를 들어 "login"은 굵게(**login**) 표시되고, "fancy"는 기울임꼴(_fancy_)로 표시됩니다. |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
사용 중인 모든 태그에 메타데이터를 추가할 필요는 없습니다. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### 태그 사용 |
||||
|
|
||||
|
`tags` 매개변수를 *경로 작동* 및 `APIRouter`와 함께 사용하여 태그에 할당할 수 있습니다: |
||||
|
|
||||
|
```Python hl_lines="21 26" |
||||
|
{!../../docs_src/metadata/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
/// 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`에 제공하도록 설정하려면: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../docs_src/metadata/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
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을 비활성화하려면: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../docs_src/metadata/tutorial003.py!} |
||||
|
``` |
@ -0,0 +1,344 @@ |
|||||
|
# Resposta Personalizada - HTML, Stream, File e outras |
||||
|
|
||||
|
Por padrão, o **FastAPI** irá retornar respostas utilizando `JSONResponse`. |
||||
|
|
||||
|
Mas você pode sobrescrever esse comportamento utilizando `Response` diretamente, como visto em [Retornando uma Resposta Diretamente](response-directly.md){.internal-link target=_blank}. |
||||
|
|
||||
|
Mas se você retornar uma `Response` diretamente (ou qualquer subclasse, como `JSONResponse`), os dados não serão convertidos automaticamente (mesmo que você declare um `response_model`), e a documentação não será gerada automaticamente (por exemplo, incluindo o "media type", no cabeçalho HTTP `Content-Type` como parte do esquema OpenAPI gerado). |
||||
|
|
||||
|
Mas você também pode declarar a `Response` que você deseja utilizar (e.g. qualquer subclasse de `Response`), em um *decorador de operação de rota* utilizando o parâmetro `response_class`. |
||||
|
|
||||
|
Os conteúdos que você retorna em sua *função de operador de rota* serão colocados dentro dessa `Response`. |
||||
|
|
||||
|
E se a `Response` tiver um media type JSON (`application/json`), como é o caso com `JSONResponse` e `UJSONResponse`, os dados que você retornar serão automaticamente convertidos (e filtrados) com qualquer `response_model` do Pydantic que for declarado em sua *função de operador de rota*. |
||||
|
|
||||
|
/// note | Nota |
||||
|
|
||||
|
Se você utilizar uma classe de Resposta sem media type, o FastAPI esperará que sua resposta não tenha conteúdo, então ele não irá documentar o formato da resposta na documentação OpenAPI gerada. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Utilizando `ORJSONResponse` |
||||
|
|
||||
|
Por exemplo, se você precisa bastante de performance, você pode instalar e utilizar o <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> e definir a resposta para ser uma `ORJSONResponse`. |
||||
|
|
||||
|
Importe a classe, ou subclasse, de `Response` que você deseja utilizar e declare ela no *decorador de operação de rota*. |
||||
|
|
||||
|
Para respostas grandes, retornar uma `Response` diretamente é muito mais rápido que retornar um dicionário. |
||||
|
|
||||
|
Isso ocorre por que, por padrão, o FastAPI irá verificar cada item dentro do dicionário e garantir que ele seja serializável para JSON, utilizando o mesmo[Codificador Compatível com JSON](../tutorial/encoder.md){.internal-link target=_blank} explicado no tutorial. Isso permite que você retorne **objetos abstratos**, como modelos do banco de dados, por exemplo. |
||||
|
|
||||
|
Mas se você tem certeza que o conteúdo que você está retornando é **serializável com JSON**, você pode passá-lo diretamente para a classe de resposta e evitar o trabalho extra que o FastAPI teria ao passar o conteúdo pelo `jsonable_encoder` antes de passar para a classe de resposta. |
||||
|
|
||||
|
```Python hl_lines="2 7" |
||||
|
{!../../docs_src/custom_response/tutorial001b.py!} |
||||
|
``` |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
O parâmetro `response_class` também será usado para definir o "media type" da resposta. |
||||
|
|
||||
|
Neste caso, o cabeçalho HTTP `Content-Type` irá ser definido como `application/json`. |
||||
|
|
||||
|
E será documentado como tal no OpenAPI. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
A `ORJSONResponse` está disponível apenas no FastAPI, e não no Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Resposta HTML |
||||
|
|
||||
|
Para retornar uma resposta com HTML diretamente do **FastAPI**, utilize `HTMLResponse`. |
||||
|
|
||||
|
* Importe `HTMLResponse` |
||||
|
* Passe `HTMLResponse` como o parâmetro de `response_class` do seu *decorador de operação de rota*. |
||||
|
|
||||
|
```Python hl_lines="2 7" |
||||
|
{!../../docs_src/custom_response/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
O parâmetro `response_class` também será usado para definir o "media type" da resposta. |
||||
|
|
||||
|
Neste caso, o cabeçalho HTTP `Content-Type` será definido como `text/html`. |
||||
|
|
||||
|
E será documentado como tal no OpenAPI. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Retornando uma `Response` |
||||
|
|
||||
|
Como visto em [Retornando uma Resposta Diretamente](response-directly.md){.internal-link target=_blank}, você também pode sobrescrever a resposta diretamente na sua *operação de rota*, ao retornar ela. |
||||
|
|
||||
|
O mesmo exemplo de antes, retornando uma `HTMLResponse`, poderia parecer com: |
||||
|
|
||||
|
```Python hl_lines="2 7 19" |
||||
|
{!../../docs_src/custom_response/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
/// warning | Aviso |
||||
|
|
||||
|
Uma `Response` retornada diretamente em sua *função de operação de rota* não será documentada no OpenAPI (por exemplo, o `Content-Type` não será documentado) e não será visível na documentação interativa automática. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Obviamente, o cabeçalho `Content-Type`, o código de status, etc, virão do objeto `Response` que você retornou. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Documentar no OpenAPI e sobrescrever `Response` |
||||
|
|
||||
|
Se você deseja sobrescrever a resposta dentro de uma função, mas ao mesmo tempo documentar o "media type" no OpenAPI, você pode utilizar o parâmetro `response_class` E retornar um objeto `Response`. |
||||
|
|
||||
|
A `response_class` será usada apenas para documentar o OpenAPI da *operação de rota*, mas sua `Response` será usada como foi definida. |
||||
|
|
||||
|
##### Retornando uma `HTMLResponse` diretamente |
||||
|
|
||||
|
Por exemplo, poderia ser algo como: |
||||
|
|
||||
|
```Python hl_lines="7 21 23" |
||||
|
{!../../docs_src/custom_response/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
Neste exemplo, a função `generate_html_response()` já cria e retorna uma `Response` em vez de retornar o HTML em uma `str`. |
||||
|
|
||||
|
Ao retornar o resultado chamando `generate_html_response()`, você já está retornando uma `Response` que irá sobrescrever o comportamento padrão do **FastAPI**. |
||||
|
|
||||
|
Mas se você passasse uma `HTMLResponse` em `response_class` também, o **FastAPI** saberia como documentar isso no OpenAPI e na documentação interativa como um HTML com `text/html`: |
||||
|
|
||||
|
<img src="/img/tutorial/custom-response/image01.png"> |
||||
|
|
||||
|
## Respostas disponíveis |
||||
|
|
||||
|
Aqui estão algumas dos tipos de resposta disponíveis. |
||||
|
|
||||
|
Lembre-se que você pode utilizar `Response` para retornar qualquer outra coisa, ou até mesmo criar uma subclasse personalizada. |
||||
|
|
||||
|
/// note | Detalhes Técnicos |
||||
|
|
||||
|
Você também pode utilizar `from starlette.responses import HTMLResponse`. |
||||
|
|
||||
|
O **FastAPI** provê a mesma `starlette.responses` como `fastapi.responses` apenas como uma facilidade para você, desenvolvedor. Mas a maioria das respostas disponíveis vêm diretamente do Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### `Response` |
||||
|
|
||||
|
A classe principal de respostas, todas as outras respostas herdam dela. |
||||
|
|
||||
|
Você pode retorná-la diretamente. |
||||
|
|
||||
|
Ela aceita os seguintes parâmetros: |
||||
|
|
||||
|
* `content` - Uma sequência de caracteres (`str`) ou `bytes`. |
||||
|
* `status_code` - Um código de status HTTP do tipo `int`. |
||||
|
* `headers` - Um dicionário `dict` de strings. |
||||
|
* `media_type` - Uma `str` informando o media type. E.g. `"text/html"`. |
||||
|
|
||||
|
O FastAPI (Starlette, na verdade) irá incluir o cabeçalho Content-Length automaticamente. Ele também irá incluir o cabeçalho Content-Type, baseado no `media_type` e acrescentando uma codificação para tipos textuais. |
||||
|
|
||||
|
```Python hl_lines="1 18" |
||||
|
{!../../docs_src/response_directly/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
### `HTMLResponse` |
||||
|
|
||||
|
Usa algum texto ou sequência de bytes e retorna uma resposta HTML. Como você leu acima. |
||||
|
|
||||
|
### `PlainTextResponse` |
||||
|
|
||||
|
Usa algum texto ou sequência de bytes para retornar uma resposta de texto não formatado. |
||||
|
|
||||
|
```Python hl_lines="2 7 9" |
||||
|
{!../../docs_src/custom_response/tutorial005.py!} |
||||
|
``` |
||||
|
|
||||
|
### `JSONResponse` |
||||
|
|
||||
|
Pega alguns dados e retorna uma resposta com codificação `application/json`. |
||||
|
|
||||
|
É a resposta padrão utilizada no **FastAPI**, como você leu acima. |
||||
|
|
||||
|
### `ORJSONResponse` |
||||
|
|
||||
|
Uma alternativa mais rápida de resposta JSON utilizando o <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, como você leu acima. |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Essa resposta requer a instalação do pacote `orjson`, com o comando `pip install orjson`, por exemplo. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### `UJSONResponse` |
||||
|
|
||||
|
Uma alternativa de resposta JSON utilizando a biblioteca <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>. |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Essa resposta requer a instalação do pacote `ujson`, com o comando `pip install ujson`, por exemplo. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// warning | Aviso |
||||
|
|
||||
|
`ujson` é menos cauteloso que a implementação nativa do Python na forma que os casos especiais são tratados |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="2 7" |
||||
|
{!../../docs_src/custom_response/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
É possível que `ORJSONResponse` seja uma alternativa mais rápida. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### `RedirectResponse` |
||||
|
|
||||
|
Retorna um redirecionamento HTTP. Utiliza o código de status 307 (Redirecionamento Temporário) por padrão. |
||||
|
|
||||
|
Você pode retornar uma `RedirectResponse` diretamente: |
||||
|
|
||||
|
```Python hl_lines="2 9" |
||||
|
{!../../docs_src/custom_response/tutorial006.py!} |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Ou você pode utilizá-la no parâmetro `response_class`: |
||||
|
|
||||
|
```Python hl_lines="2 7 9" |
||||
|
{!../../docs_src/custom_response/tutorial006b.py!} |
||||
|
``` |
||||
|
|
||||
|
Se você fizer isso, então você pode retornar a URL diretamente da sua *função de operação de rota* |
||||
|
|
||||
|
Neste caso, o `status_code` utilizada será o padrão de `RedirectResponse`, que é `307`. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Você também pode utilizar o parâmetro `status_code` combinado com o parâmetro `response_class`: |
||||
|
|
||||
|
```Python hl_lines="2 7 9" |
||||
|
{!../../docs_src/custom_response/tutorial006c.py!} |
||||
|
``` |
||||
|
|
||||
|
### `StreamingResponse` |
||||
|
|
||||
|
Recebe uma gerador assíncrono ou um gerador/iterador comum e retorna o corpo da requisição continuamente (stream). |
||||
|
|
||||
|
```Python hl_lines="2 14" |
||||
|
{!../../docs_src/custom_response/tutorial007.py!} |
||||
|
``` |
||||
|
|
||||
|
#### Utilizando `StreamingResponse` com objetos semelhantes a arquivos |
||||
|
|
||||
|
Se você tiver um objeto semelhante a um arquivo (e.g. o objeto retornado por `open()`), você pode criar uma função geradora para iterar sobre esse objeto. |
||||
|
|
||||
|
Dessa forma, você não precisa ler todo o arquivo na memória primeiro, e você pode passar essa função geradora para `StreamingResponse` e retorná-la. |
||||
|
|
||||
|
Isso inclui muitas bibliotecas que interagem com armazenamento em nuvem, processamento de vídeos, entre outras. |
||||
|
|
||||
|
```{ .python .annotate hl_lines="2 10-12 14" } |
||||
|
{!../../docs_src/custom_response/tutorial008.py!} |
||||
|
``` |
||||
|
|
||||
|
1. Essa é a função geradora. É definida como "função geradora" porque contém declarações `yield` nela. |
||||
|
2. Ao utilizar o bloco `with`, nós garantimos que o objeto semelhante a um arquivo é fechado após a função geradora ser finalizada. Isto é, após a resposta terminar de ser enivada. |
||||
|
3. Essa declaração `yield from` informa a função para iterar sobre essa coisa nomeada de `file_like`. E então, para cada parte iterada, fornece essa parte como se viesse dessa função geradora (`iterfile`). |
||||
|
|
||||
|
Então, é uma função geradora que transfere o trabalho de "geração" para alguma outra coisa interna. |
||||
|
|
||||
|
Fazendo dessa forma, podemos colocá-la em um bloco `with`, e assim garantir que o objeto semelhante a um arquivo é fechado quando a função termina. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Perceba que aqui estamos utilizando o `open()` da biblioteca padrão que não suporta `async` e `await`, e declaramos a operação de rota com o `def` básico. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### `FileResponse` |
||||
|
|
||||
|
Envia um arquivo de forma assíncrona e contínua (stream). |
||||
|
* |
||||
|
Recebe um conjunto de argumentos do construtor diferente dos outros tipos de resposta: |
||||
|
|
||||
|
* `path` - O caminho do arquivo que será transmitido |
||||
|
* `headers` - quaisquer cabeçalhos que serão incluídos, como um dicionário. |
||||
|
* `media_type` - Uma string com o media type. Se não for definida, o media type é inferido a partir do nome ou caminho do arquivo. |
||||
|
* `filename` - Se for definido, é incluído no cabeçalho `Content-Disposition`. |
||||
|
|
||||
|
Respostas de Arquivos incluem o tamanho do arquivo, data da última modificação e ETags apropriados, nos cabeçalhos `Content-Length`, `Last-Modified` e `ETag`, respectivamente. |
||||
|
|
||||
|
```Python hl_lines="2 10" |
||||
|
{!../../docs_src/custom_response/tutorial009.py!} |
||||
|
``` |
||||
|
|
||||
|
Você também pode usar o parâmetro `response_class`: |
||||
|
|
||||
|
```Python hl_lines="2 8 10" |
||||
|
{!../../docs_src/custom_response/tutorial009b.py!} |
||||
|
``` |
||||
|
|
||||
|
Nesse caso, você pode retornar o caminho do arquivo diretamente da sua *função de operação de rota*. |
||||
|
|
||||
|
## Classe de resposta personalizada |
||||
|
|
||||
|
Você pode criar sua própria classe de resposta, herdando de `Response` e usando essa nova classe. |
||||
|
|
||||
|
Por exemplo, vamos supor que você queira utilizar o <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, mas com algumas configurações personalizadas que não estão incluídas na classe `ORJSONResponse`. |
||||
|
|
||||
|
Vamos supor também que você queira retornar um JSON indentado e formatado, então você quer utilizar a opção `orjson.OPT_INDENT_2` do orjson. |
||||
|
|
||||
|
Você poderia criar uma classe `CustomORJSONResponse`. A principal coisa a ser feita é sobrecarregar o método render da classe Response, `Response.render(content)`, que retorna o conteúdo em bytes, para retornar o conteúdo que você deseja: |
||||
|
|
||||
|
```Python hl_lines="9-14 17" |
||||
|
{!../../docs_src/custom_response/tutorial009c.py!} |
||||
|
``` |
||||
|
|
||||
|
Agora em vez de retornar: |
||||
|
|
||||
|
```json |
||||
|
{"message": "Hello World"} |
||||
|
``` |
||||
|
|
||||
|
...essa resposta retornará: |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"message": "Hello World" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Obviamente, você provavelmente vai encontrar maneiras muito melhores de se aproveitar disso do que a formatação de JSON. 😉 |
||||
|
|
||||
|
## Classe de resposta padrão |
||||
|
|
||||
|
Quando você criar uma instância da classe **FastAPI** ou um `APIRouter` você pode especificar qual classe de resposta utilizar por padrão. |
||||
|
|
||||
|
O padrão que define isso é o `default_response_class`. |
||||
|
|
||||
|
No exemplo abaixo, o **FastAPI** irá utilizar `ORJSONResponse` por padrão, em todas as *operações de rota*, em vez de `JSONResponse`. |
||||
|
|
||||
|
```Python hl_lines="2 4" |
||||
|
{!../../docs_src/custom_response/tutorial010.py!} |
||||
|
``` |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Você ainda pode substituir `response_class` em *operações de rota* como antes. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Documentação adicional |
||||
|
|
||||
|
Você também pode declarar o media type e muitos outros detalhes no OpenAPI utilizando `responses`: [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}. |
@ -0,0 +1,96 @@ |
|||||
|
# Middleware Avançado |
||||
|
|
||||
|
No tutorial principal você leu como adicionar [Middleware Personalizado](../tutorial/middleware.md){.internal-link target=_blank} à sua aplicação. |
||||
|
|
||||
|
E então você também leu como lidar com [CORS com o `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. |
||||
|
|
||||
|
Nesta seção, veremos como usar outros middlewares. |
||||
|
|
||||
|
## Adicionando middlewares ASGI |
||||
|
|
||||
|
Como o **FastAPI** é baseado no Starlette e implementa a especificação <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr>, você pode usar qualquer middleware ASGI. |
||||
|
|
||||
|
O middleware não precisa ser feito para o FastAPI ou Starlette para funcionar, desde que siga a especificação ASGI. |
||||
|
|
||||
|
No geral, os middlewares ASGI são classes que esperam receber um aplicativo ASGI como o primeiro argumento. |
||||
|
|
||||
|
Então, na documentação de middlewares ASGI de terceiros, eles provavelmente dirão para você fazer algo como: |
||||
|
|
||||
|
```Python |
||||
|
from unicorn import UnicornMiddleware |
||||
|
|
||||
|
app = SomeASGIApp() |
||||
|
|
||||
|
new_app = UnicornMiddleware(app, some_config="rainbow") |
||||
|
``` |
||||
|
|
||||
|
Mas, o FastAPI (na verdade, o Starlette) fornece uma maneira mais simples de fazer isso que garante que os middlewares internos lidem com erros do servidor e que os manipuladores de exceções personalizados funcionem corretamente. |
||||
|
|
||||
|
Para isso, você usa `app.add_middleware()` (como no exemplo para CORS). |
||||
|
|
||||
|
```Python |
||||
|
from fastapi import FastAPI |
||||
|
from unicorn import UnicornMiddleware |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
app.add_middleware(UnicornMiddleware, some_config="rainbow") |
||||
|
``` |
||||
|
|
||||
|
`app.add_middleware()` recebe uma classe de middleware como o primeiro argumento e quaisquer argumentos adicionais a serem passados para o middleware. |
||||
|
|
||||
|
## Middlewares Integrados |
||||
|
|
||||
|
**FastAPI** inclui vários middlewares para casos de uso comuns, veremos a seguir como usá-los. |
||||
|
|
||||
|
/// note | Detalhes Técnicos |
||||
|
|
||||
|
Para o próximo exemplo, você também poderia usar `from starlette.middleware.something import SomethingMiddleware`. |
||||
|
|
||||
|
**FastAPI** fornece vários middlewares em `fastapi.middleware` apenas como uma conveniência para você, o desenvolvedor. Mas a maioria dos middlewares disponíveis vem diretamente do Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## `HTTPSRedirectMiddleware` |
||||
|
|
||||
|
Garante que todas as requisições devem ser `https` ou `wss`. |
||||
|
|
||||
|
Qualquer requisição para `http` ou `ws` será redirecionada para o esquema seguro. |
||||
|
|
||||
|
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} |
||||
|
|
||||
|
## `TrustedHostMiddleware` |
||||
|
|
||||
|
Garante que todas as requisições recebidas tenham um cabeçalho `Host` corretamente configurado, a fim de proteger contra ataques de cabeçalho de host HTTP. |
||||
|
|
||||
|
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} |
||||
|
|
||||
|
Os seguintes argumentos são suportados: |
||||
|
|
||||
|
* `allowed_hosts` - Uma lista de nomes de domínio que são permitidos como nomes de host. Domínios com coringa, como `*.example.com`, são suportados para corresponder a subdomínios. Para permitir qualquer nome de host, use `allowed_hosts=["*"]` ou omita o middleware. |
||||
|
|
||||
|
Se uma requisição recebida não for validada corretamente, uma resposta `400` será enviada. |
||||
|
|
||||
|
## `GZipMiddleware` |
||||
|
|
||||
|
Gerencia respostas GZip para qualquer requisição que inclua `"gzip"` no cabeçalho `Accept-Encoding`. |
||||
|
|
||||
|
O middleware lidará com respostas padrão e de streaming. |
||||
|
|
||||
|
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} |
||||
|
|
||||
|
Os seguintes argumentos são suportados: |
||||
|
|
||||
|
* `minimum_size` - Não comprima respostas menores que este tamanho mínimo em bytes. O padrão é `500`. |
||||
|
* `compresslevel` - Usado durante a compressão GZip. É um inteiro variando de 1 a 9. O padrão é `9`. Um valor menor resulta em uma compressão mais rápida, mas em arquivos maiores, enquanto um valor maior resulta em uma compressão mais lenta, mas em arquivos menores. |
||||
|
|
||||
|
## Outros middlewares |
||||
|
|
||||
|
Há muitos outros middlewares ASGI. |
||||
|
|
||||
|
Por exemplo: |
||||
|
|
||||
|
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn's `ProxyHeadersMiddleware`</a> |
||||
|
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a> |
||||
|
|
||||
|
Para checar outros middlewares disponíveis, confira <a href="https://www.starlette.io/middleware/" class="external-link" target="_blank">Documentação de Middlewares do Starlette</a> e a <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">Lista Incrível do ASGI</a>. |
@ -0,0 +1,194 @@ |
|||||
|
# Callbacks na OpenAPI |
||||
|
|
||||
|
Você poderia criar uma API com uma *operação de rota* que poderia acionar uma solicitação a uma *API externa* criada por outra pessoa (provavelmente o mesmo desenvolvedor que estaria *usando* sua API). |
||||
|
|
||||
|
O processo que acontece quando seu aplicativo de API chama a *API externa* é chamado de "callback". Porque o software que o desenvolvedor externo escreveu envia uma solicitação para sua API e então sua API *chama de volta*, enviando uma solicitação para uma *API externa* (que provavelmente foi criada pelo mesmo desenvolvedor). |
||||
|
|
||||
|
Nesse caso, você poderia querer documentar como essa API externa *deveria* ser. Que *operação de rota* ela deveria ter, que corpo ela deveria esperar, que resposta ela deveria retornar, etc. |
||||
|
|
||||
|
## Um aplicativo com callbacks |
||||
|
|
||||
|
Vamos ver tudo isso com um exemplo. |
||||
|
|
||||
|
Imagine que você tem um aplicativo que permite criar faturas. |
||||
|
|
||||
|
Essas faturas terão um `id`, `title` (opcional), `customer` e `total`. |
||||
|
|
||||
|
O usuário da sua API (um desenvolvedor externo) criará uma fatura em sua API com uma solicitação POST. |
||||
|
|
||||
|
Então sua API irá (vamos imaginar): |
||||
|
|
||||
|
* Enviar uma solicitação de pagamento para o desenvolvedor externo. |
||||
|
* Coletar o dinheiro. |
||||
|
* Enviar a notificação de volta para o usuário da API (o desenvolvedor externo). |
||||
|
* Isso será feito enviando uma solicitação POST (de *sua API*) para alguma *API externa* fornecida por esse desenvolvedor externo (este é o "callback"). |
||||
|
|
||||
|
## O aplicativo **FastAPI** normal |
||||
|
|
||||
|
Vamos primeiro ver como o aplicativo da API normal se pareceria antes de adicionar o callback. |
||||
|
|
||||
|
Ele terá uma *operação de rota* que receberá um corpo `Invoice`, e um parâmetro de consulta `callback_url` que conterá a URL para o callback. |
||||
|
|
||||
|
Essa parte é bastante normal, a maior parte do código provavelmente já é familiar para você: |
||||
|
|
||||
|
```Python hl_lines="9-13 36-53" |
||||
|
{!../../docs_src/openapi_callbacks/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
O parâmetro de consulta `callback_url` usa um tipo Pydantic <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
A única coisa nova é o argumento `callbacks=invoices_callback_router.routes` no decorador da *operação de rota*. Veremos o que é isso a seguir. |
||||
|
|
||||
|
## Documentando o callback |
||||
|
|
||||
|
O código real do callback dependerá muito do seu próprio aplicativo de API. |
||||
|
|
||||
|
E provavelmente variará muito de um aplicativo para o outro. |
||||
|
|
||||
|
Poderia ser apenas uma ou duas linhas de código, como: |
||||
|
|
||||
|
```Python |
||||
|
callback_url = "https://example.com/api/v1/invoices/events/" |
||||
|
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) |
||||
|
``` |
||||
|
|
||||
|
Mas possivelmente a parte mais importante do callback é garantir que o usuário da sua API (o desenvolvedor externo) implemente a *API externa* corretamente, de acordo com os dados que *sua API* vai enviar no corpo da solicitação do callback, etc. |
||||
|
|
||||
|
Então, o que faremos a seguir é adicionar o código para documentar como essa *API externa* deve ser para receber o callback de *sua API*. |
||||
|
|
||||
|
A documentação aparecerá na interface do Swagger em `/docs` em sua API, e permitirá que os desenvolvedores externos saibam como construir a *API externa*. |
||||
|
|
||||
|
Esse exemplo não implementa o callback em si (que poderia ser apenas uma linha de código), apenas a parte da documentação. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
O callback real é apenas uma solicitação HTTP. |
||||
|
|
||||
|
Quando implementando o callback por você mesmo, você pode usar algo como <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> ou <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requisições</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Escrevendo o código de documentação do callback |
||||
|
|
||||
|
Esse código não será executado em seu aplicativo, nós só precisamos dele para *documentar* como essa *API externa* deveria ser. |
||||
|
|
||||
|
Mas, você já sabe como criar facilmente documentação automática para uma API com o **FastAPI**. |
||||
|
|
||||
|
Então vamos usar esse mesmo conhecimento para documentar como a *API externa* deveria ser... criando as *operações de rota* que a *API externa* deveria implementar (as que sua API irá chamar). |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Quando escrever o código para documentar um callback, pode ser útil imaginar que você é aquele *desenvolvedor externo*. E que você está atualmente implementando a *API externa*, não *sua API*. |
||||
|
|
||||
|
Adotar temporariamente esse ponto de vista (do *desenvolvedor externo*) pode ajudar a sentir que é mais óbvio onde colocar os parâmetros, o modelo Pydantic para o corpo, para a resposta, etc. para essa *API externa*. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Criar um `APIRouter` para o callback |
||||
|
|
||||
|
Primeiramente crie um novo `APIRouter` que conterá um ou mais callbacks. |
||||
|
|
||||
|
```Python hl_lines="3 25" |
||||
|
{!../../docs_src/openapi_callbacks/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
### Crie a *operação de rota* do callback |
||||
|
|
||||
|
Para criar a *operação de rota* do callback, use o mesmo `APIRouter` que você criou acima. |
||||
|
|
||||
|
Ele deve parecer exatamente como uma *operação de rota* normal do FastAPI: |
||||
|
|
||||
|
* Ele provavelmente deveria ter uma declaração do corpo que deveria receber, por exemplo. `body: InvoiceEvent`. |
||||
|
* E também deveria ter uma declaração de um código de status de resposta, por exemplo. `response_model=InvoiceEventReceived`. |
||||
|
|
||||
|
```Python hl_lines="16-18 21-22 28-32" |
||||
|
{!../../docs_src/openapi_callbacks/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
Há 2 diferenças principais de uma *operação de rota* normal: |
||||
|
|
||||
|
* Ela não necessita ter nenhum código real, porque seu aplicativo nunca chamará esse código. Ele é usado apenas para documentar a *API externa*. Então, a função poderia ter apenas `pass`. |
||||
|
* A *rota* pode conter uma <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">expressão OpenAPI 3</a> (veja mais abaixo) onde pode usar variáveis com parâmetros e partes da solicitação original enviada para *sua API*. |
||||
|
|
||||
|
### A expressão do caminho do callback |
||||
|
|
||||
|
A *rota* do callback pode ter uma <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">expressão OpenAPI 3</a> que pode conter partes da solicitação original enviada para *sua API*. |
||||
|
|
||||
|
Nesse caso, é a `str`: |
||||
|
|
||||
|
```Python |
||||
|
"{$callback_url}/invoices/{$request.body.id}" |
||||
|
``` |
||||
|
|
||||
|
Então, se o usuário da sua API (o desenvolvedor externo) enviar uma solicitação para *sua API* para: |
||||
|
|
||||
|
``` |
||||
|
https://yourapi.com/invoices/?callback_url=https://www.external.org/events |
||||
|
``` |
||||
|
|
||||
|
com um corpo JSON de: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"id": "2expen51ve", |
||||
|
"customer": "Mr. Richie Rich", |
||||
|
"total": "9999" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
então *sua API* processará a fatura e, em algum momento posterior, enviará uma solicitação de callback para o `callback_url` (a *API externa*): |
||||
|
|
||||
|
``` |
||||
|
https://www.external.org/events/invoices/2expen51ve |
||||
|
``` |
||||
|
|
||||
|
com um corpo JSON contendo algo como: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"description": "Payment celebration", |
||||
|
"paid": true |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
e esperaria uma resposta daquela *API externa* com um corpo JSON como: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"ok": true |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Perceba como a URL de callback usada contém a URL recebida como um parâmetro de consulta em `callback_url` (`https://www.external.org/events`) e também o `id` da fatura de dentro do corpo JSON (`2expen51ve`). |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Adicionar o roteador de callback |
||||
|
|
||||
|
Nesse ponto você tem a(s) *operação de rota de callback* necessária(s) (a(s) que o *desenvolvedor externo* deveria implementar na *API externa*) no roteador de callback que você criou acima. |
||||
|
|
||||
|
Agora use o parâmetro `callbacks` no decorador da *operação de rota de sua API* para passar o atributo `.routes` (que é na verdade apenas uma `list` de rotas/*operações de rota*) do roteador de callback que você criou acima: |
||||
|
|
||||
|
```Python hl_lines="35" |
||||
|
{!../../docs_src/openapi_callbacks/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Perceba que você não está passando o roteador em si (`invoices_callback_router`) para `callback=`, mas o atributo `.routes`, como em `invoices_callback_router.routes`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Verifique a documentação |
||||
|
|
||||
|
Agora você pode iniciar seu aplicativo e ir para <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
||||
|
|
||||
|
Você verá sua documentação incluindo uma seção "Callbacks" para sua *operação de rota* que mostra como a *API externa* deveria ser: |
||||
|
|
||||
|
<img src="/img/tutorial/openapi-callbacks/image01.png"> |
@ -0,0 +1,212 @@ |
|||||
|
# Configuração Avançada da Operação de Rota |
||||
|
|
||||
|
## operationId do OpenAPI |
||||
|
|
||||
|
/// warning | Aviso |
||||
|
|
||||
|
Se você não é um "especialista" no OpenAPI, você provavelmente não precisa disso. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Você pode definir o `operationId` do OpenAPI que será utilizado na sua *operação de rota* com o parâmetro `operation_id`. |
||||
|
|
||||
|
Você precisa ter certeza que ele é único para cada operação. |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} |
||||
|
|
||||
|
### Utilizando o nome da *função de operação de rota* como o operationId |
||||
|
|
||||
|
Se você quiser utilizar o nome das funções da sua API como `operationId`s, você pode iterar sobre todos esses nomes e sobrescrever o `operationId` em cada *operação de rota* utilizando o `APIRoute.name` dela. |
||||
|
|
||||
|
Você deve fazer isso depois de adicionar todas as suas *operações de rota*. |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *} |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Se você chamar `app.openapi()` manualmente, os `operationId`s devem ser atualizados antes dessa chamada. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// warning | Aviso |
||||
|
|
||||
|
Se você fizer isso, você tem que ter certeza de que cada uma das suas *funções de operação de rota* tem um nome único. |
||||
|
|
||||
|
Mesmo que elas estejam em módulos (arquivos Python) diferentes. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Excluir do OpenAPI |
||||
|
|
||||
|
Para excluir uma *operação de rota* do esquema OpenAPI gerado (e por consequência, dos sistemas de documentação automáticos), utilize o parâmetro `include_in_schema` e defina ele como `False`: |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} |
||||
|
|
||||
|
## Descrição avançada a partir de docstring |
||||
|
|
||||
|
Você pode limitar as linhas utilizadas a partir de uma docstring de uma *função de operação de rota* para o OpenAPI. |
||||
|
|
||||
|
Adicionar um `\f` (um caractere de escape para alimentação de formulário) faz com que o **FastAPI** restrinja a saída utilizada pelo OpenAPI até esse ponto. |
||||
|
|
||||
|
Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto do texto. |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} |
||||
|
|
||||
|
## Respostas Adicionais |
||||
|
|
||||
|
Você provavelmente já viu como declarar o `response_model` e `status_code` para uma *operação de rota*. |
||||
|
|
||||
|
Isso define os metadados sobre a resposta principal da *operação de rota*. |
||||
|
|
||||
|
Você também pode declarar respostas adicionais, com seus modelos, códigos de status, etc. |
||||
|
|
||||
|
Existe um capítulo inteiro da nossa documentação sobre isso, você pode ler em [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}. |
||||
|
|
||||
|
## Extras do OpenAPI |
||||
|
|
||||
|
Quando você declara uma *operação de rota* na sua aplicação, o **FastAPI** irá gerar os metadados relevantes da *operação de rota* automaticamente para serem incluídos no esquema do OpenAPI. |
||||
|
|
||||
|
/// note | Nota |
||||
|
|
||||
|
Na especificação do OpenAPI, isso é chamado de um <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Objeto de Operação</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Ele possui toda a informação sobre a *operação de rota* e é usado para gerar a documentação automaticamente. |
||||
|
|
||||
|
Ele inclui os atributos `tags`, `parameters`, `requestBody`, `responses`, etc. |
||||
|
|
||||
|
Esse esquema específico para uma *operação de rota* normalmente é gerado automaticamente pelo **FastAPI**, mas você também pode estender ele. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Esse é um ponto de extensão de baixo nível. |
||||
|
|
||||
|
Caso você só precise declarar respostas adicionais, uma forma conveniente de fazer isso é com [Retornos Adicionais no OpenAPI](additional-responses.md){.internal-link target=_blank}. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Você pode estender o esquema do OpenAPI para uma *operação de rota* utilizando o parâmetro `openapi_extra`. |
||||
|
|
||||
|
### Extensões do OpenAPI |
||||
|
|
||||
|
Esse parâmetro `openapi_extra` pode ser útil, por exemplo, para declarar [Extensões do OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} |
||||
|
|
||||
|
Se você abrir os documentos criados automaticamente para a API, sua extensão aparecerá no final da *operação de rota* específica. |
||||
|
|
||||
|
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png"> |
||||
|
|
||||
|
E se você olhar o esquema OpenAPI resultante (na rota `/openapi.json` da sua API), você verá que a sua extensão também faz parte da *operação de rota* específica: |
||||
|
|
||||
|
```JSON hl_lines="22" |
||||
|
{ |
||||
|
"openapi": "3.1.0", |
||||
|
"info": { |
||||
|
"title": "FastAPI", |
||||
|
"version": "0.1.0" |
||||
|
}, |
||||
|
"paths": { |
||||
|
"/items/": { |
||||
|
"get": { |
||||
|
"summary": "Read Items", |
||||
|
"operationId": "read_items_items__get", |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": {} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"x-aperture-labs-portal": "blue" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Esquema de *operação de rota* do OpenAPI personalizado |
||||
|
|
||||
|
O dicionário em `openapi_extra` vai ter todos os seus níveis mesclados dentro do esquema OpenAPI gerado automaticamente para a *operação de rota*. |
||||
|
|
||||
|
Então, você pode adicionar dados extras para o esquema gerado automaticamente. |
||||
|
|
||||
|
Por exemplo, você poderia optar por ler e validar a requisição com seu próprio código, sem utilizar funcionalidades automatizadas do FastAPI com o Pydantic, mas você ainda pode quere definir a requisição no esquema OpenAPI. |
||||
|
|
||||
|
Você pode fazer isso com `openapi_extra`: |
||||
|
|
||||
|
{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36,39:40] *} |
||||
|
|
||||
|
Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo <abbr title="convertido de um formato plano, como bytes, para objetos Python">analisado</abbr> como JSON, ele é lido diretamente como `bytes` e a função `magic_data_reader()` seria a responsável por analisar ele de alguma forma. |
||||
|
|
||||
|
De toda forma, nós podemos declarar o esquema esperado para o corpo da requisição. |
||||
|
|
||||
|
### Tipo de conteúdo do OpenAPI personalizado |
||||
|
|
||||
|
Utilizando esse mesmo truque, você pode utilizar um modelo Pydantic para definir o esquema JSON que é então incluído na seção do esquema personalizado do OpenAPI na *operação de rota*. |
||||
|
|
||||
|
E você pode fazer isso até mesmo quando os dados da requisição não seguem o formato JSON. |
||||
|
|
||||
|
Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o esquema JSON dos modelos Pydantic nem a validação automática do JSON. Na verdade, estamos declarando o tipo do conteúdo da requisição como YAML, em vez de JSON: |
||||
|
|
||||
|
//// tab | Pydantic v2 |
||||
|
|
||||
|
```Python hl_lines="17-22 24" |
||||
|
{!> ../../docs_src/path_operation_advanced_configuration/tutorial007.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Pydantic v1 |
||||
|
|
||||
|
```Python hl_lines="17-22 24" |
||||
|
{!> ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Na versão 1 do Pydantic, o método para obter o esquema JSON de um modelo é `Item.schema()`, na versão 2 do Pydantic, o método é `Item.model_json_schema()` |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um esquema JSON manualmente para os dados que queremos receber no formato YAML. |
||||
|
|
||||
|
Então utilizamos a requisição diretamente, e extraímos o corpo como `bytes`. Isso significa que o FastAPI não vai sequer tentar analisar o corpo da requisição como JSON. |
||||
|
|
||||
|
E então no nosso código, nós analisamos o conteúdo YAML diretamente, e estamos utilizando o mesmo modelo Pydantic para validar o conteúdo YAML: |
||||
|
|
||||
|
//// tab | Pydantic v2 |
||||
|
|
||||
|
```Python hl_lines="26-33" |
||||
|
{!> ../../docs_src/path_operation_advanced_configuration/tutorial007.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Pydantic v1 |
||||
|
|
||||
|
```Python hl_lines="26-33" |
||||
|
{!> ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Na versão 1 do Pydantic, o método para analisar e validar um objeto era `Item.parse_obj()`, na versão 2 do Pydantic, o método é chamado de `Item.model_validate()`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
///tip | Dica |
||||
|
|
||||
|
Aqui reutilizamos o mesmo modelo do Pydantic. |
||||
|
|
||||
|
Mas da mesma forma, nós poderíamos ter validado de alguma outra forma. |
||||
|
|
||||
|
/// |
@ -0,0 +1,188 @@ |
|||||
|
# WebSockets |
||||
|
|
||||
|
Você pode usar <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a> com **FastAPI**. |
||||
|
|
||||
|
## Instalando `WebSockets` |
||||
|
|
||||
|
Garanta que você criou um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, o ativou e instalou o `websockets`: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install websockets |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## Cliente WebSockets |
||||
|
|
||||
|
### Em produção |
||||
|
|
||||
|
Em seu sistema de produção, você provavelmente tem um frontend criado com um framework moderno como React, Vue.js ou Angular. |
||||
|
|
||||
|
E para comunicar usando WebSockets com seu backend, você provavelmente usaria as utilidades do seu frontend. |
||||
|
|
||||
|
Ou você pode ter um aplicativo móvel nativo que se comunica diretamente com seu backend WebSocket, em código nativo. |
||||
|
|
||||
|
Ou você pode ter qualquer outra forma de comunicar com o endpoint WebSocket. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Mas para este exemplo, usaremos um documento HTML muito simples com algum JavaScript, tudo dentro de uma string longa. |
||||
|
|
||||
|
Esse, é claro, não é o ideal e você não o usaria para produção. |
||||
|
|
||||
|
Na produção, você teria uma das opções acima. |
||||
|
|
||||
|
Mas é a maneira mais simples de focar no lado do servidor de WebSockets e ter um exemplo funcional: |
||||
|
|
||||
|
```Python hl_lines="2 6-38 41-43" |
||||
|
{!../../docs_src/websockets/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
## Criando um `websocket` |
||||
|
|
||||
|
Em sua aplicação **FastAPI**, crie um `websocket`: |
||||
|
|
||||
|
{*../../docs_src/websockets/tutorial001.py hl[46:47]*} |
||||
|
|
||||
|
/// note | Detalhes Técnicos |
||||
|
|
||||
|
Você também poderia usar `from starlette.websockets import WebSocket`. |
||||
|
|
||||
|
A **FastAPI** fornece o mesmo `WebSocket` diretamente apenas como uma conveniência para você, o desenvolvedor. Mas ele vem diretamente do Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Aguardar por mensagens e enviar mensagens |
||||
|
|
||||
|
Em sua rota WebSocket você pode esperar (`await`) por mensagens e enviar mensagens. |
||||
|
|
||||
|
{*../../docs_src/websockets/tutorial001.py hl[48:52]*} |
||||
|
|
||||
|
Você pode receber e enviar dados binários, de texto e JSON. |
||||
|
|
||||
|
## Tente você mesmo |
||||
|
|
||||
|
Se seu arquivo for nomeado `main.py`, execute sua aplicação com: |
||||
|
|
||||
|
<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> |
||||
|
|
||||
|
Abra seu navegador em: <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>. |
||||
|
|
||||
|
Você verá uma página simples como: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image01.png"> |
||||
|
|
||||
|
Você pode digitar mensagens na caixa de entrada e enviá-las: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image02.png"> |
||||
|
|
||||
|
E sua aplicação **FastAPI** com WebSockets responderá de volta: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image03.png"> |
||||
|
|
||||
|
Você pode enviar (e receber) muitas mensagens: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image04.png"> |
||||
|
|
||||
|
E todas elas usarão a mesma conexão WebSocket. |
||||
|
|
||||
|
## Usando `Depends` e outros |
||||
|
|
||||
|
Nos endpoints WebSocket você pode importar do `fastapi` e usar: |
||||
|
|
||||
|
* `Depends` |
||||
|
* `Security` |
||||
|
* `Cookie` |
||||
|
* `Header` |
||||
|
* `Path` |
||||
|
* `Query` |
||||
|
|
||||
|
Eles funcionam da mesma forma que para outros endpoints FastAPI/*operações de rota*: |
||||
|
|
||||
|
{*../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82]*} |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Como isso é um WebSocket, não faz muito sentido levantar uma `HTTPException`, em vez disso levantamos uma `WebSocketException`. |
||||
|
|
||||
|
Você pode usar um código de fechamento dos <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">códigos válidos definidos na especificação</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Tente os WebSockets com dependências |
||||
|
|
||||
|
Se seu arquivo for nomeado `main.py`, execute sua aplicação com: |
||||
|
|
||||
|
<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> |
||||
|
|
||||
|
Abrar seu browser em: <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>. |
||||
|
|
||||
|
Lá você pode definir: |
||||
|
|
||||
|
* O "Item ID", usado na rota. |
||||
|
* O "Token" usado como um parâmetro de consulta. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Perceba que a consulta `token` será manipulada por uma dependência. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Com isso você pode conectar o WebSocket e então enviar e receber mensagens: |
||||
|
|
||||
|
<img src="/img/tutorial/websockets/image05.png"> |
||||
|
|
||||
|
## Lidando com desconexões e múltiplos clientes |
||||
|
|
||||
|
Quando uma conexão WebSocket é fechada, o `await websocket.receive_text()` levantará uma exceção `WebSocketDisconnect`, que você pode então capturar e lidar como neste exemplo. |
||||
|
|
||||
|
{*../../docs_src/websockets/tutorial003_py39.py hl[79:81]*} |
||||
|
|
||||
|
Para testar: |
||||
|
|
||||
|
* Abrar o aplicativo com várias abas do navegador. |
||||
|
* Escreva mensagens a partir delas. |
||||
|
* Então feche uma das abas. |
||||
|
|
||||
|
Isso levantará a exceção `WebSocketDisconnect`, e todos os outros clientes receberão uma mensagem como: |
||||
|
|
||||
|
``` |
||||
|
Client #1596980209979 left the chat |
||||
|
``` |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
O app acima é um exemplo mínimo e simples para demonstrar como lidar e transmitir mensagens para várias conexões WebSocket. |
||||
|
|
||||
|
Mas tenha em mente que, como tudo é manipulado na memória, em uma única lista, ele só funcionará enquanto o processo estiver em execução e só funcionará com um único processo. |
||||
|
|
||||
|
Se você precisa de algo fácil de integrar com o FastAPI, mas que seja mais robusto, suportado por Redis, PostgreSQL ou outros, verifique o <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Mais informações |
||||
|
|
||||
|
Para aprender mais sobre as opções, verifique a documentação do Starlette para: |
||||
|
|
||||
|
* <a href="https://www.starlette.io/websockets/" class="external-link" target="_blank">A classe `WebSocket`</a>. |
||||
|
* <a href="https://www.starlette.io/endpoints/#websocketendpoint" class="external-link" target="_blank">Manipulação de WebSockets baseada em classes</a>. |
@ -0,0 +1,258 @@ |
|||||
|
# Esquemas OpenAPI Separados para Entrada e Saída ou Não |
||||
|
|
||||
|
Ao usar **Pydantic v2**, o OpenAPI gerado é um pouco mais exato e **correto** do que antes. 😎 |
||||
|
|
||||
|
Inclusive, em alguns casos, ele terá até **dois JSON Schemas** no OpenAPI para o mesmo modelo Pydantic, para entrada e saída, dependendo se eles possuem **valores padrão**. |
||||
|
|
||||
|
Vamos ver como isso funciona e como alterar se for necessário. |
||||
|
|
||||
|
## Modelos Pydantic para Entrada e Saída |
||||
|
|
||||
|
Digamos que você tenha um modelo Pydantic com valores padrão, como este: |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="7" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-7]!} |
||||
|
|
||||
|
# Code below omitted 👇 |
||||
|
``` |
||||
|
|
||||
|
<details> |
||||
|
<summary>👀 Visualização completa do arquivo</summary> |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="9" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-9]!} |
||||
|
|
||||
|
# Code below omitted 👇 |
||||
|
``` |
||||
|
|
||||
|
<details> |
||||
|
<summary>👀 Visualização completa do arquivo</summary> |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="9" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-9]!} |
||||
|
|
||||
|
# Code below omitted 👇 |
||||
|
``` |
||||
|
|
||||
|
<details> |
||||
|
<summary>👀 Visualização completa do arquivo</summary> |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
### Modelo para Entrada |
||||
|
|
||||
|
Se você usar esse modelo como entrada, como aqui: |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="14" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py310.py[ln:1-15]!} |
||||
|
|
||||
|
# Code below omitted 👇 |
||||
|
``` |
||||
|
|
||||
|
<details> |
||||
|
<summary>👀 Visualização completa do arquivo</summary> |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="16" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py39.py[ln:1-17]!} |
||||
|
|
||||
|
# Code below omitted 👇 |
||||
|
``` |
||||
|
|
||||
|
<details> |
||||
|
<summary>👀 Visualização completa do arquivo</summary> |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="16" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001.py[ln:1-17]!} |
||||
|
|
||||
|
# Code below omitted 👇 |
||||
|
``` |
||||
|
|
||||
|
<details> |
||||
|
<summary>👀 Visualização completa do arquivo</summary> |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
... então o campo `description` não será obrigatório. Porque ele tem um valor padrão de `None`. |
||||
|
|
||||
|
### Modelo de Entrada na Documentação |
||||
|
|
||||
|
Você pode confirmar que na documentação, o campo `description` não tem um **asterisco vermelho**, não é marcado como obrigatório: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image01.png"> |
||||
|
</div> |
||||
|
|
||||
|
### Modelo para Saída |
||||
|
|
||||
|
Mas se você usar o mesmo modelo como saída, como aqui: |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="19" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="21" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="21" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
... então, como `description` tem um valor padrão, se você **não retornar nada** para esse campo, ele ainda terá o **valor padrão**. |
||||
|
|
||||
|
### Modelo para Dados de Resposta de Saída |
||||
|
|
||||
|
Se você interagir com a documentação e verificar a resposta, mesmo que o código não tenha adicionado nada em um dos campos `description`, a resposta JSON contém o valor padrão (`null`): |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image02.png"> |
||||
|
</div> |
||||
|
|
||||
|
Isso significa que ele **sempre terá um valor**, só que às vezes o valor pode ser `None` (ou `null` em termos de JSON). |
||||
|
|
||||
|
Isso quer dizer que, os clientes que usam sua API não precisam verificar se o valor existe ou não, eles podem **assumir que o campo sempre estará lá**, mas que em alguns casos terá o valor padrão de `None`. |
||||
|
|
||||
|
A maneira de descrever isso no OpenAPI é marcar esse campo como **obrigatório**, porque ele sempre estará lá. |
||||
|
|
||||
|
Por causa disso, o JSON Schema para um modelo pode ser diferente dependendo se ele é usado para **entrada ou saída**: |
||||
|
|
||||
|
* para **entrada**, o `description` **não será obrigatório** |
||||
|
* para **saída**, ele será **obrigatório** (e possivelmente `None`, ou em termos de JSON, `null`) |
||||
|
|
||||
|
### Modelo para Saída na Documentação |
||||
|
|
||||
|
Você pode verificar o modelo de saída na documentação também, ambos `name` e `description` são marcados como **obrigatórios** com um **asterisco vermelho**: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image03.png"> |
||||
|
</div> |
||||
|
|
||||
|
### Modelo para Entrada e Saída na Documentação |
||||
|
|
||||
|
E se você verificar todos os Schemas disponíveis (JSON Schemas) no OpenAPI, verá que há dois, um `Item-Input` e um `Item-Output`. |
||||
|
|
||||
|
Para `Item-Input`, `description` **não é obrigatório**, não tem um asterisco vermelho. |
||||
|
|
||||
|
Mas para `Item-Output`, `description` **é obrigatório**, tem um asterisco vermelho. |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image04.png"> |
||||
|
</div> |
||||
|
|
||||
|
Com esse recurso do **Pydantic v2**, sua documentação da API fica mais **precisa**, e se você tiver clientes e SDKs gerados automaticamente, eles serão mais precisos também, proporcionando uma melhor **experiência para desenvolvedores** e consistência. 🎉 |
||||
|
|
||||
|
## Não Separe Schemas |
||||
|
|
||||
|
Agora, há alguns casos em que você pode querer ter o **mesmo esquema para entrada e saída**. |
||||
|
|
||||
|
Provavelmente, o principal caso de uso para isso é se você já tem algum código de cliente/SDK gerado automaticamente e não quer atualizar todo o código de cliente/SDK gerado ainda, você provavelmente vai querer fazer isso em algum momento, mas talvez não agora. |
||||
|
|
||||
|
Nesse caso, você pode desativar esse recurso no **FastAPI**, com o parâmetro `separate_input_output_schemas=False`. |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
O suporte para `separate_input_output_schemas` foi adicionado no FastAPI `0.102.0`. 🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="10" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial002_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="12" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial002_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="12" |
||||
|
{!> ../../docs_src/separate_openapi_schemas/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
### Mesmo Esquema para Modelos de Entrada e Saída na Documentação |
||||
|
|
||||
|
E agora haverá um único esquema para entrada e saída para o modelo, apenas `Item`, e `description` **não será obrigatório**: |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/separate-openapi-schemas/image05.png"> |
||||
|
</div> |
||||
|
|
||||
|
Esse é o mesmo comportamento do Pydantic v1. 🤓 |
@ -0,0 +1,132 @@ |
|||||
|
# Metadados e Urls de Documentos |
||||
|
|
||||
|
Você pode personalizar várias configurações de metadados na sua aplicação **FastAPI**. |
||||
|
|
||||
|
## Metadados para API |
||||
|
|
||||
|
Você pode definir os seguintes campos que são usados na especificação OpenAPI e nas interfaces automáticas de documentação da API: |
||||
|
|
||||
|
| Parâmetro | Tipo | Descrição | |
||||
|
|------------|------|-------------| |
||||
|
| `title` | `str` | O título da API. | |
||||
|
| `summary` | `str` | Um breve resumo da API. <small>Disponível desde OpenAPI 3.1.0, FastAPI 0.99.0.</small> | |
||||
|
| `description` | `str` | Uma breve descrição da API. Pode usar Markdown. | |
||||
|
| `version` | `string` | A versão da API. Esta é a versão da sua aplicação, não do OpenAPI. Por exemplo, `2.5.0`. | |
||||
|
| `terms_of_service` | `str` | Uma URL para os Termos de Serviço da API. Se fornecido, deve ser uma URL. | |
||||
|
| `contact` | `dict` | As informações de contato da API exposta. Pode conter vários campos. <details><summary>Campos de <code>contact</code></summary><table><thead><tr><th>Parâmetro</th><th>Tipo</th><th>Descrição</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>O nome identificador da pessoa/organização de contato.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>A URL que aponta para as informações de contato. DEVE estar no formato de uma URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>O endereço de e-mail da pessoa/organização de contato. DEVE estar no formato de um endereço de e-mail.</td></tr></tbody></table></details> | |
||||
|
| `license_info` | `dict` | As informações de licença para a API exposta. Ela pode conter vários campos. <details><summary>Campos de <code>license_info</code></summary><table><thead><tr><th>Parâmetro</th><th>Tipo</th><th>Descrição</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>OBRIGATÓRIO</strong> (se um <code>license_info</code> for definido). O nome da licença usada para a API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>Uma expressão de licença <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> para a API. O campo <code>identifier</code> é mutuamente exclusivo do campo <code>url</code>. <small>Disponível desde OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>Uma URL para a licença usada para a API. DEVE estar no formato de uma URL.</td></tr></tbody></table></details> | |
||||
|
|
||||
|
Você pode defini-los da seguinte maneira: |
||||
|
|
||||
|
```Python hl_lines="3-16 19-32" |
||||
|
{!../../docs_src/metadata/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Você pode escrever Markdown no campo `description` e ele será renderizado na saída. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Com essa configuração, a documentação automática da API se pareceria com: |
||||
|
|
||||
|
<img src="/img/tutorial/metadata/image01.png"> |
||||
|
|
||||
|
## Identificador de Licença |
||||
|
|
||||
|
Desde o OpenAPI 3.1.0 e FastAPI 0.99.0, você também pode definir o license_info com um identifier em vez de uma url. |
||||
|
|
||||
|
Por exemplo: |
||||
|
|
||||
|
```Python hl_lines="31" |
||||
|
{!../../docs_src/metadata/tutorial001_1.py!} |
||||
|
``` |
||||
|
|
||||
|
## Metadados para tags |
||||
|
|
||||
|
Você também pode adicionar metadados adicionais para as diferentes tags usadas para agrupar suas operações de rota com o parâmetro `openapi_tags`. |
||||
|
|
||||
|
Ele recebe uma lista contendo um dicionário para cada tag. |
||||
|
|
||||
|
Cada dicionário pode conter: |
||||
|
|
||||
|
* `name` (**obrigatório**): uma `str` com o mesmo nome da tag que você usa no parâmetro `tags` nas suas *operações de rota* e `APIRouter`s. |
||||
|
* `description`: uma `str` com uma breve descrição da tag. Pode conter Markdown e será exibido na interface de documentação. |
||||
|
* `externalDocs`: um `dict` descrevendo a documentação externa com: |
||||
|
* `description`: uma `str` com uma breve descrição da documentação externa. |
||||
|
* `url` (**obrigatório**): uma `str` com a URL da documentação externa. |
||||
|
|
||||
|
### Criar Metadados para tags |
||||
|
|
||||
|
Vamos tentar isso em um exemplo com tags para `users` e `items`. |
||||
|
|
||||
|
Crie metadados para suas tags e passe-os para o parâmetro `openapi_tags`: |
||||
|
|
||||
|
```Python hl_lines="3-16 18" |
||||
|
{!../../docs_src/metadata/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
Observe que você pode usar Markdown dentro das descrições. Por exemplo, "login" será exibido em negrito (**login**) e "fancy" será exibido em itálico (_fancy_). |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Você não precisa adicionar metadados para todas as tags que você usa. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Use suas tags |
||||
|
|
||||
|
Use o parâmetro `tags` com suas *operações de rota* (e `APIRouter`s) para atribuí-los a diferentes tags: |
||||
|
|
||||
|
```Python hl_lines="21 26" |
||||
|
{!../../docs_src/metadata/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Leia mais sobre tags em [Configuração de Operação de Caminho](path-operation-configuration.md#tags){.internal-link target=_blank}. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Cheque os documentos |
||||
|
|
||||
|
Agora, se você verificar a documentação, ela exibirá todos os metadados adicionais: |
||||
|
|
||||
|
<img src="/img/tutorial/metadata/image02.png"> |
||||
|
|
||||
|
### Ordem das tags |
||||
|
|
||||
|
A ordem de cada dicionário de metadados de tag também define a ordem exibida na interface de documentação. |
||||
|
|
||||
|
Por exemplo, embora `users` apareça após `items` em ordem alfabética, ele é exibido antes deles, porque adicionamos seus metadados como o primeiro dicionário na lista. |
||||
|
|
||||
|
## URL da OpenAPI |
||||
|
|
||||
|
Por padrão, o esquema OpenAPI é servido em `/openapi.json`. |
||||
|
|
||||
|
Mas você pode configurá-lo com o parâmetro `openapi_url`. |
||||
|
|
||||
|
Por exemplo, para defini-lo para ser servido em `/api/v1/openapi.json`: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../docs_src/metadata/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
Se você quiser desativar completamente o esquema OpenAPI, pode definir `openapi_url=None`, o que também desativará as interfaces de documentação que o utilizam. |
||||
|
|
||||
|
## URLs da Documentação |
||||
|
|
||||
|
Você pode configurar as duas interfaces de documentação incluídas: |
||||
|
|
||||
|
* **Swagger UI**: acessível em `/docs`. |
||||
|
* Você pode definir sua URL com o parâmetro `docs_url`. |
||||
|
* Você pode desativá-la definindo `docs_url=None`. |
||||
|
* **ReDoc**: acessível em `/redoc`. |
||||
|
* Você pode definir sua URL com o parâmetro `redoc_url`. |
||||
|
* Você pode desativá-la definindo `redoc_url=None`. |
||||
|
|
||||
|
Por exemplo, para definir o Swagger UI para ser servido em `/documentation` e desativar o ReDoc: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../docs_src/metadata/tutorial003.py!} |
||||
|
``` |
@ -0,0 +1,176 @@ |
|||||
|
# Arquivos de Requisição |
||||
|
|
||||
|
Você pode definir arquivos para serem enviados pelo cliente usando `File`. |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Para receber arquivos enviados, primeiro instale o <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. |
||||
|
|
||||
|
Garanta que você criou um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, o ativou e então o instalou, por exemplo: |
||||
|
|
||||
|
```console |
||||
|
$ pip install python-multipart |
||||
|
``` |
||||
|
|
||||
|
Isso é necessário, visto que os arquivos enviados são enviados como "dados de formulário". |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Importe `File` |
||||
|
|
||||
|
Importe `File` e `UploadFile` de `fastapi`: |
||||
|
|
||||
|
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} |
||||
|
|
||||
|
## Definir Parâmetros `File` |
||||
|
|
||||
|
Crie parâmetros de arquivo da mesma forma que você faria para `Body` ou `Form`: |
||||
|
|
||||
|
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
`File` é uma classe que herda diretamente de `Form`. |
||||
|
|
||||
|
Mas lembre-se que quando você importa `Query`, `Path`, `File` e outros de `fastapi`, eles são, na verdade, funções que retornam classes especiais. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Para declarar corpos de arquivos, você precisa usar `File`, caso contrário, os parâmetros seriam interpretados como parâmetros de consulta ou parâmetros de corpo (JSON). |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Os arquivos serão enviados como "dados de formulário". |
||||
|
|
||||
|
Se você declarar o tipo do parâmetro da função da sua *operação de rota* como `bytes`, o **FastAPI** lerá o arquivo para você e você receberá o conteúdo como `bytes`. |
||||
|
|
||||
|
Mantenha em mente que isso significa que todo o conteúdo será armazenado na memória. Isso funcionará bem para arquivos pequenos. |
||||
|
|
||||
|
Mas há muitos casos em que você pode se beneficiar do uso de `UploadFile`. |
||||
|
|
||||
|
## Parâmetros de Arquivo com `UploadFile` |
||||
|
|
||||
|
Defina um parâmetro de arquivo com um tipo de `UploadFile`: |
||||
|
|
||||
|
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} |
||||
|
|
||||
|
Utilizar `UploadFile` tem várias vantagens sobre `bytes`: |
||||
|
|
||||
|
* Você não precisa utilizar o `File()` no valor padrão do parâmetro. |
||||
|
* Ele utiliza um arquivo "spooled": |
||||
|
* Um arquivo armazenado na memória até um limite máximo de tamanho, e após passar esse limite, ele será armazenado no disco. |
||||
|
* Isso significa que funcionará bem para arquivos grandes como imagens, vídeos, binários grandes, etc., sem consumir toda a memória. |
||||
|
* Você pode receber metadados do arquivo enviado. |
||||
|
* Ele tem uma <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> interface `assíncrona`. |
||||
|
* Ele expõe um objeto python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> que você pode passar diretamente para outras bibliotecas que esperam um objeto semelhante a um arquivo("file-like"). |
||||
|
|
||||
|
### `UploadFile` |
||||
|
|
||||
|
`UploadFile` tem os seguintes atributos: |
||||
|
|
||||
|
* `filename`: Uma `str` com o nome do arquivo original que foi enviado (por exemplo, `myimage.jpg`). |
||||
|
* `content_type`: Uma `str` com o tipo de conteúdo (tipo MIME / tipo de mídia) (por exemplo, `image/jpeg`). |
||||
|
* `file`: Um <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (um <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> objeto). Este é o objeto de arquivo Python que você pode passar diretamente para outras funções ou bibliotecas que esperam um objeto semelhante a um arquivo("file-like"). |
||||
|
|
||||
|
`UploadFile` tem os seguintes métodos `assíncronos`. Todos eles chamam os métodos de arquivo correspondentes por baixo dos panos (usando o `SpooledTemporaryFile` interno). |
||||
|
|
||||
|
* `write(data)`: Escreve `data` (`str` ou `bytes`) no arquivo. |
||||
|
* `read(size)`: Lê `size` (`int`) bytes/caracteres do arquivo. |
||||
|
* `seek(offset)`: Vai para o byte na posição `offset` (`int`) no arquivo. |
||||
|
* Por exemplo, `await myfile.seek(0)` irá para o início do arquivo. |
||||
|
* Isso é especialmente útil se você executar `await myfile.read()` uma vez e precisar ler o conteúdo novamente. |
||||
|
* `close()`: Fecha o arquivo. |
||||
|
|
||||
|
Como todos esses métodos são métodos `assíncronos`, você precisa "aguardar" por eles. |
||||
|
|
||||
|
Por exemplo, dentro de uma função de *operação de rota* `assíncrona`, você pode obter o conteúdo com: |
||||
|
|
||||
|
```Python |
||||
|
contents = await myfile.read() |
||||
|
``` |
||||
|
|
||||
|
Se você estiver dentro de uma função de *operação de rota* normal `def`, você pode acessar o `UploadFile.file` diretamente, por exemplo: |
||||
|
|
||||
|
```Python |
||||
|
contents = myfile.file.read() |
||||
|
``` |
||||
|
|
||||
|
/// note | Detalhes Técnicos do `async` |
||||
|
|
||||
|
Quando você usa os métodos `async`, o **FastAPI** executa os métodos de arquivo em um threadpool e aguarda por eles. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// note | "Detalhes Técnicos do Starlette" |
||||
|
|
||||
|
O `UploadFile` do ***FastAPI** herda diretamente do `UploadFile` do **Starlette** , mas adiciona algumas partes necessárias para torná-lo compatível com o **Pydantic** e as outras partes do FastAPI. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## O que é "Form Data" |
||||
|
|
||||
|
O jeito que os formulários HTML (`<form></form>`) enviam os dados para o servidor normalmente usa uma codificação "especial" para esses dados, a qual é diferente do JSON. |
||||
|
|
||||
|
**FastAPI** se certificará de ler esses dados do lugar certo, ao invés de JSON. |
||||
|
|
||||
|
/// note | "Detalhes Técnicos" |
||||
|
|
||||
|
Dados de formulários normalmente são codificados usando o "media type" (tipo de mídia) `application/x-www-form-urlencoded` quando não incluem arquivos. |
||||
|
|
||||
|
Mas quando o formulário inclui arquivos, ele é codificado como `multipart/form-data`. Se você usar `File`, o **FastAPI** saberá que tem que pegar os arquivos da parte correta do corpo da requisição. |
||||
|
|
||||
|
Se você quiser ler mais sobre essas codificações e campos de formulário, vá para a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs para <code>POST</code></a>. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// warning | Aviso |
||||
|
|
||||
|
Você pode declarar múltiplos parâmetros `File` e `Form` em uma *operação de rota*, mas você não pode declarar campos `Body` que você espera receber como JSON, pois a requisição terá o corpo codificado usando `multipart/form-data` ao invés de `application/json`. |
||||
|
|
||||
|
Isso não é uma limitação do **FastAPI**, é parte do protocolo HTTP. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Upload de Arquivo Opcional |
||||
|
|
||||
|
Você pode tornar um arquivo opcional usando anotações de tipo padrão e definindo um valor padrão de `None`: |
||||
|
|
||||
|
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} |
||||
|
|
||||
|
## `UploadFile` com Metadados Adicionais |
||||
|
|
||||
|
Você também pode usar `File()` com `UploadFile`, por exemplo, para definir metadados adicionais: |
||||
|
|
||||
|
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} |
||||
|
|
||||
|
## Uploads de Múltiplos Arquivos |
||||
|
|
||||
|
É possível realizar o upload de vários arquivos ao mesmo tempo. |
||||
|
|
||||
|
Eles serão associados ao mesmo "campo de formulário" enviado usando "dados de formulário". |
||||
|
|
||||
|
Para usar isso, declare uma lista de `bytes` ou `UploadFile`: |
||||
|
|
||||
|
{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} |
||||
|
|
||||
|
Você receberá, tal como declarado, uma `list` de `bytes` ou `UploadFile`. |
||||
|
|
||||
|
/// note | "Detalhes Técnicos" |
||||
|
|
||||
|
Você pode também pode usar `from starlette.responses import HTMLResponse`. |
||||
|
|
||||
|
**FastAPI** providencia o mesmo `starlette.responses` que `fastapi.responses` apenas como uma conveniência para você, o desenvolvedor. Mas a maioria das respostas disponíveis vem diretamente do Starlette. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Uploads de Múltiplos Arquivos com Metadados Adicionais |
||||
|
|
||||
|
Da mesma forma de antes, você pode usar `File()` para definir parâmetros adicionais, mesmo para `UploadFile`: |
||||
|
|
||||
|
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} |
||||
|
|
||||
|
## Recapitulando |
||||
|
|
||||
|
Utilize `File`, `bytes` e `UploadFile` para declarar arquivos a serem enviados na requisição, enviados como dados de formulário. |
@ -0,0 +1,539 @@ |
|||||
|
# Simples OAuth2 com senha e Bearer |
||||
|
|
||||
|
Agora vamos construir a partir do capítulo anterior e adicionar as partes que faltam para ter um fluxo de segurança completo. |
||||
|
|
||||
|
## Pegue o `username` (nome de usuário) e `password` (senha) |
||||
|
|
||||
|
É utilizado o utils de segurança da **FastAPI** para obter o `username` e a `password`. |
||||
|
|
||||
|
OAuth2 especifica que ao usar o "password flow" (fluxo de senha), que estamos usando, o cliente/usuário deve enviar os campos `username` e `password` como dados do formulário. |
||||
|
|
||||
|
E a especificação diz que os campos devem ser nomeados assim. Portanto, `user-name` ou `email` não funcionariam. |
||||
|
|
||||
|
Mas não se preocupe, você pode mostrá-lo como quiser aos usuários finais no frontend. |
||||
|
|
||||
|
E seus modelos de banco de dados podem usar qualquer outro nome que você desejar. |
||||
|
|
||||
|
Mas para a *operação de rota* de login, precisamos usar esses nomes para serem compatíveis com a especificação (e poder, por exemplo, usar o sistema integrado de documentação da API). |
||||
|
|
||||
|
A especificação também afirma que o `username` e a `password` devem ser enviados como dados de formulário (portanto, não há JSON aqui). |
||||
|
|
||||
|
### `scope` |
||||
|
|
||||
|
A especificação também diz que o cliente pode enviar outro campo de formulário "`scope`" (Escopo). |
||||
|
|
||||
|
O nome do campo do formulário é `scope` (no singular), mas na verdade é uma longa string com "escopos" separados por espaços. |
||||
|
|
||||
|
Cada “scope” é apenas uma string (sem espaços). |
||||
|
|
||||
|
Normalmente são usados para declarar permissões de segurança específicas, por exemplo: |
||||
|
|
||||
|
* `users:read` ou `users:write` são exemplos comuns. |
||||
|
* `instagram_basic` é usado pelo Facebook e Instagram. |
||||
|
* `https://www.googleapis.com/auth/drive` é usado pelo Google. |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
No OAuth2, um "scope" é apenas uma string que declara uma permissão específica necessária. |
||||
|
|
||||
|
Não importa se tem outros caracteres como `:` ou se é uma URL. |
||||
|
|
||||
|
Esses detalhes são específicos da implementação. |
||||
|
|
||||
|
Para OAuth2 são apenas strings. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Código para conseguir o `username` e a `password` |
||||
|
|
||||
|
Agora vamos usar os utilitários fornecidos pelo **FastAPI** para lidar com isso. |
||||
|
|
||||
|
### `OAuth2PasswordRequestForm` |
||||
|
|
||||
|
Primeiro, importe `OAuth2PasswordRequestForm` e use-o como uma dependência com `Depends` na *operação de rota* para `/token`: |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="4 78" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="4 78" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="4 79" |
||||
|
{!> ../../docs_src/security/tutorial003_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.10+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="2 74" |
||||
|
{!> ../../docs_src/security/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="4 76" |
||||
|
{!> ../../docs_src/security/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
`OAuth2PasswordRequestForm` é uma dependência de classe que declara um corpo de formulário com: |
||||
|
|
||||
|
* O `username`. |
||||
|
* A `password`. |
||||
|
* Um campo `scope` opcional como uma string grande, composta de strings separadas por espaços. |
||||
|
* Um `grant_type` (tipo de concessão) opcional. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
A especificação OAuth2 na verdade *requer* um campo `grant_type` com um valor fixo de `password`, mas `OAuth2PasswordRequestForm` não o impõe. |
||||
|
|
||||
|
Se você precisar aplicá-lo, use `OAuth2PasswordRequestFormStrict` em vez de `OAuth2PasswordRequestForm`. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
* Um `client_id` opcional (não precisamos dele em nosso exemplo). |
||||
|
* Um `client_secret` opcional (não precisamos dele em nosso exemplo). |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
O `OAuth2PasswordRequestForm` não é uma classe especial para **FastAPI** como é `OAuth2PasswordBearer`. |
||||
|
|
||||
|
`OAuth2PasswordBearer` faz com que **FastAPI** saiba que é um esquema de segurança. Portanto, é adicionado dessa forma ao OpenAPI. |
||||
|
|
||||
|
Mas `OAuth2PasswordRequestForm` é apenas uma dependência de classe que você mesmo poderia ter escrito ou poderia ter declarado os parâmetros do `Form` (formulário) diretamente. |
||||
|
|
||||
|
Mas como é um caso de uso comum, ele é fornecido diretamente pelo **FastAPI**, apenas para facilitar. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Use os dados do formulário |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
A instância da classe de dependência `OAuth2PasswordRequestForm` não terá um atributo `scope` com a string longa separada por espaços, em vez disso, terá um atributo `scopes` com a lista real de strings para cada escopo enviado. |
||||
|
|
||||
|
Não estamos usando `scopes` neste exemplo, mas a funcionalidade está disponível se você precisar. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Agora, obtenha os dados do usuário do banco de dados (falso), usando o `username` do campo do formulário. |
||||
|
|
||||
|
Se não existir tal usuário, retornaremos um erro dizendo "Incorrect username or password" (Nome de usuário ou senha incorretos). |
||||
|
|
||||
|
Para o erro, usamos a exceção `HTTPException`: |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="3 79-81" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="3 79-81" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="3 80-82" |
||||
|
{!> ../../docs_src/security/tutorial003_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.10+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="1 75-77" |
||||
|
{!> ../../docs_src/security/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="3 77-79" |
||||
|
{!> ../../docs_src/security/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
### Confira a password (senha) |
||||
|
|
||||
|
Neste ponto temos os dados do usuário do nosso banco de dados, mas não verificamos a senha. |
||||
|
|
||||
|
Vamos colocar esses dados primeiro no modelo `UserInDB` do Pydantic. |
||||
|
|
||||
|
Você nunca deve salvar senhas em texto simples, portanto, usaremos o sistema de hashing de senhas (falsas). |
||||
|
|
||||
|
Se as senhas não corresponderem, retornaremos o mesmo erro. |
||||
|
|
||||
|
#### Hashing de senha |
||||
|
|
||||
|
"Hashing" significa: converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece algo sem sentido. |
||||
|
|
||||
|
Sempre que você passa exatamente o mesmo conteúdo (exatamente a mesma senha), você obtém exatamente a mesma sequência aleatória de caracteres. |
||||
|
|
||||
|
Mas você não pode converter a sequência aleatória de caracteres de volta para a senha. |
||||
|
|
||||
|
##### Porque usar hashing de senha |
||||
|
|
||||
|
Se o seu banco de dados for roubado, o ladrão não terá as senhas em texto simples dos seus usuários, apenas os hashes. |
||||
|
|
||||
|
Assim, o ladrão não poderá tentar usar essas mesmas senhas em outro sistema (como muitos usuários usam a mesma senha em todos os lugares, isso seria perigoso). |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="82-85" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="82-85" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="83-86" |
||||
|
{!> ../../docs_src/security/tutorial003_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.10+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="78-81" |
||||
|
{!> ../../docs_src/security/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="80-83" |
||||
|
{!> ../../docs_src/security/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
#### Sobre `**user_dict` |
||||
|
|
||||
|
`UserInDB(**user_dict)` significa: |
||||
|
|
||||
|
*Passe as keys (chaves) e values (valores) de `user_dict` diretamente como argumentos de valor-chave, equivalente a:* |
||||
|
|
||||
|
```Python |
||||
|
UserInDB( |
||||
|
username = user_dict["username"], |
||||
|
email = user_dict["email"], |
||||
|
full_name = user_dict["full_name"], |
||||
|
disabled = user_dict["disabled"], |
||||
|
hashed_password = user_dict["hashed_password"], |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
Para uma explicação mais completa de `**user_dict`, verifique [a documentação para **Extra Models**](../extra-models.md#about-user_indict){.internal-link target=_blank}. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Retorne o token |
||||
|
|
||||
|
A resposta do endpoint `token` deve ser um objeto JSON. |
||||
|
|
||||
|
Deve ter um `token_type`. No nosso caso, como estamos usando tokens "Bearer", o tipo de token deve ser "`bearer`". |
||||
|
|
||||
|
E deve ter um `access_token`, com uma string contendo nosso token de acesso. |
||||
|
|
||||
|
Para este exemplo simples, seremos completamente inseguros e retornaremos o mesmo `username` do token. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
No próximo capítulo, você verá uma implementação realmente segura, com hash de senha e tokens <abbr title="JSON Web Tokens">JWT</abbr>. |
||||
|
|
||||
|
Mas, por enquanto, vamos nos concentrar nos detalhes específicos de que precisamos. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="87" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="87" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="88" |
||||
|
{!> ../../docs_src/security/tutorial003_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.10+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="83" |
||||
|
{!> ../../docs_src/security/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="85" |
||||
|
{!> ../../docs_src/security/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Pela especificação, você deve retornar um JSON com um `access_token` e um `token_type`, o mesmo que neste exemplo. |
||||
|
|
||||
|
Isso é algo que você mesmo deve fazer em seu código e certifique-se de usar essas chaves JSON. |
||||
|
|
||||
|
É quase a única coisa que você deve se lembrar de fazer corretamente, para estar em conformidade com as especificações. |
||||
|
|
||||
|
De resto, **FastAPI** cuida disso para você. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Atualize as dependências |
||||
|
|
||||
|
Agora vamos atualizar nossas dependências. |
||||
|
|
||||
|
Queremos obter o `user_user` *somente* se este usuário estiver ativo. |
||||
|
|
||||
|
Portanto, criamos uma dependência adicional `get_current_active_user` que por sua vez usa `get_current_user` como dependência. |
||||
|
|
||||
|
Ambas as dependências retornarão apenas um erro HTTP se o usuário não existir ou se estiver inativo. |
||||
|
|
||||
|
Portanto, em nosso endpoint, só obteremos um usuário se o usuário existir, tiver sido autenticado corretamente e estiver ativo: |
||||
|
|
||||
|
//// tab | Python 3.10+ |
||||
|
|
||||
|
```Python hl_lines="58-66 69-74 94" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.9+ |
||||
|
|
||||
|
```Python hl_lines="58-66 69-74 94" |
||||
|
{!> ../../docs_src/security/tutorial003_an_py39.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ |
||||
|
|
||||
|
```Python hl_lines="59-67 70-75 95" |
||||
|
{!> ../../docs_src/security/tutorial003_an.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.10+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="56-64 67-70 88" |
||||
|
{!> ../../docs_src/security/tutorial003_py310.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Python 3.8+ non-Annotated |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Prefira usar a versão `Annotated`, se possível. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
```Python hl_lines="58-66 69-72 90" |
||||
|
{!> ../../docs_src/security/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
/// info | Informação |
||||
|
|
||||
|
O cabeçalho adicional `WWW-Authenticate` com valor `Bearer` que estamos retornando aqui também faz parte da especificação. |
||||
|
|
||||
|
Qualquer código de status HTTP (erro) 401 "UNAUTHORIZED" também deve retornar um cabeçalho `WWW-Authenticate`. |
||||
|
|
||||
|
No caso de tokens ao portador (nosso caso), o valor desse cabeçalho deve ser `Bearer`. |
||||
|
|
||||
|
Na verdade, você pode pular esse cabeçalho extra e ainda funcionaria. |
||||
|
|
||||
|
Mas é fornecido aqui para estar em conformidade com as especificações. |
||||
|
|
||||
|
Além disso, pode haver ferramentas que esperam e usam isso (agora ou no futuro) e que podem ser úteis para você ou seus usuários, agora ou no futuro. |
||||
|
|
||||
|
Esse é o benefício dos padrões... |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Veja em ação |
||||
|
|
||||
|
Abra o docs interativo: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
||||
|
|
||||
|
### Autenticação |
||||
|
|
||||
|
Clique no botão "Authorize". |
||||
|
|
||||
|
Use as credenciais: |
||||
|
|
||||
|
User: `johndoe` |
||||
|
|
||||
|
Password: `secret` |
||||
|
|
||||
|
<img src="/img/tutorial/security/image04.png"> |
||||
|
|
||||
|
Após autenticar no sistema, você verá assim: |
||||
|
|
||||
|
<img src="/img/tutorial/security/image05.png"> |
||||
|
|
||||
|
### Obtenha seus próprios dados de usuário |
||||
|
|
||||
|
Agora use a operação `GET` com o caminho `/users/me`. |
||||
|
|
||||
|
Você obterá os dados do seu usuário, como: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"username": "johndoe", |
||||
|
"email": "johndoe@example.com", |
||||
|
"full_name": "John Doe", |
||||
|
"disabled": false, |
||||
|
"hashed_password": "fakehashedsecret" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
<img src="/img/tutorial/security/image06.png"> |
||||
|
|
||||
|
Se você clicar no ícone de cadeado, sair e tentar a mesma operação novamente, receberá um erro HTTP 401 de: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"detail": "Not authenticated" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Usuário inativo |
||||
|
|
||||
|
Agora tente com um usuário inativo, autentique-se com: |
||||
|
|
||||
|
User: `alice` |
||||
|
|
||||
|
Password: `secret2` |
||||
|
|
||||
|
E tente usar a operação `GET` com o caminho `/users/me`. |
||||
|
|
||||
|
Você receberá um erro "Usuário inativo", como: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"detail": "Inactive user" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Recaptulando |
||||
|
|
||||
|
Agora você tem as ferramentas para implementar um sistema de segurança completo baseado em `username` e `password` para sua API. |
||||
|
|
||||
|
Usando essas ferramentas, você pode tornar o sistema de segurança compatível com qualquer banco de dados e com qualquer usuário ou modelo de dados. |
||||
|
|
||||
|
O único detalhe que falta é que ainda não é realmente "seguro". |
||||
|
|
||||
|
No próximo capítulo você verá como usar uma biblioteca de hashing de senha segura e tokens <abbr title="JSON Web Tokens">JWT</abbr>. |
@ -0,0 +1,359 @@ |
|||||
|
# Bancos de Dados SQL (Relacionais) |
||||
|
|
||||
|
**FastAPI** não exige que você use um banco de dados SQL (relacional). Mas você pode usar **qualquer banco de dados** que quiser. |
||||
|
|
||||
|
Aqui veremos um exemplo usando <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>. |
||||
|
|
||||
|
**SQLModel** é construído sobre <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> e Pydantic. Ele foi criado pelo mesmo autor do **FastAPI** para ser o par perfeito para aplicações **FastAPI** que precisam usar **bancos de dados SQL**. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Você pode usar qualquer outra biblioteca de banco de dados SQL ou NoSQL que quiser (em alguns casos chamadas de <abbr title="Object Relational Mapper, um termo sofisticado para uma biblioteca onde algumas classes representam tabelas SQL e instâncias representam linhas nessas tabelas">"ORMs"</abbr>), o FastAPI não obriga você a usar nada. 😎 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Como o SQLModel é baseado no SQLAlchemy, você pode facilmente usar **qualquer banco de dados suportado** pelo SQLAlchemy (o que também os torna suportados pelo SQLModel), como: |
||||
|
|
||||
|
* PostgreSQL |
||||
|
* MySQL |
||||
|
* SQLite |
||||
|
* Oracle |
||||
|
* Microsoft SQL Server, etc. |
||||
|
|
||||
|
Neste exemplo, usaremos **SQLite**, porque ele usa um único arquivo e o Python tem suporte integrado. Assim, você pode copiar este exemplo e executá-lo como está. |
||||
|
|
||||
|
Mais tarde, para sua aplicação em produção, você pode querer usar um servidor de banco de dados como o **PostgreSQL**. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Existe um gerador de projetos oficial com **FastAPI** e **PostgreSQL** incluindo um frontend e mais ferramentas: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a> |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Este é um tutorial muito simples e curto, se você quiser aprender sobre bancos de dados em geral, sobre SQL ou recursos mais avançados, acesse a <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">documentação do SQLModel</a>. |
||||
|
|
||||
|
## Instalar o `SQLModel` |
||||
|
|
||||
|
Primeiro, certifique-se de criar seu [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e, em seguida, instalar o `sqlmodel`: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install sqlmodel |
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## Criar o App com um Único Modelo |
||||
|
|
||||
|
Vamos criar a primeira versão mais simples do app com um único modelo **SQLModel**. |
||||
|
|
||||
|
Depois, vamos melhorá-lo aumentando a segurança e versatilidade com **múltiplos modelos** abaixo. 🤓 |
||||
|
|
||||
|
### Criar Modelos |
||||
|
|
||||
|
Importe o `SQLModel` e crie um modelo de banco de dados: |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} |
||||
|
|
||||
|
A classe `Hero` é muito semelhante a um modelo Pydantic (na verdade, por baixo dos panos, ela *é um modelo Pydantic*). |
||||
|
|
||||
|
Existem algumas diferenças: |
||||
|
|
||||
|
* `table=True` informa ao SQLModel que este é um *modelo de tabela*, ele deve representar uma **tabela** no banco de dados SQL, não é apenas um *modelo de dados* (como seria qualquer outra classe Pydantic comum). |
||||
|
|
||||
|
* `Field(primary_key=True)` informa ao SQLModel que o `id` é a **chave primária** no banco de dados SQL (você pode aprender mais sobre chaves primárias SQL na documentação do SQLModel). |
||||
|
|
||||
|
Ao ter o tipo como `int | None`, o SQLModel saberá que essa coluna deve ser um `INTEGER` no banco de dados SQL e que ela deve ser `NULLABLE`. |
||||
|
|
||||
|
* `Field(index=True)` informa ao SQLModel que ele deve criar um **índice SQL** para essa coluna, o que permitirá buscas mais rápidas no banco de dados ao ler dados filtrados por essa coluna. |
||||
|
|
||||
|
O SQLModel saberá que algo declarado como `str` será uma coluna SQL do tipo `TEXT` (ou `VARCHAR`, dependendo do banco de dados). |
||||
|
|
||||
|
### Criar um Engine |
||||
|
Um `engine` SQLModel (por baixo dos panos, ele é na verdade um `engine` do SQLAlchemy) é o que **mantém as conexões** com o banco de dados. |
||||
|
|
||||
|
Você teria **um único objeto `engine`** para todo o seu código se conectar ao mesmo banco de dados. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} |
||||
|
|
||||
|
Usar `check_same_thread=False` permite que o FastAPI use o mesmo banco de dados SQLite em diferentes threads. Isso é necessário, pois **uma única requisição** pode usar **mais de uma thread** (por exemplo, em dependências). |
||||
|
|
||||
|
Não se preocupe, com a forma como o código está estruturado, garantiremos que usamos **uma única *sessão* SQLModel por requisição** mais tarde, isso é realmente o que o `check_same_thread` está tentando conseguir. |
||||
|
|
||||
|
### Criar as Tabelas |
||||
|
|
||||
|
Em seguida, adicionamos uma função que usa `SQLModel.metadata.create_all(engine)` para **criar as tabelas** para todos os *modelos de tabela*. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} |
||||
|
|
||||
|
### Criar uma Dependência de Sessão |
||||
|
|
||||
|
Uma **`Session`** é o que armazena os **objetos na memória** e acompanha as alterações necessárias nos dados, para então **usar o `engine`** para se comunicar com o banco de dados. |
||||
|
|
||||
|
Vamos criar uma **dependência** do FastAPI com `yield` que fornecerá uma nova `Session` para cada requisição. Isso é o que garante que usamos uma única sessão por requisição. 🤓 |
||||
|
|
||||
|
Então, criamos uma dependência `Annotated` chamada `SessionDep` para simplificar o restante do código que usará essa dependência. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} |
||||
|
|
||||
|
### Criar Tabelas de Banco de Dados na Inicialização |
||||
|
|
||||
|
Vamos criar as tabelas do banco de dados quando o aplicativo for iniciado. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} |
||||
|
|
||||
|
Aqui, criamos as tabelas em um evento de inicialização do aplicativo. |
||||
|
|
||||
|
Para produção, você provavelmente usaria um script de migração que é executado antes de iniciar seu app. 🤓 |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
O SQLModel terá utilitários de migração envolvendo o Alembic, mas por enquanto, você pode usar o <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> diretamente. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Criar um Hero |
||||
|
|
||||
|
Como cada modelo SQLModel também é um modelo Pydantic, você pode usá-lo nas mesmas **anotações de tipo** que usaria para modelos Pydantic. |
||||
|
|
||||
|
Por exemplo, se você declarar um parâmetro do tipo `Hero`, ele será lido do **corpo JSON**. |
||||
|
|
||||
|
Da mesma forma, você pode declará-lo como o **tipo de retorno** da função, e então o formato dos dados aparecerá na interface de documentação automática da API. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
Aqui, usamos a dependência `SessionDep` (uma `Session`) para adicionar o novo `Hero` à instância `Session`, fazer commit das alterações no banco de dados, atualizar os dados no `hero` e então retorná-lo. |
||||
|
|
||||
|
### Ler Heroes |
||||
|
|
||||
|
Podemos **ler** `Hero`s do banco de dados usando um `select()`. Podemos incluir um `limit` e `offset` para paginar os resultados. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} |
||||
|
|
||||
|
### Ler um Único Hero |
||||
|
|
||||
|
Podemos **ler** um único `Hero`. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} |
||||
|
|
||||
|
### Deletar um Hero |
||||
|
|
||||
|
Também podemos **deletar** um `Hero`. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} |
||||
|
|
||||
|
### Executar o App |
||||
|
|
||||
|
Você pode executar o app: |
||||
|
|
||||
|
<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> |
||||
|
|
||||
|
Então, vá para a interface `/docs`, você verá que o **FastAPI** está usando esses **modelos** para **documentar** a API, e ele também os usará para **serializar** e **validar** os dados. |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/sql-databases/image01.png"> |
||||
|
</div> |
||||
|
|
||||
|
## Atualizar o App com Múltiplos Modelos |
||||
|
|
||||
|
Agora vamos **refatorar** este app um pouco para aumentar a **segurança** e **versatilidade**. |
||||
|
|
||||
|
Se você verificar o app anterior, na interface você pode ver que, até agora, ele permite que o cliente decida o `id` do `Hero` a ser criado. 😱 |
||||
|
|
||||
|
Não deveríamos deixar isso acontecer, eles poderiam sobrescrever um `id` que já atribuimos na base de dados. Decidir o `id` deve ser feito pelo **backend** ou pelo **banco de dados**, **não pelo cliente**. |
||||
|
|
||||
|
Além disso, criamos um `secret_name` para o hero, mas até agora estamos retornando ele em todos os lugares, isso não é muito **secreto**... 😅 |
||||
|
|
||||
|
Vamos corrigir essas coisas adicionando alguns **modelos extras**. Aqui é onde o SQLModel vai brilhar. ✨ |
||||
|
|
||||
|
### Criar Múltiplos Modelos |
||||
|
|
||||
|
No **SQLModel**, qualquer classe de modelo que tenha `table=True` é um **modelo de tabela**. |
||||
|
|
||||
|
E qualquer classe de modelo que não tenha `table=True` é um **modelo de dados**, esses são na verdade apenas modelos Pydantic (com alguns recursos extras pequenos). 🤓 |
||||
|
|
||||
|
Com o SQLModel, podemos usar a **herança** para **evitar duplicação** de todos os campos em todos os casos. |
||||
|
|
||||
|
#### `HeroBase` - a classe base |
||||
|
|
||||
|
Vamos começar com um modelo `HeroBase` que tem todos os **campos compartilhados** por todos os modelos: |
||||
|
|
||||
|
* `name` |
||||
|
* `age` |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} |
||||
|
|
||||
|
#### `Hero` - o *modelo de tabela* |
||||
|
|
||||
|
Em seguida, vamos criar `Hero`, o verdadeiro *modelo de tabela*, com os **campos extras** que nem sempre estão nos outros modelos: |
||||
|
|
||||
|
* `id` |
||||
|
* `secret_name` |
||||
|
|
||||
|
Como `Hero` herda de `HeroBase`, ele **também** tem os **campos** declarados em `HeroBase`, então todos os campos para `Hero` são: |
||||
|
|
||||
|
* `id` |
||||
|
* `name` |
||||
|
* `age` |
||||
|
* `secret_name` |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} |
||||
|
|
||||
|
#### `HeroPublic` - o *modelo de dados* público |
||||
|
|
||||
|
Em seguida, criamos um modelo `HeroPublic`, que será **retornado** para os clientes da API. |
||||
|
|
||||
|
Ele tem os mesmos campos que `HeroBase`, então não incluirá `secret_name`. |
||||
|
|
||||
|
Finalmente, a identidade dos nossos heróis está protegida! 🥷 |
||||
|
|
||||
|
Ele também declara novamente `id: int`. Ao fazer isso, estamos fazendo um **contrato** com os clientes da API, para que eles possam sempre esperar que o `id` estará lá e será um `int` (nunca será `None`). |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Fazer com que o modelo de retorno garanta que um valor esteja sempre disponível e sempre seja um `int` (não `None`) é muito útil para os clientes da API, eles podem escrever código muito mais simples com essa certeza. |
||||
|
|
||||
|
Além disso, **clientes gerados automaticamente** terão interfaces mais simples, para que os desenvolvedores que se comunicam com sua API possam ter uma experiência muito melhor trabalhando com sua API. 😎 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Todos os campos em `HeroPublic` são os mesmos que em `HeroBase`, com `id` declarado como `int` (não `None`): |
||||
|
|
||||
|
* `id` |
||||
|
* `name` |
||||
|
* `age` |
||||
|
* `secret_name` |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} |
||||
|
|
||||
|
#### `HeroCreate` - o *modelo de dados* para criar um hero |
||||
|
|
||||
|
Agora criamos um modelo `HeroCreate`, este é o que **validará** os dados dos clientes. |
||||
|
|
||||
|
Ele tem os mesmos campos que `HeroBase`, e também tem `secret_name`. |
||||
|
|
||||
|
Agora, quando os clientes **criarem um novo hero**, eles enviarão o `secret_name`, ele será armazenado no banco de dados, mas esses nomes secretos não serão retornados na API para os clientes. |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
É assim que você trataria **senhas**. Receba-as, mas não as retorne na API. |
||||
|
|
||||
|
Você também faria um **hash** com os valores das senhas antes de armazená-los, **nunca os armazene em texto simples**. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
Os campos de `HeroCreate` são: |
||||
|
|
||||
|
* `name` |
||||
|
* `age` |
||||
|
* `secret_name` |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} |
||||
|
|
||||
|
#### `HeroUpdate` - o *modelo de dados* para atualizar um hero |
||||
|
|
||||
|
Não tínhamos uma maneira de **atualizar um hero** na versão anterior do app, mas agora com **múltiplos modelos**, podemos fazer isso. 🎉 |
||||
|
|
||||
|
O *modelo de dados* `HeroUpdate` é um pouco especial, ele tem **todos os mesmos campos** que seriam necessários para criar um novo hero, mas todos os campos são **opcionais** (todos têm um valor padrão). Dessa forma, quando você atualizar um hero, poderá enviar apenas os campos que deseja atualizar. |
||||
|
|
||||
|
Como todos os **campos realmente mudam** (o tipo agora inclui `None` e eles agora têm um valor padrão de `None`), precisamos **declarar novamente** todos eles. |
||||
|
|
||||
|
Não precisamos herdar de `HeroBase`, pois estamos redeclarando todos os campos. Vou deixá-lo herdando apenas por consistência, mas isso não é necessário. É mais uma questão de gosto pessoal. 🤷 |
||||
|
|
||||
|
Os campos de `HeroUpdate` são: |
||||
|
|
||||
|
* `name` |
||||
|
* `age` |
||||
|
* `secret_name` |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} |
||||
|
|
||||
|
### Criar com `HeroCreate` e retornar um `HeroPublic` |
||||
|
|
||||
|
Agora que temos **múltiplos modelos**, podemos atualizar as partes do app que os utilizam. |
||||
|
|
||||
|
Recebemos na requisição um *modelo de dados* `HeroCreate`, e a partir dele, criamos um *modelo de tabela* `Hero`. |
||||
|
|
||||
|
Esse novo *modelo de tabela* `Hero` terá os campos enviados pelo cliente, e também terá um `id` gerado pelo banco de dados. |
||||
|
|
||||
|
Em seguida, retornamos o mesmo *modelo de tabela* `Hero` como está na função. Mas como declaramos o `response_model` com o *modelo de dados* `HeroPublic`, o **FastAPI** usará `HeroPublic` para validar e serializar os dados. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} |
||||
|
|
||||
|
/// tip | Dica |
||||
|
|
||||
|
Agora usamos `response_model=HeroPublic` em vez da **anotação de tipo de retorno** `-> HeroPublic` porque o valor que estamos retornando na verdade *não* é um `HeroPublic`. |
||||
|
|
||||
|
Se tivéssemos declarado `-> HeroPublic`, seu editor e o linter reclamariam (com razão) que você está retornando um `Hero` em vez de um `HeroPublic`. |
||||
|
|
||||
|
Ao declará-lo no `response_model`, estamos dizendo ao **FastAPI** para fazer o seu trabalho, sem interferir nas anotações de tipo e na ajuda do seu editor e de outras ferramentas. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
### Ler Heroes com `HeroPublic` |
||||
|
|
||||
|
Podemos fazer o mesmo que antes para **ler** `Hero`s, novamente, usamos `response_model=list[HeroPublic]` para garantir que os dados sejam validados e serializados corretamente. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} |
||||
|
|
||||
|
### Ler Um Hero com `HeroPublic` |
||||
|
|
||||
|
Podemos **ler** um único herói: |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} |
||||
|
|
||||
|
### Atualizar um Hero com `HeroUpdate` |
||||
|
|
||||
|
Podemos **atualizar um hero**. Para isso, usamos uma operação HTTP `PATCH`. |
||||
|
|
||||
|
E no código, obtemos um `dict` com todos os dados enviados pelo cliente, **apenas os dados enviados pelo cliente**, excluindo quaisquer valores que estariam lá apenas por serem os valores padrão. Para fazer isso, usamos `exclude_unset=True`. Este é o truque principal. 🪄 |
||||
|
|
||||
|
Em seguida, usamos `hero_db.sqlmodel_update(hero_data)` para atualizar o `hero_db` com os dados de `hero_data`. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} |
||||
|
|
||||
|
### Deletar um Hero Novamente |
||||
|
|
||||
|
**Deletar** um hero permanece praticamente o mesmo. |
||||
|
|
||||
|
Não vamos satisfazer o desejo de refatorar tudo neste aqui. 😅 |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} |
||||
|
|
||||
|
### Executar o App Novamente |
||||
|
|
||||
|
Você pode executar o app novamente: |
||||
|
|
||||
|
<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> |
||||
|
|
||||
|
If you go to the `/docs` API UI, you will see that it is now updated, and it won't expect to receive the `id` from the client when creating a hero, etc. |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/sql-databases/image02.png"> |
||||
|
</div> |
||||
|
|
||||
|
## Recapitulando |
||||
|
|
||||
|
Você pode usar <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> para interagir com um banco de dados SQL e simplificar o código com *modelos de dados* e *modelos de tabela*. |
||||
|
|
||||
|
Você pode aprender muito mais na documentação do **SQLModel**, há um mini <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">tutorial sobre como usar SQLModel com **FastAPI**</a> mais longo. 🚀 |
@ -1,13 +1,13 @@ |
|||||
# 使用指南 - 範例集 |
# 使用指南 - 範例集 |
||||
|
|
||||
在這裡,你將會看到 **不同主題** 的範例或「如何使用」的指南。 |
在這裡,你將會看到**不同主題**的範例或「如何使用」的指南。 |
||||
|
|
||||
大多數這些想法都是 **獨立** 的,在大多數情況下,你只需要研究那些直接適用於 **你的專案** 的東西。 |
大多數這些想法都是**獨立**的,在大多數情況下,你只需要研究那些直接適用於**你的專案**的東西。 |
||||
|
|
||||
如果有些東西看起來很有趣且對你的專案很有用的話再去讀它,否則你可能可以跳過它們。 |
如果有些東西看起來很有趣且對你的專案很有用的話再去讀它,否則你可能可以跳過它們。 |
||||
|
|
||||
/// tip |
/// tip |
||||
|
|
||||
如果你想要以結構化的方式 **學習 FastAPI**(推薦),請前往[教學 - 使用者指南](../tutorial/index.md){.internal-link target=_blank}逐章閱讀。 |
如果你想要以結構化的方式**學習 FastAPI**(推薦),請前往[教學 - 使用者指南](../tutorial/index.md){.internal-link target=_blank}逐章閱讀。 |
||||
|
|
||||
/// |
/// |
||||
|
@ -0,0 +1,844 @@ |
|||||
|
# 虚拟环境 |
||||
|
|
||||
|
当你在 Python 工程中工作时,你可能会有必要用到一个**虚拟环境**(或类似的机制)来隔离你为每个工程安装的包。 |
||||
|
|
||||
|
/// info |
||||
|
|
||||
|
如果你已经了解虚拟环境,知道如何创建和使用它们,你可以考虑跳过这一部分。🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
**虚拟环境**和**环境变量**是不同的。 |
||||
|
|
||||
|
**环境变量**是系统中的一个变量,可以被程序使用。 |
||||
|
|
||||
|
**虚拟环境**是一个包含一些文件的目录。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
/// 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`: 使用名为 `venv` 的模块,这个模块通常随 Python 一起安装 |
||||
|
* `.venv`: 在新目录 `.venv` 中创建虚拟环境 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | `uv` |
||||
|
|
||||
|
如果你安装了 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>,你也可以使用它来创建一个虚拟环境。 |
||||
|
|
||||
|
<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="命令行界面">CLI</abbr>)程序** 时,你使用的是你的虚拟环境中的程序,而不是全局安装、可能版本不同的程序。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## 检查虚拟环境是否激活 |
||||
|
|
||||
|
检查虚拟环境是否激活 (前面的命令是否生效)。 |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
这是 **可选的**,但这是一个很好的方法,可以 **检查** 一切是否按预期工作,以及你是否使用了你打算使用的虚拟环境。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
//// tab | Linux, macOS, Windows Bash |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ which python |
||||
|
|
||||
|
/home/user/code/awesome-project/.venv/bin/python |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
如果它显示了在你工程 (在这个例子中是 `awesome-project`) 的 `.venv/bin/python` 中的 `python` 二进制文件,那么它就生效了。🎉 |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Windows PowerShell |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ Get-Command python |
||||
|
|
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts\python |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
如果它显示了在你工程 (在这个例子中是 `awesome-project`) 的 `.venv\Scripts\python` 中的 `python` 二进制文件,那么它就生效了。🎉 |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
## 升级 `pip` |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
如果你使用 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> 来安装内容,而不是 `pip`,那么你就不需要升级 `pip`。😎 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
如果你使用 `pip` 来安装包(它是 Python 的默认组件),你应该将它 **升级** 到最新版本。 |
||||
|
|
||||
|
在安装包时出现的许多奇怪的错误都可以通过先升级 `pip` 来解决。 |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
通常你只需要在创建虚拟环境后 **执行一次** 这个操作。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
确保虚拟环境是激活的 (使用上面的命令),然后运行: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ python -m pip install --upgrade pip |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## 添加 `.gitignore` |
||||
|
|
||||
|
如果你使用 **Git** (这是你应该使用的),添加一个 `.gitignore` 文件来排除你的 `.venv` 中的所有内容。 |
||||
|
|
||||
|
/// 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 "*"`: 将在终端中 "打印" 文本 `*`(接下来的部分会对这个操作进行一些修改) |
||||
|
* `>`: 使左边的命令打印到终端的任何内容实际上都不会被打印,而是会被写入到右边的文件中 |
||||
|
* `.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> |
||||
|
|
||||
|
## 配置编辑器 |
||||
|
|
||||
|
你可能会用到编辑器(即 IDE —— 译者注),请确保配置它使用与你创建的相同的虚拟环境(它可能会自动检测到),以便你可以获得自动补全和内联错误提示。 |
||||
|
|
||||
|
例如: |
||||
|
|
||||
|
* <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` 时,它不会尝试从安装了软件包的虚拟环境中运行。(即,它将不再会尝试从虚拟环境中运行,也不会使用其中安装的软件包。—— 译者注) |
||||
|
|
||||
|
## 开始工作 |
||||
|
|
||||
|
现在你已经准备好开始你的工作了。 |
||||
|
|
||||
|
|
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
你想要理解上面的所有内容吗? |
||||
|
|
||||
|
继续阅读。👇🤓 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## 为什么要使用虚拟环境 |
||||
|
|
||||
|
你需要安装 <a href="https://www.python.org/" class="external-link" target="_blank">Python</a> 才能使用 FastAPI。 |
||||
|
|
||||
|
之后,你需要**安装** FastAPI 和你想要使用的任何其他**软件包**。 |
||||
|
|
||||
|
要安装软件包,你通常会使用随 Python 一起提供的 `pip` 命令(或类似的替代方案)。 |
||||
|
|
||||
|
然而,如果你直接使用 `pip`,软件包将被安装在你的**全局 Python 环境**中(即 Python 的全局安装)。 |
||||
|
|
||||
|
### 存在的问题 |
||||
|
|
||||
|
那么,在全局 Python 环境中安装软件包有什么问题呢? |
||||
|
|
||||
|
有些时候,你可能会编写许多不同的程序,这些程序依赖于**不同的软件包**;你所做的一些工程也会依赖于**同一软件包的不同版本**。😱 |
||||
|
|
||||
|
例如,你可能会创建一个名为 `philosophers-stone` 的工程,这个程序依赖于另一个名为 **`harry` 的软件包,使用版本 `1`**。因此,你需要安装 `harry`。 |
||||
|
|
||||
|
```mermaid |
||||
|
flowchart LR |
||||
|
stone(philosophers-stone) -->|需要| harry-1[harry v1] |
||||
|
``` |
||||
|
|
||||
|
然而在此之后,你又创建了另一个名为 `prisoner-of-azkaban` 的工程,这个工程也依赖于 `harry`,但是这个工程需要 **`harry` 版本 `3`**。 |
||||
|
|
||||
|
```mermaid |
||||
|
flowchart LR |
||||
|
azkaban(prisoner-of-azkaban) --> |需要| harry-3[harry v3] |
||||
|
``` |
||||
|
|
||||
|
那么现在的问题是,如果你将软件包安装在全局环境中而不是在本地**虚拟环境**中,你将不得不面临选择安装哪个版本的 `harry` 的问题。 |
||||
|
|
||||
|
如果你想运行 `philosophers-stone`,你需要首先安装 `harry` 版本 `1`,例如: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install "harry==1" |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
然后你将在全局 Python 环境中安装 `harry` 版本 `1`。 |
||||
|
|
||||
|
```mermaid |
||||
|
flowchart LR |
||||
|
subgraph global[全局环境] |
||||
|
harry-1[harry v1] |
||||
|
end |
||||
|
subgraph stone-project[工程 philosophers-stone] |
||||
|
stone(philosophers-stone) -->|需要| harry-1 |
||||
|
end |
||||
|
``` |
||||
|
|
||||
|
但是如果你想运行 `prisoner-of-azkaban`,你需要卸载 `harry` 版本 `1` 并安装 `harry` 版本 `3`(或者说,只要你安装版本 `3` ,版本 `1` 就会自动卸载)。 |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install "harry==3" |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
于是,你在你的全局 Python 环境中安装了 `harry` 版本 `3`。 |
||||
|
|
||||
|
如果你再次尝试运行 `philosophers-stone`,有可能它**无法正常工作**,因为它需要 `harry` 版本 `1`。 |
||||
|
|
||||
|
```mermaid |
||||
|
flowchart LR |
||||
|
subgraph global[全局环境] |
||||
|
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] |
||||
|
stone(philosophers-stone) -.-x|⛔️| harry-1 |
||||
|
end |
||||
|
subgraph azkaban-project[工程 prisoner-of-azkaban] |
||||
|
azkaban(prisoner-of-azkaban) --> |需要| harry-3 |
||||
|
end |
||||
|
``` |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
Python 包在推出**新版本**时通常会尽量**避免破坏性更改**,但最好还是要小心,要想清楚再安装新版本,而且在运行测试以确保一切能正常工作时再安装。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
现在,想象一下,如果有**许多**其他**软件包**,它们都是你的**工程所依赖的**。这是非常难以管理的。你可能会发现,有些工程使用了一些**不兼容的软件包版本**,而不知道为什么某些东西无法正常工作。 |
||||
|
|
||||
|
此外,取决于你的操作系统(例如 Linux、Windows、macOS),它可能已经预先安装了 Python。在这种情况下,它可能已经预先安装了一些软件包,这些软件包的特定版本是**系统所需的**。如果你在全局 Python 环境中安装软件包,你可能会**破坏**一些随操作系统一起安装的程序。 |
||||
|
|
||||
|
## 软件包安装在哪里 |
||||
|
|
||||
|
当你安装 Python 时,它会在你的计算机上创建一些目录,并在这些目录中放一些文件。 |
||||
|
|
||||
|
其中一些目录负责存放你安装的所有软件包。 |
||||
|
|
||||
|
当你运行: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
// 先别去运行这个命令,这只是一个示例 🤓 |
||||
|
$ pip install "fastapi[standard]" |
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
这将会从 <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a> 下载一个压缩文件,其中包含 FastAPI 代码。 |
||||
|
|
||||
|
它还会**下载** FastAPI 依赖的其他软件包的文件。 |
||||
|
|
||||
|
然后它会**解压**所有这些文件,并将它们放在你的计算机上的一个目录中。 |
||||
|
|
||||
|
默认情况下,它会将下载并解压的这些文件放在随 Python 安装的目录中,这就是**全局环境**。 |
||||
|
|
||||
|
## 什么是虚拟环境 |
||||
|
|
||||
|
解决软件包都安装在全局环境中的问题的方法是为你所做的每个工程使用一个**虚拟环境**。 |
||||
|
|
||||
|
虚拟环境是一个**目录**,与全局环境非常相似,你可以在其中专为某个工程安装软件包。 |
||||
|
|
||||
|
这样,每个工程都会有自己的虚拟环境(`.venv` 目录),其中包含自己的软件包。 |
||||
|
|
||||
|
```mermaid |
||||
|
flowchart TB |
||||
|
subgraph stone-project[工程 philosophers-stone] |
||||
|
stone(philosophers-stone) --->|需要| harry-1 |
||||
|
subgraph venv1[.venv] |
||||
|
harry-1[harry v1] |
||||
|
end |
||||
|
end |
||||
|
subgraph azkaban-project[工程 prisoner-of-azkaban] |
||||
|
azkaban(prisoner-of-azkaban) --->|需要| harry-3 |
||||
|
subgraph venv2[.venv] |
||||
|
harry-3[harry v3] |
||||
|
end |
||||
|
end |
||||
|
stone-project ~~~ azkaban-project |
||||
|
``` |
||||
|
|
||||
|
## 激活虚拟环境意味着什么 |
||||
|
|
||||
|
当你激活了一个虚拟环境,例如: |
||||
|
|
||||
|
//// tab | Linux, macOS |
||||
|
|
||||
|
<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 |
||||
|
|
||||
|
你可以在 [环境变量](environment-variables.md#path-environment-variable){.internal-link target=_blank} 部分了解更多关于 `PATH` 环境变量的内容。 |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
激活虚拟环境会将其路径 `.venv/bin`(在 Linux 和 macOS 上)或 `.venv\Scripts`(在 Windows 上)添加到 `PATH` 环境变量中。 |
||||
|
|
||||
|
假设在激活环境之前,`PATH` 变量看起来像这样: |
||||
|
|
||||
|
//// tab | Linux, macOS |
||||
|
|
||||
|
```plaintext |
||||
|
/usr/bin:/bin:/usr/sbin:/sbin |
||||
|
``` |
||||
|
|
||||
|
这意味着系统会在以下目录中查找程序: |
||||
|
|
||||
|
* `/usr/bin` |
||||
|
* `/bin` |
||||
|
* `/usr/sbin` |
||||
|
* `/sbin` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Windows |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Windows\System32 |
||||
|
``` |
||||
|
|
||||
|
这意味着系统会在以下目录中查找程序: |
||||
|
|
||||
|
* `C:\Windows\System32` |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
激活虚拟环境后,`PATH` 变量会变成这样: |
||||
|
|
||||
|
//// tab | Linux, macOS |
||||
|
|
||||
|
```plaintext |
||||
|
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin |
||||
|
``` |
||||
|
|
||||
|
这意味着系统现在会首先在以下目录中查找程序: |
||||
|
|
||||
|
```plaintext |
||||
|
/home/user/code/awesome-project/.venv/bin |
||||
|
``` |
||||
|
|
||||
|
然后再在其他目录中查找。 |
||||
|
|
||||
|
因此,当你在终端中输入 `python` 时,系统会在以下目录中找到 Python 程序: |
||||
|
|
||||
|
```plaintext |
||||
|
/home/user/code/awesome-project/.venv/bin/python |
||||
|
``` |
||||
|
|
||||
|
并使用这个。 |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
//// tab | Windows |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 |
||||
|
``` |
||||
|
|
||||
|
这意味着系统现在会首先在以下目录中查找程序: |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts |
||||
|
``` |
||||
|
|
||||
|
然后再在其他目录中查找。 |
||||
|
|
||||
|
因此,当你在终端中输入 `python` 时,系统会在以下目录中找到 Python 程序: |
||||
|
|
||||
|
```plaintext |
||||
|
C:\Users\user\code\awesome-project\.venv\Scripts\python |
||||
|
``` |
||||
|
|
||||
|
并使用这个。 |
||||
|
|
||||
|
//// |
||||
|
|
||||
|
一个重要的细节是,虚拟环境路径会被放在 `PATH` 变量的**开头**。系统会在找到任何其他可用的 Python **之前**找到它。这样,当你运行 `python` 时,它会使用**虚拟环境中**的 Python,而不是任何其他 `python`(例如,全局环境中的 `python`)。 |
||||
|
|
||||
|
激活虚拟环境还会改变其他一些东西,但这是它所做的最重要的事情之一。 |
||||
|
|
||||
|
## 检查虚拟环境 |
||||
|
|
||||
|
当你检查虚拟环境是否激活时,例如: |
||||
|
|
||||
|
//// tab | Linux, macOS, Windows Bash |
||||
|
|
||||
|
<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> |
||||
|
|
||||
|
但是如果你停用虚拟环境并激活 `prisoner-of-askaban` 的新虚拟环境,那么当你运行 `python` 时,它会使用 `prisoner-of-askaban` 中的虚拟环境中的 Python。 |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ cd ~/code/prisoner-of-azkaban |
||||
|
|
||||
|
// 你不需要在旧目录中操作停用,你可以在任何地方操作停用,甚至在转到另一个工程之后 😎 |
||||
|
$ deactivate |
||||
|
|
||||
|
// 激活 prisoner-of-azkaban/.venv 中的虚拟环境 🚀 |
||||
|
$ source .venv/bin/activate |
||||
|
|
||||
|
// 现在当你运行 python 时,它会在这个虚拟环境中找到安装的 sirius 包 ✨ |
||||
|
$ python main.py |
||||
|
|
||||
|
I solemnly swear 🐺 |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## 替代方案 |
||||
|
|
||||
|
这是一个简单的指南,可以帮助你入门并教会你如何理解一切**底层**的东西。 |
||||
|
|
||||
|
有许多**替代方案**来管理虚拟环境、包依赖(requirements)、工程。 |
||||
|
|
||||
|
一旦你准备好并想要使用一个工具来**管理整个工程**、包依赖、虚拟环境等,建议你尝试 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>。 |
||||
|
|
||||
|
`uv` 可以做很多事情,它可以: |
||||
|
|
||||
|
* 为你**安装 Python**,包括不同的版本 |
||||
|
* 为你的工程管理**虚拟环境** |
||||
|
* 安装**软件包** |
||||
|
* 为你的工程管理软件包的**依赖和版本** |
||||
|
* 确保你有一个**确切**的软件包和版本集合来安装,包括它们的依赖项,这样你就可以确保在生产中运行你的工程与在开发时在你的计算机上运行的工程完全相同,这被称为**锁定** |
||||
|
* 还有很多其他功能 |
||||
|
|
||||
|
## 结论 |
||||
|
|
||||
|
如果你读过并理解了所有这些,现在**你对虚拟环境的了解比很多开发者都要多**。🤓 |
||||
|
|
||||
|
在未来当你调试看起来复杂的东西时,了解这些细节很可能会有用,你会知道**它是如何在底层工作的**。😎 |
@ -1,6 +1,6 @@ |
|||||
-e .[all] |
-e .[all] |
||||
-r requirements-tests.txt |
-r requirements-tests.txt |
||||
-r requirements-docs.txt |
-r requirements-docs.txt |
||||
pre-commit >=2.17.0,<4.0.0 |
pre-commit >=2.17.0,<5.0.0 |
||||
# For generating screenshots |
# For generating screenshots |
||||
playwright |
playwright |
||||
|
Loading…
Reference in new issue