56 changed files with 3178 additions and 200 deletions
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,84 @@ |
|||||
|
# 교차 출처 리소스 공유 |
||||
|
|
||||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS 또는 "교차-출처 리소스 공유"</a>란, 브라우저에서 동작하는 프론트엔드가 자바스크립트로 코드로 백엔드와 통신하고, 백엔드는 해당 프론트엔드와 다른 "출처"에 존재하는 상황을 의미합니다. |
||||
|
|
||||
|
## 출처 |
||||
|
|
||||
|
출처란 프로토콜(`http` , `https`), 도메인(`myapp.com`, `localhost`, `localhost.tiangolo.com` ), 그리고 포트(`80`, `443`, `8080` )의 조합을 의미합니다. |
||||
|
|
||||
|
따라서, 아래는 모두 상이한 출처입니다: |
||||
|
|
||||
|
* `http://localhost` |
||||
|
* `https://localhost` |
||||
|
* `http://localhost:8080` |
||||
|
|
||||
|
모두 `localhost` 에 있지만, 서로 다른 프로토콜과 포트를 사용하고 있으므로 다른 "출처"입니다. |
||||
|
|
||||
|
## 단계 |
||||
|
|
||||
|
브라우저 내 `http://localhost:8080`에서 동작하는 프론트엔드가 있고, 자바스크립트는 `http://localhost`를 통해 백엔드와 통신한다고 가정해봅시다(포트를 명시하지 않는 경우, 브라우저는 `80` 을 기본 포트로 간주합니다). |
||||
|
|
||||
|
그러면 브라우저는 백엔드에 HTTP `OPTIONS` 요청을 보내고, 백엔드에서 이 다른 출처(`http://localhost:8080`)와의 통신을 허가하는 적절한 헤더를 보내면, 브라우저는 프론트엔드의 자바스크립트가 백엔드에 요청을 보낼 수 있도록 합니다. |
||||
|
|
||||
|
이를 위해, 백엔드는 "허용된 출처(allowed origins)" 목록을 가지고 있어야만 합니다. |
||||
|
|
||||
|
이 경우, 프론트엔드가 제대로 동작하기 위해 `http://localhost:8080`을 목록에 포함해야 합니다. |
||||
|
|
||||
|
## 와일드카드 |
||||
|
|
||||
|
모든 출처를 허용하기 위해 목록을 `"*"` ("와일드카드")로 선언하는 것도 가능합니다. |
||||
|
|
||||
|
하지만 이것은 특정한 유형의 통신만을 허용하며, 쿠키 및 액세스 토큰과 사용되는 인증 헤더(Authoriztion header) 등이 포함된 경우와 같이 자격 증명(credentials)이 포함된 통신은 허용되지 않습니다. |
||||
|
|
||||
|
따라서 모든 작업을 의도한대로 실행하기 위해, 허용되는 출처를 명시적으로 지정하는 것이 좋습니다. |
||||
|
|
||||
|
## `CORSMiddleware` 사용 |
||||
|
|
||||
|
`CORSMiddleware` 을 사용하여 **FastAPI** 응용 프로그램의 교차 출처 리소스 공유 환경을 설정할 수 있습니다. |
||||
|
|
||||
|
* `CORSMiddleware` 임포트. |
||||
|
* 허용되는 출처(문자열 형식)의 리스트 생성. |
||||
|
* FastAPI 응용 프로그램에 "미들웨어(middleware)"로 추가. |
||||
|
|
||||
|
백엔드에서 다음의 사항을 허용할지에 대해 설정할 수도 있습니다: |
||||
|
|
||||
|
* 자격증명 (인증 헤더, 쿠키 등). |
||||
|
* 특정한 HTTP 메소드(`POST`, `PUT`) 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 메소드. |
||||
|
* 특정한 HTTP 헤더 또는 와일드카드 `"*"` 를 사용한 모든 HTTP 헤더. |
||||
|
|
||||
|
```Python hl_lines="2 6-11 13-19" |
||||
|
{!../../../docs_src/cors/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
`CORSMiddleware` 에서 사용하는 기본 매개변수는 제한적이므로, 브라우저가 교차-도메인 상황에서 특정한 출처, 메소드, 헤더 등을 사용할 수 있도록 하려면 이들을 명시적으로 허용해야 합니다. |
||||
|
|
||||
|
다음의 인자들이 지원됩니다: |
||||
|
|
||||
|
* `allow_origins` - 교차-출처 요청을 보낼 수 있는 출처의 리스트입니다. 예) `['https://example.org', 'https://www.example.org']`. 모든 출처를 허용하기 위해 `['*']` 를 사용할 수 있습니다. |
||||
|
* `allow_origin_regex` - 교차-출처 요청을 보낼 수 있는 출처를 정규표현식 문자열로 나타냅니다. `'https://.*\.example\.org'`. |
||||
|
* `allow_methods` - 교차-출처 요청을 허용하는 HTTP 메소드의 리스트입니다. 기본값은 `['GET']` 입니다. `['*']` 을 사용하여 모든 표준 메소드들을 허용할 수 있습니다. |
||||
|
* `allow_headers` - 교차-출처를 지원하는 HTTP 요청 헤더의 리스트입니다. 기본값은 `[]` 입니다. 모든 헤더들을 허용하기 위해 `['*']` 를 사용할 수 있습니다. `Accept`, `Accept-Language`, `Content-Language` 그리고 `Content-Type` 헤더는 CORS 요청시 언제나 허용됩니다. |
||||
|
* `allow_credentials` - 교차-출처 요청시 쿠키 지원 여부를 설정합니다. 기본값은 `False` 입니다. 또한 해당 항목을 허용할 경우 `allow_origins` 는 `['*']` 로 설정할 수 없으며, 출처를 반드시 특정해야 합니다. |
||||
|
* `expose_headers` - 브라우저에 접근할 수 있어야 하는 모든 응답 헤더를 가리킵니다. 기본값은 `[]` 입니다. |
||||
|
* `max_age` - 브라우저가 CORS 응답을 캐시에 저장하는 최대 시간을 초 단위로 설정합니다. 기본값은 `600` 입니다. |
||||
|
|
||||
|
미들웨어는 두가지 특정한 종류의 HTTP 요청에 응답합니다... |
||||
|
|
||||
|
### CORS 사전 요청 |
||||
|
|
||||
|
`Origin` 및 `Access-Control-Request-Method` 헤더와 함께 전송하는 모든 `OPTIONS` 요청입니다. |
||||
|
|
||||
|
이 경우 미들웨어는 들어오는 요청을 가로채 적절한 CORS 헤더와, 정보 제공을 위한 `200` 또는 `400` 응답으로 응답합니다. |
||||
|
|
||||
|
### 단순한 요청 |
||||
|
|
||||
|
`Origin` 헤더를 가진 모든 요청. 이 경우 미들웨어는 요청을 정상적으로 전달하지만, 적절한 CORS 헤더를 응답에 포함시킵니다. |
||||
|
|
||||
|
## 더 많은 정보 |
||||
|
|
||||
|
<abbr title="교차-출처 리소스 공유">CORS</abbr>에 대한 더 많은 정보를 알고싶다면, <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS 문서</a>를 참고하기 바랍니다. |
||||
|
|
||||
|
!!! note "기술적 세부 사항" |
||||
|
`from starlette.middleware.cors import CORSMiddleware` 역시 사용할 수 있습니다. |
||||
|
|
||||
|
**FastAPI**는 개발자인 당신의 편의를 위해 `fastapi.middleware` 에서 몇가지의 미들웨어를 제공합니다. 하지만 대부분의 미들웨어가 Stralette으로부터 직접 제공됩니다. |
@ -0,0 +1,180 @@ |
|||||
|
|
||||
|
# Люди, поддерживающие FastAPI |
||||
|
|
||||
|
У FastAPI замечательное сообщество, которое доброжелательно к людям с любым уровнем знаний. |
||||
|
|
||||
|
## Создатель и хранитель |
||||
|
|
||||
|
Ку! 👋 |
||||
|
|
||||
|
Это я: |
||||
|
|
||||
|
{% if people %} |
||||
|
<div class="user-list user-list-center"> |
||||
|
{% for user in people.maintainers %} |
||||
|
|
||||
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Answers: {{ user.answers }}</div><div class="count">Pull Requests: {{ user.prs }}</div></div> |
||||
|
{% endfor %} |
||||
|
|
||||
|
</div> |
||||
|
{% endif %} |
||||
|
|
||||
|
Я создал и продолжаю поддерживать **FastAPI**. Узнать обо мне больше можно тут [Помочь FastAPI - Получить помощь - Связаться с автором](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}. |
||||
|
|
||||
|
... но на этой странице я хочу показать вам наше сообщество. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**FastAPI** получает огромную поддержку от своего сообщества. И я хочу отметить вклад его участников. |
||||
|
|
||||
|
Это люди, которые: |
||||
|
|
||||
|
* [Помогают другим с их проблемами (вопросами) на GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}. |
||||
|
* [Создают пул-реквесты](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}. |
||||
|
* Делают ревью пул-реквестов, [что особенно важно для переводов на другие языки](contributing.md#translations){.internal-link target=_blank}. |
||||
|
|
||||
|
Поаплодируем им! 👏 🙇 |
||||
|
|
||||
|
## Самые активные участники за прошедший месяц |
||||
|
|
||||
|
Эти участники [оказали наибольшую помощь другим с решением их проблем (вопросов) на GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} в течение последнего месяца. ☕ |
||||
|
|
||||
|
{% if people %} |
||||
|
<div class="user-list user-list-center"> |
||||
|
{% for user in people.last_month_active %} |
||||
|
|
||||
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Issues replied: {{ user.count }}</div></div> |
||||
|
{% endfor %} |
||||
|
|
||||
|
</div> |
||||
|
{% endif %} |
||||
|
|
||||
|
## Эксперты |
||||
|
|
||||
|
Здесь представлены **Эксперты FastAPI**. 🤓 |
||||
|
|
||||
|
Эти участники [оказали наибольшую помощь другим с решением их проблем (вопросов) на GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} за *всё время*. |
||||
|
|
||||
|
Оказывая помощь многим другим, они подтвердили свой уровень знаний. ✨ |
||||
|
|
||||
|
{% if people %} |
||||
|
<div class="user-list user-list-center"> |
||||
|
{% for user in people.experts %} |
||||
|
|
||||
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Issues replied: {{ user.count }}</div></div> |
||||
|
{% endfor %} |
||||
|
|
||||
|
</div> |
||||
|
{% endif %} |
||||
|
|
||||
|
## Рейтинг участников, внёсших вклад в код |
||||
|
|
||||
|
Здесь представлен **Рейтинг участников, внёсших вклад в код**. 👷 |
||||
|
|
||||
|
Эти люди [сделали наибольшее количество пул-реквестов](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}, *включённых в основной код*. |
||||
|
|
||||
|
Они сделали наибольший вклад в исходный код, документацию, переводы и т.п. 📦 |
||||
|
|
||||
|
{% if people %} |
||||
|
<div class="user-list user-list-center"> |
||||
|
{% for user in people.top_contributors %} |
||||
|
|
||||
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Pull Requests: {{ user.count }}</div></div> |
||||
|
{% endfor %} |
||||
|
|
||||
|
</div> |
||||
|
{% endif %} |
||||
|
|
||||
|
На самом деле таких людей довольно много (более сотни), вы можете увидеть всех на этой странице <a href="https://github.com/tiangolo/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷 |
||||
|
|
||||
|
## Рейтинг ревьюеров |
||||
|
|
||||
|
Здесь представлен **Рейтинг ревьюеров**. 🕵️ |
||||
|
|
||||
|
### Проверки переводов на другие языки |
||||
|
|
||||
|
Я знаю не очень много языков (и не очень хорошо 😅). |
||||
|
Итак, ревьюеры - это люди, которые могут [**подтвердить предложенный вами перевод** документации](contributing.md#translations){.internal-link target=_blank}. Без них не было бы документации на многих языках. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
В **Рейтинге ревьюеров** 🕵️ представлены те, кто проверил наибольшее количество пул-реквестов других участников, обеспечивая качество кода, документации и, особенно, **переводов на другие языки**. |
||||
|
|
||||
|
{% if people %} |
||||
|
<div class="user-list user-list-center"> |
||||
|
{% for user in people.top_reviewers %} |
||||
|
|
||||
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Reviews: {{ user.count }}</div></div> |
||||
|
{% endfor %} |
||||
|
|
||||
|
</div> |
||||
|
{% endif %} |
||||
|
|
||||
|
## Спонсоры |
||||
|
|
||||
|
Здесь представлены **Спонсоры**. 😎 |
||||
|
|
||||
|
Спонсоры поддерживают мою работу над **FastAPI** (и другими проектами) главным образом через <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub Sponsors</a>. |
||||
|
|
||||
|
{% if sponsors %} |
||||
|
|
||||
|
{% if sponsors.gold %} |
||||
|
|
||||
|
### Золотые спонсоры |
||||
|
|
||||
|
{% for sponsor in sponsors.gold -%} |
||||
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> |
||||
|
{% endfor %} |
||||
|
{% endif %} |
||||
|
|
||||
|
{% if sponsors.silver %} |
||||
|
|
||||
|
### Серебрянные спонсоры |
||||
|
|
||||
|
{% for sponsor in sponsors.silver -%} |
||||
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> |
||||
|
{% endfor %} |
||||
|
{% endif %} |
||||
|
|
||||
|
{% if sponsors.bronze %} |
||||
|
|
||||
|
### Бронзовые спонсоры |
||||
|
|
||||
|
{% for sponsor in sponsors.bronze -%} |
||||
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a> |
||||
|
{% endfor %} |
||||
|
{% endif %} |
||||
|
|
||||
|
{% endif %} |
||||
|
|
||||
|
### Индивидуальные спонсоры |
||||
|
|
||||
|
{% if github_sponsors %} |
||||
|
{% for group in github_sponsors.sponsors %} |
||||
|
|
||||
|
<div class="user-list user-list-center"> |
||||
|
|
||||
|
{% for user in group %} |
||||
|
{% if user.login not in sponsors_badge.logins %} |
||||
|
|
||||
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a></div> |
||||
|
|
||||
|
{% endif %} |
||||
|
{% endfor %} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
{% endfor %} |
||||
|
{% endif %} |
||||
|
|
||||
|
## О данных - технические детали |
||||
|
|
||||
|
Основная цель этой страницы - подчеркнуть усилия сообщества по оказанию помощи другим. |
||||
|
|
||||
|
Особенно это касается усилий, которые обычно менее заметны и во многих случаях более трудоемки, таких как помощь другим в решении проблем и проверка пул-реквестов с переводами. |
||||
|
|
||||
|
Данные рейтинги подсчитываются каждый месяц, ознакомиться с тем, как это работает можно <a href="https://github.com/tiangolo/fastapi/blob/master/.github/actions/people/app/main.py" class="external-link" target="_blank">тут</a>. |
||||
|
|
||||
|
Кроме того, я также подчеркиваю вклад спонсоров. |
||||
|
|
||||
|
И я оставляю за собой право обновлять алгоритмы подсчёта, виды рейтингов, пороговые значения и т.д. (так, на всякий случай 🤷). |
@ -0,0 +1,336 @@ |
|||||
|
# İlk Adımlar |
||||
|
|
||||
|
En basit FastAPI dosyası şu şekildedir: |
||||
|
|
||||
|
```Python |
||||
|
{!../../../docs_src/first_steps/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
Bunu bir `main.py` dosyasına kopyalayın. |
||||
|
|
||||
|
Projeyi çalıştırın: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ uvicorn main:app --reload |
||||
|
|
||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
<span style="color: green;">INFO</span>: Started reloader process [28720] |
||||
|
<span style="color: green;">INFO</span>: Started server process [28722] |
||||
|
<span style="color: green;">INFO</span>: Waiting for application startup. |
||||
|
<span style="color: green;">INFO</span>: Application startup complete. |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
!!! note |
||||
|
`uvicorn main:app` komutu şunu ifade eder: |
||||
|
|
||||
|
* `main`: `main.py` dosyası (the Python "module"). |
||||
|
* `app`: `main.py` dosyası içerisinde `app = FastAPI()` satırıyla oluşturulan nesne. |
||||
|
* `--reload`: Kod değişikliği sonrasında sunucunun yeniden başlatılmasını sağlar. Yalnızca geliştirme için kullanın. |
||||
|
|
||||
|
Çıktıda şu şekilde bir satır vardır: |
||||
|
|
||||
|
```hl_lines="4" |
||||
|
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
``` |
||||
|
|
||||
|
Bu satır, yerel makinenizde uygulamanızın sunulduğu URL'yi gösterir. |
||||
|
|
||||
|
### Kontrol Et |
||||
|
|
||||
|
Tarayıcınızda <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a> adresini açın. |
||||
|
|
||||
|
Bir JSON yanıtı göreceksiniz: |
||||
|
|
||||
|
```JSON |
||||
|
{"message": "Hello World"} |
||||
|
``` |
||||
|
|
||||
|
### İnteraktif API dokümantasyonu |
||||
|
|
||||
|
<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> adresine gidin. |
||||
|
|
||||
|
Otomatik oluşturulmuş( <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> tarafından sağlanan) interaktif bir API dokümanı göreceksiniz: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### Alternatif API dokümantasyonu |
||||
|
|
||||
|
Şimdi, <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a> adresine gidin. |
||||
|
|
||||
|
Otomatik oluşturulmuş(<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> tarafından sağlanan) bir API dokümanı göreceksiniz: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### OpenAPI |
||||
|
|
||||
|
**FastAPI**, **OpenAPI** standardını kullanarak tüm API'lerinizi açıklayan bir "şema" oluşturur. |
||||
|
|
||||
|
#### "Şema" |
||||
|
|
||||
|
Bir "şema", bir şeyin tanımı veya açıklamasıdır. Soyut bir açıklamadır, uygulayan kod değildir. |
||||
|
|
||||
|
#### API "şemaları" |
||||
|
|
||||
|
Bu durumda, <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a>, API şemasını nasıl tanımlayacağınızı belirten şartnamelerdir. |
||||
|
|
||||
|
Bu şema tanımı, API yollarınızı, aldıkları olası parametreleri vb. içerir. |
||||
|
|
||||
|
#### Data "şema" |
||||
|
|
||||
|
"Şema" terimi, JSON içeriği gibi bazı verilerin şeklini de ifade edebilir. |
||||
|
|
||||
|
Bu durumda, JSON öznitelikleri ve sahip oldukları veri türleri vb. anlamına gelir. |
||||
|
|
||||
|
#### OpenAPI and JSON Şema |
||||
|
|
||||
|
OpenAPI, API'niz için bir API şeması tanımlar. Ve bu şema, JSON veri şemaları standardı olan **JSON Şema** kullanılarak API'niz tarafından gönderilen ve alınan verilerin tanımlarını (veya "şemalarını") içerir. |
||||
|
|
||||
|
#### `openapi.json` kontrol et |
||||
|
|
||||
|
OpenAPI şemasının nasıl göründüğünü merak ediyorsanız, FastAPI otomatik olarak tüm API'nizin açıklamalarını içeren bir JSON (şema) oluşturur. |
||||
|
|
||||
|
Doğrudan şu adreste görebilirsiniz: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>. |
||||
|
|
||||
|
Aşağıdaki gibi bir şeyle başlayan bir JSON gösterecektir: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"openapi": "3.0.2", |
||||
|
"info": { |
||||
|
"title": "FastAPI", |
||||
|
"version": "0.1.0" |
||||
|
}, |
||||
|
"paths": { |
||||
|
"/items/": { |
||||
|
"get": { |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
|
||||
|
|
||||
|
|
||||
|
... |
||||
|
``` |
||||
|
|
||||
|
#### OpenAPI ne içindir? |
||||
|
|
||||
|
OpenAPI şeması, dahili olarak bulunan iki etkileşimli dokümantasyon sistemine güç veren şeydir. |
||||
|
|
||||
|
Ve tamamen OpenAPI'ye dayalı düzinelerce alternatif vardır. **FastAPI** ile oluşturulmuş uygulamanıza bu alternatiflerden herhangi birini kolayca ekleyebilirsiniz. |
||||
|
|
||||
|
API'nizle iletişim kuran istemciler için otomatik olarak kod oluşturmak için de kullanabilirsiniz. Örneğin, frontend, mobil veya IoT uygulamaları. |
||||
|
|
||||
|
## Adım adım özet |
||||
|
|
||||
|
### Adım 1: `FastAPI`yi içe aktarın |
||||
|
|
||||
|
```Python hl_lines="1" |
||||
|
{!../../../docs_src/first_steps/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
`FastAPI`, API'niz için tüm fonksiyonları sağlayan bir Python sınıfıdır. |
||||
|
|
||||
|
!!! note "Teknik Detaylar" |
||||
|
`FastAPI` doğrudan `Starlette` kalıtım alan bir sınıftır. |
||||
|
|
||||
|
Tüm <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> fonksiyonlarını `FastAPI` ile de kullanabilirsiniz. |
||||
|
|
||||
|
### Adım 2: Bir `FastAPI` örneği oluşturun |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../../docs_src/first_steps/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
Burada `app` değişkeni `FastAPI` sınıfının bir örneği olacaktır. |
||||
|
|
||||
|
Bu tüm API'yi oluşturmak için ana etkileşim noktası olacaktır. |
||||
|
|
||||
|
`uvicorn` komutunda atıfta bulunulan `app` ile aynıdır. |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ uvicorn main:app --reload |
||||
|
|
||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
Uygulamanızı aşağıdaki gibi oluşturursanız: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../../docs_src/first_steps/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
Ve bunu `main.py` dosyasına koyduktan sonra `uvicorn` komutunu şu şekilde çağırabilirsiniz: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ uvicorn main:my_awesome_api --reload |
||||
|
|
||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
### Adım 3: *Path işlemleri* oluşturmak |
||||
|
|
||||
|
#### Path |
||||
|
|
||||
|
Burada "Path" URL'de ilk "\" ile başlayan son bölümü ifade eder. |
||||
|
|
||||
|
Yani, şu şekilde bir URL'de: |
||||
|
|
||||
|
``` |
||||
|
https://example.com/items/foo |
||||
|
``` |
||||
|
|
||||
|
... path şöyle olabilir: |
||||
|
|
||||
|
``` |
||||
|
/items/foo |
||||
|
``` |
||||
|
|
||||
|
!!! info |
||||
|
Genellikle bir "path", "endpoint" veya "route" olarak adlandırılabilir. |
||||
|
|
||||
|
Bir API oluştururken, "path", "resource" ile "concern" ayırmanın ana yoludur. |
||||
|
|
||||
|
#### İşlemler |
||||
|
|
||||
|
Burada "işlem" HTTP methodlarından birini ifade eder. |
||||
|
|
||||
|
Onlardan biri: |
||||
|
|
||||
|
* `POST` |
||||
|
* `GET` |
||||
|
* `PUT` |
||||
|
* `DELETE` |
||||
|
|
||||
|
... ve daha egzotik olanları: |
||||
|
|
||||
|
* `OPTIONS` |
||||
|
* `HEAD` |
||||
|
* `PATCH` |
||||
|
* `TRACE` |
||||
|
|
||||
|
HTTP protokolünde, bu "methodlardan" birini (veya daha fazlasını) kullanarak her path ile iletişim kurabilirsiniz. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
API'lerinizi oluştururkan, belirli bir işlemi gerçekleştirirken belirli HTTP methodlarını kullanırsınız. |
||||
|
|
||||
|
Normalde kullanılan: |
||||
|
|
||||
|
* `POST`: veri oluşturmak. |
||||
|
* `GET`: veri okumak. |
||||
|
* `PUT`: veriyi güncellemek. |
||||
|
* `DELETE`: veriyi silmek. |
||||
|
|
||||
|
Bu nedenle, OpenAPI'de HTTP methodlarından her birine "işlem" denir. |
||||
|
|
||||
|
Bizde onlara "**işlemler**" diyeceğiz. |
||||
|
|
||||
|
#### Bir *Path işlem decoratorleri* tanımlanmak |
||||
|
|
||||
|
```Python hl_lines="6" |
||||
|
{!../../../docs_src/first_steps/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
`@app.get("/")` **FastAPI'ye** aşağıdaki fonksiyonun adresine giden istekleri işlemekten sorumlu olduğunu söyler: |
||||
|
|
||||
|
* path `/` |
||||
|
* <abbr title="an HTTP GET method"><code>get</code> işlemi</abbr> kullanılarak |
||||
|
|
||||
|
|
||||
|
!!! info "`@decorator` Bilgisi" |
||||
|
Python `@something` şeklinde ifadeleri "decorator" olarak adlandırır. |
||||
|
|
||||
|
Decoratoru bir fonksiyonun üzerine koyarsınız. Dekoratif bir şapka gibi (Sanırım terim buradan gelmektedir). |
||||
|
|
||||
|
Bir "decorator" fonksiyonu alır ve bazı işlemler gerçekleştir. |
||||
|
|
||||
|
Bizim durumumzda decarator **FastAPI'ye** fonksiyonun bir `get` işlemi ile `/` pathine geldiğini söyler. |
||||
|
|
||||
|
Bu **path işlem decoratordür** |
||||
|
|
||||
|
Ayrıca diğer işlemleri de kullanabilirsiniz: |
||||
|
|
||||
|
* `@app.post()` |
||||
|
* `@app.put()` |
||||
|
* `@app.delete()` |
||||
|
|
||||
|
Ve daha egzotik olanları: |
||||
|
|
||||
|
* `@app.options()` |
||||
|
* `@app.head()` |
||||
|
* `@app.patch()` |
||||
|
* `@app.trace()` |
||||
|
|
||||
|
!!! tip |
||||
|
Her işlemi (HTTP method) istediğiniz gibi kullanmakta özgürsünüz. |
||||
|
|
||||
|
**FastAPI** herhangi bir özel anlamı zorlamaz. |
||||
|
|
||||
|
Buradaki bilgiler bir gereklilik değil, bir kılavuz olarak sunulmaktadır. |
||||
|
|
||||
|
Örneğin, GraphQL kullanırkan normalde tüm işlemleri yalnızca `POST` işlemini kullanarak gerçekleştirirsiniz. |
||||
|
|
||||
|
### Adım 4: **path işlem fonksiyonunu** tanımlayın |
||||
|
|
||||
|
Aşağıdakiler bizim **path işlem fonksiyonlarımızdır**: |
||||
|
|
||||
|
* **path**: `/` |
||||
|
* **işlem**: `get` |
||||
|
* **function**: "decorator"ün altındaki fonksiyondur (`@app.get("/")` altında). |
||||
|
|
||||
|
```Python hl_lines="7" |
||||
|
{!../../../docs_src/first_steps/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
Bu bir Python fonksiyonudur. |
||||
|
|
||||
|
Bir `GET` işlemi kullanarak "`/`" URL'sine bir istek geldiğinde **FastAPI** tarafından çağrılır. |
||||
|
|
||||
|
Bu durumda bir `async` fonksiyonudur. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
Bunu `async def` yerine normal bir fonksiyon olarakta tanımlayabilirsiniz. |
||||
|
|
||||
|
```Python hl_lines="7" |
||||
|
{!../../../docs_src/first_steps/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! note |
||||
|
|
||||
|
Eğer farkı bilmiyorsanız, [Async: *"Acelesi var?"*](../async.md#in-a-hurry){.internal-link target=_blank} kontrol edebilirsiniz. |
||||
|
|
||||
|
### Adım 5: İçeriği geri döndürün |
||||
|
|
||||
|
|
||||
|
```Python hl_lines="8" |
||||
|
{!../../../docs_src/first_steps/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
Bir `dict`, `list` döndürebilir veya `str`, `int` gibi tekil değerler döndürebilirsiniz. |
||||
|
|
||||
|
Ayrıca, Pydantic modellerini de döndürebilirsiniz. (Bununla ilgili daha sonra ayrıntılı bilgi göreceksiniz.) |
||||
|
|
||||
|
Otomatik olarak JSON'a dönüştürülecek(ORM'ler vb. dahil) başka birçok nesne ve model vardır. En beğendiklerinizi kullanmayı deneyin, yüksek ihtimalle destekleniyordur. |
||||
|
|
||||
|
## Özet |
||||
|
|
||||
|
* `FastAPI`'yi içe aktarın. |
||||
|
* Bir `app` örneği oluşturun. |
||||
|
* **path işlem decorator** yazın. (`@app.get("/")` gibi) |
||||
|
* **path işlem fonksiyonu** yazın. (`def root(): ...` gibi) |
||||
|
* Development sunucunuzu çalıştırın. (`uvicorn main:app --reload` gibi) |
@ -0,0 +1,27 @@ |
|||||
|
from typing import List, Union |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: Union[str, None] = None |
||||
|
price: float |
||||
|
tax: Union[float, None] = None |
||||
|
tags: List[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_item(item: Item) -> Item: |
||||
|
return item |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items() -> List[Item]: |
||||
|
return [ |
||||
|
Item(name="Portal Gun", price=42.0), |
||||
|
Item(name="Plumbus", price=32.0), |
||||
|
] |
@ -0,0 +1,25 @@ |
|||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str | None = None |
||||
|
price: float |
||||
|
tax: float | None = None |
||||
|
tags: list[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_item(item: Item) -> Item: |
||||
|
return item |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items() -> list[Item]: |
||||
|
return [ |
||||
|
Item(name="Portal Gun", price=42.0), |
||||
|
Item(name="Plumbus", price=32.0), |
||||
|
] |
@ -0,0 +1,27 @@ |
|||||
|
from typing import Union |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: Union[str, None] = None |
||||
|
price: float |
||||
|
tax: Union[float, None] = None |
||||
|
tags: list[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.post("/items/") |
||||
|
async def create_item(item: Item) -> Item: |
||||
|
return item |
||||
|
|
||||
|
|
||||
|
@app.get("/items/") |
||||
|
async def read_items() -> list[Item]: |
||||
|
return [ |
||||
|
Item(name="Portal Gun", price=42.0), |
||||
|
Item(name="Plumbus", price=32.0), |
||||
|
] |
@ -0,0 +1,21 @@ |
|||||
|
from typing import Union |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel, EmailStr |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class BaseUser(BaseModel): |
||||
|
username: str |
||||
|
email: EmailStr |
||||
|
full_name: Union[str, None] = None |
||||
|
|
||||
|
|
||||
|
class UserIn(BaseUser): |
||||
|
password: str |
||||
|
|
||||
|
|
||||
|
@app.post("/user/") |
||||
|
async def create_user(user: UserIn) -> BaseUser: |
||||
|
return user |
@ -0,0 +1,19 @@ |
|||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel, EmailStr |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class BaseUser(BaseModel): |
||||
|
username: str |
||||
|
email: EmailStr |
||||
|
full_name: str | None = None |
||||
|
|
||||
|
|
||||
|
class UserIn(BaseUser): |
||||
|
password: str |
||||
|
|
||||
|
|
||||
|
@app.post("/user/") |
||||
|
async def create_user(user: UserIn) -> BaseUser: |
||||
|
return user |
@ -0,0 +1,11 @@ |
|||||
|
from fastapi import FastAPI, Response |
||||
|
from fastapi.responses import JSONResponse, RedirectResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/portal") |
||||
|
async def get_portal(teleport: bool = False) -> Response: |
||||
|
if teleport: |
||||
|
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") |
||||
|
return JSONResponse(content={"message": "Here's your interdimensional portal."}) |
@ -0,0 +1,9 @@ |
|||||
|
from fastapi import FastAPI |
||||
|
from fastapi.responses import RedirectResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/teleport") |
||||
|
async def get_teleport() -> RedirectResponse: |
||||
|
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") |
@ -0,0 +1,13 @@ |
|||||
|
from typing import Union |
||||
|
|
||||
|
from fastapi import FastAPI, Response |
||||
|
from fastapi.responses import RedirectResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/portal") |
||||
|
async def get_portal(teleport: bool = False) -> Union[Response, dict]: |
||||
|
if teleport: |
||||
|
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") |
||||
|
return {"message": "Here's your interdimensional portal."} |
@ -0,0 +1,11 @@ |
|||||
|
from fastapi import FastAPI, Response |
||||
|
from fastapi.responses import RedirectResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/portal") |
||||
|
async def get_portal(teleport: bool = False) -> Response | dict: |
||||
|
if teleport: |
||||
|
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") |
||||
|
return {"message": "Here's your interdimensional portal."} |
@ -0,0 +1,13 @@ |
|||||
|
from typing import Union |
||||
|
|
||||
|
from fastapi import FastAPI, Response |
||||
|
from fastapi.responses import RedirectResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/portal", response_model=None) |
||||
|
async def get_portal(teleport: bool = False) -> Union[Response, dict]: |
||||
|
if teleport: |
||||
|
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") |
||||
|
return {"message": "Here's your interdimensional portal."} |
@ -0,0 +1,11 @@ |
|||||
|
from fastapi import FastAPI, Response |
||||
|
from fastapi.responses import RedirectResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/portal", response_model=None) |
||||
|
async def get_portal(teleport: bool = False) -> Response | dict: |
||||
|
if teleport: |
||||
|
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") |
||||
|
return {"message": "Here's your interdimensional portal."} |
File diff suppressed because it is too large
@ -0,0 +1,120 @@ |
|||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
from docs_src.response_model.tutorial003_01 import app |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
openapi_schema = { |
||||
|
"openapi": "3.0.2", |
||||
|
"info": {"title": "FastAPI", "version": "0.1.0"}, |
||||
|
"paths": { |
||||
|
"/user/": { |
||||
|
"post": { |
||||
|
"summary": "Create User", |
||||
|
"operationId": "create_user_user__post", |
||||
|
"requestBody": { |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": {"$ref": "#/components/schemas/UserIn"} |
||||
|
} |
||||
|
}, |
||||
|
"required": True, |
||||
|
}, |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": {"$ref": "#/components/schemas/BaseUser"} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
"422": { |
||||
|
"description": "Validation Error", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": { |
||||
|
"$ref": "#/components/schemas/HTTPValidationError" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"components": { |
||||
|
"schemas": { |
||||
|
"BaseUser": { |
||||
|
"title": "BaseUser", |
||||
|
"required": ["username", "email"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"username": {"title": "Username", "type": "string"}, |
||||
|
"email": {"title": "Email", "type": "string", "format": "email"}, |
||||
|
"full_name": {"title": "Full Name", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
"HTTPValidationError": { |
||||
|
"title": "HTTPValidationError", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"detail": { |
||||
|
"title": "Detail", |
||||
|
"type": "array", |
||||
|
"items": {"$ref": "#/components/schemas/ValidationError"}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
"UserIn": { |
||||
|
"title": "UserIn", |
||||
|
"required": ["username", "email", "password"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"username": {"title": "Username", "type": "string"}, |
||||
|
"email": {"title": "Email", "type": "string", "format": "email"}, |
||||
|
"full_name": {"title": "Full Name", "type": "string"}, |
||||
|
"password": {"title": "Password", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
"ValidationError": { |
||||
|
"title": "ValidationError", |
||||
|
"required": ["loc", "msg", "type"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"loc": { |
||||
|
"title": "Location", |
||||
|
"type": "array", |
||||
|
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, |
||||
|
}, |
||||
|
"msg": {"title": "Message", "type": "string"}, |
||||
|
"type": {"title": "Error Type", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_openapi_schema(): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == openapi_schema |
||||
|
|
||||
|
|
||||
|
def test_post_user(): |
||||
|
response = client.post( |
||||
|
"/user/", |
||||
|
json={ |
||||
|
"username": "foo", |
||||
|
"password": "fighter", |
||||
|
"email": "foo@example.com", |
||||
|
"full_name": "Grave Dohl", |
||||
|
}, |
||||
|
) |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == { |
||||
|
"username": "foo", |
||||
|
"email": "foo@example.com", |
||||
|
"full_name": "Grave Dohl", |
||||
|
} |
@ -0,0 +1,129 @@ |
|||||
|
import pytest |
||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
from ...utils import needs_py310 |
||||
|
|
||||
|
openapi_schema = { |
||||
|
"openapi": "3.0.2", |
||||
|
"info": {"title": "FastAPI", "version": "0.1.0"}, |
||||
|
"paths": { |
||||
|
"/user/": { |
||||
|
"post": { |
||||
|
"summary": "Create User", |
||||
|
"operationId": "create_user_user__post", |
||||
|
"requestBody": { |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": {"$ref": "#/components/schemas/UserIn"} |
||||
|
} |
||||
|
}, |
||||
|
"required": True, |
||||
|
}, |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": {"$ref": "#/components/schemas/BaseUser"} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
"422": { |
||||
|
"description": "Validation Error", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": { |
||||
|
"$ref": "#/components/schemas/HTTPValidationError" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"components": { |
||||
|
"schemas": { |
||||
|
"BaseUser": { |
||||
|
"title": "BaseUser", |
||||
|
"required": ["username", "email"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"username": {"title": "Username", "type": "string"}, |
||||
|
"email": {"title": "Email", "type": "string", "format": "email"}, |
||||
|
"full_name": {"title": "Full Name", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
"HTTPValidationError": { |
||||
|
"title": "HTTPValidationError", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"detail": { |
||||
|
"title": "Detail", |
||||
|
"type": "array", |
||||
|
"items": {"$ref": "#/components/schemas/ValidationError"}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
"UserIn": { |
||||
|
"title": "UserIn", |
||||
|
"required": ["username", "email", "password"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"username": {"title": "Username", "type": "string"}, |
||||
|
"email": {"title": "Email", "type": "string", "format": "email"}, |
||||
|
"full_name": {"title": "Full Name", "type": "string"}, |
||||
|
"password": {"title": "Password", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
"ValidationError": { |
||||
|
"title": "ValidationError", |
||||
|
"required": ["loc", "msg", "type"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"loc": { |
||||
|
"title": "Location", |
||||
|
"type": "array", |
||||
|
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, |
||||
|
}, |
||||
|
"msg": {"title": "Message", "type": "string"}, |
||||
|
"type": {"title": "Error Type", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@pytest.fixture(name="client") |
||||
|
def get_client(): |
||||
|
from docs_src.response_model.tutorial003_01_py310 import app |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
return client |
||||
|
|
||||
|
|
||||
|
@needs_py310 |
||||
|
def test_openapi_schema(client: TestClient): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == openapi_schema |
||||
|
|
||||
|
|
||||
|
@needs_py310 |
||||
|
def test_post_user(client: TestClient): |
||||
|
response = client.post( |
||||
|
"/user/", |
||||
|
json={ |
||||
|
"username": "foo", |
||||
|
"password": "fighter", |
||||
|
"email": "foo@example.com", |
||||
|
"full_name": "Grave Dohl", |
||||
|
}, |
||||
|
) |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == { |
||||
|
"username": "foo", |
||||
|
"email": "foo@example.com", |
||||
|
"full_name": "Grave Dohl", |
||||
|
} |
@ -0,0 +1,93 @@ |
|||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
from docs_src.response_model.tutorial003_02 import app |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
openapi_schema = { |
||||
|
"openapi": "3.0.2", |
||||
|
"info": {"title": "FastAPI", "version": "0.1.0"}, |
||||
|
"paths": { |
||||
|
"/portal": { |
||||
|
"get": { |
||||
|
"summary": "Get Portal", |
||||
|
"operationId": "get_portal_portal_get", |
||||
|
"parameters": [ |
||||
|
{ |
||||
|
"required": False, |
||||
|
"schema": { |
||||
|
"title": "Teleport", |
||||
|
"type": "boolean", |
||||
|
"default": False, |
||||
|
}, |
||||
|
"name": "teleport", |
||||
|
"in": "query", |
||||
|
} |
||||
|
], |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": {"application/json": {"schema": {}}}, |
||||
|
}, |
||||
|
"422": { |
||||
|
"description": "Validation Error", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": { |
||||
|
"$ref": "#/components/schemas/HTTPValidationError" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"components": { |
||||
|
"schemas": { |
||||
|
"HTTPValidationError": { |
||||
|
"title": "HTTPValidationError", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"detail": { |
||||
|
"title": "Detail", |
||||
|
"type": "array", |
||||
|
"items": {"$ref": "#/components/schemas/ValidationError"}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
"ValidationError": { |
||||
|
"title": "ValidationError", |
||||
|
"required": ["loc", "msg", "type"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"loc": { |
||||
|
"title": "Location", |
||||
|
"type": "array", |
||||
|
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, |
||||
|
}, |
||||
|
"msg": {"title": "Message", "type": "string"}, |
||||
|
"type": {"title": "Error Type", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_openapi_schema(): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == openapi_schema |
||||
|
|
||||
|
|
||||
|
def test_get_portal(): |
||||
|
response = client.get("/portal") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == {"message": "Here's your interdimensional portal."} |
||||
|
|
||||
|
|
||||
|
def test_get_redirect(): |
||||
|
response = client.get("/portal", params={"teleport": True}, follow_redirects=False) |
||||
|
assert response.status_code == 307, response.text |
||||
|
assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" |
@ -0,0 +1,36 @@ |
|||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
from docs_src.response_model.tutorial003_03 import app |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
openapi_schema = { |
||||
|
"openapi": "3.0.2", |
||||
|
"info": {"title": "FastAPI", "version": "0.1.0"}, |
||||
|
"paths": { |
||||
|
"/teleport": { |
||||
|
"get": { |
||||
|
"summary": "Get Teleport", |
||||
|
"operationId": "get_teleport_teleport_get", |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": {"application/json": {"schema": {}}}, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_openapi_schema(): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == openapi_schema |
||||
|
|
||||
|
|
||||
|
def test_get_portal(): |
||||
|
response = client.get("/teleport", follow_redirects=False) |
||||
|
assert response.status_code == 307, response.text |
||||
|
assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" |
@ -0,0 +1,9 @@ |
|||||
|
import pytest |
||||
|
from fastapi.exceptions import FastAPIError |
||||
|
|
||||
|
|
||||
|
def test_invalid_response_model(): |
||||
|
with pytest.raises(FastAPIError): |
||||
|
from docs_src.response_model.tutorial003_04 import app |
||||
|
|
||||
|
assert app # pragma: no cover |
@ -0,0 +1,12 @@ |
|||||
|
import pytest |
||||
|
from fastapi.exceptions import FastAPIError |
||||
|
|
||||
|
from ...utils import needs_py310 |
||||
|
|
||||
|
|
||||
|
@needs_py310 |
||||
|
def test_invalid_response_model(): |
||||
|
with pytest.raises(FastAPIError): |
||||
|
from docs_src.response_model.tutorial003_04_py310 import app |
||||
|
|
||||
|
assert app # pragma: no cover |
@ -0,0 +1,93 @@ |
|||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
from docs_src.response_model.tutorial003_05 import app |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
openapi_schema = { |
||||
|
"openapi": "3.0.2", |
||||
|
"info": {"title": "FastAPI", "version": "0.1.0"}, |
||||
|
"paths": { |
||||
|
"/portal": { |
||||
|
"get": { |
||||
|
"summary": "Get Portal", |
||||
|
"operationId": "get_portal_portal_get", |
||||
|
"parameters": [ |
||||
|
{ |
||||
|
"required": False, |
||||
|
"schema": { |
||||
|
"title": "Teleport", |
||||
|
"type": "boolean", |
||||
|
"default": False, |
||||
|
}, |
||||
|
"name": "teleport", |
||||
|
"in": "query", |
||||
|
} |
||||
|
], |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": {"application/json": {"schema": {}}}, |
||||
|
}, |
||||
|
"422": { |
||||
|
"description": "Validation Error", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": { |
||||
|
"$ref": "#/components/schemas/HTTPValidationError" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"components": { |
||||
|
"schemas": { |
||||
|
"HTTPValidationError": { |
||||
|
"title": "HTTPValidationError", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"detail": { |
||||
|
"title": "Detail", |
||||
|
"type": "array", |
||||
|
"items": {"$ref": "#/components/schemas/ValidationError"}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
"ValidationError": { |
||||
|
"title": "ValidationError", |
||||
|
"required": ["loc", "msg", "type"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"loc": { |
||||
|
"title": "Location", |
||||
|
"type": "array", |
||||
|
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, |
||||
|
}, |
||||
|
"msg": {"title": "Message", "type": "string"}, |
||||
|
"type": {"title": "Error Type", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def test_openapi_schema(): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == openapi_schema |
||||
|
|
||||
|
|
||||
|
def test_get_portal(): |
||||
|
response = client.get("/portal") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == {"message": "Here's your interdimensional portal."} |
||||
|
|
||||
|
|
||||
|
def test_get_redirect(): |
||||
|
response = client.get("/portal", params={"teleport": True}, follow_redirects=False) |
||||
|
assert response.status_code == 307, response.text |
||||
|
assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" |
@ -0,0 +1,103 @@ |
|||||
|
import pytest |
||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
from ...utils import needs_py310 |
||||
|
|
||||
|
openapi_schema = { |
||||
|
"openapi": "3.0.2", |
||||
|
"info": {"title": "FastAPI", "version": "0.1.0"}, |
||||
|
"paths": { |
||||
|
"/portal": { |
||||
|
"get": { |
||||
|
"summary": "Get Portal", |
||||
|
"operationId": "get_portal_portal_get", |
||||
|
"parameters": [ |
||||
|
{ |
||||
|
"required": False, |
||||
|
"schema": { |
||||
|
"title": "Teleport", |
||||
|
"type": "boolean", |
||||
|
"default": False, |
||||
|
}, |
||||
|
"name": "teleport", |
||||
|
"in": "query", |
||||
|
} |
||||
|
], |
||||
|
"responses": { |
||||
|
"200": { |
||||
|
"description": "Successful Response", |
||||
|
"content": {"application/json": {"schema": {}}}, |
||||
|
}, |
||||
|
"422": { |
||||
|
"description": "Validation Error", |
||||
|
"content": { |
||||
|
"application/json": { |
||||
|
"schema": { |
||||
|
"$ref": "#/components/schemas/HTTPValidationError" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"components": { |
||||
|
"schemas": { |
||||
|
"HTTPValidationError": { |
||||
|
"title": "HTTPValidationError", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"detail": { |
||||
|
"title": "Detail", |
||||
|
"type": "array", |
||||
|
"items": {"$ref": "#/components/schemas/ValidationError"}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
"ValidationError": { |
||||
|
"title": "ValidationError", |
||||
|
"required": ["loc", "msg", "type"], |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"loc": { |
||||
|
"title": "Location", |
||||
|
"type": "array", |
||||
|
"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, |
||||
|
}, |
||||
|
"msg": {"title": "Message", "type": "string"}, |
||||
|
"type": {"title": "Error Type", "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@pytest.fixture(name="client") |
||||
|
def get_client(): |
||||
|
from docs_src.response_model.tutorial003_05_py310 import app |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
return client |
||||
|
|
||||
|
|
||||
|
@needs_py310 |
||||
|
def test_openapi_schema(client: TestClient): |
||||
|
response = client.get("/openapi.json") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == openapi_schema |
||||
|
|
||||
|
|
||||
|
@needs_py310 |
||||
|
def test_get_portal(client: TestClient): |
||||
|
response = client.get("/portal") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == {"message": "Here's your interdimensional portal."} |
||||
|
|
||||
|
|
||||
|
@needs_py310 |
||||
|
def test_get_redirect(client: TestClient): |
||||
|
response = client.get("/portal", params={"teleport": True}, follow_redirects=False) |
||||
|
assert response.status_code == 307, response.text |
||||
|
assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ" |
Loading…
Reference in new issue