mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-14 16:21:08 -05:00
Add missing
This commit is contained in:
502
docs/uk/docs/_llm-test.md
Normal file
502
docs/uk/docs/_llm-test.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# Тестовий файл LLM { #llm-test-file }
|
||||
|
||||
Цей документ перевіряє, чи розуміє <abbr title="Large Language Model - Велика мовна модель">LLM</abbr>, який перекладає документацію, `general_prompt` у `scripts/translate.py` та мовно-специфічну підсказку в `docs/{language code}/llm-prompt.md`. Мовно-специфічна підсказка додається до `general_prompt`.
|
||||
|
||||
Тести, додані тут, побачать усі автори мовно-специфічних підсказок.
|
||||
|
||||
Використовуйте так:
|
||||
|
||||
* Майте мовно-специфічну підсказку - `docs/{language code}/llm-prompt.md`.
|
||||
* Зробіть свіжий переклад цього документа на бажану цільову мову (див., наприклад, команду `translate-page` у `translate.py`). Це створить переклад у `docs/{language code}/docs/_llm-test.md`.
|
||||
* Перевірте, чи все гаразд у перекладі.
|
||||
* За потреби покращіть вашу мовно-специфічну підсказку, загальну підсказку або англійський документ.
|
||||
* Потім вручну виправте решту проблем у перекладі, щоб він був якісним.
|
||||
* Перекладіть повторно, маючи якісний переклад на місці. Ідеальний результат - коли LLM більше не вносить змін до перекладу. Це означає, що загальна підсказка та ваша мовно-специфічна підсказка настільки добрі, наскільки це можливо (інколи він робитиме кілька, здавалося б, випадкових змін, причина в тому, що <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM-и не є детерміністичними алгоритмами</a>).
|
||||
|
||||
Тести:
|
||||
|
||||
## Фрагменти коду { #code-snippets }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
Це фрагмент коду: `foo`. А це ще один фрагмент коду: `bar`. І ще один: `baz quux`.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
Вміст фрагментів коду слід залишати без змін.
|
||||
|
||||
Див. розділ `### Content of code snippets` в загальній підсказці в `scripts/translate.py`.
|
||||
|
||||
////
|
||||
|
||||
## Цитати { #quotes }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
Вчора мій друг написав: «Якщо ви правильно напишете "incorrectly", ви написали це неправильно». На що я відповів: «Правильно, але 'incorrectly' - це неправильно, а не '"incorrectly"'».
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
LLM, ймовірно, перекладе це неправильно. Цікаво лише те, чи збереже він виправлений переклад під час повторного перекладу.
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
Автор підсказки може обрати, чи конвертувати нейтральні лапки в типографські. Можна залишити як є.
|
||||
|
||||
Див., наприклад, розділ `### Quotes` у `docs/de/llm-prompt.md`.
|
||||
|
||||
////
|
||||
|
||||
## Лапки у фрагментах коду { #quotes-in-code-snippets }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
`pip install "foo[bar]"`
|
||||
|
||||
Приклади строкових літералів у фрагментах коду: `"this"`, `'that'`.
|
||||
|
||||
Складний приклад строкових літералів у фрагментах коду: `f"I like {'oranges' if orange else "apples"}"`
|
||||
|
||||
Хардкор: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"`
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
... Однак лапки всередині фрагментів коду мають залишатися як є.
|
||||
|
||||
////
|
||||
|
||||
## Блоки коду { #code-blocks }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
Приклад коду Bash...
|
||||
|
||||
```bash
|
||||
# Вивести привітання всесвіту
|
||||
echo "Hello universe"
|
||||
```
|
||||
|
||||
...і приклад коду консолі...
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting server
|
||||
Searching for package file structure
|
||||
```
|
||||
|
||||
...і ще один приклад коду консолі...
|
||||
|
||||
```console
|
||||
// Створіть каталог "code"
|
||||
$ mkdir code
|
||||
// Перейдіть до цього каталогу
|
||||
$ cd code
|
||||
```
|
||||
|
||||
...і приклад коду Python...
|
||||
|
||||
```Python
|
||||
wont_work() # Це не спрацює 😱
|
||||
works(foo="bar") # Це працює 🎉
|
||||
```
|
||||
|
||||
...і це все.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
Код у блоках коду не слід змінювати, за винятком коментарів.
|
||||
|
||||
Див. розділ `### Content of code blocks` в загальній підсказці в `scripts/translate.py`.
|
||||
|
||||
////
|
||||
|
||||
## Вкладки та кольорові блоки { #tabs-and-colored-boxes }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
/// info | Інформація
|
||||
Some text
|
||||
///
|
||||
|
||||
/// note | Примітка
|
||||
Some text
|
||||
///
|
||||
|
||||
/// note | Технічні деталі
|
||||
Some text
|
||||
///
|
||||
|
||||
/// check | Перевірте
|
||||
Some text
|
||||
///
|
||||
|
||||
/// tip | Порада
|
||||
Some text
|
||||
///
|
||||
|
||||
/// warning | Попередження
|
||||
Some text
|
||||
///
|
||||
|
||||
/// danger | Обережно
|
||||
Some text
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
У вкладках і блоках `Info`/`Note`/`Warning`/тощо необхідно додати переклад їхнього заголовка після вертикальної риски (`|`).
|
||||
|
||||
Див. розділи `### Special blocks` і `### Tab blocks` в загальній підсказці в `scripts/translate.py`.
|
||||
|
||||
////
|
||||
|
||||
## Веб- і внутрішні посилання { #web-and-internal-links }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
Текст посилання має бути перекладений, адреса посилання має залишитися незмінною:
|
||||
|
||||
* [Посилання на заголовок вище](#code-snippets)
|
||||
* [Внутрішнє посилання](index.md#installation){.internal-link target=_blank}
|
||||
* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">Зовнішнє посилання</a>
|
||||
* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">Посилання на стиль</a>
|
||||
* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">Посилання на скрипт</a>
|
||||
* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">Посилання на зображення</a>
|
||||
|
||||
Текст посилання має бути перекладений, адреса посилання має вказувати на переклад:
|
||||
|
||||
* <a href="https://fastapi.tiangolo.com/uk/" class="external-link" target="_blank">Посилання на FastAPI</a>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
Посилання слід перекладати, але їхні адреси мають лишатися незмінними. Виняток - абсолютні посилання на сторінки документації FastAPI. У такому випадку вони мають вказувати на переклад.
|
||||
|
||||
Див. розділ `### Links` в загальній підсказці в `scripts/translate.py`.
|
||||
|
||||
////
|
||||
|
||||
## Елементи HTML «abbr» { #html-abbr-elements }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
Ось деякі речі, обгорнуті в елементи HTML «abbr» (деякі вигадані):
|
||||
|
||||
### Скорочення містить повну фразу { #the-abbr-gives-a-full-phrase }
|
||||
|
||||
* <abbr title="Getting Things Done - Впорядкування справ">GTD</abbr>
|
||||
* <abbr title="less than - менше ніж"><code>lt</code></abbr>
|
||||
* <abbr title="XML Web Token - XML веб-токен">XWT</abbr>
|
||||
* <abbr title="Parallel Server Gateway Interface - Інтерфейс Шлюзу Паралельного Сервера">PSGI</abbr>
|
||||
|
||||
### Скорочення містить повну фразу та пояснення { #the-abbr-gives-a-full-phrase-and-an-explanation }
|
||||
|
||||
* <abbr title="Mozilla Developer Network - Мережа Розробників Mozilla: документація для розробників, написана людьми з Firefox">MDN</abbr>
|
||||
* <abbr title="Input/Output - Введення/Виведення: читання або запис на диск, мережеві комунікації.">I/O</abbr>.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
Атрибути "title" елементів "abbr" перекладаються за певними інструкціями.
|
||||
|
||||
Переклади можуть додавати власні елементи "abbr", які LLM не повинен прибирати. Наприклад, щоб пояснити англійські слова.
|
||||
|
||||
Див. розділ `### HTML abbr elements` в загальній підсказці в `scripts/translate.py`.
|
||||
|
||||
////
|
||||
|
||||
## Елементи HTML «dfn» { #html-dfn-elements }
|
||||
|
||||
* <dfn title="Група машин, налаштованих бути з'єднаними та працювати разом певним чином.">кластер</dfn>
|
||||
* <dfn title="Метод машинного навчання, що використовує штучні нейронні мережі з численними прихованими шарами між вхідними та вихідними шарами, завдяки чому формується комплексна внутрішня структура">Глибоке навчання</dfn>
|
||||
|
||||
## Заголовки { #headings }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
### Розробка вебзастосунку - навчальний посібник { #develop-a-webapp-a-tutorial }
|
||||
|
||||
Привіт.
|
||||
|
||||
### Підказки типів та - анотації { #type-hints-and-annotations }
|
||||
|
||||
Ще раз привіт.
|
||||
|
||||
### Супер- та підкласи { #super-and-subclasses }
|
||||
|
||||
Ще раз привіт.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
Єдине жорстке правило для заголовків - LLM має залишати хеш-частину в фігурних дужках незмінною, щоб посилання не ламалися.
|
||||
|
||||
Див. розділ `### Headings` у загальній підсказці в `scripts/translate.py`.
|
||||
|
||||
Для деяких мовно-специфічних інструкцій див., наприклад, розділ `### Headings` у `docs/de/llm-prompt.md`.
|
||||
|
||||
////
|
||||
|
||||
## Терміни, що використовуються в документації { #terms-used-in-the-docs }
|
||||
|
||||
//// tab | Тест
|
||||
|
||||
* ви
|
||||
* ваш
|
||||
|
||||
* напр.
|
||||
* тощо
|
||||
|
||||
* `foo` як `int`
|
||||
* `bar` як `str`
|
||||
* `baz` як `list`
|
||||
|
||||
* навчальний посібник - Керівництво користувача
|
||||
* просунутий посібник користувача
|
||||
* документація SQLModel
|
||||
* документація API
|
||||
* автоматична документація
|
||||
|
||||
* Наука про дані
|
||||
* глибоке навчання
|
||||
* машинне навчання
|
||||
* впровадження залежностей
|
||||
* HTTP базова автентифікація
|
||||
* HTTP дайджест
|
||||
* формат ISO
|
||||
* стандарт Схеми JSON
|
||||
* Схема JSON
|
||||
* визначення схеми
|
||||
* потік паролю
|
||||
* мобільний
|
||||
|
||||
* застаріле
|
||||
* спроєктовано
|
||||
* недійсний
|
||||
* на льоту
|
||||
* стандарт
|
||||
* типове
|
||||
* чутливий до регістру
|
||||
* нечутливий до регістру
|
||||
|
||||
* обслуговувати застосунок
|
||||
* обслуговувати сторінку
|
||||
|
||||
* застосунок
|
||||
* застосунок
|
||||
|
||||
* запит
|
||||
* відповідь
|
||||
* відповідь з помилкою
|
||||
|
||||
* операція шляху
|
||||
* декоратор операції шляху
|
||||
* функція операції шляху
|
||||
|
||||
* тіло
|
||||
* тіло запиту
|
||||
* тіло відповіді
|
||||
* тіло JSON
|
||||
* тіло форми
|
||||
* тіло функції
|
||||
|
||||
* параметр
|
||||
* параметр тіла
|
||||
* параметр шляху
|
||||
* параметр запиту
|
||||
* параметр кукі
|
||||
* параметр заголовка
|
||||
* параметр форми
|
||||
* параметр функції
|
||||
|
||||
* подія
|
||||
* подія запуску
|
||||
* запуск сервера
|
||||
* подія вимкнення
|
||||
* подія тривалості життя
|
||||
|
||||
* обробник
|
||||
* обробник події
|
||||
* обробник винятків
|
||||
* обробляти
|
||||
|
||||
* модель
|
||||
* модель Pydantic
|
||||
* модель даних
|
||||
* модель бази даних
|
||||
* модель форми
|
||||
* об'єкт моделі
|
||||
|
||||
* клас
|
||||
* базовий клас
|
||||
* батьківський клас
|
||||
* підклас
|
||||
* дочірній клас
|
||||
* споріднений клас
|
||||
* метод класу
|
||||
|
||||
* заголовок
|
||||
* заголовки
|
||||
* заголовок авторизації
|
||||
* заголовок `Authorization`
|
||||
* направлений заголовок
|
||||
|
||||
* система впровадження залежностей
|
||||
* залежність
|
||||
* залежний
|
||||
* залежний
|
||||
|
||||
* I/O-обмежений
|
||||
* CPU-обмежений
|
||||
* рівночасність
|
||||
* паралелізм
|
||||
* багатопроцесорність
|
||||
|
||||
* змінна оточення
|
||||
* змінна оточення
|
||||
* `PATH`
|
||||
* змінна `PATH`
|
||||
|
||||
* автентифікація
|
||||
* постачальник автентифікації
|
||||
* авторизація
|
||||
* форма авторизації
|
||||
* постачальник авторизації
|
||||
* користувач автентифікується
|
||||
* система автентифікує користувача
|
||||
|
||||
* CLI
|
||||
* інтерфейс командного рядка
|
||||
|
||||
* сервер
|
||||
* клієнт
|
||||
|
||||
* хмарний постачальник
|
||||
* хмарний сервіс
|
||||
|
||||
* розробка
|
||||
* етапи розробки
|
||||
|
||||
* словник
|
||||
* словник
|
||||
* перелік
|
||||
* перелік
|
||||
* елемент переліку
|
||||
|
||||
* кодувальник
|
||||
* декодувальник
|
||||
* кодувати
|
||||
* декодувати
|
||||
|
||||
* виняток
|
||||
* породжувати
|
||||
|
||||
* вираз
|
||||
* оператор
|
||||
|
||||
* фронтенд
|
||||
* бекенд
|
||||
|
||||
* обговорення GitHub
|
||||
* проблема GitHub
|
||||
|
||||
* продуктивність
|
||||
* оптимізація продуктивності
|
||||
|
||||
* тип, що повертається
|
||||
* повернене значення
|
||||
|
||||
* безпека
|
||||
* схема безпеки
|
||||
|
||||
* завдання
|
||||
* фонове завдання
|
||||
* функція завдання
|
||||
|
||||
* шаблон
|
||||
* рушій шаблонів
|
||||
|
||||
* анотація типу
|
||||
* підказка типу
|
||||
|
||||
* серверний працівник
|
||||
* працівник Uvicorn
|
||||
* працівник Gunicorn
|
||||
* процес працівника
|
||||
* клас працівника
|
||||
* робоче навантаження
|
||||
|
||||
* розгортання
|
||||
* розгортати
|
||||
|
||||
* SDK
|
||||
* набір для розробки програмного забезпечення
|
||||
|
||||
* `APIRouter`
|
||||
* `requirements.txt`
|
||||
* токен носія
|
||||
* несумісна зміна
|
||||
* помилка
|
||||
* кнопка
|
||||
* викликаємий
|
||||
* код
|
||||
* фіксація
|
||||
* менеджер контексту
|
||||
* співпрограма
|
||||
* сеанс бази даних
|
||||
* диск
|
||||
* домен
|
||||
* рушій
|
||||
* фальшивий X
|
||||
* метод HTTP GET
|
||||
* предмет
|
||||
* бібліотека
|
||||
* тривалість життя
|
||||
* блокування
|
||||
* проміжне програмне забезпечення
|
||||
* мобільний застосунок
|
||||
* модуль
|
||||
* монтування
|
||||
* мережа
|
||||
* джерело
|
||||
* переписування
|
||||
* корисне навантаження
|
||||
* процесор
|
||||
* властивість
|
||||
* представник
|
||||
* запит на витяг
|
||||
* запит
|
||||
* пам'ять з довільним доступом
|
||||
* віддалена машина
|
||||
* код статусу
|
||||
* строка
|
||||
* мітка
|
||||
* веб-фреймворк
|
||||
* дика карта
|
||||
* повертати
|
||||
* перевіряти
|
||||
|
||||
////
|
||||
|
||||
//// tab | Інформація
|
||||
|
||||
Це неповний і не нормативний список (переважно) технічних термінів, що зустрічаються в документації. Він може бути корисним автору підсказки, щоб зрозуміти, для яких термінів LLM потрібна допомога. Наприклад, коли він постійно повертає хороший переклад до менш вдалого. Або коли йому складно відмінювати термін вашою мовою.
|
||||
|
||||
Див., наприклад, розділ `### List of English terms and their preferred German translations` у `docs/de/llm-prompt.md`.
|
||||
|
||||
////
|
||||
3
docs/uk/docs/about/index.md
Normal file
3
docs/uk/docs/about/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Про { #about }
|
||||
|
||||
Про FastAPI, його проєктування, натхнення та інше. 🤓
|
||||
247
docs/uk/docs/advanced/additional-responses.md
Normal file
247
docs/uk/docs/advanced/additional-responses.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Додаткові відповіді в OpenAPI { #additional-responses-in-openapi }
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Це доволі просунута тема.
|
||||
|
||||
Якщо ви лише починаєте з **FastAPI**, ймовірно, вам це не потрібно.
|
||||
|
||||
///
|
||||
|
||||
Ви можете оголосити додаткові відповіді з додатковими кодами статусу, типами медіа, описами тощо.
|
||||
|
||||
Ці додаткові відповіді буде включено до схеми OpenAPI, тож вони з'являться і в документації API.
|
||||
|
||||
Але для таких додаткових відповідей потрібно повертати `Response` на кшталт `JSONResponse` безпосередньо, із потрібним кодом статусу та вмістом.
|
||||
|
||||
## Додаткова відповідь з `model` { #additional-response-with-model }
|
||||
|
||||
Ви можете передати вашим декораторам операцій шляху параметр `responses`.
|
||||
|
||||
Він приймає `dict`: ключі - це коди статусу для кожної відповіді (наприклад, `200`), а значення - інші `dict` з інформацією для кожної з них.
|
||||
|
||||
Кожен із цих словників відповіді може мати ключ `model`, що містить Pydantic-модель, подібно до `response_model`.
|
||||
|
||||
**FastAPI** візьме цю модель, згенерує її Схему JSON і додасть у відповідне місце в OpenAPI.
|
||||
|
||||
Наприклад, щоб оголосити іншу відповідь з кодом статусу `404` і Pydantic-моделлю `Message`, ви можете написати:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *}
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Майте на увазі, що потрібно повертати `JSONResponse` безпосередньо.
|
||||
|
||||
///
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Ключ `model` не є частиною OpenAPI.
|
||||
|
||||
**FastAPI** візьме звідти Pydantic-модель, згенерує Схему JSON і помістить у відповідне місце.
|
||||
|
||||
Відповідне місце це:
|
||||
|
||||
- У ключі `content`, значенням якого є інший JSON-об'єкт (`dict`), що містить:
|
||||
- Ключ із типом медіа, напр. `application/json`, значенням якого є інший JSON-об'єкт, що містить:
|
||||
- Ключ `schema`, значенням якого є Схема JSON з моделі - ось це і є правильне місце.
|
||||
- **FastAPI** додає тут посилання на глобальні Схеми JSON в іншому місці вашого OpenAPI замість прямого включення. Так інші застосунки та клієнти можуть напряму використовувати ці Схеми JSON, надавати кращі інструменти генерації коду тощо.
|
||||
|
||||
///
|
||||
|
||||
Згенеровані відповіді в OpenAPI для цієї операції шляху будуть такими:
|
||||
|
||||
```JSON hl_lines="3-12"
|
||||
{
|
||||
"responses": {
|
||||
"404": {
|
||||
"description": "Additional Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Item"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Схеми посилаються на інше місце всередині схеми OpenAPI:
|
||||
|
||||
```JSON hl_lines="4-16"
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Message": {
|
||||
"title": "Message",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": [
|
||||
"id",
|
||||
"value"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"title": "Value",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"msg": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"title": "Error Type",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Додаткові типи медіа для основної відповіді { #additional-media-types-for-the-main-response }
|
||||
|
||||
Можна використати цей самий параметр `responses`, щоб додати різні типи медіа для тієї ж основної відповіді.
|
||||
|
||||
Наприклад, можна додати додатковий тип медіа `image/png`, оголосивши, що ваша операція шляху може повертати JSON-об'єкт (з типом медіа `application/json`) або PNG-зображення:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Зверніть увагу, що потрібно повертати зображення безпосередньо за допомогою `FileResponse`.
|
||||
|
||||
///
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Поки ви явно не вкажете інший тип медіа в параметрі `responses`, FastAPI вважатиме, що відповідь має той самий тип медіа, що й основний клас відповіді (типово `application/json`).
|
||||
|
||||
Але якщо ви вказали власний клас відповіді з `None` як типом медіа, FastAPI використає `application/json` для будь-якої додаткової відповіді, що має пов'язану модель.
|
||||
|
||||
///
|
||||
|
||||
## Комбінування інформації { #combining-information }
|
||||
|
||||
Ви також можете поєднувати інформацію про відповіді з кількох місць, зокрема з параметрів `response_model`, `status_code` і `responses`.
|
||||
|
||||
Ви можете оголосити `response_model`, використовуючи типовий код статусу `200` (або власний за потреби), а потім оголосити додаткову інформацію для цієї ж відповіді в `responses`, безпосередньо в схемі OpenAPI.
|
||||
|
||||
**FastAPI** збереже додаткову інформацію з `responses` і поєднає її зі Схемою JSON з вашої моделі.
|
||||
|
||||
Наприклад, ви можете оголосити відповідь з кодом статусу `404`, яка використовує Pydantic-модель і має власний `description`.
|
||||
|
||||
І відповідь з кодом статусу `200`, яка використовує ваш `response_model`, але містить власний `example`:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *}
|
||||
|
||||
Усе це буде поєднано та включено до вашого OpenAPI і показано в документації API:
|
||||
|
||||
<img src="/img/tutorial/additional-responses/image01.png">
|
||||
|
||||
## Комбінуйте попередньо визначені та власні відповіді { #combine-predefined-responses-and-custom-ones }
|
||||
|
||||
Можливо, ви захочете мати кілька попередньо визначених відповідей, що застосовуються до багатьох операцій шляху, але поєднувати їх із власними відповідями, потрібними для кожної операції шляху.
|
||||
|
||||
Для таких випадків можна скористатися прийомом Python «розпакування» `dict` за допомогою `**dict_to_unpack`:
|
||||
|
||||
```Python
|
||||
old_dict = {
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
}
|
||||
new_dict = {**old_dict, "new key": "new value"}
|
||||
```
|
||||
|
||||
Тут `new_dict` міститиме всі пари ключ-значення з `old_dict` плюс нову пару ключ-значення:
|
||||
|
||||
```Python
|
||||
{
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
"new key": "new value",
|
||||
}
|
||||
```
|
||||
|
||||
Цей прийом можна використати, щоб перевикористовувати деякі попередньо визначені відповіді у ваших операціях шляху та поєднувати їх із додатковими власними.
|
||||
|
||||
Наприклад:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
|
||||
|
||||
## Докладніше про відповіді OpenAPI { #more-information-about-openapi-responses }
|
||||
|
||||
Щоб побачити, що саме можна включати у відповіді, ознайомтеся з цими розділами специфікації OpenAPI:
|
||||
|
||||
- <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">Об'єкт відповідей OpenAPI</a>, він включає `Response Object`.
|
||||
- <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">Об'єкт відповіді OpenAPI</a>, ви можете включити будь-що з цього безпосередньо в кожну відповідь у параметрі `responses`. Зокрема `description`, `headers`, `content` (усередині нього ви оголошуєте різні типи медіа та Схеми JSON) і `links`.
|
||||
41
docs/uk/docs/advanced/additional-status-codes.md
Normal file
41
docs/uk/docs/advanced/additional-status-codes.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Додаткові коди статусу { #additional-status-codes }
|
||||
|
||||
За замовчуванням **FastAPI** повертатиме відповіді за допомогою `JSONResponse`, поміщаючи вміст, який ви повертаєте з вашої *операції шляху*, у цей `JSONResponse`.
|
||||
|
||||
Він використовуватиме код статусу за замовчуванням або той, який ви встановите у своїй *операції шляху*.
|
||||
|
||||
## Додаткові коди статусу { #additional-status-codes_1 }
|
||||
|
||||
Якщо ви хочете повертати додаткові коди статусу, окрім основного, зробіть це, повертаючи `Response` безпосередньо, наприклад `JSONResponse`, і встановіть додатковий код статусу напряму.
|
||||
|
||||
Наприклад, припустімо, ви хочете мати *операцію шляху*, яка дозволяє оновлювати предмети та повертає код статусу HTTP 200 «OK» у разі успіху.
|
||||
|
||||
Але ви також хочете приймати нові предмети. І коли таких предметів раніше не існувало, операція створює їх і повертає код статусу HTTP 201 «Created».
|
||||
|
||||
Щоб це реалізувати, імпортуйте `JSONResponse` і поверніть ваш вміст безпосередньо там, встановивши потрібний `status_code`:
|
||||
|
||||
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Коли ви повертаєте `Response` безпосередньо, як у прикладі вище, він і буде повернений безпосередньо.
|
||||
|
||||
Він не буде серіалізований за допомогою моделі тощо.
|
||||
|
||||
Переконайтеся, що він містить саме ті дані, які вам потрібні, і що значення є коректним JSON (якщо ви використовуєте `JSONResponse`).
|
||||
|
||||
///
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette.responses import JSONResponse`.
|
||||
|
||||
**FastAPI** надає ті самі `starlette.responses` як `fastapi.responses` просто для вашої зручності як розробника. Але більшість доступних відповідей надходить безпосередньо зі Starlette. Те саме і зі `status`.
|
||||
|
||||
///
|
||||
|
||||
## OpenAPI і документація API { #openapi-and-api-docs }
|
||||
|
||||
Якщо ви повертаєте додаткові коди статусу та відповіді безпосередньо, вони не будуть включені до схеми OpenAPI (документації API), адже FastAPI не має способу заздалегідь знати, що саме ви повернете.
|
||||
|
||||
Але ви можете задокументувати це у своєму коді, використовуючи: [Додаткові відповіді](additional-responses.md){.internal-link target=_blank}.
|
||||
163
docs/uk/docs/advanced/advanced-dependencies.md
Normal file
163
docs/uk/docs/advanced/advanced-dependencies.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Просунуті залежності { #advanced-dependencies }
|
||||
|
||||
## Параметризовані залежності { #parameterized-dependencies }
|
||||
|
||||
Усі залежності, які ми бачили, - це фіксована функція або клас.
|
||||
|
||||
Але можуть бути випадки, коли ви хочете мати змогу задавати параметри залежності, не оголошуючи багато різних функцій або класів.
|
||||
|
||||
Уявімо, що ми хочемо мати залежність, яка перевіряє, чи параметр запиту `q` містить певний фіксований вміст.
|
||||
|
||||
Але ми хочемо мати змогу параметризувати цей фіксований вміст.
|
||||
|
||||
## Екземпляр «callable» { #a-callable-instance }
|
||||
|
||||
У Python є спосіб зробити екземпляр класу «callable».
|
||||
|
||||
Не сам клас (який уже є «callable»), а екземпляр цього класу.
|
||||
|
||||
Щоб це зробити, оголошуємо метод `__call__`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *}
|
||||
|
||||
У цьому випадку саме `__call__` **FastAPI** використає для перевірки додаткових параметрів і підзалежностей, і саме його буде викликано, щоб передати значення параметру у вашу *функцію операції шляху* пізніше.
|
||||
|
||||
## Параметризувати екземпляр { #parameterize-the-instance }
|
||||
|
||||
Тепер ми можемо використати `__init__`, щоб оголосити параметри екземпляра, які можна застосувати для «параметризації» залежності:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *}
|
||||
|
||||
У цьому випадку **FastAPI** ніколи не торкається `__init__` і не покладається на нього - ми використовуватимемо його безпосередньо у своєму коді.
|
||||
|
||||
## Створити екземпляр { #create-an-instance }
|
||||
|
||||
Ми можемо створити екземпляр цього класу так:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *}
|
||||
|
||||
Таким чином ми «параметризуємо» нашу залежність, яка тепер містить «bar» як атрибут `checker.fixed_content`.
|
||||
|
||||
## Використовувати екземпляр як залежність { #use-the-instance-as-a-dependency }
|
||||
|
||||
Далі ми можемо використати цей `checker` у `Depends(checker)` замість `Depends(FixedContentQueryChecker)`, адже залежністю є екземпляр `checker`, а не сам клас.
|
||||
|
||||
Коли залежність розв'язується, **FastAPI** викличе цей `checker` так:
|
||||
|
||||
```Python
|
||||
checker(q="somequery")
|
||||
```
|
||||
|
||||
...і передасть усе, що він поверне, як значення залежності у нашій *функції операції шляху* як параметр `fixed_content_included`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Усе це може здаватися надуманим. І поки що може бути не дуже зрозуміло, навіщо це корисно.
|
||||
|
||||
Ці приклади навмисно прості, але показують, як усе працює.
|
||||
|
||||
У розділах про безпеку є утилітарні функції, реалізовані таким самим способом.
|
||||
|
||||
Якщо ви все це зрозуміли, ви вже знаєте, як під капотом працюють ті утиліти для безпеки.
|
||||
|
||||
///
|
||||
|
||||
## Залежності з `yield`, `HTTPException`, `except` та фоновими задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Найімовірніше, вам не знадобляться ці технічні деталі.
|
||||
|
||||
Вони корисні головно, якщо у вас був застосунок FastAPI старіший за 0.121.0 і ви стикаєтеся з проблемами залежностей із `yield`.
|
||||
|
||||
///
|
||||
|
||||
Залежності з `yield` еволюціонували з часом, щоб охопити різні сценарії використання та виправити деякі проблеми, ось підсумок змін.
|
||||
|
||||
### Залежності з `yield` і `scope` { #dependencies-with-yield-and-scope }
|
||||
|
||||
У версії 0.121.0 **FastAPI** додано підтримку `Depends(scope="function")` для залежностей з `yield`.
|
||||
|
||||
З `Depends(scope="function")` завершальний код після `yield` виконується одразу після завершення *функції операції шляху*, до того як відповідь буде надіслана клієнту.
|
||||
|
||||
А при використанні `Depends(scope="request")` (типове значення) завершальний код після `yield` виконується після відправлення відповіді.
|
||||
|
||||
Докладніше читайте в документації: [Залежності з `yield` - Ранній вихід і `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope).
|
||||
|
||||
### Залежності з `yield` і `StreamingResponse`, технічні деталі { #dependencies-with-yield-and-streamingresponse-technical-details }
|
||||
|
||||
До **FastAPI** 0.118.0, якщо ви використовували залежність із `yield`, завершальний код виконувався після повернення з *функції операції шляху*, але безпосередньо перед відправленням відповіді.
|
||||
|
||||
Метою було уникнути утримання ресурсів довше, ніж потрібно, очікуючи, поки відповідь пройде мережею.
|
||||
|
||||
Це також означало, що якщо ви повертали `StreamingResponse`, завершальний код залежності з `yield` уже було б виконано.
|
||||
|
||||
Наприклад, якщо у залежності з `yield` була сесія бази даних, `StreamingResponse` не зміг би використовувати цю сесію під час потокової передачі даних, тому що сесію вже закрито в завершальному коді після `yield`.
|
||||
|
||||
Цю поведінку змінено у 0.118.0: завершальний код після `yield` знову виконується після відправлення відповіді.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Як побачите нижче, це дуже схоже на поведінку до версії 0.106.0, але з кількома покращеннями та виправленнями помилок у крайових випадках.
|
||||
|
||||
///
|
||||
|
||||
#### Випадки використання з раннім завершальним кодом { #use-cases-with-early-exit-code }
|
||||
|
||||
Є кілька сценаріїв із певними умовами, які можуть виграти від старої поведінки - виконувати завершальний код залежностей з `yield` до надсилання відповіді.
|
||||
|
||||
Наприклад, уявіть, що у вас є код, який використовує сесію бази даних у залежності з `yield` лише для перевірки користувача, але сесія більше не використовується у *функції операції шляху*, тільки в залежності, і відправлення відповіді триває довго - як у `StreamingResponse`, що повільно надсилає дані, але з якоїсь причини не використовує базу даних.
|
||||
|
||||
У такому разі сесія БД утримувалася б до завершення відправлення відповіді, але якщо ви її не використовуєте, утримувати її немає потреби.
|
||||
|
||||
Ось як це може виглядати:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
|
||||
|
||||
Завершальний код - автоматичне закриття `Session` у:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
|
||||
|
||||
...виконається після того, як відповідь завершить надсилати повільні дані:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
|
||||
|
||||
Але оскільки `generate_stream()` не використовує сесію бази даних, немає реальної потреби тримати сесію відкритою під час надсилання відповіді.
|
||||
|
||||
Якщо у вас саме такий випадок із SQLModel (або SQLAlchemy), ви можете явно закрити сесію, коли вона більше не потрібна:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
|
||||
|
||||
Так сесія звільнить з'єднання з базою даних, і його зможуть використовувати інші запити.
|
||||
|
||||
Якщо у вас інший сценарій, де потрібно раннє завершення залежності з `yield`, створіть, будь ласка, <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">питання в обговореннях GitHub</a> із вашим конкретним випадком і поясненням, чому вам корисне раннє закриття для залежностей з `yield`.
|
||||
|
||||
Якщо будуть переконливі приклади для раннього закриття в залежностях з `yield`, я розгляну додавання нового способу увімкнути раннє закриття.
|
||||
|
||||
### Залежності з `yield` і `except`, технічні деталі { #dependencies-with-yield-and-except-technical-details }
|
||||
|
||||
До **FastAPI** 0.110.0, якщо ви використовували залежність із `yield`, перехоплювали виняток через `except` у цій залежності і не піднімали його знову, виняток автоматично піднімався/пересилався до будь-яких обробників винятків або внутрішнього обробника помилок сервера.
|
||||
|
||||
Це змінено у версії 0.110.0, щоб усунути неконтрольоване споживання пам'яті від пересланих винятків без обробника (внутрішні помилки сервера) та зробити поведінку узгодженою зі звичайним Python-кодом.
|
||||
|
||||
### Фонові задачі та залежності з `yield`, технічні деталі { #background-tasks-and-dependencies-with-yield-technical-details }
|
||||
|
||||
До **FastAPI** 0.106.0 піднімати винятки після `yield` було неможливо: завершальний код у залежностях з `yield` виконувався після надсилання відповіді, тож [обробники винятків](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже відпрацювали б.
|
||||
|
||||
Так було спроєктовано головно для того, щоб дозволити використовувати ті самі об'єкти, «віддані» залежностями через `yield`, усередині фонових задач, оскільки завершальний код виконувався після завершення фонових задач.
|
||||
|
||||
У **FastAPI** 0.106.0 це змінено, щоб не утримувати ресурси під час очікування, поки відповідь піде мережею.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Крім того, фонова задача зазвичай є незалежним набором логіки, який слід обробляти окремо, з власними ресурсами (наприклад, власним з'єднанням з базою даних).
|
||||
|
||||
Тож так у вас, ймовірно, буде чистіший код.
|
||||
|
||||
///
|
||||
|
||||
Якщо ви раніше покладалися на цю поведінку, тепер слід створювати ресурси для фонових задач усередині самої фонової задачі та використовувати всередині лише дані, що не залежать від ресурсів залежностей із `yield`.
|
||||
|
||||
Наприклад, замість використання тієї самої сесії бази даних ви створюватимете нову сесію в самій фоновій задачі та отримуватимете об'єкти з бази даних, використовуючи цю нову сесію. І далі, замість передавання об'єкта з бази даних як параметра у функцію фонової задачі, ви передасте ідентифікатор цього об'єкта, а потім отримаєте об'єкт знову всередині функції фонової задачі.
|
||||
61
docs/uk/docs/advanced/advanced-python-types.md
Normal file
61
docs/uk/docs/advanced/advanced-python-types.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Просунуті типи Python { #advanced-python-types }
|
||||
|
||||
Ось кілька додаткових ідей, які можуть бути корисні під час роботи з типами в Python.
|
||||
|
||||
## Використання `Union` або `Optional` { #using-union-or-optional }
|
||||
|
||||
Якщо ваш код з якоїсь причини не може використовувати `|`, наприклад, якщо це не анотація типів, а щось на кшталт `response_model=`, замість вертикальної риски (`|`) ви можете використати `Union` з `typing`.
|
||||
|
||||
Наприклад, ви можете оголосити, що щось може бути `str` або `None`:
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
|
||||
|
||||
def say_hi(name: Union[str, None]):
|
||||
print(f"Hi {name}!")
|
||||
```
|
||||
|
||||
У `typing` також є скорочення, щоб оголосити, що щось може бути `None`, - `Optional`.
|
||||
|
||||
Ось порада з моєї дуже «суб'єктивної» точки зору:
|
||||
|
||||
- 🚨 Уникайте використання `Optional[SomeType]`
|
||||
- Натомість ✨ **використовуйте `Union[SomeType, None]`** ✨.
|
||||
|
||||
Обидва варіанти еквівалентні і під капотом однакові, але я рекомендую `Union` замість `Optional`, тому що слово «**optional**» ніби натякає, що значення є необов'язковим, а насправді означає «може бути `None`», навіть якщо воно не є необов'язковим і все ще обов'язкове.
|
||||
|
||||
Вважаю, `Union[SomeType, None]` більш явно це передає.
|
||||
|
||||
Йдеться лише про слова та назви. Але ці слова можуть впливати на те, як ви та ваша команда думаєте про код.
|
||||
|
||||
Як приклад, розгляньмо цю функцію:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def say_hi(name: Optional[str]):
|
||||
print(f"Hey {name}!")
|
||||
```
|
||||
|
||||
Параметр `name` визначено як `Optional[str]`, але він не є необов'язковим, ви не можете викликати функцію без цього параметра:
|
||||
|
||||
```Python
|
||||
say_hi() # О ні, це викликає помилку! 😱
|
||||
```
|
||||
|
||||
Параметр `name` все ще обов'язковий (не «необов'язковий»), тому що не має значення за замовчуванням. Водночас `name` приймає `None` як значення:
|
||||
|
||||
```Python
|
||||
say_hi(name=None) # Це працює, None припустимий 🎉
|
||||
```
|
||||
|
||||
Гарна новина: у більшості випадків ви зможете просто використовувати `|`, щоб визначати об'єднання типів:
|
||||
|
||||
```python
|
||||
def say_hi(name: str | None):
|
||||
print(f"Hey {name}!")
|
||||
```
|
||||
|
||||
Тож зазвичай вам не доведеться перейматися такими назвами, як `Optional` і `Union`. 😎
|
||||
99
docs/uk/docs/advanced/async-tests.md
Normal file
99
docs/uk/docs/advanced/async-tests.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Асинхронні тести { #async-tests }
|
||||
|
||||
Ви вже бачили, як тестувати ваші застосунки **FastAPI** за допомогою наданого `TestClient`. До цього часу ви бачили лише, як писати синхронні тести, без використання функцій `async`.
|
||||
|
||||
Можливість використовувати асинхронні функції у тестах може бути корисною, наприклад, коли ви асинхронно звертаєтеся до бази даних. Уявіть, що ви хочете протестувати надсилання запитів до вашого застосунку FastAPI, а потім перевірити, що ваш бекенд успішно записав коректні дані в базу даних, використовуючи асинхронну бібліотеку для бази даних.
|
||||
|
||||
Розгляньмо, як це реалізувати.
|
||||
|
||||
## pytest.mark.anyio { #pytest-mark-anyio }
|
||||
|
||||
Якщо ми хочемо викликати асинхронні функції у тестах, самі тестові функції мають бути асинхронними. AnyIO надає зручний плагін, який дозволяє вказати, що деякі тестові функції слід виконувати асинхронно.
|
||||
|
||||
## HTTPX { #httpx }
|
||||
|
||||
Навіть якщо ваш застосунок **FastAPI** використовує звичайні функції `def` замість `async def`, під капотом це все одно `async`-застосунок.
|
||||
|
||||
`TestClient` робить певну «магію» всередині, щоб викликати асинхронний застосунок FastAPI у ваших звичайних тестових функціях `def`, використовуючи стандартний pytest. Але ця «магія» більше не працює, коли ми використовуємо його всередині асинхронних функцій. Запускаючи тести асинхронно, ми більше не можемо використовувати `TestClient` у наших тестових функціях.
|
||||
|
||||
`TestClient` побудований на основі <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, і на щастя, ми можемо використовувати його безпосередньо для тестування API.
|
||||
|
||||
## Приклад { #example }
|
||||
|
||||
Для простого прикладу розгляньмо структуру файлів, подібну до описаної в [Більші застосунки](../tutorial/bigger-applications.md){.internal-link target=_blank} та [Тестування](../tutorial/testing.md){.internal-link target=_blank}:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
Файл `main.py` міститиме:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/main.py *}
|
||||
|
||||
Файл `test_main.py` міститиме тести для `main.py`, тепер це може виглядати так:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py *}
|
||||
|
||||
## Запуск { #run-it }
|
||||
|
||||
Ви можете запустити тести як зазвичай:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Докладно { #in-detail }
|
||||
|
||||
Маркер `@pytest.mark.anyio` повідомляє pytest, що цю тестову функцію слід викликати асинхронно:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зауважте, що тестова функція тепер `async def` замість просто `def`, як це було раніше при використанні `TestClient`.
|
||||
|
||||
///
|
||||
|
||||
Далі ми можемо створити `AsyncClient` із застосунком і надсилати до нього асинхронні запити, використовуючи `await`.
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *}
|
||||
|
||||
Це еквівалентно:
|
||||
|
||||
```Python
|
||||
response = client.get('/')
|
||||
```
|
||||
|
||||
...що ми раніше використовували для надсилання запитів за допомогою `TestClient`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зауважте, що ми використовуємо async/await із новим `AsyncClient` - запит є асинхронним.
|
||||
|
||||
///
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Якщо ваш застосунок залежить від подій тривалості життя, `AsyncClient` не ініціюватиме ці події. Щоб гарантувати їх ініціалізацію, використовуйте `LifespanManager` з <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.
|
||||
|
||||
///
|
||||
|
||||
## Інші асинхронні виклики функцій { #other-asynchronous-function-calls }
|
||||
|
||||
Оскільки тестова функція тепер асинхронна, ви також можете викликати (і `await`) інші `async`-функції окрім надсилання запитів до вашого застосунку FastAPI у тестах - так само, як ви робили б це будь-де у вашому коді.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви натрапили на `RuntimeError: Task attached to a different loop` під час інтеграції асинхронних викликів у ваші тести (наприклад, при використанні <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB's MotorClient</a>), пам'ятайте створювати об'єкти, яким потрібен цикл подій, лише всередині асинхронних функцій, наприклад, у зворотному виклику `@app.on_event("startup")`.
|
||||
|
||||
///
|
||||
466
docs/uk/docs/advanced/behind-a-proxy.md
Normal file
466
docs/uk/docs/advanced/behind-a-proxy.md
Normal file
@@ -0,0 +1,466 @@
|
||||
# За представником { #behind-a-proxy }
|
||||
|
||||
У багатьох випадках ви будете використовувати **представника** на кшталт Traefik або Nginx перед вашим застосунком FastAPI.
|
||||
|
||||
Такі представники можуть обробляти сертифікати HTTPS та інші речі.
|
||||
|
||||
## Направлені заголовки представника { #proxy-forwarded-headers }
|
||||
|
||||
**Представник** перед вашим застосунком зазвичай встановлює деякі заголовки на льоту перед відправленням запитів до вашого **сервера**, щоб повідомити серверу, що запит було **направлено** через представника, вказати оригінальну (публічну) URL-адресу, включно з доменом, що використовується HTTPS тощо.
|
||||
|
||||
Програмний **сервер** (наприклад, **Uvicorn** через **FastAPI CLI**) здатний інтерпретувати ці заголовки і передавати цю інформацію вашому застосунку.
|
||||
|
||||
Але з міркувань безпеки, оскільки сервер не знає, що він працює за довіреним представником, він не інтерпретуватиме ці заголовки.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Заголовки представника:
|
||||
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
|
||||
|
||||
///
|
||||
|
||||
### Увімкнути направлені заголовки представника { #enable-proxy-forwarded-headers }
|
||||
|
||||
Ви можете запустити FastAPI CLI з *опцією CLI* `--forwarded-allow-ips` і передати IP-адреси, яким слід довіряти для читання цих направлених заголовків.
|
||||
|
||||
Якщо ви встановите `--forwarded-allow-ips="*"`, це означатиме довіру до всіх вхідних IP.
|
||||
|
||||
Якщо ваш **сервер** знаходиться за довіреним **представником** і тільки представник спілкується з ним, це змусить сервер приймати будь-яку IP-адресу цього **представника**.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run --forwarded-allow-ips="*"
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Перенаправлення з HTTPS { #redirects-with-https }
|
||||
|
||||
Наприклад, припустимо, ви визначили *операцію шляху* `/items/`:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *}
|
||||
|
||||
Якщо клієнт спробує перейти до `/items`, за замовчуванням буде виконано перенаправлення на `/items/`.
|
||||
|
||||
Але до встановлення *опції CLI* `--forwarded-allow-ips` це могло б перенаправити на `http://localhost:8000/items/`.
|
||||
|
||||
Втім, можливо ваш застосунок розміщено на `https://mysuperapp.com`, і перенаправлення має бути на `https://mysuperapp.com/items/`.
|
||||
|
||||
Налаштувавши `--proxy-headers`, FastAPI зможе перенаправляти на правильну адресу. 😎
|
||||
|
||||
```
|
||||
https://mysuperapp.com/items/
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо хочете дізнатися більше про HTTPS, перегляньте посібник [Про HTTPS](../deployment/https.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
### Як працюють направлені заголовки представника { #how-proxy-forwarded-headers-work }
|
||||
|
||||
Ось візуальне подання того, як **представник** додає направлені заголовки між клієнтом і **сервером застосунку**:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Proxy as Proxy/Load Balancer
|
||||
participant Server as FastAPI Server
|
||||
|
||||
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
|
||||
|
||||
Note over Proxy: Proxy adds forwarded headers
|
||||
|
||||
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
|
||||
|
||||
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
|
||||
|
||||
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
|
||||
|
||||
Proxy->>Client: HTTPS Response
|
||||
```
|
||||
|
||||
**Представник** перехоплює початковий запит клієнта і додає спеціальні *направлені* заголовки (`X-Forwarded-*`) перед передачею запиту на **сервер застосунку**.
|
||||
|
||||
Ці заголовки зберігають інформацію про оригінальний запит, яка інакше була б втрачена:
|
||||
|
||||
- X-Forwarded-For: оригінальна IP-адреса клієнта
|
||||
- X-Forwarded-Proto: оригінальний протокол (`https`)
|
||||
- X-Forwarded-Host: оригінальний хост (`mysuperapp.com`)
|
||||
|
||||
Коли **FastAPI CLI** налаштовано з `--forwarded-allow-ips`, він довіряє цим заголовкам і використовує їх, наприклад, для побудови коректних URL-адрес у перенаправленнях.
|
||||
|
||||
## Представник із вилученим префіксом шляху { #proxy-with-a-stripped-path-prefix }
|
||||
|
||||
У вас може бути представник, що додає префікс шляху до вашого застосунку.
|
||||
|
||||
У таких випадках ви можете використати `root_path` для конфігурації застосунку.
|
||||
|
||||
`root_path` - це механізм, наданий специфікацією ASGI (на якій побудовано FastAPI через Starlette).
|
||||
|
||||
`root_path` використовується для обробки саме таких випадків.
|
||||
|
||||
Його також використано внутрішньо під час монтування підзастосунків.
|
||||
|
||||
Наявність представника з вилученим префіксом шляху означає, що ви могли б оголосити шлях `/app` у вашому коді, але потім додати шар зверху (представника), який розмістить ваш застосунок **FastAPI** під шляхом на кшталт `/api/v1`.
|
||||
|
||||
У такому разі початковий шлях `/app` фактично буде доступний за `/api/v1/app`.
|
||||
|
||||
Хоча весь ваш код написано з припущенням, що є лише `/app`.
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *}
|
||||
|
||||
І представник буде **«зрізати»** **префікс шляху** на льоту перед передачею запиту серверу застосунку (ймовірно, Uvicorn через FastAPI CLI), зберігаючи переконання вашого застосунку, що його обслуговують за `/app`, тож вам не доведеться оновлювати весь код, щоб додати префікс `/api/v1`.
|
||||
|
||||
Дотепер усе працювало б як зазвичай.
|
||||
|
||||
Але коли ви відкриєте вбудований інтерфейс документації (фронтенд), він очікуватиме отримати схему OpenAPI за `/openapi.json`, замість `/api/v1/openapi.json`.
|
||||
|
||||
Отже, фронтенд (що працює у браузері) спробує звернутися до `/openapi.json` і не зможе отримати схему OpenAPI.
|
||||
|
||||
Оскільки у нас є представник з префіксом шляху `/api/v1` для нашого застосунку, фронтенду потрібно отримувати схему OpenAPI за `/api/v1/openapi.json`.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
|
||||
browser("Browser")
|
||||
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"]
|
||||
server["Server on http://127.0.0.1:8000/app"]
|
||||
|
||||
browser --> proxy
|
||||
proxy --> server
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
IP `0.0.0.0` зазвичай означає, що програма слухає на всіх IP-адресах, доступних на цій машині/сервері.
|
||||
|
||||
///
|
||||
|
||||
Інтерфейсу документації також потрібно, щоб схема OpenAPI оголошувала, що цей `server` API розташований за `/api/v1` (за представником). Наприклад:
|
||||
|
||||
```JSON hl_lines="4-8"
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
// Ще дещо тут
|
||||
"servers": [
|
||||
{
|
||||
"url": "/api/v1"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
// Ще дещо тут
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
У цьому прикладі «Proxy» може бути чимось на кшталт **Traefik**. А сервером буде щось на кшталт FastAPI CLI з **Uvicorn**, який запускає ваш застосунок FastAPI.
|
||||
|
||||
### Задання `root_path` { #providing-the-root-path }
|
||||
|
||||
Щоб цього досягти, ви можете використати опцію командного рядка `--root-path`, наприклад:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Якщо ви використовуєте Hypercorn, у нього також є опція `--root-path`.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Специфікація ASGI визначає `root_path` для такого випадку використання.
|
||||
|
||||
А опція командного рядка `--root-path` надає цей `root_path`.
|
||||
|
||||
///
|
||||
|
||||
### Перевірка поточного `root_path` { #checking-the-current-root-path }
|
||||
|
||||
Ви можете отримати поточний `root_path`, який використовує ваш застосунок для кожного запиту, він є частиною словника `scope` (це частина специфікації ASGI).
|
||||
|
||||
Тут ми додаємо його у повідомлення лише для демонстрації.
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *}
|
||||
|
||||
Потім, якщо ви запустите Uvicorn так:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Відповідь буде приблизно такою:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
### Встановлення `root_path` у застосунку FastAPI { #setting-the-root-path-in-the-fastapi-app }
|
||||
|
||||
Альтернативно, якщо у вас немає способу передати опцію командного рядка на кшталт `--root-path` чи еквівалентну, ви можете встановити параметр `root_path` під час створення вашого застосунку FastAPI:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *}
|
||||
|
||||
Передача `root_path` до `FastAPI` еквівалентна передачі опції командного рядка `--root-path` до Uvicorn або Hypercorn.
|
||||
|
||||
### Про `root_path` { #about-root-path }
|
||||
|
||||
Майте на увазі, що сервер (Uvicorn) не використовуватиме `root_path` ні для чого, окрім передачі його застосунку.
|
||||
|
||||
Але якщо ви перейдете у вашому браузері на <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, ви побачите звичайну відповідь:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
Тобто він не очікуватиме доступу за адресою `http://127.0.0.1:8000/api/v1/app`.
|
||||
|
||||
Uvicorn очікуватиме, що представник буде звертатися до Uvicorn на `http://127.0.0.1:8000/app`, і тоді саме представник відповідатиме за додавання додаткового префікса `/api/v1` зверху.
|
||||
|
||||
## Про представників із вилученим префіксом шляху { #about-proxies-with-a-stripped-path-prefix }
|
||||
|
||||
Майте на увазі, що представник із вилученим префіксом шляху - це лише один зі способів його налаштування.
|
||||
|
||||
Ймовірно, в багатьох випадках за замовчуванням представник не матиме вилученого префікса шляху.
|
||||
|
||||
У такому випадку (без вилученого префікса шляху) представник слухатиме щось на кшталт `https://myawesomeapp.com`, і коли браузер перейде на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (наприклад, Uvicorn) слухає на `http://127.0.0.1:8000`, представник (без вилученого префікса) звернеться до Uvicorn за тим самим шляхом: `http://127.0.0.1:8000/api/v1/app`.
|
||||
|
||||
## Локальне тестування з Traefik { #testing-locally-with-traefik }
|
||||
|
||||
Ви можете легко провести експеримент локально з вилученим префіксом шляху, використовуючи <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a>.
|
||||
|
||||
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Завантажте Traefik</a>, це один бінарний файл, ви можете розпакувати архів і запустити його безпосередньо з термінала.
|
||||
|
||||
Потім створіть файл `traefik.toml` з таким вмістом:
|
||||
|
||||
```TOML hl_lines="3"
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":9999"
|
||||
|
||||
[providers]
|
||||
[providers.file]
|
||||
filename = "routes.toml"
|
||||
```
|
||||
|
||||
Це вказує Traefik слухати порт 9999 і використовувати інший файл `routes.toml`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ми використовуємо порт 9999 замість стандартного HTTP-порту 80, щоб вам не довелося запускати його з адмін-повноваженнями (`sudo`).
|
||||
|
||||
///
|
||||
|
||||
Тепер створіть інший файл `routes.toml`:
|
||||
|
||||
```TOML hl_lines="5 12 20"
|
||||
[http]
|
||||
[http.middlewares]
|
||||
|
||||
[http.middlewares.api-stripprefix.stripPrefix]
|
||||
prefixes = ["/api/v1"]
|
||||
|
||||
[http.routers]
|
||||
|
||||
[http.routers.app-http]
|
||||
entryPoints = ["http"]
|
||||
service = "app"
|
||||
rule = "PathPrefix(`/api/v1`)"
|
||||
middlewares = ["api-stripprefix"]
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.app]
|
||||
[http.services.app.loadBalancer]
|
||||
[[http.services.app.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:8000"
|
||||
```
|
||||
|
||||
Цей файл налаштовує Traefik на використання префікса шляху `/api/v1`.
|
||||
|
||||
Потім Traefik переспрямовуватиме свої запити до вашого Uvicorn, який працює на `http://127.0.0.1:8000`.
|
||||
|
||||
Тепер запустіть Traefik:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ ./traefik --configFile=traefik.toml
|
||||
|
||||
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
А тепер запустіть ваш застосунок, використовуючи опцію `--root-path`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Перевірте відповіді { #check-the-responses }
|
||||
|
||||
Тепер, якщо ви перейдете за URL із портом Uvicorn: <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>, ви побачите звичайну відповідь:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу, що хоча ви звертаєтеся за адресою `http://127.0.0.1:8000/app`, відображається `root_path` `/api/v1`, взятий із опції `--root-path`.
|
||||
|
||||
///
|
||||
|
||||
А тепер відкрийте URL із портом Traefik, включно з префіксом шляху: <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>.
|
||||
|
||||
Ми отримуємо ту саму відповідь:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
але цього разу за URL із префіксом шляху, який надає представник: `/api/v1`.
|
||||
|
||||
Звісно, ідея полягає в тому, що всі мають доступатися до застосунку через представника, тож варіант із префіксом шляху `/api/v1` є «правильним».
|
||||
|
||||
А варіант без префікса шляху (`http://127.0.0.1:8000/app`), який надає безпосередньо Uvicorn, має бути виключно для того, щоб _представник_ (Traefik) звертався до нього.
|
||||
|
||||
Це демонструє, як Представник (Traefik) використовує префікс шляху і як сервер (Uvicorn) використовує `root_path` з опції `--root-path`.
|
||||
|
||||
### Перевірте інтерфейс документації { #check-the-docs-ui }
|
||||
|
||||
А тепер до цікавої частини. ✨
|
||||
|
||||
«Офіційний» спосіб доступу до застосунку - через представника з префіксом шляху, який ми визначили. Тож, як і очікується, якщо ви спробуєте інтерфейс документації, який обслуговує безпосередньо Uvicorn без префікса шляху в URL, це не запрацює, оскільки він очікує доступу через представника.
|
||||
|
||||
Ви можете перевірити це на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image01.png">
|
||||
|
||||
Але якщо ми звернемося до інтерфейсу документації за «офіційним» URL, використовуючи представника з портом `9999`, за адресою `/api/v1/docs`, усе працює коректно! 🎉
|
||||
|
||||
Ви можете перевірити це на <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image02.png">
|
||||
|
||||
Саме так, як ми хотіли. ✔️
|
||||
|
||||
Це тому, що FastAPI використовує `root_path`, щоб створити типовий `server` в OpenAPI з URL, наданою `root_path`.
|
||||
|
||||
## Додаткові сервери { #additional-servers }
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Це більш просунутий випадок використання. Можете пропустити його.
|
||||
|
||||
///
|
||||
|
||||
За замовчуванням **FastAPI** створить `server` у схемі OpenAPI з URL для `root_path`.
|
||||
|
||||
Але ви також можете надати інші альтернативні `servers`, наприклад, якщо хочете, щоб той самий інтерфейс документації взаємодіяв і зі стейджингом, і з продакшном.
|
||||
|
||||
Якщо ви передасте власний список `servers`, і є `root_path` (тому що ваш API знаходиться за представником), **FastAPI** вставить «server» з цим `root_path` на початок списку.
|
||||
|
||||
Наприклад:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *}
|
||||
|
||||
Буде згенерована схема OpenAPI на кшталт:
|
||||
|
||||
```JSON hl_lines="5-7"
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
// Ще дещо тут
|
||||
"servers": [
|
||||
{
|
||||
"url": "/api/v1"
|
||||
},
|
||||
{
|
||||
"url": "https://stag.example.com",
|
||||
"description": "Staging environment"
|
||||
},
|
||||
{
|
||||
"url": "https://prod.example.com",
|
||||
"description": "Production environment"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
// Ще дещо тут
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу на автоматично згенерований сервер із значенням `url` `/api/v1`, взятим із `root_path`.
|
||||
|
||||
///
|
||||
|
||||
В інтерфейсі документації за адресою <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> це виглядатиме так:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image03.png">
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Інтерфейс документації взаємодіятиме з сервером, який ви оберете.
|
||||
|
||||
///
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Властивість `servers` у специфікації OpenAPI є необовʼязковою.
|
||||
|
||||
Якщо ви не вкажете параметр `servers`, і `root_path` дорівнює `/`, властивість `servers` у згенерованій схемі OpenAPI буде повністю пропущено за замовчуванням, що еквівалентно одному серверу зі значенням `url` рівним `/`.
|
||||
|
||||
///
|
||||
|
||||
### Вимкнути автоматичний сервер із `root_path` { #disable-automatic-server-from-root-path }
|
||||
|
||||
Якщо ви не хочете, щоб **FastAPI** додавав автоматичний сервер, використовуючи `root_path`, скористайтеся параметром `root_path_in_servers=False`:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *}
|
||||
|
||||
і тоді він не буде включений у схему OpenAPI.
|
||||
|
||||
## Монтування підзастосунку { #mounting-a-sub-application }
|
||||
|
||||
Якщо вам потрібно змонтувати підзастосунок (як описано в [Підзастосунки - монтування](sub-applications.md){.internal-link target=_blank}), одночасно використовуючи представника з `root_path`, ви можете робити це звичайним чином, як і очікуєте.
|
||||
|
||||
FastAPI внутрішньо розумно використовуватиме `root_path`, тож усе просто працюватиме. ✨
|
||||
312
docs/uk/docs/advanced/custom-response.md
Normal file
312
docs/uk/docs/advanced/custom-response.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# Користувацька відповідь - HTML, стрім, файл, інше { #custom-response-html-stream-file-others }
|
||||
|
||||
Типово **FastAPI** повертатиме відповіді, використовуючи `JSONResponse`.
|
||||
|
||||
Ви можете переписати це, повернувши безпосередньо `Response`, як показано в [Повернути відповідь безпосередньо](response-directly.md){.internal-link target=_blank}.
|
||||
|
||||
Але якщо ви повертаєте `Response` безпосередньо (або будь-який його підклас, як-от `JSONResponse`), дані не будуть автоматично конвертовані (навіть якщо ви оголосите `response_model`), і документація не буде автоматично згенерована (наприклад, із включенням конкретного «медіа-типу» в HTTP-заголовку `Content-Type` як частини згенерованого OpenAPI).
|
||||
|
||||
Ви також можете оголосити `Response`, який слід використовувати (наприклад, будь-який підклас `Response`), у декораторі операції шляху через параметр `response_class`.
|
||||
|
||||
Вміст, який ви повертаєте з вашої функції операції шляху, буде поміщений усередину цього `Response`.
|
||||
|
||||
І якщо цей `Response` має JSON медіа-тип (`application/json`), як у випадку з `JSONResponse` та `UJSONResponse`, дані, що повертаються, будуть автоматично перетворені (і відфільтровані) з урахуванням будь-якого Pydantic `response_model`, який ви оголосили в декораторі операції шляху.
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Якщо ви використовуєте клас відповіді без медіа-типу, FastAPI очікуватиме, що у вашої відповіді не буде вмісту, тож формат відповіді не буде задокументовано в згенерованих OpenAPI-документах.
|
||||
|
||||
///
|
||||
|
||||
## Використовуйте `ORJSONResponse` { #use-orjsonresponse }
|
||||
|
||||
Наприклад, якщо ви максимально оптимізуєте продуктивність, можете встановити та використовувати <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> і встановити відповідь як `ORJSONResponse`.
|
||||
|
||||
Імпортуйте потрібний клас `Response` (підклас) і оголосіть його в декораторі операції шляху.
|
||||
|
||||
Для великих відповідей повернення `Response` безпосередньо значно швидше, ніж повернення словника.
|
||||
|
||||
Це тому, що за замовчуванням FastAPI перевірятиме кожен елемент усередині та переконуватиметься, що його можна серіалізувати як JSON, використовуючи той самий [Сумісний кодувальник JSON](../tutorial/encoder.md){.internal-link target=_blank}, описаний у навчальному посібнику. Саме це дозволяє повертати довільні об'єкти, наприклад моделі бази даних.
|
||||
|
||||
Але якщо ви впевнені, що вміст, який повертається, серіалізується в JSON, ви можете передати його безпосередньо класу відповіді та уникнути додаткових витрат FastAPI на пропускання вашого вмісту через `jsonable_encoder` перед передаванням його класу відповіді.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *}
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Параметр `response_class` також визначатиме «медіа-тип» відповіді.
|
||||
|
||||
У цьому випадку HTTP-заголовок `Content-Type` буде встановлено в `application/json`.
|
||||
|
||||
І це буде задокументовано відповідно в OpenAPI.
|
||||
|
||||
///
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
`ORJSONResponse` доступний лише у FastAPI, не в Starlette.
|
||||
|
||||
///
|
||||
|
||||
## HTML-відповідь { #html-response }
|
||||
|
||||
Щоб повернути відповідь із HTML безпосередньо з **FastAPI**, використовуйте `HTMLResponse`.
|
||||
|
||||
- Імпортуйте `HTMLResponse`.
|
||||
- Передайте `HTMLResponse` як параметр `response_class` вашого декоратора операції шляху.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *}
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Параметр `response_class` також визначатиме «медіа-тип» відповіді.
|
||||
|
||||
У цьому випадку HTTP-заголовок `Content-Type` буде встановлено в `text/html`.
|
||||
|
||||
І це буде задокументовано відповідно в OpenAPI.
|
||||
|
||||
///
|
||||
|
||||
### Повернути `Response` { #return-a-response }
|
||||
|
||||
Як показано в [Повернути відповідь безпосередньо](response-directly.md){.internal-link target=_blank}, ви також можете переписати відповідь безпосередньо у вашій операції шляху, просто повернувши її.
|
||||
|
||||
Той самий приклад вище, що повертає `HTMLResponse`, може виглядати так:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *}
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
`Response`, повернений безпосередньо вашою функцією операції шляху, не буде задокументовано в OpenAPI (наприклад, `Content-Type` не буде задокументовано) і не буде видно в автоматичній інтерактивній документації.
|
||||
|
||||
///
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Звісно, фактичні заголовок `Content-Type`, код статусу тощо прийдуть з об'єкта `Response`, який ви повернули.
|
||||
|
||||
///
|
||||
|
||||
### Задокументуйте в OpenAPI і перепишіть `Response` { #document-in-openapi-and-override-response }
|
||||
|
||||
Якщо ви хочете переписати відповідь усередині функції, але водночас задокументувати «медіа-тип» в OpenAPI, ви можете використати параметр `response_class` І повернути об'єкт `Response`.
|
||||
|
||||
Тоді `response_class` буде використано лише для документування операції шляху в OpenAPI, а ваша `Response` буде використана як є.
|
||||
|
||||
#### Повернути `HTMLResponse` безпосередньо { #return-an-htmlresponse-directly }
|
||||
|
||||
Наприклад, це може бути щось на кшталт:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *}
|
||||
|
||||
У цьому прикладі функція `generate_html_response()` уже генерує та повертає `Response` замість повернення HTML як `str`.
|
||||
|
||||
Повертаючи результат виклику `generate_html_response()`, ви вже повертаєте `Response`, яка перепише типову поведінку **FastAPI**.
|
||||
|
||||
Але оскільки ви також передали `HTMLResponse` у `response_class`, **FastAPI** знатиме, як задокументувати це в OpenAPI та інтерактивній документації як HTML з `text/html`:
|
||||
|
||||
<img src="/img/tutorial/custom-response/image01.png">
|
||||
|
||||
## Доступні відповіді { #available-responses }
|
||||
|
||||
Ось деякі з доступних відповідей.
|
||||
|
||||
Майте на увазі, що ви можете використовувати `Response`, щоб повертати що завгодно інше, або навіть створити власний підклас.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette.responses import HTMLResponse`.
|
||||
|
||||
**FastAPI** надає ті ж `starlette.responses` як `fastapi.responses` лише для вашої зручності як розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
|
||||
|
||||
///
|
||||
|
||||
### `Response` { #response }
|
||||
|
||||
Головний клас `Response`, від якого успадковуються всі інші відповіді.
|
||||
|
||||
Ви можете повертати його безпосередньо.
|
||||
|
||||
Він приймає такі параметри:
|
||||
|
||||
- `content` - `str` або `bytes`.
|
||||
- `status_code` - `int` - код статусу HTTP.
|
||||
- `headers` - `dict` строк.
|
||||
- `media_type` - `str`, що задає медіа-тип, напр. `"text/html"`.
|
||||
|
||||
FastAPI (насправді Starlette) автоматично додасть заголовок Content-Length. Також буде додано заголовок Content-Type, на основі `media_type` з додаванням набору символів для текстових типів.
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *}
|
||||
|
||||
### `HTMLResponse` { #htmlresponse }
|
||||
|
||||
Приймає текст або байти та повертає HTML-відповідь, як описано вище.
|
||||
|
||||
### `PlainTextResponse` { #plaintextresponse }
|
||||
|
||||
Приймає текст або байти та повертає відповідь звичайним текстом.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *}
|
||||
|
||||
### `JSONResponse` { #jsonresponse }
|
||||
|
||||
Приймає дані та повертає відповідь, закодовану як `application/json`.
|
||||
|
||||
Це типова відповідь, яку використовує **FastAPI**, як зазначено вище.
|
||||
|
||||
### `ORJSONResponse` { #orjsonresponse }
|
||||
|
||||
Швидка альтернативна JSON-відповідь з використанням <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, як описано вище.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Потрібно встановити `orjson`, наприклад `pip install orjson`.
|
||||
|
||||
///
|
||||
|
||||
### `UJSONResponse` { #ujsonresponse }
|
||||
|
||||
Альтернативна JSON-відповідь з використанням <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a>.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Потрібно встановити `ujson`, наприклад `pip install ujson`.
|
||||
|
||||
///
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
`ujson` менш обережний, ніж вбудована реалізація Python, у поводженні з деякими крайніми випадками.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ймовірно, `ORJSONResponse` може бути швидшою альтернативою.
|
||||
|
||||
///
|
||||
|
||||
### `RedirectResponse` { #redirectresponse }
|
||||
|
||||
Повертає HTTP-перенаправлення. Типово використовує код статусу 307 (Temporary Redirect).
|
||||
|
||||
Ви можете повернути `RedirectResponse` безпосередньо:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *}
|
||||
|
||||
---
|
||||
|
||||
Або ви можете використати його в параметрі `response_class`:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *}
|
||||
|
||||
У такому разі ви можете повертати URL безпосередньо з вашої функції операції шляху.
|
||||
|
||||
У цьому випадку `status_code` буде типовим для `RedirectResponse`, тобто `307`.
|
||||
|
||||
---
|
||||
|
||||
Ви також можете використати параметр `status_code` разом із параметром `response_class`:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *}
|
||||
|
||||
### `StreamingResponse` { #streamingresponse }
|
||||
|
||||
Приймає async-генератор або звичайний генератор/ітератор і транслює тіло відповіді потоково.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *}
|
||||
|
||||
#### Використання `StreamingResponse` з об'єктами типу file-like { #using-streamingresponse-with-file-like-objects }
|
||||
|
||||
Якщо у вас є <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> об'єкт (наприклад, об'єкт, що повертається `open()`), ви можете створити генераторну функцію для ітерації по цьому file-like об'єкту.
|
||||
|
||||
Таким чином, вам не потрібно спочатку читати все в пам'ять, і ви можете передати цю генераторну функцію в `StreamingResponse` і повернути її.
|
||||
|
||||
Сюди входить багато бібліотек для взаємодії з хмарними сховищами, обробки відео та інші.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *}
|
||||
|
||||
1. Це генераторна функція. Вона є «генераторною функцією», бо містить оператори `yield` усередині.
|
||||
2. Використовуючи блок `with`, ми гарантуємо, що file-like об'єкт буде закрито після завершення роботи генераторної функції. Тобто після того, як вона завершить надсилання відповіді.
|
||||
3. Цей `yield from` вказує функції ітеруватися по об'єкту з назвою `file_like`. А потім, для кожної ітерованої частини, повертати цю частину, ніби вона надходить з цієї генераторної функції (`iterfile`).
|
||||
|
||||
Тож це генераторна функція, яка всередині передає роботу «генерації» чомусь іншому.
|
||||
|
||||
Роблячи це таким чином, ми можемо помістити її в блок `with` і таким чином гарантувати, що file-like об'єкт буде закрито після завершення.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу, що тут ми використовуємо стандартний `open()`, який не підтримує `async` та `await`, тому ми оголошуємо операцію шляху звичайною `def`.
|
||||
|
||||
///
|
||||
|
||||
### `FileResponse` { #fileresponse }
|
||||
|
||||
Асинхронно транслює файл як відповідь.
|
||||
|
||||
Приймає інший набір аргументів для створення екземпляра, ніж інші типи відповідей:
|
||||
|
||||
- `path` - шлях до файлу для трансляції.
|
||||
- `headers` - будь-які користувацькі заголовки як словник.
|
||||
- `media_type` - строка, що задає медіа-тип. Якщо не встановлено, медіа-тип буде виведено з імені файлу або шляху.
|
||||
- `filename` - якщо встановлено, буде включено до `Content-Disposition` відповіді.
|
||||
|
||||
Відповіді з файлами включатимуть відповідні заголовки `Content-Length`, `Last-Modified` і `ETag`.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *}
|
||||
|
||||
Ви також можете використати параметр `response_class`:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *}
|
||||
|
||||
У цьому випадку ви можете повертати шлях до файлу безпосередньо з вашої функції операції шляху.
|
||||
|
||||
## Власний клас відповіді { #custom-response-class }
|
||||
|
||||
Ви можете створити власний клас відповіді, успадкувавши його від `Response`, і використовувати його.
|
||||
|
||||
Наприклад, скажімо, ви хочете використовувати <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, але з деякими користувацькими налаштуваннями, які не використовуються у вбудованому класі `ORJSONResponse`.
|
||||
|
||||
Припустімо, ви хочете, щоб повертався відформатований із відступами JSON, тож ви хочете використати опцію orjson `orjson.OPT_INDENT_2`.
|
||||
|
||||
Ви можете створити `CustomORJSONResponse`. Головне, що потрібно зробити, це створити метод `Response.render(content)`, який повертає вміст як `bytes`:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *}
|
||||
|
||||
Тепер замість повернення:
|
||||
|
||||
```json
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
...ця відповідь повертатиме:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello World"
|
||||
}
|
||||
```
|
||||
|
||||
Звісно, ви, ймовірно, знайдете значно кращі способи скористатися цим, ніж просто форматування JSON. 😉
|
||||
|
||||
## Типова відповідь за замовчуванням { #default-response-class }
|
||||
|
||||
Створюючи екземпляр класу **FastAPI** або `APIRouter`, ви можете вказати, який клас відповіді використовувати за замовчуванням.
|
||||
|
||||
Параметр, що це визначає, - `default_response_class`.
|
||||
|
||||
У прикладі нижче **FastAPI** використовуватиме `ORJSONResponse` за замовчуванням в усіх операціях шляху замість `JSONResponse`.
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ви все одно можете переписати `response_class` в операціях шляху, як і раніше.
|
||||
|
||||
///
|
||||
|
||||
## Додаткова документація { #additional-documentation }
|
||||
|
||||
Ви також можете оголосити медіа-тип і багато інших деталей в OpenAPI, використовуючи `responses`: [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}.
|
||||
95
docs/uk/docs/advanced/dataclasses.md
Normal file
95
docs/uk/docs/advanced/dataclasses.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Використання dataclasses { #using-dataclasses }
|
||||
|
||||
FastAPI побудовано поверх **Pydantic**, і я показував вам, як використовувати моделі Pydantic для оголошення запитів і відповідей.
|
||||
|
||||
Але FastAPI також підтримує використання <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> таким самим чином:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
|
||||
|
||||
Це підтримується завдяки **Pydantic**, адже він має <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">внутрішню підтримку `dataclasses`</a>.
|
||||
|
||||
Тож навіть із наведеним вище кодом, який явно не використовує Pydantic, FastAPI використовує Pydantic, щоб перетворити стандартні dataclasses у власний варіант dataclasses Pydantic.
|
||||
|
||||
І, звісно, підтримується те саме:
|
||||
|
||||
* валідація даних
|
||||
* серіалізація даних
|
||||
* документація даних тощо
|
||||
|
||||
Це працює так само, як із моделями Pydantic. Насправді під капотом це також досягається за допомогою Pydantic.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Майте на увазі, що dataclasses не можуть робити все те, що можуть моделі Pydantic.
|
||||
|
||||
Тож вам усе ще може знадобитися використовувати моделі Pydantic.
|
||||
|
||||
Але якщо у вас вже є чимало dataclasses, це зручний трюк, щоб задіяти їх для веб-API на FastAPI. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Dataclasses у `response_model` { #dataclasses-in-response-model }
|
||||
|
||||
Ви також можете використовувати `dataclasses` у параметрі `response_model`:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
|
||||
|
||||
Dataclass буде автоматично перетворено на dataclass Pydantic.
|
||||
|
||||
Таким чином його схема з'явиться в інтерфейсі користувача документації API:
|
||||
|
||||
<img src="/img/tutorial/dataclasses/image01.png">
|
||||
|
||||
## Dataclasses у вкладених структурах даних { #dataclasses-in-nested-data-structures }
|
||||
|
||||
Можна поєднувати `dataclasses` з іншими анотаціями типів, щоб створювати вкладені структури даних.
|
||||
|
||||
У деяких випадках вам усе ж доведеться використовувати варіант `dataclasses` від Pydantic. Наприклад, якщо виникають помилки з автоматично згенерованою документацією API.
|
||||
|
||||
У такому разі ви можете просто замінити стандартні `dataclasses` на `pydantic.dataclasses`, що є взаємозамінником:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
|
||||
|
||||
1. Ми все ще імпортуємо `field` зі стандартних `dataclasses`.
|
||||
|
||||
2. `pydantic.dataclasses` - це взаємозамінник для `dataclasses`.
|
||||
|
||||
3. Dataclass `Author` містить список dataclass `Item`.
|
||||
|
||||
4. Dataclass `Author` використовується як параметр `response_model`.
|
||||
|
||||
5. Ви можете використовувати інші стандартні анотації типів із dataclasses як тіло запиту.
|
||||
|
||||
У цьому випадку це список dataclass `Item`.
|
||||
|
||||
6. Тут ми повертаємо словник, що містить `items`, який є списком dataclass.
|
||||
|
||||
FastAPI усе ще здатний <dfn title="перетворення даних у формат, який можна передати">серіалізувати</dfn> дані до JSON.
|
||||
|
||||
7. Тут у `response_model` використано анотацію типу список dataclass `Author`.
|
||||
|
||||
Знову ж, ви можете поєднувати `dataclasses` зі стандартними анотаціями типів.
|
||||
|
||||
8. Зверніть увагу, що ця *функція операції шляху* використовує звичайний `def` замість `async def`.
|
||||
|
||||
Як завжди, у FastAPI ви можете поєднувати `def` і `async def` за потреби.
|
||||
|
||||
Якщо вам потрібне коротке нагадування, коли що використовувати, перегляньте розділ _«Поспішаєте?»_ у документації про [`async` та `await`](../async.md#in-a-hurry){.internal-link target=_blank}.
|
||||
|
||||
9. Ця *функція операції шляху* не повертає dataclasses (хоча могла б), а список словників із внутрішніми даними.
|
||||
|
||||
FastAPI використає параметр `response_model` (що включає dataclasses), щоб перетворити відповідь.
|
||||
|
||||
Ви можете поєднувати `dataclasses` з іншими анотаціями типів у багатьох поєднаннях, щоб формувати складні структури даних.
|
||||
|
||||
Перегляньте підказки щодо анотацій у коді вище, щоб побачити більше деталей.
|
||||
|
||||
## Дізнатися більше { #learn-more }
|
||||
|
||||
Можна поєднувати `dataclasses` з іншими моделями Pydantic, наслідувати їх, включати у власні моделі тощо.
|
||||
|
||||
Щоб дізнатися більше, перегляньте <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">документацію Pydantic про dataclasses</a>.
|
||||
|
||||
## Версія { #version }
|
||||
|
||||
Доступно починаючи з версії FastAPI `0.67.0`. 🔖
|
||||
165
docs/uk/docs/advanced/events.md
Normal file
165
docs/uk/docs/advanced/events.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# Події тривалості життя { #lifespan-events }
|
||||
|
||||
Ви можете визначити логіку (код), яку слід виконати перед тим, як застосунок запуститься. Це означає, що цей код буде виконано один раз, перед тим як застосунок почне отримувати запити.
|
||||
|
||||
Так само ви можете визначити логіку (код), яку слід виконати під час вимкнення застосунку. У цьому випадку код буде виконано один раз, після обробки можливо багатьох запитів.
|
||||
|
||||
Оскільки цей код виконується перед тим, як застосунок почне приймати запити, і одразу після того, як він завершить їх обробку, він охоплює всю тривалість життя застосунку (слово «lifespan» буде важливим за мить 😉).
|
||||
|
||||
Це дуже корисно для налаштування ресурсів, які потрібні для всього застосунку, які спільні між запитами, та/або які потрібно потім прибрати. Наприклад, пул з’єднань з базою даних або завантаження спільної моделі машинного навчання.
|
||||
|
||||
## Випадок використання { #use-case }
|
||||
|
||||
Почнемо з прикладу випадку використання, а потім подивимось, як це вирішити.
|
||||
|
||||
Уявімо, що у вас є моделі машинного навчання, якими ви хочете обробляти запити. 🤖
|
||||
|
||||
Ті самі моделі спільні між запитами, тобто це не окрема модель на запит чи на користувача.
|
||||
|
||||
Уявімо, що завантаження моделі може займати чимало часу, бо треба читати багато даних з диска. Тож ви не хочете робити це для кожного запиту.
|
||||
|
||||
Ви могли б завантажити її на верхньому рівні модуля/файлу, але це означало б, що модель завантажиться навіть якщо ви просто запускаєте простий автоматизований тест - тоді тест буде повільним, бо йому доведеться чекати завантаження моделі перед виконанням незалежної частини коду.
|
||||
|
||||
Ось це ми й вирішимо: завантажимо модель перед обробкою запитів, але лише безпосередньо перед тим, як застосунок почне отримувати запити, а не під час завантаження коду.
|
||||
|
||||
## Тривалість життя { #lifespan }
|
||||
|
||||
Ви можете визначити цю логіку запуску і вимкнення за допомогою параметра `lifespan` застосунку `FastAPI` та «менеджера контексту» (зараз покажу, що це).
|
||||
|
||||
Почнемо з прикладу, а потім розберемо детально.
|
||||
|
||||
Ми створюємо асинхронну функцію `lifespan()` з `yield` так:
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *}
|
||||
|
||||
Тут ми імітуємо дорогу операцію запуску із завантаженням моделі, поміщаючи (фальшиву) функцію моделі у словник з моделями машинного навчання перед `yield`. Цей код буде виконано перед тим, як застосунок почне приймати запити, під час запуску.
|
||||
|
||||
А одразу після `yield` ми розвантажуємо модель. Цей код буде виконано після того, як застосунок завершить обробку запитів, безпосередньо перед вимкненням. Це, наприклад, може звільнити ресурси на кшталт пам’яті або GPU.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Подія `shutdown` відбувається, коли ви зупиняєте застосунок.
|
||||
|
||||
Можливо, вам треба запустити нову версію, або ви просто втомилися її запускати. 🤷
|
||||
|
||||
///
|
||||
|
||||
### Функція тривалості життя { #lifespan-function }
|
||||
|
||||
Перше, на що слід звернути увагу: ми визначаємо асинхронну функцію з `yield`. Це дуже схоже на залежності з `yield`.
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *}
|
||||
|
||||
Перша частина функції до `yield` буде виконана перед запуском застосунку.
|
||||
|
||||
А частина після `yield` буде виконана після завершення роботи застосунку.
|
||||
|
||||
### Асинхронний менеджер контексту { #async-context-manager }
|
||||
|
||||
Якщо придивитися, функція задекорована за допомогою `@asynccontextmanager`.
|
||||
|
||||
Це перетворює функцію на так званий «асинхронний менеджер контексту».
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *}
|
||||
|
||||
Менеджер контексту в Python - це те, що можна використовувати в операторі `with`, наприклад, `open()` можна використовувати як менеджер контексту:
|
||||
|
||||
```Python
|
||||
with open("file.txt") as file:
|
||||
file.read()
|
||||
```
|
||||
|
||||
У новіших версіях Python також є асинхронний менеджер контексту. Його використовують з `async with`:
|
||||
|
||||
```Python
|
||||
async with lifespan(app):
|
||||
await do_stuff()
|
||||
```
|
||||
|
||||
Коли ви створюєте менеджер контексту або асинхронний менеджер контексту, як вище, перед входом у блок `with` буде виконано код перед `yield`, а після виходу з блоку `with` буде виконано код після `yield`.
|
||||
|
||||
У нашому прикладі коду вище ми не використовуємо його напряму, а передаємо його до FastAPI, щоб він його використав.
|
||||
|
||||
Параметр `lifespan` застосунку `FastAPI` приймає асинхронний менеджер контексту, тож ми можемо передати йому наш новий асинхронний менеджер контексту `lifespan`.
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[22] *}
|
||||
|
||||
## Альтернативні події (застаріло) { #alternative-events-deprecated }
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Рекомендований спосіб обробляти запуск і вимкнення - використовувати параметр `lifespan` застосунку `FastAPI`, як описано вище. Якщо ви надаєте параметр `lifespan`, обробники подій `startup` і `shutdown` більше не будуть викликані. Або все через `lifespan`, або все через події - не обидва одночасно.
|
||||
|
||||
Можете, ймовірно, пропустити цю частину.
|
||||
|
||||
///
|
||||
|
||||
Є альтернативний спосіб визначити логіку, яку слід виконати під час запуску і під час вимкнення.
|
||||
|
||||
Ви можете визначити обробники подій (функції), які потрібно виконати перед запуском застосунку або коли застосунок вимикається.
|
||||
|
||||
Ці функції можна оголошувати як з `async def`, так і звичайним `def`.
|
||||
|
||||
### Подія `startup` { #startup-event }
|
||||
|
||||
Щоб додати функцію, яку слід виконати перед запуском застосунку, оголосіть її з подією `"startup"`:
|
||||
|
||||
{* ../../docs_src/events/tutorial001_py310.py hl[8] *}
|
||||
|
||||
У цьому випадку функція-обробник події `startup` ініціалізує «базу даних» предметів (це лише `dict`) деякими значеннями.
|
||||
|
||||
Ви можете додати більше ніж один обробник події.
|
||||
|
||||
І ваш застосунок не почне приймати запити, доки всі обробники події `startup` не завершаться.
|
||||
|
||||
### Подія `shutdown` { #shutdown-event }
|
||||
|
||||
Щоб додати функцію, яку слід виконати, коли застосунок вимикається, оголосіть її з подією `"shutdown"`:
|
||||
|
||||
{* ../../docs_src/events/tutorial002_py310.py hl[6] *}
|
||||
|
||||
Тут функція-обробник події `shutdown` запише текстовий рядок `"Application shutdown"` у файл `log.txt`.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
У функції `open()` параметр `mode="a"` означає «append», тож рядок буде додано після всього, що є у файлі, без перезапису попереднього вмісту.
|
||||
|
||||
///
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зауважте, що в цьому випадку ми використовуємо стандартну Python-функцію `open()`, яка працює з файлом.
|
||||
|
||||
Тобто вона включає I/O (input/output), де потрібно «чекати», поки дані буде записано на диск.
|
||||
|
||||
Але `open()` не використовує `async` і `await`.
|
||||
|
||||
Тому ми оголошуємо функцію-обробник події зі стандартним `def`, а не `async def`.
|
||||
|
||||
///
|
||||
|
||||
### Разом `startup` і `shutdown` { #startup-and-shutdown-together }
|
||||
|
||||
Велика ймовірність, що логіка для вашого запуску і вимкнення пов’язана: ви можете хотіти щось запустити, а потім завершити, отримати ресурс, а потім звільнити його тощо.
|
||||
|
||||
Робити це в окремих функціях, які не діляться логікою чи змінними, складніше - доведеться зберігати значення у глобальних змінних або вдаватися до подібних трюків.
|
||||
|
||||
Тому зараз рекомендується натомість використовувати `lifespan`, як пояснено вище.
|
||||
|
||||
## Технічні деталі { #technical-details }
|
||||
|
||||
Невелика технічна деталь для допитливих нердів. 🤓
|
||||
|
||||
Під капотом, у технічній специфікації ASGI, це частина <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Протоколу тривалості життя</a>, і там визначені події `startup` і `shutdown`.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Ви можете прочитати більше про обробники `lifespan` у <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">документації Starlette про Lifespan</a>.
|
||||
|
||||
Зокрема, як працювати зі станом тривалості життя, який можна використовувати в інших ділянках вашого коду.
|
||||
|
||||
///
|
||||
|
||||
## Підзастосунки { #sub-applications }
|
||||
|
||||
🚨 Майте на увазі, що ці події тривалості життя (startup і shutdown) виконуються лише для головного застосунку, а не для [Підзастосунки - монтування](sub-applications.md){.internal-link target=_blank}.
|
||||
208
docs/uk/docs/advanced/generate-clients.md
Normal file
208
docs/uk/docs/advanced/generate-clients.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Генерація SDK { #generating-sdks }
|
||||
|
||||
Оскільки **FastAPI** базується на специфікації **OpenAPI**, його API можна описати у стандартному форматі, який розуміють багато інструментів.
|
||||
|
||||
Це спрощує створення актуальної **документації**, клієнтських бібліотек (<abbr title="Software Development Kits - Набори для розробки програмного забезпечення">**SDKs**</abbr>) багатьма мовами, а також **тестування** чи **автоматизованих робочих процесів**, що залишаються синхронізованими з вашим кодом.
|
||||
|
||||
У цьому посібнику ви дізнаєтеся, як згенерувати **TypeScript SDK** для вашого бекенда на FastAPI.
|
||||
|
||||
## Генератори SDK з відкритим кодом { #open-source-sdk-generators }
|
||||
|
||||
Універсальним варіантом є <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>, який підтримує **багато мов програмування** та може генерувати SDK з вашої специфікації OpenAPI.
|
||||
|
||||
Для **клієнтів TypeScript** <a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a> — спеціалізоване рішення, що надає оптимізований досвід для екосистеми TypeScript.
|
||||
|
||||
Більше генераторів SDK ви можете знайти на <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a>.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
FastAPI автоматично генерує специфікації **OpenAPI 3.1**, тож будь-який інструмент, який ви використовуєте, має підтримувати цю версію.
|
||||
|
||||
///
|
||||
|
||||
## Генератори SDK від спонсорів FastAPI { #sdk-generators-from-fastapi-sponsors }
|
||||
|
||||
У цьому розділі представлено рішення від компаній, що спонсорують FastAPI: вони мають **венчурну підтримку** та **корпоративну підтримку**. Ці продукти надають **додаткові можливості** та **інтеграції** поверх високоякісно згенерованих SDK.
|
||||
|
||||
Завдяки ✨ [**спонсорству FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ ці компанії допомагають підтримувати фреймворк та його **екосистему** здоровими та **сталими**.
|
||||
|
||||
Їхня підтримка також демонструє сильну відданість **спільноті** FastAPI (вам), показуючи, що їм важливо не лише надавати **відмінний сервіс**, а й підтримувати **міцний і процвітаючий фреймворк**, FastAPI. 🙇
|
||||
|
||||
Наприклад, ви можете спробувати:
|
||||
|
||||
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
|
||||
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
|
||||
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
|
||||
|
||||
Деякі з цих рішень також можуть бути з відкритим кодом або мати безкоштовні тарифи, тож ви можете спробувати їх без фінансових зобов'язань. Інші комерційні генератори SDK також доступні й їх можна знайти онлайн. 🤓
|
||||
|
||||
## Створити TypeScript SDK { #create-a-typescript-sdk }
|
||||
|
||||
Почнімо з простого застосунку FastAPI:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *}
|
||||
|
||||
Зверніть увагу, що *операції шляху* визначають моделі, які вони використовують для корисного навантаження запиту та корисного навантаження відповіді, використовуючи моделі `Item` і `ResponseMessage`.
|
||||
|
||||
### Документація API { #api-docs }
|
||||
|
||||
Якщо ви перейдете до `/docs`, ви побачите **схеми** даних, які надсилаються в запитах і приймаються у відповідях:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image01.png">
|
||||
|
||||
Ви бачите ці схеми, оскільки їх було оголошено як моделі в застосунку.
|
||||
|
||||
Ця інформація доступна у **схемі OpenAPI** застосунку, а потім показується в документації API.
|
||||
|
||||
Та сама інформація з моделей, яку включено до OpenAPI, може бути використана для **генерації клієнтського коду**.
|
||||
|
||||
### Hey API { #hey-api }
|
||||
|
||||
Коли у нас є застосунок FastAPI з моделями, ми можемо використати Hey API для генерації клієнта TypeScript. Найшвидший спосіб зробити це — через npx.
|
||||
|
||||
```sh
|
||||
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
|
||||
```
|
||||
|
||||
Це згенерує TypeScript SDK у `./src/client`.
|
||||
|
||||
Ви можете дізнатися, як <a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">встановити `@hey-api/openapi-ts`</a>, і почитати про <a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">згенерований результат</a> на їхньому сайті.
|
||||
|
||||
### Використання SDK { #using-the-sdk }
|
||||
|
||||
Тепер ви можете імпортувати та використовувати клієнтський код. Це може виглядати так; зверніть увагу, що ви отримуєте «автодоповнення» для методів:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image02.png">
|
||||
|
||||
Ви також отримаєте автодоповнення для корисного навантаження, яке надсилаєте:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image03.png">
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу на автодоповнення для `name` і `price`, які були визначені в застосунку FastAPI, у моделі `Item`.
|
||||
|
||||
///
|
||||
|
||||
Ви бачитимете вбудовані помилки для даних, які надсилаєте:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image04.png">
|
||||
|
||||
Об'єкт відповіді також матиме автодоповнення:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image05.png">
|
||||
|
||||
## Застосунок FastAPI з мітками { #fastapi-app-with-tags }
|
||||
|
||||
У багатьох випадках ваш застосунок FastAPI буде більшим, і ви, ймовірно, використовуватимете мітки, щоб розділяти різні групи *операцій шляху*.
|
||||
|
||||
Наприклад, у вас може бути секція для **items** і окрема секція для **users**, і їх можна розділити мітками:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *}
|
||||
|
||||
### Згенерувати TypeScript-клієнт із мітками { #generate-a-typescript-client-with-tags }
|
||||
|
||||
Якщо ви згенеруєте клієнт для застосунку FastAPI, що використовує мітки, зазвичай клієнтський код також буде розділено за цими мітками.
|
||||
|
||||
Таким чином, ви матимете правильно впорядковані та згруповані частини клієнтського коду:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image06.png">
|
||||
|
||||
У цьому випадку у вас є:
|
||||
|
||||
* `ItemsService`
|
||||
* `UsersService`
|
||||
|
||||
### Назви методів клієнта { #client-method-names }
|
||||
|
||||
Зараз згенеровані назви методів на кшталт `createItemItemsPost` виглядають не дуже охайно:
|
||||
|
||||
```TypeScript
|
||||
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
|
||||
```
|
||||
|
||||
...це тому, що генератор клієнта використовує внутрішній OpenAPI **operation ID** для кожної *операції шляху*.
|
||||
|
||||
OpenAPI вимагає, щоб кожен operation ID був унікальним для всіх *операцій шляху*, тому FastAPI використовує **назву функції**, **шлях** і **HTTP-метод/операцію** для генерації цього operation ID, адже так воно може гарантувати унікальність operation ID.
|
||||
|
||||
Але далі я покажу, як це покращити. 🤓
|
||||
|
||||
## Користувацькі operation ID та кращі назви методів { #custom-operation-ids-and-better-method-names }
|
||||
|
||||
Ви можете **змінити** спосіб **генерації** цих operation ID, щоб зробити їх простішими та мати **простіші назви методів** у клієнтах.
|
||||
|
||||
У цьому випадку вам потрібно буде іншим способом гарантувати, що кожен operation ID є **унікальним**.
|
||||
|
||||
Наприклад, ви можете переконатися, що кожна *операція шляху* має мітку, а потім генерувати operation ID на основі **мітки** та **назви** *операції шляху* (назви функції).
|
||||
|
||||
### Користувацька функція генерування унікального ID { #custom-generate-unique-id-function }
|
||||
|
||||
FastAPI використовує **унікальний ID** для кожної *операції шляху*, який застосовується для **operation ID**, а також для назв будь-яких потрібних користувацьких моделей для запитів чи відповідей.
|
||||
|
||||
Ви можете налаштувати цю функцію. Вона приймає `APIRoute` і повертає строку.
|
||||
|
||||
Наприклад, тут використовується перша мітка (у вас, ймовірно, буде лише одна мітка) і назва *операції шляху* (назва функції).
|
||||
|
||||
Потім ви можете передати цю користувацьку функцію до **FastAPI** як параметр `generate_unique_id_function`:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *}
|
||||
|
||||
### Згенерувати TypeScript-клієнт з користувацькими operation ID { #generate-a-typescript-client-with-custom-operation-ids }
|
||||
|
||||
Тепер, якщо ви згенеруєте клієнт знову, ви побачите покращені назви методів:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image07.png">
|
||||
|
||||
Як бачите, тепер у назвах методів є мітка, а потім назва функції; вони більше не містять інформації з URL-шляху та HTTP-операції.
|
||||
|
||||
### Попередня обробка специфікації OpenAPI для генератора клієнта { #preprocess-the-openapi-specification-for-the-client-generator }
|
||||
|
||||
У згенерованому коді все ще є певна **дубльована інформація**.
|
||||
|
||||
Ми вже знаємо, що цей метод стосується **items**, адже це слово є в `ItemsService` (взято з мітки), але все ще маємо назву мітки як префікс у назві методу. 😕
|
||||
|
||||
Ми, ймовірно, все одно захочемо зберегти це загалом для OpenAPI, адже так гарантується унікальність operation ID.
|
||||
|
||||
Але для згенерованого клієнта ми можемо **змінити** operation ID в OpenAPI безпосередньо перед генерацією клієнтів, просто щоб зробити назви методів приємнішими та **чистішими**.
|
||||
|
||||
Ми можемо завантажити JSON OpenAPI у файл `openapi.json`, а потім **прибрати цей префікс із міткою** за допомогою такого скрипту:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial004_py310.py *}
|
||||
|
||||
//// tab | Node.js
|
||||
|
||||
```Javascript
|
||||
{!> ../../docs_src/generate_clients/tutorial004.js!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Після цього operation ID буде перейменовано з чогось на кшталт `items-get_items` просто на `get_items`, тож генератор клієнта зможе створити простіші назви методів.
|
||||
|
||||
### Згенерувати TypeScript-клієнт із попередньо обробленим OpenAPI { #generate-a-typescript-client-with-the-preprocessed-openapi }
|
||||
|
||||
Оскільки кінцевий результат тепер у файлі `openapi.json`, вам потрібно оновити шлях до вхідних даних:
|
||||
|
||||
```sh
|
||||
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
|
||||
```
|
||||
|
||||
Після генерації нового клієнта ви отримаєте **чисті назви методів**, із усім **автодоповненням**, **вбудованими помилками** тощо:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image08.png">
|
||||
|
||||
## Переваги { #benefits }
|
||||
|
||||
Використовуючи автоматично згенеровані клієнти, ви отримаєте **автодоповнення** для:
|
||||
|
||||
* Методів.
|
||||
* Корисного навантаження запиту в тілі, параметрах запиту тощо.
|
||||
* Корисного навантаження відповіді.
|
||||
|
||||
Також ви матимете **вбудовані помилки** для всього.
|
||||
|
||||
І щоразу, коли ви оновлюєте код бекенда та **перегенеровуєте** фронтенд, у ньому з'являтимуться нові *операції шляху* як методи, старі буде видалено, а будь-які інші зміни відобразяться у згенерованому коді. 🤓
|
||||
|
||||
Це також означає, що якщо щось змінилося, це буде **відображено** в клієнтському коді автоматично. А якщо ви **зіберете** клієнт, буде повідомлено про помилку, якщо є будь-яка **невідповідність** у використаних даних.
|
||||
|
||||
Таким чином, ви **виявлятимете багато помилок** дуже рано в циклі розробки, замість того, щоб чекати, поки помилки проявляться у ваших кінцевих користувачів у продакшені, і лише потім намагатися з'ясувати, у чому проблема. ✨
|
||||
21
docs/uk/docs/advanced/index.md
Normal file
21
docs/uk/docs/advanced/index.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Просунутий посібник користувача { #advanced-user-guide }
|
||||
|
||||
## Додаткові можливості { #additional-features }
|
||||
|
||||
Основний [Навчальний посібник - Посібник користувача](../tutorial/index.md){.internal-link target=_blank} має бути достатнім, щоб провести вас через усі основні можливості **FastAPI**.
|
||||
|
||||
У наступних розділах ви побачите інші опції, конфігурації та додаткові можливості.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Наступні розділи не обов'язково «просунуті».
|
||||
|
||||
І можливо, що рішення для вашого випадку використання може бути в одному з них.
|
||||
|
||||
///
|
||||
|
||||
## Спершу прочитайте навчальний посібник { #read-the-tutorial-first }
|
||||
|
||||
Ви все ще можете використовувати більшість можливостей **FastAPI**, маючи знання з основного [Навчального посібника - Посібника користувача](../tutorial/index.md){.internal-link target=_blank}.
|
||||
|
||||
А в наступних розділах передбачається, що ви вже його прочитали і знайомі з основними ідеями.
|
||||
97
docs/uk/docs/advanced/middleware.md
Normal file
97
docs/uk/docs/advanced/middleware.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Просунуте проміжне програмне забезпечення { #advanced-middleware }
|
||||
|
||||
У головному навчальному посібнику ви читали, як додати [Користувацьке проміжне ПЗ](../tutorial/middleware.md){.internal-link target=_blank} до вашого застосунку.
|
||||
|
||||
Також ви читали, як обробляти [CORS за допомогою `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
|
||||
|
||||
У цьому розділі розглянемо, як використовувати інше проміжне ПЗ.
|
||||
|
||||
## Додавання middleware ASGI { #adding-asgi-middlewares }
|
||||
|
||||
Оскільки **FastAPI** базується на Starlette і реалізує специфікацію <abbr title="Asynchronous Server Gateway Interface - Асинхронний інтерфейс шлюзу сервера">ASGI</abbr>, ви можете використовувати будь-яке проміжне ПЗ ASGI.
|
||||
|
||||
Middleware не обов'язково має бути створене саме для FastAPI або Starlette, головне - щоб воно відповідало специфікації ASGI.
|
||||
|
||||
Загалом, middleware ASGI — це класи, які очікують отримати застосунок ASGI як перший аргумент.
|
||||
|
||||
Тож у документації до сторонніх middleware ASGI вам, імовірно, порадять зробити приблизно так:
|
||||
|
||||
```Python
|
||||
from unicorn import UnicornMiddleware
|
||||
|
||||
app = SomeASGIApp()
|
||||
|
||||
new_app = UnicornMiddleware(app, some_config="rainbow")
|
||||
```
|
||||
|
||||
Але FastAPI (точніше Starlette) надає простіший спосіб, який гарантує, що внутрішнє middleware обробляє помилки сервера, а користувацькі обробники винятків працюють коректно.
|
||||
|
||||
Для цього використовуйте `app.add_middleware()` (як у прикладі для CORS).
|
||||
|
||||
```Python
|
||||
from fastapi import FastAPI
|
||||
from unicorn import UnicornMiddleware
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(UnicornMiddleware, some_config="rainbow")
|
||||
```
|
||||
|
||||
`app.add_middleware()` приймає клас middleware як перший аргумент і будь-які додаткові аргументи, що будуть передані цьому middleware.
|
||||
|
||||
## Вбудоване middleware { #integrated-middlewares }
|
||||
|
||||
**FastAPI** містить кілька middleware для поширених випадків використання, далі розглянемо, як їх використовувати.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
У наступних прикладах ви також можете використовувати `from starlette.middleware.something import SomethingMiddleware`.
|
||||
|
||||
**FastAPI** надає кілька middleware у `fastapi.middleware` виключно для зручності розробника. Але більшість доступних middleware походять безпосередньо зі Starlette.
|
||||
|
||||
///
|
||||
|
||||
## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
|
||||
|
||||
Примушує, щоб усі вхідні запити були або `https`, або `wss`.
|
||||
|
||||
Будь-який вхідний запит до `http` або `ws` буде перенаправлено на захищену схему.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *}
|
||||
|
||||
## `TrustedHostMiddleware` { #trustedhostmiddleware }
|
||||
|
||||
Примушує, щоб усі вхідні запити мали коректно встановлений заголовок `Host`, щоб захиститися від атак HTTP Host Header.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *}
|
||||
|
||||
Підтримуються такі аргументи:
|
||||
|
||||
- `allowed_hosts` - Список доменних імен, які слід дозволити як імена хостів. Підтримуються домени з «дикою картою», такі як `*.example.com`, для зіставлення піддоменів. Щоб дозволити будь-яке ім'я хоста, або використовуйте `allowed_hosts=["*"]`, або не додавайте це middleware.
|
||||
- `www_redirect` - Якщо встановлено True, запити до не-www версій дозволених хостів буде перенаправлено до їхніх www-варіантів. Типово `True`.
|
||||
|
||||
Якщо вхідний запит не проходить перевірку, буде надіслано відповідь `400`.
|
||||
|
||||
## `GZipMiddleware` { #gzipmiddleware }
|
||||
|
||||
Обробляє відповіді GZip для будь-якого запиту, що містить `"gzip"` у заголовку `Accept-Encoding`.
|
||||
|
||||
Middleware обробляє як стандартні, так і потокові відповіді.
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *}
|
||||
|
||||
Підтримуються такі аргументи:
|
||||
|
||||
- `minimum_size` - Не GZip-увати відповіді, менші за цей мінімальний розмір у байтах. Типово `500`.
|
||||
- `compresslevel` - Використовується під час стиснення GZip. Це ціле число в діапазоні від 1 до 9. Типово `9`. Менше значення дає швидше стиснення, але більший розмір файлів; більше значення дає повільніше стиснення, але менший розмір файлів.
|
||||
|
||||
## Інше middleware { #other-middlewares }
|
||||
|
||||
Є багато іншого проміжного ПЗ ASGI.
|
||||
|
||||
Наприклад:
|
||||
|
||||
- <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">`ProxyHeadersMiddleware` з Uvicorn</a>
|
||||
- <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
|
||||
|
||||
Щоб переглянути інші доступні middleware, ознайомтеся з <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">документацією Starlette щодо middleware</a> та <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">списком ASGI Awesome</a>.
|
||||
186
docs/uk/docs/advanced/openapi-callbacks.md
Normal file
186
docs/uk/docs/advanced/openapi-callbacks.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Зворотні виклики OpenAPI { #openapi-callbacks }
|
||||
|
||||
Ви можете створити API з операцією шляху, яка ініціюватиме запит до зовнішнього API, створеного кимось іншим (ймовірно тим самим розробником, який буде використовувати ваш API).
|
||||
|
||||
Процес, що відбувається, коли ваш застосунок API викликає зовнішній API, називається «зворотний виклик». Тому що програмне забезпечення, написане зовнішнім розробником, надсилає запит до вашого API, а потім ваш API виконує зворотний виклик, надсилаючи запит до зовнішнього API (його, ймовірно, також створив той самий розробник).
|
||||
|
||||
У такому випадку вам може знадобитися задокументувати, яким має бути той зовнішній API: які операції шляху він має мати, яке тіло очікувати, яку відповідь повертати тощо.
|
||||
|
||||
## Застосунок зі зворотними викликами { #an-app-with-callbacks }
|
||||
|
||||
Розгляньмо це на прикладі.
|
||||
|
||||
Уявімо, що ви розробляєте застосунок для створення рахунків.
|
||||
|
||||
Ці рахунки матимуть `id`, `title` (необов'язково), `customer` і `total`.
|
||||
|
||||
Користувач вашого API (зовнішній розробник) створить рахунок у вашому API за допомогою POST-запиту.
|
||||
|
||||
Потім ваш API буде (уявімо):
|
||||
|
||||
- Надсилати рахунок деякому клієнту зовнішнього розробника.
|
||||
- Отримувати оплату.
|
||||
- Надсилати сповіщення назад користувачу API (зовнішньому розробнику).
|
||||
- Це буде зроблено шляхом надсилання POST-запиту (з вашого API) до деякого зовнішнього API, наданого тим зовнішнім розробником (це і є «зворотний виклик»).
|
||||
|
||||
## Звичайний застосунок FastAPI { #the-normal-fastapi-app }
|
||||
|
||||
Спочатку подивімося, як виглядав би звичайний застосунок API до додавання зворотного виклику.
|
||||
|
||||
Він матиме операцію шляху, яка отримуватиме тіло `Invoice`, і параметр запиту `callback_url`, що міститиме URL для зворотного виклику.
|
||||
|
||||
Ця частина цілком звична, більшість коду вам, ймовірно, уже знайома:
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Параметр запиту `callback_url` використовує тип Pydantic <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a>.
|
||||
|
||||
///
|
||||
|
||||
Єдина нова річ - це `callbacks=invoices_callback_router.routes` як аргумент декоратора операції шляху. Далі розглянемо, що це таке.
|
||||
|
||||
## Документування зворотного виклику { #documenting-the-callback }
|
||||
|
||||
Фактичний код зворотного виклику сильно залежатиме від вашого застосунку API.
|
||||
|
||||
І, ймовірно, сильно відрізнятиметься від застосунку до застосунку.
|
||||
|
||||
Це можуть бути лише один-два рядки коду, наприклад:
|
||||
|
||||
```Python
|
||||
callback_url = "https://example.com/api/v1/invoices/events/"
|
||||
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
```
|
||||
|
||||
Але, можливо, найважливіша частина зворотного виклику - переконатися, що користувач вашого API (зовнішній розробник) правильно реалізує зовнішній API відповідно до даних, які ваш API надсилатиме в тілі запиту зворотного виклику тощо.
|
||||
|
||||
Тому далі ми додамо код, щоб задокументувати, яким має бути цей зовнішній API, щоб приймати зворотний виклик від вашого API.
|
||||
|
||||
Ця документація з'явиться в Swagger UI за адресою `/docs` у вашому API і дасть змогу зовнішнім розробникам зрозуміти, як створити зовнішній API.
|
||||
|
||||
У цьому прикладі сам зворотний виклик не реалізовано (це може бути лише один рядок коду), лише частину з документацією.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Фактичний зворотний виклик - це просто HTTP-запит.
|
||||
|
||||
Реалізуючи зворотний виклик самостійно, ви можете скористатися, наприклад, <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> або <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>.
|
||||
|
||||
///
|
||||
|
||||
## Напишіть код документації для зворотного виклику { #write-the-callback-documentation-code }
|
||||
|
||||
Цей код не виконуватиметься у вашому застосунку, він потрібен лише, щоб задокументувати, яким має бути зовнішній API.
|
||||
|
||||
Але ви вже знаєте, як легко створювати автоматичну документацію для API за допомогою FastAPI.
|
||||
|
||||
Тож ми скористаємося цими знаннями, щоб задокументувати, яким має бути зовнішній API... створивши операції шляху, які має реалізувати зовнішній API (ті, які викликатиме ваш API).
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Пишучи код для документування зворотного виклику, корисно уявити, що ви - той *зовнішній розробник*. І що ви зараз реалізуєте *зовнішній API*, а не *ваш API*.
|
||||
|
||||
Тимчасово прийнявши цю точку зору ( *зовнішнього розробника* ), вам буде очевидніше, куди помістити параметри, яку Pydantic-модель використати для тіла, для відповіді тощо для того *зовнішнього API*.
|
||||
|
||||
///
|
||||
|
||||
### Створіть callback `APIRouter` { #create-a-callback-apirouter }
|
||||
|
||||
Спочатку створіть новий `APIRouter`, який міститиме один або кілька зворотних викликів.
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
|
||||
|
||||
### Створіть операцію шляху зворотного виклику { #create-the-callback-path-operation }
|
||||
|
||||
Щоб створити операцію шляху зворотного виклику, використайте той самий `APIRouter`, який ви створили вище.
|
||||
|
||||
Вона має виглядати як звичайна операція шляху FastAPI:
|
||||
|
||||
- Ймовірно має містити оголошення тіла, яке вона приймає, напр. `body: InvoiceEvent`.
|
||||
- І також може містити оголошення відповіді, яку вона повертає, напр. `response_model=InvoiceEventReceived`.
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
|
||||
|
||||
Є 2 основні відмінності від звичайної операції шляху:
|
||||
|
||||
- Їй не потрібен реальний код, адже ваш застосунок ніколи не викликатиме цей код. Вона використовується лише для документування зовнішнього API. Тому функція може просто містити `pass`.
|
||||
- Шлях може містити <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">вираз OpenAPI 3</a> (див. нижче), де можна використовувати змінні з параметрами та частини оригінального запиту, надісланого до вашого API.
|
||||
|
||||
### Вираз шляху зворотного виклику { #the-callback-path-expression }
|
||||
|
||||
Шлях зворотного виклику може містити <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">вираз OpenAPI 3</a>, який включає частини оригінального запиту, надісланого до вашого API.
|
||||
|
||||
У цьому випадку це строка:
|
||||
|
||||
```Python
|
||||
"{$callback_url}/invoices/{$request.body.id}"
|
||||
```
|
||||
|
||||
Отже, якщо користувач вашого API (зовнішній розробник) надішле запит до *вашого API* на:
|
||||
|
||||
```
|
||||
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
|
||||
```
|
||||
|
||||
з JSON-тілом:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"id": "2expen51ve",
|
||||
"customer": "Mr. Richie Rich",
|
||||
"total": "9999"
|
||||
}
|
||||
```
|
||||
|
||||
тоді *ваш API* опрацює рахунок і згодом надішле запит зворотного виклику на `callback_url` ( *зовнішній API* ):
|
||||
|
||||
```
|
||||
https://www.external.org/events/invoices/2expen51ve
|
||||
```
|
||||
|
||||
з JSON-тілом на кшталт:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"description": "Payment celebration",
|
||||
"paid": true
|
||||
}
|
||||
```
|
||||
|
||||
і очікуватиме відповідь від того *зовнішнього API* з JSON-тілом на кшталт:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу, що використаний URL зворотного виклику містить URL, отриманий як параметр запиту в `callback_url` (`https://www.external.org/events`), а також `id` рахунку зсередини JSON-тіла (`2expen51ve`).
|
||||
|
||||
///
|
||||
|
||||
### Додайте маршрутизатор зворотного виклику { #add-the-callback-router }
|
||||
|
||||
На цьому етапі ви маєте потрібні операції шляху зворотного виклику (ті, які має реалізувати *зовнішній розробник* у *зовнішньому API*) у створеному вище маршрутизаторі зворотного виклику.
|
||||
|
||||
Тепер використайте параметр `callbacks` у декораторі операції шляху вашого API, щоб передати атрибут `.routes` (це насправді просто `list` маршрутів/операцій шляху) з цього маршрутизатора зворотного виклику:
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу, що ви передаєте не сам маршрутизатор (`invoices_callback_router`) у `callback=`, а атрибут `.routes`, тобто `invoices_callback_router.routes`.
|
||||
|
||||
///
|
||||
|
||||
### Перевірте документацію { #check-the-docs }
|
||||
|
||||
Тепер ви можете запустити застосунок і перейти за адресою <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Ви побачите вашу документацію з розділом «Callbacks» для вашої операції шляху, який показує, як має виглядати зовнішній API:
|
||||
|
||||
<img src="/img/tutorial/openapi-callbacks/image01.png">
|
||||
55
docs/uk/docs/advanced/openapi-webhooks.md
Normal file
55
docs/uk/docs/advanced/openapi-webhooks.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Вебхуки OpenAPI { #openapi-webhooks }
|
||||
|
||||
Бувають випадки, коли ви хочете повідомити **користувачів** вашого API, що ваш застосунок може викликати *їхній* застосунок (надсилаючи запит) із деякими даними, зазвичай щоб **сповістити** про певний тип **події**.
|
||||
|
||||
Це означає, що замість звичного процесу, коли ваші користувачі надсилають запити до вашого API, саме **ваш API** (або ваш застосунок) може **надсилати запити до їхньої системи** (до їх API, їх застосунку).
|
||||
|
||||
Зазвичай це називають **вебхуком**.
|
||||
|
||||
## Кроки вебхуків { #webhooks-steps }
|
||||
|
||||
Зазвичай процес такий: ви визначаєте у своєму коді, яке повідомлення надсилатимете, тобто **тіло запиту**.
|
||||
|
||||
Ви також якимось чином визначаєте моменти, коли ваш застосунок надсилатиме ці запити або події.
|
||||
|
||||
А **ваші користувачі** якимось чином (наприклад, у веб-дашборді) визначають **URL**, куди ваш застосунок має надсилати ці запити.
|
||||
|
||||
Уся **логіка** щодо реєстрації URL для вебхуків і код для фактичного надсилання цих запитів - на ваш розсуд. Ви пишете це у **власному коді** так, як вважаєте за потрібне.
|
||||
|
||||
## Документування вебхуків у **FastAPI** та OpenAPI { #documenting-webhooks-with-fastapi-and-openapi }
|
||||
|
||||
У **FastAPI**, використовуючи OpenAPI, ви можете визначити назви цих вебхуків, типи HTTP-операцій, які ваш застосунок може надсилати (наприклад, `POST`, `PUT` тощо), і **тіла** запитів, які ваш застосунок надсилатиме.
|
||||
|
||||
Це значно спростить для ваших користувачів **реалізацію їхніх API** для отримання ваших запитів **вебхуків**; вони навіть зможуть згенерувати частину власного коду API автоматично.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Вебхуки доступні в OpenAPI 3.1.0 і вище, підтримуються FastAPI `0.99.0` і вище.
|
||||
|
||||
///
|
||||
|
||||
## Застосунок із вебхуками { #an-app-with-webhooks }
|
||||
|
||||
Коли ви створюєте застосунок **FastAPI**, є атрибут `webhooks`, який можна використати для визначення *вебхуків* так само, як ви визначаєте *операції шляху*, наприклад за допомогою `@app.webhooks.post()`.
|
||||
|
||||
{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *}
|
||||
|
||||
Визначені вами вебхуки потраплять до **схеми OpenAPI** та автоматичного **інтерфейсу документації**.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Об'єкт `app.webhooks` насправді є просто `APIRouter` - тим самим типом, який ви використовуєте, структуризуючи застосунок у кількох файлах.
|
||||
|
||||
///
|
||||
|
||||
Зверніть увагу, що з вебхуками ви фактично не оголошуєте *шлях* (на кшталт `/items/`), текст, який ви передаєте там, - це лише **ідентифікатор** вебхука (назва події). Наприклад, у `@app.webhooks.post("new-subscription")` назва вебхука - `new-subscription`.
|
||||
|
||||
Це тому, що очікується, що **ваші користувачі** іншим способом (наприклад, у веб-дашборді) визначать фактичний **URL-шлях**, де вони хочуть отримувати запит вебхука.
|
||||
|
||||
### Перевірте документацію { #check-the-docs }
|
||||
|
||||
Тепер ви можете запустити свій застосунок і перейти за адресою <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Ви побачите у своїй документації звичайні *операції шляху*, а також деякі **вебхуки**:
|
||||
|
||||
<img src="/img/tutorial/openapi-webhooks/image01.png">
|
||||
172
docs/uk/docs/advanced/path-operation-advanced-configuration.md
Normal file
172
docs/uk/docs/advanced/path-operation-advanced-configuration.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Додаткова конфігурація операцій шляху { #path-operation-advanced-configuration }
|
||||
|
||||
## OpenAPI operationId { #openapi-operationid }
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Якщо ви не «експерт» з OpenAPI, імовірно, вам це не потрібно.
|
||||
|
||||
///
|
||||
|
||||
Ви можете встановити OpenAPI `operationId`, який буде використано у вашій *операції шляху*, за допомогою параметра `operation_id`.
|
||||
|
||||
Потрібно переконатися, що він унікальний для кожної операції.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *}
|
||||
|
||||
### Використання назви *функції операції шляху* як operationId { #using-the-path-operation-function-name-as-the-operationid }
|
||||
|
||||
Якщо ви хочете використовувати назви функцій ваших API як `operationId`, ви можете пройтися по всіх них і переписати `operation_id` кожної *операції шляху*, використовуючи їхній `APIRoute.name`.
|
||||
|
||||
Зробіть це після додавання всіх *операцій шляху*.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви вручну викликаєте `app.openapi()`, оновіть значення `operationId` до цього.
|
||||
|
||||
///
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Якщо ви робите це, переконайтеся, що кожна з ваших *функцій операцій шляху* має унікальну назву.
|
||||
|
||||
Навіть якщо вони в різних модулях (файлах Python).
|
||||
|
||||
///
|
||||
|
||||
## Виключення з OpenAPI { #exclude-from-openapi }
|
||||
|
||||
Щоб виключити *операцію шляху* зі згенерованої Схеми OpenAPI (а отже, і з автоматичних систем документації), використайте параметр `include_in_schema` і встановіть його в `False`:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *}
|
||||
|
||||
## Розширений опис із docstring { #advanced-description-from-docstring }
|
||||
|
||||
Ви можете обмежити кількість рядків із docstring *функції операції шляху*, що використовуються для OpenAPI.
|
||||
|
||||
Додавання `\f` (екранованого символу «form feed») змусить **FastAPI** обрізати вивід для OpenAPI в цій точці.
|
||||
|
||||
Це не з’явиться в документації, але інші інструменти (такі як Sphinx) зможуть використати решту.
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
|
||||
|
||||
## Додаткові відповіді { #additional-responses }
|
||||
|
||||
Ймовірно, ви вже бачили, як оголошувати `response_model` і `status_code` для *операції шляху*.
|
||||
|
||||
Це визначає метадані про основну відповідь *операції шляху*.
|
||||
|
||||
Також можна оголосити додаткові відповіді з їхніми моделями, кодами статусу тощо.
|
||||
|
||||
У документації є цілий розділ про це, ви можете прочитати його тут: [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}.
|
||||
|
||||
## Додатково в OpenAPI { #openapi-extra }
|
||||
|
||||
Коли ви оголошуєте *операцію шляху* у своєму застосунку, **FastAPI** автоматично генерує відповідні метадані про цю *операцію шляху* для включення в Схему OpenAPI.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
У специфікації OpenAPI це називається <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Об'єкт Operation</a>.
|
||||
|
||||
///
|
||||
|
||||
Він містить усю інформацію про *операцію шляху* і використовується для побудови автоматичної документації.
|
||||
|
||||
Він включає `tags`, `parameters`, `requestBody`, `responses` тощо.
|
||||
|
||||
Цю OpenAPI-схему, специфічну для *операції шляху*, зазвичай генерує **FastAPI** автоматично, але ви також можете її розширити.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Це низькорівнева точка розширення.
|
||||
|
||||
Якщо вам потрібно лише оголосити додаткові відповіді, зручніше зробити це через [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
Ви можете розширити OpenAPI-схему для *операції шляху*, використовуючи параметр `openapi_extra`.
|
||||
|
||||
### Розширення OpenAPI { #openapi-extensions }
|
||||
|
||||
`openapi_extra` може бути корисним, наприклад, для оголошення [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *}
|
||||
|
||||
Якщо ви відкриєте автоматичну документацію API, ваше розширення з’явиться внизу конкретної *операції шляху*.
|
||||
|
||||
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
|
||||
|
||||
І якщо ви відкриєте згенерований OpenAPI (за адресою `/openapi.json` у вашому API), ви також побачите своє розширення як частину конкретної *операції шляху*:
|
||||
|
||||
```JSON hl_lines="22"
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"summary": "Read Items",
|
||||
"operationId": "read_items_items__get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-aperture-labs-portal": "blue"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Власна схема OpenAPI для *операції шляху* { #custom-openapi-path-operation-schema }
|
||||
|
||||
Словник у `openapi_extra` буде глибоко об’єднано з автоматично згенерованою OpenAPI-схемою для *операції шляху*.
|
||||
|
||||
Тож ви можете додати додаткові дані до автоматично згенерованої схеми.
|
||||
|
||||
Наприклад, ви можете вирішити читати та перевіряти запит власним кодом, не використовуючи автоматичні можливості FastAPI з Pydantic, але все ж захотіти визначити запит у Схемі OpenAPI.
|
||||
|
||||
Ви можете зробити це за допомогою `openapi_extra`:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *}
|
||||
|
||||
У цьому прикладі ми не оголошували жодної моделі Pydantic. Насправді тіло запиту навіть не <dfn title="перетворено з простого формату, як-от bytes, у об'єкти Python">розібрано</dfn> як JSON, воно читається безпосередньо як `bytes`, а функція `magic_data_reader()` відповідатиме за його розбір певним чином.
|
||||
|
||||
Водночас ми можемо оголосити очікувану схему для тіла запиту.
|
||||
|
||||
### Власний тип вмісту OpenAPI { #custom-openapi-content-type }
|
||||
|
||||
Використовуючи той самий прийом, ви можете застосувати модель Pydantic, щоб визначити Схему JSON, яка потім включається в користувацький розділ OpenAPI-схеми для *операції шляху*.
|
||||
|
||||
І ви можете зробити це, навіть якщо тип даних у запиті - не JSON.
|
||||
|
||||
Наприклад, у цьому застосунку ми не використовуємо вбудовану функціональність FastAPI для отримання Схеми JSON з моделей Pydantic і не використовуємо автоматичну валідацію для JSON. Насправді ми оголошуємо тип вмісту запиту як YAML, а не JSON:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *}
|
||||
|
||||
Попри те, що ми не використовуємо типову вбудовану функціональність, ми все одно використовуємо модель Pydantic, щоб вручну згенерувати Схему JSON для даних, які хочемо отримати у форматі YAML.
|
||||
|
||||
Потім ми працюємо із запитом безпосередньо і отримуємо тіло як `bytes`. Це означає, що FastAPI навіть не намагатиметься розібрати корисне навантаження запиту як JSON.
|
||||
|
||||
Далі у нашому коді ми безпосередньо розбираємо цей YAML-вміст і знову використовуємо ту саму модель Pydantic, щоб перевірити YAML-вміст:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Тут ми перевикористовуємо ту саму модель Pydantic.
|
||||
|
||||
Але так само ми могли б перевіряти дані іншим способом.
|
||||
|
||||
///
|
||||
31
docs/uk/docs/advanced/response-change-status-code.md
Normal file
31
docs/uk/docs/advanced/response-change-status-code.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Відповідь - зміна коду статусу { #response-change-status-code }
|
||||
|
||||
Ймовірно, ви вже читали, що можна встановити типовий [код статусу відповіді](../tutorial/response-status-code.md){.internal-link target=_blank}.
|
||||
|
||||
Але інколи потрібно повернути інший код статусу, ніж типовий.
|
||||
|
||||
## Випадок використання { #use-case }
|
||||
|
||||
Наприклад, уявімо, що ви хочете за замовчуванням повертати код статусу HTTP «OK» `200`.
|
||||
|
||||
Але якщо даних не існувало, ви хочете створити їх і повернути код статусу HTTP «CREATED» `201`.
|
||||
|
||||
Водночас ви все одно хочете мати змогу фільтрувати та перетворювати повернені дані за допомогою `response_model`.
|
||||
|
||||
Для таких випадків ви можете використати параметр `Response`.
|
||||
|
||||
## Використовуйте параметр `Response` { #use-a-response-parameter }
|
||||
|
||||
Ви можете оголосити параметр типу `Response` у своїй функції операції шляху (так само, як для кукі та заголовків).
|
||||
|
||||
Потім ви можете встановити `status_code` у цьому *тимчасовому* об'єкті відповіді.
|
||||
|
||||
{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *}
|
||||
|
||||
Після цього ви можете повернути будь-який потрібний об'єкт, як зазвичай (`dict`, модель бази даних тощо).
|
||||
|
||||
І якщо ви оголосили `response_model`, він усе одно буде використаний для фільтрації та перетворення поверненого об'єкта.
|
||||
|
||||
**FastAPI** використає цей *тимчасовий* об'єкт відповіді, щоб отримати код статусу (а також кукі та заголовки), і помістить їх у фінальну відповідь, що містить повернуте вами значення, відфільтроване за допомогою `response_model`.
|
||||
|
||||
Ви також можете оголосити параметр `Response` у залежностях і встановлювати там код статусу. Але майте на увазі: останнє встановлене значення матиме пріоритет.
|
||||
51
docs/uk/docs/advanced/response-cookies.md
Normal file
51
docs/uk/docs/advanced/response-cookies.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Кукі у відповіді { #response-cookies }
|
||||
|
||||
## Використовуйте параметр `Response` { #use-a-response-parameter }
|
||||
|
||||
Ви можете оголосити параметр типу `Response` у вашій *функції операції шляху*.
|
||||
|
||||
Потім ви можете встановити кукі в цьому *тимчасовому* об'єкті відповіді.
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *}
|
||||
|
||||
Після цього ви можете повернути будь-який потрібний об'єкт, як зазвичай (наприклад, `dict`, модель бази даних тощо).
|
||||
|
||||
І якщо ви оголосили `response_model`, він усе одно буде використаний для фільтрації та перетворення об'єкта, який ви повернули.
|
||||
|
||||
**FastAPI** використає цю *тимчасову* відповідь, щоб витягнути кукі (а також заголовки та код статусу) і помістить їх у фінальну відповідь, що містить значення, яке ви повернули, відфільтроване будь-якою `response_model`.
|
||||
|
||||
Ви також можете оголосити параметр `Response` у залежностях і встановлювати в них кукі (і заголовки).
|
||||
|
||||
## Повертайте `Response` безпосередньо { #return-a-response-directly }
|
||||
|
||||
Ви також можете створювати кукі, повертаючи `Response` безпосередньо у вашому коді.
|
||||
|
||||
Для цього ви можете створити відповідь, як описано в [Повернути відповідь безпосередньо](response-directly.md){.internal-link target=_blank}.
|
||||
|
||||
Потім встановіть у ньому кукі і поверніть його:
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Майте на увазі, що якщо ви повертаєте відповідь безпосередньо замість використання параметра `Response`, FastAPI поверне її напряму.
|
||||
|
||||
Тому вам потрібно переконатися, що ваші дані мають коректний тип. Наприклад, сумісні з JSON, якщо ви повертаєте `JSONResponse`.
|
||||
|
||||
А також що ви не надсилаєте дані, які мали б бути відфільтровані за допомогою `response_model`.
|
||||
|
||||
///
|
||||
|
||||
### Більше інформації { #more-info }
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette.responses import Response` або `from starlette.responses import JSONResponse`.
|
||||
|
||||
**FastAPI** надає ті самі `starlette.responses` як `fastapi.responses` лише для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
|
||||
|
||||
І оскільки `Response` часто використовується для встановлення заголовків і кукі, **FastAPI** також надає його як `fastapi.Response`.
|
||||
|
||||
///
|
||||
|
||||
Щоб побачити всі доступні параметри та опції, перегляньте <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">документацію в Starlette</a>.
|
||||
65
docs/uk/docs/advanced/response-directly.md
Normal file
65
docs/uk/docs/advanced/response-directly.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Повернення Response безпосередньо { #return-a-response-directly }
|
||||
|
||||
Коли ви створюєте операцію шляху FastAPI, зазвичай ви можете повертати з неї будь-які дані: `dict`, `list`, модель Pydantic, модель бази даних тощо.
|
||||
|
||||
Типово FastAPI автоматично перетворить це значення повернення на JSON, використовуючи `jsonable_encoder`, описаний у [Сумісному з JSON кодері](../tutorial/encoder.md){.internal-link target=_blank}.
|
||||
|
||||
Потім, за лаштунками, він помістить ці дані, сумісні з JSON (наприклад, `dict`), у `JSONResponse`, який буде використано для надсилання відповіді клієнту.
|
||||
|
||||
Але ви можете повертати `JSONResponse` безпосередньо з ваших операцій шляху.
|
||||
|
||||
Це може бути корисним, наприклад, щоб повертати власні заголовки або кукі.
|
||||
|
||||
## Повернення `Response` { #return-a-response }
|
||||
|
||||
Насправді ви можете повертати будь-який `Response` або будь-який його підклас.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
`JSONResponse` сам є підкласом `Response`.
|
||||
|
||||
///
|
||||
|
||||
І коли ви повертаєте `Response`, FastAPI передасть його безпосередньо.
|
||||
|
||||
Він не виконуватиме жодних перетворень даних за допомогою моделей Pydantic, не перетворюватиме вміст на будь-який тип тощо.
|
||||
|
||||
Це дає вам багато гнучкості. Ви можете повертати будь-які типи даних, переписувати будь-які оголошення або перевірки даних тощо.
|
||||
|
||||
## Використання `jsonable_encoder` у `Response` { #using-the-jsonable-encoder-in-a-response }
|
||||
|
||||
Оскільки FastAPI не вносить змін у `Response`, який ви повертаєте, вам потрібно впевнитися, що його вміст готовий.
|
||||
|
||||
Наприклад, ви не можете покласти модель Pydantic у `JSONResponse`, не перетворивши її спочатку на `dict` з усіма типами даних (як-от `datetime`, `UUID` тощо), перетвореними на типи, сумісні з JSON.
|
||||
|
||||
Для таких випадків ви можете використати `jsonable_encoder`, щоб перетворити ваші дані перед тим, як передати їх у відповідь:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette.responses import JSONResponse`.
|
||||
|
||||
FastAPI надає ті самі `starlette.responses` як `fastapi.responses` просто як зручність для вас, розробника. Але більшість доступних `Response` походять безпосередньо зі Starlette.
|
||||
|
||||
///
|
||||
|
||||
## Повернення власного `Response` { #returning-a-custom-response }
|
||||
|
||||
Наведений вище приклад показує всі необхідні частини, але він ще не дуже корисний, адже ви могли просто повернути `item` безпосередньо, і FastAPI помістив би його у `JSONResponse`, перетворивши на `dict` тощо. Усе це відбувається за замовчуванням.
|
||||
|
||||
Тепер подивімося, як це використати, щоб повернути власну відповідь.
|
||||
|
||||
Припустімо, ви хочете повернути відповідь <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a>.
|
||||
|
||||
Ви можете помістити свій вміст XML у строку, помістити це в `Response` і повернути:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *}
|
||||
|
||||
## Примітки { #notes }
|
||||
|
||||
Коли ви повертаєте `Response` безпосередньо, його дані не перевіряються, не перетворюються (серіалізуються) і не документуються автоматично.
|
||||
|
||||
Але ви все ще можете задокументувати це, як описано в [Додаткових відповідях в OpenAPI](additional-responses.md){.internal-link target=_blank}.
|
||||
|
||||
У подальших розділах ви побачите, як використовувати/оголошувати ці власні `Response`, водночас зберігаючи автоматичне перетворення даних, документацію тощо.
|
||||
41
docs/uk/docs/advanced/response-headers.md
Normal file
41
docs/uk/docs/advanced/response-headers.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Заголовки відповіді { #response-headers }
|
||||
|
||||
## Використовуйте параметр `Response` { #use-a-response-parameter }
|
||||
|
||||
Ви можете оголосити параметр типу `Response` у вашій функції операції шляху (так само, як і для кукі).
|
||||
|
||||
Потім ви можете встановлювати заголовки в цьому *тимчасовому* обʼєкті відповіді.
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *}
|
||||
|
||||
Далі ви можете повернути будь-який потрібний обʼєкт, як зазвичай (наприклад, `dict`, модель бази даних тощо).
|
||||
|
||||
Якщо ви оголосили `response_model`, його все одно буде використано для фільтрації та перетворення поверненого обʼєкта.
|
||||
|
||||
FastAPI використає цей *тимчасовий* обʼєкт відповіді, щоб витягти заголовки (а також кукі та код статусу) і помістить їх у кінцеву відповідь, яка міститиме повернуте вами значення, відфільтроване будь-яким `response_model`.
|
||||
|
||||
Також ви можете оголосити параметр `Response` у залежностях і встановлювати в них заголовки (та кукі).
|
||||
|
||||
## Поверніть `Response` безпосередньо { #return-a-response-directly }
|
||||
|
||||
Ви також можете додавати заголовки, коли повертаєте `Response` безпосередньо.
|
||||
|
||||
Створіть відповідь, як описано в [Повернення Response безпосередньо](response-directly.md){.internal-link target=_blank}, і передайте заголовки як додатковий параметр:
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *}
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette.responses import Response` або `from starlette.responses import JSONResponse`.
|
||||
|
||||
FastAPI надає ті самі `starlette.responses` як `fastapi.responses` просто для зручності для вас, розробника. Але більшість доступних типів відповідей походять безпосередньо зі Starlette.
|
||||
|
||||
Оскільки `Response` часто використовують для встановлення заголовків і кукі, FastAPI також надає його як `fastapi.Response`.
|
||||
|
||||
///
|
||||
|
||||
## Власні заголовки { #custom-headers }
|
||||
|
||||
Майте на увазі, що власні закриті заголовки можна додавати <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">за допомогою префікса `X-`</a>.
|
||||
|
||||
Але якщо у вас є власні заголовки, які клієнт у браузері має бачити, вам потрібно додати їх у вашу конфігурацію CORS (докладніше в [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), використовуючи параметр `expose_headers`, задокументований у <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">документації Starlette щодо CORS</a>.
|
||||
107
docs/uk/docs/advanced/security/http-basic-auth.md
Normal file
107
docs/uk/docs/advanced/security/http-basic-auth.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# HTTP Basic Auth { #http-basic-auth }
|
||||
|
||||
Для найпростіших випадків ви можете використовувати HTTP Basic Auth.
|
||||
|
||||
У HTTP Basic Auth застосунок очікує заголовок, що містить ім'я користувача та пароль.
|
||||
|
||||
Якщо він його не отримує, повертається помилка HTTP 401 «Unauthorized».
|
||||
|
||||
І повертається заголовок `WWW-Authenticate` зі значенням `Basic` та необов'язковим параметром `realm`.
|
||||
|
||||
Це каже браузеру показати вбудовану підсказку для введення імені користувача та пароля.
|
||||
|
||||
Потім, коли ви введете це ім'я користувача та пароль, браузер автоматично надішле їх у заголовку.
|
||||
|
||||
## Простий HTTP Basic Auth { #simple-http-basic-auth }
|
||||
|
||||
- Імпортуйте `HTTPBasic` і `HTTPBasicCredentials`.
|
||||
- Створіть «`security` scheme» за допомогою `HTTPBasic`.
|
||||
- Використайте цей `security` як залежність у вашій операції шляху.
|
||||
- Він повертає об'єкт типу `HTTPBasicCredentials`:
|
||||
- Він містить надіслані `username` і `password`.
|
||||
|
||||
{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *}
|
||||
|
||||
Коли ви спробуєте відкрити URL вперше (або натиснете кнопку «Execute» у документації), браузер попросить вас ввести ім'я користувача та пароль:
|
||||
|
||||
<img src="/img/tutorial/security/image12.png">
|
||||
|
||||
## Перевірте ім'я користувача { #check-the-username }
|
||||
|
||||
Ось більш повний приклад.
|
||||
|
||||
Використайте залежність, щоб перевірити, чи правильні ім'я користувача та пароль.
|
||||
|
||||
Для цього використайте стандартний модуль Python <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a>, щоб перевірити ім'я користувача та пароль.
|
||||
|
||||
`secrets.compare_digest()` повинен отримувати `bytes` або `str`, що містить лише ASCII-символи (англійські), це означає, що він не працюватиме з символами на кшталт `á`, як у `Sebastián`.
|
||||
|
||||
Щоб це обійти, ми спочатку перетворюємо `username` і `password` у `bytes`, кодувавши їх у UTF-8.
|
||||
|
||||
Потім ми можемо використати `secrets.compare_digest()`, щоб упевнитися, що `credentials.username` дорівнює `"stanleyjobson"`, а `credentials.password` дорівнює `"swordfish"`.
|
||||
|
||||
{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *}
|
||||
|
||||
Це було б подібно до:
|
||||
|
||||
```Python
|
||||
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
|
||||
# Return some error
|
||||
...
|
||||
```
|
||||
|
||||
Але використовуючи `secrets.compare_digest()`, це буде захищено від типу атак, що називаються «атаки за часом» (timing attacks).
|
||||
|
||||
### Атаки за часом { #timing-attacks }
|
||||
|
||||
Що таке «атака за часом»?
|
||||
|
||||
Уявімо, що зловмисники намагаються вгадати ім'я користувача та пароль.
|
||||
|
||||
Вони надсилають запит з ім'ям користувача `johndoe` та паролем `love123`.
|
||||
|
||||
Тоді Python-код у вашому застосунку буде еквівалентний чомусь на кшталт:
|
||||
|
||||
```Python
|
||||
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
|
||||
...
|
||||
```
|
||||
|
||||
Але в той момент, коли Python порівнює першу `j` у `johndoe` з першою `s` у `stanleyjobson`, він поверне `False`, тому що вже знає, що ці дві строки не однакові, «немає сенсу витрачати обчислення на порівняння решти літер». І ваш застосунок скаже «Невірні ім'я користувача або пароль».
|
||||
|
||||
А потім зловмисники спробують з ім'ям користувача `stanleyjobsox` і паролем `love123`.
|
||||
|
||||
І ваш код застосунку зробить щось на кшталт:
|
||||
|
||||
```Python
|
||||
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
|
||||
...
|
||||
```
|
||||
|
||||
Python доведеться порівняти весь `stanleyjobso` у обох `stanleyjobsox` і `stanleyjobson`, перш ніж зрозуміти, що строки різні. Тому відповідь «Невірні ім'я користувача або пароль» займе на кілька мікросекунд довше.
|
||||
|
||||
#### Час відповіді допомагає зловмисникам { #the-time-to-answer-helps-the-attackers }
|
||||
|
||||
У цей момент, помітивши, що сервер витратив на кілька мікросекунд більше, щоб надіслати відповідь «Невірні ім'я користувача або пароль», зловмисники знатимуть, що вони щось вгадали, деякі початкові літери правильні.
|
||||
|
||||
І тоді вони можуть спробувати знову, знаючи, що це, ймовірно, щось більш схоже на `stanleyjobsox`, ніж на `johndoe`.
|
||||
|
||||
#### «Професійна» атака { #a-professional-attack }
|
||||
|
||||
Звісно, зловмисники не робитимуть усе це вручну, вони напишуть програму, можливо з тисячами або мільйонами перевірок за секунду. І вони отримуватимуть лише по одній правильній літері за раз.
|
||||
|
||||
Але так за кілька хвилин або годин зловмисники вгадають правильні ім'я користувача та пароль, «за допомогою» нашого застосунку, просто використовуючи час, потрібний для відповіді.
|
||||
|
||||
#### Виправте за допомогою `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest }
|
||||
|
||||
Але в нашому коді ми насправді використовуємо `secrets.compare_digest()`.
|
||||
|
||||
Коротко, він витрачає однаковий час на порівняння `stanleyjobsox` зі `stanleyjobson`, як і на порівняння `johndoe` зі `stanleyjobson`. І так само для пароля.
|
||||
|
||||
Таким чином, використовуючи `secrets.compare_digest()` у коді вашого застосунку, він буде захищений від усього цього класу атак безпеки.
|
||||
|
||||
### Поверніть помилку { #return-the-error }
|
||||
|
||||
Після виявлення, що облікові дані неправильні, поверніть `HTTPException` з кодом статусу 401 (такий самий повертається, коли облікові дані не надані) і додайте заголовок `WWW-Authenticate`, щоб браузер знову показав підсказку входу:
|
||||
|
||||
{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *}
|
||||
19
docs/uk/docs/advanced/security/index.md
Normal file
19
docs/uk/docs/advanced/security/index.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Просунута безпека { #advanced-security }
|
||||
|
||||
## Додаткові можливості { #additional-features }
|
||||
|
||||
Є кілька додаткових можливостей для роботи з безпекою, окрім тих, що розглянуті в [Навчальний посібник - Посібник користувача: Безпека](../../tutorial/security/index.md){.internal-link target=_blank}.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Наступні розділи не обов'язково «просунуті».
|
||||
|
||||
І можливо, що для вашого випадку використання рішення є в одному з них.
|
||||
|
||||
///
|
||||
|
||||
## Спершу прочитайте навчальний посібник { #read-the-tutorial-first }
|
||||
|
||||
У наступних розділах передбачається, що ви вже прочитали основний [Навчальний посібник - Посібник користувача: Безпека](../../tutorial/security/index.md){.internal-link target=_blank}.
|
||||
|
||||
Усі вони базуються на тих самих концепціях, але надають деякі додаткові можливості.
|
||||
274
docs/uk/docs/advanced/security/oauth2-scopes.md
Normal file
274
docs/uk/docs/advanced/security/oauth2-scopes.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# OAuth2 scopes { #oauth2-scopes }
|
||||
|
||||
Ви можете використовувати OAuth2 scopes безпосередньо з **FastAPI**, вони інтегровані для безшовної роботи.
|
||||
|
||||
Це дозволить мати більш детальну систему дозволів, відповідно до стандарту OAuth2, інтегровану у ваш застосунок OpenAPI (і документацію API).
|
||||
|
||||
OAuth2 зі scopes - це механізм, який використовують багато великих провайдерів автентифікації, як-от Facebook, Google, GitHub, Microsoft, X (Twitter) тощо. Вони застосовують його, щоб надавати конкретні дозволи користувачам і застосункам.
|
||||
|
||||
Кожного разу, коли ви «log in with» Facebook, Google, GitHub, Microsoft, X (Twitter), цей застосунок використовує OAuth2 зі scopes.
|
||||
|
||||
У цьому розділі ви побачите, як керувати автентифікацією та авторизацією за допомогою того ж OAuth2 зі scopes у вашому застосунку **FastAPI**.
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Це більш-менш просунутий розділ. Якщо ви тільки починаєте, можете пропустити його.
|
||||
|
||||
Вам не обов’язково потрібні OAuth2 scopes, ви можете керувати автентифікацією та авторизацією будь-яким зручним способом.
|
||||
|
||||
Але OAuth2 зі scopes можна гарно інтегрувати у ваш API (з OpenAPI) і документацію API.
|
||||
|
||||
Водночас ви все одно примушуєте виконувати ці scopes або будь-які інші вимоги безпеки/авторизації так, як потрібно, у своєму коді.
|
||||
|
||||
У багатьох випадках OAuth2 зі scopes - це надмірність.
|
||||
|
||||
Але якщо ви знаєте, що це потрібно, або просто цікаво, читайте далі.
|
||||
|
||||
///
|
||||
|
||||
## OAuth2 scopes та OpenAPI { #oauth2-scopes-and-openapi }
|
||||
|
||||
Специфікація OAuth2 визначає «scopes» як список строк, розділених пробілами.
|
||||
|
||||
Вміст кожної з цих строк може мати будь-який формат, але не повинен містити пробілів.
|
||||
|
||||
Ці scopes представляють «дозволи».
|
||||
|
||||
В OpenAPI (наприклад, у документації API) ви можете визначати «схеми безпеки».
|
||||
|
||||
Коли одна з цих схем безпеки використовує OAuth2, ви також можете оголошувати та використовувати scopes.
|
||||
|
||||
Кожен «scope» - це просто строка (без пробілів).
|
||||
|
||||
Зазвичай їх використовують для оголошення конкретних дозволів безпеки, наприклад:
|
||||
|
||||
- `users:read` або `users:write` - поширені приклади.
|
||||
- `instagram_basic` використовується Facebook / Instagram.
|
||||
- `https://www.googleapis.com/auth/drive` використовується Google.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
В OAuth2 «scope» - це просто строка, що декларує конкретний потрібний дозвіл.
|
||||
|
||||
Не має значення, чи містить вона інші символи на кшталт `:` або чи це URL.
|
||||
|
||||
Ці деталі специфічні для реалізації.
|
||||
|
||||
Для OAuth2 це просто строки.
|
||||
|
||||
///
|
||||
|
||||
## Загальний огляд { #global-view }
|
||||
|
||||
Спочатку швидко подивімося на частини, що відрізняються від прикладів у головному **Навчальному посібнику - Керівництві користувача** для [OAuth2 з паролем (і хешуванням), Bearer з JWT-токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Тепер із використанням OAuth2 scopes:
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
|
||||
|
||||
Тепер розгляньмо ці зміни крок за кроком.
|
||||
|
||||
## Схема безпеки OAuth2 { #oauth2-security-scheme }
|
||||
|
||||
Перша зміна - тепер ми оголошуємо схему безпеки OAuth2 з двома доступними scopes: `me` і `items`.
|
||||
|
||||
Параметр `scopes` приймає `dict`, де кожен scope - це ключ, а опис - значення:
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
|
||||
|
||||
Оскільки тепер ми оголошуємо ці scopes, вони з’являться в документації API, коли ви увійдете/авторизуєтеся.
|
||||
|
||||
І ви зможете обрати, які scopes надати доступ: `me` і `items`.
|
||||
|
||||
Це той самий механізм, який використовується, коли ви надаєте дозволи під час входу через Facebook, Google, GitHub тощо:
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
## JWT токен зі scopes { #jwt-token-with-scopes }
|
||||
|
||||
Тепер змініть операцію шляху токена, щоб повертати запитані scopes.
|
||||
|
||||
Ми все ще використовуємо той самий `OAuth2PasswordRequestForm`. Він містить властивість `scopes` зі `list` з `str`, по одному scope, отриманому в запиті.
|
||||
|
||||
І ми повертаємо scopes як частину JWT токена.
|
||||
|
||||
/// danger | Обережно
|
||||
|
||||
Для простоти тут ми просто додаємо отримані scopes безпосередньо до токена.
|
||||
|
||||
Але у вашому застосунку, з міркувань безпеки, переконайтеся, що ви додаєте лише ті scopes, які користувач дійсно може мати, або ті, що ви попередньо визначили.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
|
||||
|
||||
## Оголосіть scopes в операціях шляху та залежностях { #declare-scopes-in-path-operations-and-dependencies }
|
||||
|
||||
Тепер ми оголошуємо, що операція шляху для `/users/me/items/` вимагає scope `items`.
|
||||
|
||||
Для цього імпортуємо і використовуємо `Security` з `fastapi`.
|
||||
|
||||
Ви можете використовувати `Security` для оголошення залежностей (так само як `Depends`), але `Security` також приймає параметр `scopes` зі списком scopes (строк).
|
||||
|
||||
У цьому випадку ми передаємо функцію-залежність `get_current_active_user` до `Security` (так само, як зробили б із `Depends`).
|
||||
|
||||
А також передаємо `list` scopes, у цьому випадку лише один scope: `items` (їх могло б бути більше).
|
||||
|
||||
І функція-залежність `get_current_active_user` також може оголошувати підзалежності не лише з `Depends`, а й з `Security`. Оголошуючи свою підзалежність (`get_current_user`) і додаткові вимоги до scopes.
|
||||
|
||||
У цьому випадку вона вимагає scope `me` (вона могла б вимагати більш ніж один scope).
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Вам не обов’язково додавати різні scopes у різних місцях.
|
||||
|
||||
Ми робимо це тут, щоб показати, як **FastAPI** обробляє scopes, оголошені на різних рівнях.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
|
||||
|
||||
/// info | Технічні деталі
|
||||
|
||||
`Security` насправді є підкласом `Depends`, і має лише один додатковий параметр, який ми побачимо пізніше.
|
||||
|
||||
Але використовуючи `Security` замість `Depends`, **FastAPI** знатиме, що можна оголошувати scopes безпеки, використовувати їх внутрішньо та документувати API через OpenAPI.
|
||||
|
||||
Коли ви імпортуєте `Query`, `Path`, `Depends`, `Security` та інші з `fastapi`, це насправді функції, що повертають спеціальні класи.
|
||||
|
||||
///
|
||||
|
||||
## Використовуйте `SecurityScopes` { #use-securityscopes }
|
||||
|
||||
Тепер оновіть залежність `get_current_user`.
|
||||
|
||||
Вона використовується наведеними вище залежностями.
|
||||
|
||||
Тут ми використовуємо ту саму схему OAuth2, створену раніше, оголошуючи її як залежність: `oauth2_scheme`.
|
||||
|
||||
Оскільки ця функція-залежність не має власних вимог до scopes, ми можемо використовувати `Depends` з `oauth2_scheme`, немає потреби застосовувати `Security`, коли не потрібно вказувати scopes безпеки.
|
||||
|
||||
Ми також оголошуємо спеціальний параметр типу `SecurityScopes`, імпортований з `fastapi.security`.
|
||||
|
||||
Клас `SecurityScopes` подібний до `Request` (у `Request` ми напряму отримували об’єкт запиту).
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
|
||||
|
||||
## Використовуйте scopes { #use-the-scopes }
|
||||
|
||||
Параметр `security_scopes` матиме тип `SecurityScopes`.
|
||||
|
||||
Він матиме властивість `scopes` зі списком, що містить усі scopes, потрібні самій функції та всім залежним, які використовують її як підзалежність. Тобто всім «залежним»... це може звучати заплутано, нижче пояснено ще раз.
|
||||
|
||||
Об’єкт `security_scopes` (класу `SecurityScopes`) також надає атрибут `scope_str` з одним рядком, що містить ці scopes, розділені пробілами (ми його використаємо).
|
||||
|
||||
Ми створюємо `HTTPException`, який зможемо повторно використати (`raise`) у кількох місцях.
|
||||
|
||||
У цьому винятку ми включаємо потрібні scopes (якщо є) як строку, розділену пробілами (використовуючи `scope_str`). Ми поміщаємо цю строку зі scopes в заголовок `WWW-Authenticate` (це частина специфікації).
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
|
||||
|
||||
## Перевірте `username` і структуру даних { #verify-the-username-and-data-shape }
|
||||
|
||||
Ми перевіряємо, що отримали `username`, і видобуваємо scopes.
|
||||
|
||||
Потім валідовуємо ці дані за допомогою Pydantic-моделі (перехоплюючи виняток `ValidationError`), і якщо виникає помилка читання JWT токена або валідації даних Pydantic, підіймаємо раніше створений `HTTPException`.
|
||||
|
||||
Для цього ми оновлюємо Pydantic-модель `TokenData` новою властивістю `scopes`.
|
||||
|
||||
Валідувавши дані через Pydantic, ми гарантуємо, що, наприклад, маємо саме `list` із `str` для scopes і `str` для `username`.
|
||||
|
||||
Замість, наприклад, `dict` або чогось іншого, що може зламати застосунок далі, створивши ризик безпеки.
|
||||
|
||||
Ми також перевіряємо, що існує користувач із цим username, інакше підіймаємо той самий виняток.
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
|
||||
|
||||
## Перевірте `scopes` { #verify-the-scopes }
|
||||
|
||||
Тепер перевіряємо, що всі потрібні scopes - для цієї залежності та всіх залежних (включно з операціями шляху) - містяться в scopes, наданих у отриманому токені, інакше підіймаємо `HTTPException`.
|
||||
|
||||
Для цього використовуємо `security_scopes.scopes`, що містить `list` із усіма цими scopes як `str`.
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
|
||||
|
||||
## Дерево залежностей і scopes { #dependency-tree-and-scopes }
|
||||
|
||||
Ще раз розгляньмо дерево залежностей і scopes.
|
||||
|
||||
Оскільки залежність `get_current_active_user` має підзалежність `get_current_user`, scope «me», оголошений у `get_current_active_user`, буде включений до списку потрібних scopes у `security_scopes.scopes`, переданого до `get_current_user`.
|
||||
|
||||
Сама операція шляху також оголошує scope «items», отже він також буде у списку `security_scopes.scopes`, переданому до `get_current_user`.
|
||||
|
||||
Ось як виглядає ієрархія залежностей і scopes:
|
||||
|
||||
- Операція шляху `read_own_items` має:
|
||||
- Потрібні scopes `["items"]` із залежністю:
|
||||
- `get_current_active_user`:
|
||||
- Функція-залежність `get_current_active_user` має:
|
||||
- Потрібні scopes `["me"]` із залежністю:
|
||||
- `get_current_user`:
|
||||
- Функція-залежність `get_current_user` має:
|
||||
- Власних scopes не потребує.
|
||||
- Залежність, що використовує `oauth2_scheme`.
|
||||
- Параметр `security_scopes` типу `SecurityScopes`:
|
||||
- Цей параметр `security_scopes` має властивість `scopes` із `list`, що містить усі наведені вище scopes, отже:
|
||||
- `security_scopes.scopes` міститиме `["me", "items"]` для операції шляху `read_own_items`.
|
||||
- `security_scopes.scopes` міститиме `["me"]` для операції шляху `read_users_me`, адже він оголошений у залежності `get_current_active_user`.
|
||||
- `security_scopes.scopes` міститиме `[]` (нічого) для операції шляху `read_system_status`, бо вона не оголошує жодного `Security` зі `scopes`, і її залежність `get_current_user` також не оголошує жодних `scopes`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Важливе і «магічне» тут у тому, що `get_current_user` матиме різні списки `scopes` для перевірки для кожної операції шляху.
|
||||
|
||||
Усе залежить від `scopes`, оголошених у кожній операції шляху та кожній залежності в дереві залежностей для конкретної операції шляху.
|
||||
|
||||
///
|
||||
|
||||
## Більше деталей про `SecurityScopes` { #more-details-about-securityscopes }
|
||||
|
||||
Ви можете використовувати `SecurityScopes` у будь-якому місці й у кількох місцях, він не обов’язково має бути в «кореневій» залежності.
|
||||
|
||||
Він завжди міститиме scopes безпеки, оголошені в поточних залежностях `Security` і всіх залежних для **цієї конкретної** операції шляху і **цього конкретного** дерева залежностей.
|
||||
|
||||
Оскільки `SecurityScopes` міститиме всі scopes, оголошені залежними, ви можете використовувати його, щоб перевірити, що токен має потрібні scopes, у центральній функції-залежності, а потім оголошувати різні вимоги до scopes у різних операціях шляху.
|
||||
|
||||
Вони перевірятимуться незалежно для кожної операції шляху.
|
||||
|
||||
## Перевірте { #check-it }
|
||||
|
||||
Якщо ви відкриєте документацію API, ви зможете автентифікуватися і вказати, які scopes хочете авторизувати.
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
Якщо ви не оберете жодного scope, ви будете «автентифіковані», але при спробі доступу до `/users/me/` або `/users/me/items/` отримаєте помилку про недостатні дозволи. Ви все ще матимете доступ до `/status/`.
|
||||
|
||||
Якщо оберете scope `me`, але не scope `items`, ви зможете отримати доступ до `/users/me/`, але не до `/users/me/items/`.
|
||||
|
||||
Так станеться зі стороннім застосунком, який спробує звернутися до однієї з цих операцій шляху з токеном, наданим користувачем, залежно від того, скільки дозволів користувач надав застосунку.
|
||||
|
||||
## Про сторонні інтеграції { #about-third-party-integrations }
|
||||
|
||||
У цьому прикладі ми використовуємо «потік паролю» OAuth2.
|
||||
|
||||
Це доречно, коли ми входимо у власний застосунок, ймовірно, з власним фронтендом.
|
||||
|
||||
Адже ми можемо довіряти йому отримання `username` і `password`, бо ми його контролюємо.
|
||||
|
||||
Але якщо ви створюєте OAuth2-застосунок, до якого підключатимуться інші (тобто якщо ви створюєте провайдера автентифікації на кшталт Facebook, Google, GitHub тощо), слід використовувати один з інших потоків.
|
||||
|
||||
Найпоширеніший - неявний потік (implicit flow).
|
||||
|
||||
Найбезпечніший - потік коду (code flow), але його складніше реалізувати, оскільки він потребує більше кроків. Через складність багато провайдерів у підсумку радять неявний потік.
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Часто кожен провайдер автентифікації називає свої потоки по-різному, роблячи це частиною свого бренду.
|
||||
|
||||
Але зрештою вони реалізують той самий стандарт OAuth2.
|
||||
|
||||
///
|
||||
|
||||
**FastAPI** містить утиліти для всіх цих потоків автентифікації OAuth2 у `fastapi.security.oauth2`.
|
||||
|
||||
## `Security` у параметрі декоратора `dependencies` { #security-in-decorator-dependencies }
|
||||
|
||||
Так само як ви можете визначити `list` із `Depends` у параметрі `dependencies` декоратора (як пояснено в [Залежності в декораторах операцій шляху](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ви також можете використовувати там `Security` зі `scopes`.
|
||||
302
docs/uk/docs/advanced/settings.md
Normal file
302
docs/uk/docs/advanced/settings.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Налаштування та змінні оточення { #settings-and-environment-variables }
|
||||
|
||||
У багатьох випадках вашому застосунку можуть знадобитися зовнішні налаштування або конфігурації, наприклад секретні ключі, облікові дані бази даних, облікові дані для email-сервісів тощо.
|
||||
|
||||
Більшість із цих налаштувань змінні (можуть змінюватися), як-от URL-адреси баз даних. І багато з них можуть бути чутливими, як-от секрети.
|
||||
|
||||
З цієї причини поширено надавати їх у змінних оточення, які зчитуються застосунком.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Щоб зрозуміти змінні оточення, ви можете прочитати [Змінні оточення](../environment-variables.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Типи та перевірка { #types-and-validation }
|
||||
|
||||
Ці змінні оточення можуть містити лише текстові строки, оскільки вони зовнішні до Python і мають бути сумісні з іншими програмами та рештою системи (і навіть з різними операційними системами, як-от Linux, Windows, macOS).
|
||||
|
||||
Це означає, що будь-яке значення, прочитане в Python зі змінної оточення, буде `str`, і будь-яке перетворення в інший тип або будь-яка перевірка мають виконуватися в коді.
|
||||
|
||||
## Pydantic `Settings` { #pydantic-settings }
|
||||
|
||||
На щастя, Pydantic надає чудовий інструмент для обробки цих налаштувань із змінних оточення - <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>.
|
||||
|
||||
### Встановіть `pydantic-settings` { #install-pydantic-settings }
|
||||
|
||||
Спершу переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет `pydantic-settings`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pydantic-settings
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Він також входить у склад, якщо ви встановлюєте додаткові можливості «all» за допомогою:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[all]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Створіть об'єкт `Settings` { #create-the-settings-object }
|
||||
|
||||
Імпортуйте `BaseSettings` із Pydantic і створіть підклас, дуже подібно до моделі Pydantic.
|
||||
|
||||
Так само, як і з моделями Pydantic, ви оголошуєте атрибути класу з анотаціями типів і, за потреби, значеннями за замовчуванням.
|
||||
|
||||
Ви можете використовувати всі ті самі можливості перевірки та інструменти, що й для моделей Pydantic, як-от різні типи даних і додаткові перевірки з `Field()`.
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо вам потрібно щось швидко скопіювати й вставити, не використовуйте цей приклад, скористайтеся останнім нижче.
|
||||
|
||||
///
|
||||
|
||||
Потім, коли ви створите екземпляр цього класу `Settings` (у цьому випадку в об'єкті `settings`), Pydantic зчитуватиме змінні оточення без урахування регістру, тож верхньорегістрова змінна `APP_NAME` все одно буде прочитана для атрибута `app_name`.
|
||||
|
||||
Далі він перетворить і перевірить дані. Тож коли ви використовуватимете об'єкт `settings`, у вас будуть дані тих типів, які ви оголосили (наприклад, `items_per_user` буде `int`).
|
||||
|
||||
### Використовуйте `settings` { #use-the-settings }
|
||||
|
||||
Потім ви можете використати новий об'єкт `settings` у вашому застосунку:
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *}
|
||||
|
||||
### Запустіть сервер { #run-the-server }
|
||||
|
||||
Далі ви б запустили сервер, передаючи конфігурації як змінні оточення, наприклад, ви можете встановити `ADMIN_EMAIL` і `APP_NAME` так:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Щоб встановити кілька змінних оточення для однієї команди, просто розділіть їх пробілами і розмістіть усі перед командою.
|
||||
|
||||
///
|
||||
|
||||
Після цього налаштування `admin_email` буде встановлено в `"deadpool@example.com"`.
|
||||
|
||||
`app_name` буде `"ChimichangApp"`.
|
||||
|
||||
А `items_per_user` збереже своє значення за замовчуванням `50`.
|
||||
|
||||
## Налаштування в іншому модулі { #settings-in-another-module }
|
||||
|
||||
Ви можете розмістити ці налаштування в іншому модулі, як ви бачили в [Більші застосунки - кілька файлів](../tutorial/bigger-applications.md){.internal-link target=_blank}.
|
||||
|
||||
Наприклад, у вас може бути файл `config.py` з:
|
||||
|
||||
{* ../../docs_src/settings/app01_py310/config.py *}
|
||||
|
||||
А потім використати його у файлі `main.py`:
|
||||
|
||||
{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Вам також знадобиться файл `__init__.py`, як ви бачили в [Більші застосунки - кілька файлів](../tutorial/bigger-applications.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Налаштування як залежність { #settings-in-a-dependency }
|
||||
|
||||
Іноді може бути корисно надавати налаштування через залежність, замість того, щоб мати глобальний об'єкт `settings`, який використовується всюди.
|
||||
|
||||
Це може бути особливо корисно під час тестування, оскільки дуже легко переписати залежність власними налаштуваннями.
|
||||
|
||||
### Файл конфігурації { #the-config-file }
|
||||
|
||||
Продовжуючи попередній приклад, ваш файл `config.py` може виглядати так:
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *}
|
||||
|
||||
Зверніть увагу, що тепер ми не створюємо екземпляр за замовчуванням `settings = Settings()`.
|
||||
|
||||
### Основний файл застосунку { #the-main-app-file }
|
||||
|
||||
Тепер ми створюємо залежність, яка повертає новий `config.Settings()`.
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ми обговоримо `@lru_cache` трохи згодом.
|
||||
|
||||
Поки що можете вважати, що `get_settings()` - це звичайна функція.
|
||||
|
||||
///
|
||||
|
||||
А далі ми можемо вимагати її у *функції операції шляху* як залежність і використовувати будь-де, де це потрібно.
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *}
|
||||
|
||||
### Налаштування і тестування { #settings-and-testing }
|
||||
|
||||
Потім буде дуже просто надати інший об'єкт налаштувань під час тестування, створивши переписування залежності для `get_settings`:
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *}
|
||||
|
||||
У переписуванні залежності ми встановлюємо нове значення `admin_email` під час створення нового об'єкта `Settings`, а потім повертаємо цей новий об'єкт.
|
||||
|
||||
Після цього ми можемо перевірити, що саме він використовується.
|
||||
|
||||
## Читання файлу `.env` { #reading-a-env-file }
|
||||
|
||||
Якщо у вас багато налаштувань, які можуть часто змінюватися, можливо в різних оточеннях, може бути корисно розмістити їх у файлі, а потім зчитувати їх із нього так, ніби це змінні оточення.
|
||||
|
||||
Ця практика достатньо поширена, тож має назву - ці змінні оточення зазвичай розміщуються у файлі `.env`, а сам файл називається «dotenv».
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Файл, що починається з крапки (`.`), є прихованим у системах, подібних до Unix, як-от Linux і macOS.
|
||||
|
||||
Але файл dotenv не обов'язково має мати саме таку назву.
|
||||
|
||||
///
|
||||
|
||||
Pydantic має підтримку читання з таких типів файлів за допомогою зовнішньої бібліотеки. Ви можете дізнатися більше тут: <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Щоб це працювало, потрібно виконати `pip install python-dotenv`.
|
||||
|
||||
///
|
||||
|
||||
### Файл `.env` { #the-env-file }
|
||||
|
||||
У вас може бути файл `.env` із вмістом:
|
||||
|
||||
```bash
|
||||
ADMIN_EMAIL="deadpool@example.com"
|
||||
APP_NAME="ChimichangApp"
|
||||
```
|
||||
|
||||
### Зчитування налаштувань із `.env` { #read-settings-from-env }
|
||||
|
||||
Потім оновіть ваш `config.py` так:
|
||||
|
||||
{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Атрибут `model_config` використовується лише для конфігурації Pydantic. Докладніше: <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a>.
|
||||
|
||||
///
|
||||
|
||||
Тут ми визначаємо конфіг `env_file` усередині вашого класу Pydantic `Settings` і задаємо значення - ім'я файла з dotenv, який ми хочемо використати.
|
||||
|
||||
### Створення `Settings` лише один раз за допомогою `lru_cache` { #creating-the-settings-only-once-with-lru-cache }
|
||||
|
||||
Читання файла з диска зазвичай є дорогою (повільною) операцією, тож, імовірно, ви захочете робити це лише один раз і потім перевикористовувати той самий об'єкт налаштувань замість зчитування для кожного запиту.
|
||||
|
||||
Але щоразу, коли ми робимо:
|
||||
|
||||
```Python
|
||||
Settings()
|
||||
```
|
||||
|
||||
буде створено новий об'єкт `Settings`, і під час створення він знову зчитуватиме файл `.env`.
|
||||
|
||||
Якби функція залежності виглядала так:
|
||||
|
||||
```Python
|
||||
def get_settings():
|
||||
return Settings()
|
||||
```
|
||||
|
||||
ми створювали б цей об'єкт для кожного запиту і читали б файл `.env` для кожного запиту. ⚠️
|
||||
|
||||
Але оскільки ми використовуємо декоратор `@lru_cache` зверху, об'єкт `Settings` буде створено лише один раз, під час першого виклику. ✔️
|
||||
|
||||
{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *}
|
||||
|
||||
Потім для будь-якого подальшого виклику `get_settings()` у залежностях для наступних запитів, замість виконання внутрішнього коду `get_settings()` і створення нового об'єкта `Settings`, він повертатиме той самий об'єкт, що був повернутий під час першого виклику, знову і знову.
|
||||
|
||||
#### Технічні деталі `lru_cache` { #lru-cache-technical-details }
|
||||
|
||||
`@lru_cache` модифікує функцію, яку він декорує, так, щоб вона повертала те саме значення, що й уперше, замість повторного обчислення, виконуючи код функції щоразу.
|
||||
|
||||
Тобто функція під ним буде виконана один раз для кожної комбінації аргументів. А потім значення, повернені кожною з цих комбінацій аргументів, використовуватимуться знову і знову щоразу, коли функцію викликають із точно такою ж комбінацією аргументів.
|
||||
|
||||
Наприклад, якщо у вас є функція:
|
||||
|
||||
```Python
|
||||
@lru_cache
|
||||
def say_hi(name: str, salutation: str = "Ms."):
|
||||
return f"Hello {salutation} {name}"
|
||||
```
|
||||
|
||||
ваша програма може виконуватись так:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant code as Code
|
||||
participant function as say_hi()
|
||||
participant execute as Execute function
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick", salutation="Mr.")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
```
|
||||
|
||||
У випадку з нашою залежністю `get_settings()` функція взагалі не приймає жодних аргументів, тож вона завжди повертає те саме значення.
|
||||
|
||||
Таким чином, вона поводиться майже так само, якби це була просто глобальна змінна. Але оскільки використовується функція залежності, ми можемо легко переписати її для тестування.
|
||||
|
||||
`@lru_cache` є частиною `functools`, що входить до стандартної бібліотеки Python, більше про це можна прочитати в <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">документації Python для `@lru_cache`</a>.
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Ви можете використовувати Pydantic Settings для обробки налаштувань або конфігурацій вашого застосунку, з усією потужністю моделей Pydantic.
|
||||
|
||||
- Використовуючи залежність, ви можете спростити тестування.
|
||||
- Ви можете використовувати з ним файли `.env`.
|
||||
- Використання `@lru_cache` дає змогу уникнути повторного читання файла dotenv для кожного запиту, водночас дозволяючи переписувати його під час тестування.
|
||||
67
docs/uk/docs/advanced/sub-applications.md
Normal file
67
docs/uk/docs/advanced/sub-applications.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Підзастосунки - монтування { #sub-applications-mounts }
|
||||
|
||||
Якщо вам потрібно мати два незалежні застосунки FastAPI з власними незалежними OpenAPI та власними інтерфейсами документації, ви можете мати головний застосунок і «змонтувати» один або кілька підзастосунків.
|
||||
|
||||
## Монтування застосунку **FastAPI** { #mounting-a-fastapi-application }
|
||||
|
||||
«Монтування» означає додавання повністю «незалежного» застосунку на конкретний шлях, який далі обробляє все під цим шляхом за допомогою _операцій шляху_, оголошених у цьому підзастосунку.
|
||||
|
||||
### Застосунок верхнього рівня { #top-level-application }
|
||||
|
||||
Спочатку створіть головний, верхньорівневий застосунок **FastAPI** та його _операції шляху_:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *}
|
||||
|
||||
### Підзастосунок { #sub-application }
|
||||
|
||||
Потім створіть свій підзастосунок та його _операції шляху_.
|
||||
|
||||
Цей підзастосунок - просто ще один стандартний застосунок FastAPI, але саме його буде «змонтовано»:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *}
|
||||
|
||||
### Змонтуйте підзастосунок { #mount-the-sub-application }
|
||||
|
||||
У вашому застосунку верхнього рівня, `app`, змонтуйте підзастосунок `subapi`.
|
||||
|
||||
У цьому випадку його буде змонтовано за шляхом `/subapi`:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *}
|
||||
|
||||
### Перевірте автоматичну документацію API { #check-the-automatic-api-docs }
|
||||
|
||||
Тепер запустіть команду `fastapi` з вашим файлом:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
І відкрийте документацію за адресою <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Ви побачите автоматичну документацію API для головного застосунку, що містить лише його власні _операції шляху_:
|
||||
|
||||
<img src="/img/tutorial/sub-applications/image01.png">
|
||||
|
||||
А потім відкрийте документацію для підзастосунку за адресою <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>.
|
||||
|
||||
Ви побачите автоматичну документацію API для підзастосунку, що містить лише його власні _операції шляху_, усі з правильним префіксом підшляху `/subapi`:
|
||||
|
||||
<img src="/img/tutorial/sub-applications/image02.png">
|
||||
|
||||
Якщо ви спробуєте взаємодіяти з будь-яким із двох інтерфейсів користувача, вони працюватимуть коректно, адже браузер зможе спілкуватися з кожним конкретним застосунком або підзастосунком.
|
||||
|
||||
### Технічні деталі: `root_path` { #technical-details-root-path }
|
||||
|
||||
Коли ви монтуєте підзастосунок, як описано вище, FastAPI подбає про передачу шляху монтування для підзастосунку, використовуючи механізм зі специфікації ASGI під назвою `root_path`.
|
||||
|
||||
Таким чином підзастосунок знатиме, що слід використовувати цей префікс шляху для інтерфейсу документації.
|
||||
|
||||
Підзастосунок також може мати власні змонтовані підзастосунки, і все працюватиме коректно, оскільки FastAPI автоматично обробляє всі ці `root_path`.
|
||||
|
||||
Ви дізнаєтеся більше про `root_path` і як використовувати його явно в розділі [За представником](behind-a-proxy.md){.internal-link target=_blank}.
|
||||
126
docs/uk/docs/advanced/templates.md
Normal file
126
docs/uk/docs/advanced/templates.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Шаблони { #templates }
|
||||
|
||||
Ви можете використовувати будь-який рушій шаблонів з **FastAPI**.
|
||||
|
||||
Поширений вибір - Jinja2, той самий, що використовується у Flask та інших інструментах.
|
||||
|
||||
Є утиліти для простої конфігурації, які ви можете використовувати безпосередньо у вашому застосунку **FastAPI** (надає Starlette).
|
||||
|
||||
## Встановіть залежності { #install-dependencies }
|
||||
|
||||
Переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили `jinja2`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install jinja2
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Використання `Jinja2Templates` { #using-jinja2templates }
|
||||
|
||||
- Імпортуйте `Jinja2Templates`.
|
||||
- Створіть об'єкт `templates`, який ви зможете перевикористовувати.
|
||||
- Оголосіть параметр `Request` в *операції шляху*, яка повертатиме шаблон.
|
||||
- Використайте створені `templates`, щоб зрендерити та повернути `TemplateResponse`; передайте назву шаблону, об'єкт `request` і словник «контекст» з парами ключ-значення, які будуть використані всередині шаблону Jinja2.
|
||||
|
||||
{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *}
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
До FastAPI 0.108.0, Starlette 0.29.0, параметр `name` був першим.
|
||||
|
||||
Також раніше, у попередніх версіях, об'єкт `request` передавався як частина пар ключ-значення в контексті для Jinja2.
|
||||
|
||||
///
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо вказати `response_class=HTMLResponse`, інтерфейс документації знатиме, що відповідь буде HTML.
|
||||
|
||||
///
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Можна також використати `from starlette.templating import Jinja2Templates`.
|
||||
|
||||
**FastAPI** надає той самий `starlette.templating` як `fastapi.templating` просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Так само з `Request` і `StaticFiles`.
|
||||
|
||||
///
|
||||
|
||||
## Створення шаблонів { #writing-templates }
|
||||
|
||||
Потім ви можете написати шаблон у `templates/item.html`, наприклад:
|
||||
|
||||
```jinja hl_lines="7"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
### Значення контексту шаблону { #template-context-values }
|
||||
|
||||
У HTML, який містить:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
Item ID: {{ id }}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...буде показано `id`, взятий із «контексту» `dict`, який ви передали:
|
||||
|
||||
```Python
|
||||
{"id": id}
|
||||
```
|
||||
|
||||
Наприклад, з ID `42` це буде відображено як:
|
||||
|
||||
```html
|
||||
Item ID: 42
|
||||
```
|
||||
|
||||
### Аргументи `url_for` у шаблоні { #template-url-for-arguments }
|
||||
|
||||
Ви також можете використовувати `url_for()` у шаблоні - вона приймає ті самі аргументи, що й ваша *функція операції шляху*.
|
||||
|
||||
Тож фрагмент:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
<a href="{{ url_for('read_item', id=id) }}">
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...згенерує посилання на той самий URL, який оброблятиме *функція операції шляху* `read_item(id=id)`.
|
||||
|
||||
Наприклад, з ID `42` це буде відображено як:
|
||||
|
||||
```html
|
||||
<a href="/items/42">
|
||||
```
|
||||
|
||||
## Шаблони і статичні файли { #templates-and-static-files }
|
||||
|
||||
Ви також можете використовувати `url_for()` у шаблоні, наприклад з `StaticFiles`, які ви змонтували з `name="static"`.
|
||||
|
||||
```jinja hl_lines="4"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
У цьому прикладі це посилатиметься на файл CSS у `static/styles.css` за допомогою:
|
||||
|
||||
```CSS hl_lines="4"
|
||||
{!../../docs_src/templates/static/styles.css!}
|
||||
```
|
||||
|
||||
І оскільки ви використовуєте `StaticFiles`, цей файл CSS буде автоматично обслуговуватись вашим застосунком **FastAPI** за URL `/static/styles.css`.
|
||||
|
||||
## Детальніше { #more-details }
|
||||
|
||||
Докладніше, зокрема як тестувати шаблони, дивіться <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">документацію Starlette щодо шаблонів</a>.
|
||||
53
docs/uk/docs/advanced/testing-dependencies.md
Normal file
53
docs/uk/docs/advanced/testing-dependencies.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Тестування залежностей з переписуваннями { #testing-dependencies-with-overrides }
|
||||
|
||||
## Переписування залежностей під час тестування { #overriding-dependencies-during-testing }
|
||||
|
||||
Є сценарії, коли ви можете захотіти переписати залежність під час тестування.
|
||||
|
||||
Ви не хочете запускати оригінальну залежність (ані будь-які її підзалежності).
|
||||
|
||||
Натомість ви хочете надати іншу залежність, яку буде використано лише під час тестів (можливо, тільки в окремих тестах), і яка повертатиме значення, що підставляється всюди, де раніше використовувалося значення оригінальної залежності.
|
||||
|
||||
### Випадки використання: зовнішній сервіс { #use-cases-external-service }
|
||||
|
||||
Наприклад, у вас є зовнішній постачальник автентифікації, який потрібно викликати.
|
||||
|
||||
Ви надсилаєте йому токен, і він повертає автентифікованого користувача.
|
||||
|
||||
Такий постачальник може стягувати плату за кожний запит, а виклик може займати більше часу, ніж використання фіксованого фальшивого користувача для тестів.
|
||||
|
||||
Ймовірно, ви хочете протестувати зовнішнього постачальника один раз, але не обов'язково викликати його для кожного тесту.
|
||||
|
||||
У такому разі ви можете переписати залежність, що викликає цього постачальника, і використати власну залежність, яка повертає фальшивого користувача, лише для ваших тестів.
|
||||
|
||||
### Використовуйте атрибут `app.dependency_overrides` { #use-the-app-dependency-overrides-attribute }
|
||||
|
||||
Для таких випадків ваш застосунок FastAPI має атрибут `app.dependency_overrides`, це простий `dict`.
|
||||
|
||||
Щоб переписати залежність для тестування, вкажіть у ключі оригінальну залежність (функцію), а значенням - ваше переписування залежності (іншу функцію).
|
||||
|
||||
Після цього FastAPI викликатиме це переписування замість оригінальної залежності.
|
||||
|
||||
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ви можете налаштувати переписування для залежності, яка використовується де завгодно у вашому застосунку FastAPI.
|
||||
|
||||
Оригінальна залежність може використовуватися у функції операції шляху, у декораторі операції шляху (коли ви не використовуєте повернуте значення), у виклику `.include_router()` тощо.
|
||||
|
||||
FastAPI усе одно зможе її переписати.
|
||||
|
||||
///
|
||||
|
||||
Потім ви можете скинути переписування (видалити їх), встановивши для `app.dependency_overrides` порожній `dict`:
|
||||
|
||||
```Python
|
||||
app.dependency_overrides = {}
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви хочете переписувати залежність лише під час окремих тестів, встановіть переписування на початку тесту (всередині тестової функції) і скиньте його наприкінці (в кінці тестової функції).
|
||||
|
||||
///
|
||||
11
docs/uk/docs/advanced/testing-events.md
Normal file
11
docs/uk/docs/advanced/testing-events.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Тестування подій: тривалість життя та запуск - вимкнення { #testing-events-lifespan-and-startup-shutdown }
|
||||
|
||||
Коли вам потрібно, щоб `lifespan` виконувався у ваших тестах, ви можете використати `TestClient` з оператором `with`:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *}
|
||||
|
||||
Ви можете прочитати більше у [«Запуск тривалості життя у тестах на офіційному сайті документації Starlette.»](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)
|
||||
|
||||
Для застарілих подій `startup` і `shutdown` ви можете використовувати `TestClient` так:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *}
|
||||
13
docs/uk/docs/advanced/testing-websockets.md
Normal file
13
docs/uk/docs/advanced/testing-websockets.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Тестування WebSocket { #testing-websockets }
|
||||
|
||||
Ви можете використовувати той самий `TestClient` для тестування WebSocket.
|
||||
|
||||
Для цього використайте `TestClient` в інструкції `with`, підключаючись до WebSocket:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *}
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Докладніше дивіться документацію Starlette з <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">тестування WebSocket</a>.
|
||||
|
||||
///
|
||||
55
docs/uk/docs/advanced/using-request-directly.md
Normal file
55
docs/uk/docs/advanced/using-request-directly.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Використання Request безпосередньо { #using-the-request-directly }
|
||||
|
||||
Дотепер ви оголошували потрібні вам частини запиту разом з їх типами.
|
||||
|
||||
Отримуючи дані з:
|
||||
- шляху як параметрів;
|
||||
- заголовків;
|
||||
- кукі;
|
||||
- тощо.
|
||||
|
||||
І таким чином **FastAPI** перевіряє ці дані, перетворює їх і автоматично генерує документацію для вашого API.
|
||||
|
||||
Але бувають ситуації, коли може знадобитися доступ до об'єкта `Request` безпосередньо.
|
||||
|
||||
## Деталі про об'єкт `Request` { #details-about-the-request-object }
|
||||
|
||||
Оскільки під капотом **FastAPI** - це **Starlette** з шаром інструментів зверху, ви можете за потреби використовувати <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">об'єкт `Request`</a> Starlette безпосередньо.
|
||||
|
||||
Це також означає, що якщо ви отримуєте дані безпосередньо з об'єкта `Request` (наприклад, читаєте тіло), FastAPI не буде їх перевіряти, перетворювати або документувати (через OpenAPI для автоматичного інтерфейсу користувача API).
|
||||
|
||||
Водночас будь-який інший параметр, оголошений звичайним способом (наприклад, тіло з моделлю Pydantic), і надалі буде перевірений, перетворений, анотований тощо.
|
||||
|
||||
Але є окремі випадки, коли корисно отримати об'єкт `Request`.
|
||||
|
||||
## Використовуйте об'єкт `Request` безпосередньо { #use-the-request-object-directly }
|
||||
|
||||
Припустімо, ви хочете отримати IP-адресу/хост клієнта всередині вашої функції операції шляху.
|
||||
|
||||
Для цього потрібно звернутися до запиту безпосередньо.
|
||||
|
||||
{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *}
|
||||
|
||||
Якщо вказати у функції операції шляху параметр типу `Request`, **FastAPI** передасть у нього об'єкт `Request`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зауважте, що в цьому прикладі ми оголошуємо параметр шляху поряд із параметром запиту.
|
||||
|
||||
Тому параметр шляху буде витягнуто, перевірено, перетворено до вказаного типу та анотовано в OpenAPI.
|
||||
|
||||
Так само ви можете оголошувати будь-які інші параметри звичайним чином і додатково отримувати `Request`.
|
||||
|
||||
///
|
||||
|
||||
## Документація `Request` { #request-documentation }
|
||||
|
||||
Докладніше про <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">об'єкт `Request` на офіційному сайті документації Starlette</a>.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette.requests import Request`.
|
||||
|
||||
**FastAPI** надає його безпосередньо як зручність для вас, розробника. Але він походить безпосередньо із Starlette.
|
||||
|
||||
///
|
||||
186
docs/uk/docs/advanced/websockets.md
Normal file
186
docs/uk/docs/advanced/websockets.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# WebSockets { #websockets }
|
||||
|
||||
Ви можете використовувати <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a> з **FastAPI**.
|
||||
|
||||
## Встановіть `websockets` { #install-websockets }
|
||||
|
||||
Переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили `websockets` (бібліотеку Python, що полегшує використання протоколу «WebSocket»):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install websockets
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Клієнт WebSockets { #websockets-client }
|
||||
|
||||
### У продакшені { #in-production }
|
||||
|
||||
У вашій продакшен-системі у вас, напевно, є фронтенд, створений за допомогою сучасного фреймворку на кшталт React, Vue.js або Angular.
|
||||
|
||||
Для спілкування через WebSockets з бекендом ви, ймовірно, використовуватимете утиліти вашого фронтенду.
|
||||
|
||||
Або у вас може бути нативний мобільний застосунок, що напряму спілкується з вашим WebSocket-бекендом нативним кодом.
|
||||
|
||||
Або будь-який інший спосіб спілкування з кінцевою точкою WebSocket.
|
||||
|
||||
---
|
||||
|
||||
Але для цього прикладу ми використаємо дуже простий HTML-документ з невеликим JavaScript, усе всередині довгого рядка.
|
||||
|
||||
Звісно, це не оптимально і ви б не використовували це у продакшені.
|
||||
|
||||
У продакшені ви б використали один з варіантів вище.
|
||||
|
||||
Але це найпростіший спосіб зосередитися на серверній частині WebSockets і мати робочий приклад:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *}
|
||||
|
||||
## Створіть `websocket` { #create-a-websocket }
|
||||
|
||||
У вашому застосунку **FastAPI** створіть `websocket`:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *}
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette.websockets import WebSocket`.
|
||||
|
||||
**FastAPI** надає той самий `WebSocket` напряму як зручність для вас, розробника. Але він походить безпосередньо зі Starlette.
|
||||
|
||||
///
|
||||
|
||||
## Очікуйте повідомлення та надсилайте повідомлення { #await-for-messages-and-send-messages }
|
||||
|
||||
У вашому маршруті WebSocket ви можете `await` повідомлення і надсилати повідомлення.
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *}
|
||||
|
||||
Ви можете отримувати та надсилати бінарні, текстові та JSON-дані.
|
||||
|
||||
## Спробуйте { #try-it }
|
||||
|
||||
Якщо ваш файл називається `main.py`, запустіть ваш застосунок командою:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Відкрийте у браузері <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
Ви побачите просту сторінку на кшталт:
|
||||
|
||||
<img src="/img/tutorial/websockets/image01.png">
|
||||
|
||||
Ви можете вводити повідомлення у поле вводу та надсилати їх:
|
||||
|
||||
<img src="/img/tutorial/websockets/image02.png">
|
||||
|
||||
І ваш застосунок **FastAPI** з WebSockets відповість:
|
||||
|
||||
<img src="/img/tutorial/websockets/image03.png">
|
||||
|
||||
Ви можете надсилати (і отримувати) багато повідомлень:
|
||||
|
||||
<img src="/img/tutorial/websockets/image04.png">
|
||||
|
||||
І всі вони використовуватимуть те саме з'єднання WebSocket.
|
||||
|
||||
## Використання `Depends` та іншого { #using-depends-and-others }
|
||||
|
||||
У кінцевих точках WebSocket ви можете імпортувати з `fastapi` і використовувати:
|
||||
|
||||
* `Depends`
|
||||
* `Security`
|
||||
* `Cookie`
|
||||
* `Header`
|
||||
* `Path`
|
||||
* `Query`
|
||||
|
||||
Вони працюють так само, як для інших ендпойнтів FastAPI/*операцій шляху*:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
|
||||
|
||||
/// info
|
||||
|
||||
Оскільки це WebSocket, не має сенсу піднімати `HTTPException`, натомість ми піднімаємо `WebSocketException`.
|
||||
|
||||
Ви можете використати код закриття з <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">чинних кодів, визначених у специфікації</a>.
|
||||
|
||||
///
|
||||
|
||||
### Спробуйте WebSockets із залежностями { #try-the-websockets-with-dependencies }
|
||||
|
||||
Якщо ваш файл називається `main.py`, запустіть ваш застосунок командою:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Відкрийте у браузері <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
|
||||
|
||||
Там ви можете встановити:
|
||||
|
||||
* «Item ID», який використовується у шляху.
|
||||
* «Token», який використовується як параметр запиту.
|
||||
|
||||
/// tip
|
||||
|
||||
Зверніть увагу, що параметр запиту `token` буде оброблено залежністю.
|
||||
|
||||
///
|
||||
|
||||
Після цього ви зможете під'єднати WebSocket, а далі надсилати й отримувати повідомлення:
|
||||
|
||||
<img src="/img/tutorial/websockets/image05.png">
|
||||
|
||||
## Обробка відключень і кількох клієнтів { #handling-disconnections-and-multiple-clients }
|
||||
|
||||
Коли з'єднання WebSocket закривається, `await websocket.receive_text()` підніме виняток `WebSocketDisconnect`, який ви можете перехопити й обробити, як у цьому прикладі.
|
||||
|
||||
{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *}
|
||||
|
||||
Щоб спробувати:
|
||||
|
||||
* Відкрийте застосунок у кількох вкладках браузера.
|
||||
* Надсилайте з них повідомлення.
|
||||
* Потім закрийте одну з вкладок.
|
||||
|
||||
Це підніме виняток `WebSocketDisconnect`, і всі інші клієнти отримають повідомлення на кшталт:
|
||||
|
||||
```
|
||||
Client #1596980209979 left the chat
|
||||
```
|
||||
|
||||
/// tip
|
||||
|
||||
Застосунок вище - це мінімальний і простий приклад, що демонструє, як обробляти та розсилати повідомлення кільком з'єднанням WebSocket.
|
||||
|
||||
Але майте на увазі, що оскільки все обробляється в пам'яті, в одному списку, це працюватиме лише поки процес запущений, і лише з одним процесом.
|
||||
|
||||
Якщо вам потрібне щось просте для інтеграції з FastAPI, але більш надійне, з підтримкою Redis, PostgreSQL чи інших, перегляньте <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>.
|
||||
|
||||
///
|
||||
|
||||
## Детальніше { #more-info }
|
||||
|
||||
Щоб дізнатися більше про можливості, перегляньте документацію Starlette:
|
||||
|
||||
* <a href="https://www.starlette.dev/websockets/" class="external-link" target="_blank">Клас `WebSocket`</a>.
|
||||
* <a href="https://www.starlette.dev/endpoints/#websocketendpoint" class="external-link" target="_blank">Обробка WebSocket на основі класів</a>.
|
||||
51
docs/uk/docs/advanced/wsgi.md
Normal file
51
docs/uk/docs/advanced/wsgi.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Підключення WSGI - Flask, Django та інші { #including-wsgi-flask-django-others }
|
||||
|
||||
Ви можете монтувати застосунки WSGI, як ви бачили в [Підзастосунки - монтування](sub-applications.md){.internal-link target=_blank}, [За представником](behind-a-proxy.md){.internal-link target=_blank}.
|
||||
|
||||
Для цього ви можете використати `WSGIMiddleware` і обгорнути ним ваш застосунок WSGI, наприклад Flask, Django тощо.
|
||||
|
||||
## Використання `WSGIMiddleware` { #using-wsgimiddleware }
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Для цього потрібно встановити `a2wsgi`, наприклад за допомогою `pip install a2wsgi`.
|
||||
|
||||
///
|
||||
|
||||
Потрібно імпортувати `WSGIMiddleware` з `a2wsgi`.
|
||||
|
||||
Потім обгорніть застосунок WSGI (напр., Flask) цим проміжним програмним забезпеченням.
|
||||
|
||||
І змонтуйте його під певним шляхом.
|
||||
|
||||
{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *}
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Раніше рекомендувалося використовувати `WSGIMiddleware` з `fastapi.middleware.wsgi`, але тепер його визнано застарілим.
|
||||
|
||||
Замість цього радимо використовувати пакет `a2wsgi`. Використання залишається таким самим.
|
||||
|
||||
Просто переконайтеся, що у вас встановлено пакет `a2wsgi`, і імпортуйте `WSGIMiddleware` коректно з `a2wsgi`.
|
||||
|
||||
///
|
||||
|
||||
## Перевірте { #check-it }
|
||||
|
||||
Тепер кожен запит за шляхом `/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>, ви побачите відповідь від FastAPI:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World"
|
||||
}
|
||||
```
|
||||
444
docs/uk/docs/async.md
Normal file
444
docs/uk/docs/async.md
Normal file
@@ -0,0 +1,444 @@
|
||||
# Рівночасність і async / await { #concurrency-and-async-await }
|
||||
|
||||
Деталі щодо синтаксису `async def` для функцій операції шляху і деякі відомості про асинхронний код, рівночасність і паралелізм.
|
||||
|
||||
## Поспішаєте? { #in-a-hurry }
|
||||
|
||||
<abbr title="too long; didn't read - занадто довго; не читав"><strong>TL;DR:</strong></abbr>
|
||||
|
||||
Якщо ви використовуєте сторонні бібліотеки, які вимагають виклику з `await`, наприклад:
|
||||
|
||||
```Python
|
||||
results = await some_library()
|
||||
```
|
||||
|
||||
Тоді оголошуйте ваші функції операції шляху з `async def`, наприклад:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
async def read_results():
|
||||
results = await some_library()
|
||||
return results
|
||||
```
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Ви можете використовувати `await` лише всередині функцій, створених з `async def`.
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
Якщо ви використовуєте сторонню бібліотеку, яка взаємодіє з чимось (база даних, API, файлова система тощо) і не підтримує використання `await` (наразі це стосується більшості бібліотек баз даних), тоді оголошуйте ваші функції операції шляху як зазвичай, просто з `def`, наприклад:
|
||||
|
||||
```Python hl_lines="2"
|
||||
@app.get('/')
|
||||
def results():
|
||||
results = some_library()
|
||||
return results
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Якщо ваш застосунок (якимось чином) не має комунікувати з чимось іншим і чекати на відповідь, використовуйте `async def`, навіть якщо вам не потрібно використовувати `await` всередині.
|
||||
|
||||
---
|
||||
|
||||
Якщо ви не певні, використовуйте звичайний `def`.
|
||||
|
||||
---
|
||||
|
||||
Примітка: ви можете змішувати `def` і `async def` у ваших функціях операції шляху скільки завгодно і визначати кожну з них найкращим для вас способом. FastAPI зробить з ними все правильно.
|
||||
|
||||
У будь-якому з наведених випадків FastAPI все одно працюватиме асинхронно і буде надзвичайно швидким.
|
||||
|
||||
Але слідуючи крокам вище, він зможе зробити деякі оптимізації продуктивності.
|
||||
|
||||
## Технічні деталі { #technical-details }
|
||||
|
||||
Сучасні версії Python мають підтримку «асинхронного коду» за допомогою так званих «співпрограм» з синтаксисом **`async` і `await`**.
|
||||
|
||||
Розгляньмо цю фразу по частинах у секціях нижче:
|
||||
|
||||
- Асинхронний код
|
||||
- `async` і `await`
|
||||
- Співпрограми
|
||||
|
||||
## Асинхронний код { #asynchronous-code }
|
||||
|
||||
Асинхронний код означає, що мова 💬 має спосіб сказати комп’ютеру/програмі 🤖, що в певний момент у коді він 🤖 має почекати, поки «щось інше» завершиться десь ще. Скажімо, це «щось інше» називається «slow-file» 📝.
|
||||
|
||||
Отже, в цей час комп’ютер може піти і зробити іншу роботу, доки «slow-file» 📝 завершується.
|
||||
|
||||
Далі комп’ютер/програма 🤖 повертатиметься щоразу, коли матиме можливість, бо знову чекає, або коли він 🤖 завершив усю роботу, яка була в нього на той момент. І він 🤖 перевірить, чи якась із задач, на які він чекав, уже завершилася, виконавши все, що потрібно.
|
||||
|
||||
Потім він 🤖 бере першу завершену задачу (скажімо, наш «slow-file» 📝) і продовжує робити те, що потрібно було зробити з нею.
|
||||
|
||||
Це «чекати на щось інше» зазвичай стосується операцій <abbr title="Input and Output - Ввід та Вивід">I/O</abbr>, які відносно «повільні» (порівняно зі швидкістю процесора та пам’яті з довільним доступом), наприклад, очікування:
|
||||
|
||||
- даних від клієнта, що надсилаються мережею
|
||||
- даних, надісланих вашим застосунком, які клієнт має отримати мережею
|
||||
- вмісту файла на диску, який система має прочитати і передати вашому застосунку
|
||||
- вмісту, який ваш застосунок передав системі, щоб він був записаний на диск
|
||||
- віддаленої операції API
|
||||
- завершення операції бази даних
|
||||
- повернення результатів запиту до бази даних
|
||||
- тощо
|
||||
|
||||
Оскільки час виконання переважно витрачається на очікування операцій <abbr title="Input and Output - Ввід та Вивід">I/O</abbr>, їх називають операціями «I/O bound».
|
||||
|
||||
Це називається «асинхронним», тому що комп’ютеру/програмі не потрібно бути «синхронізованими» з повільною задачею, очікуючи точного моменту її завершення, нічого не роблячи, лише щоб отримати результат задачі та продовжити роботу.
|
||||
|
||||
Натомість, у «асинхронній» системі щойно завершена задача може трохи зачекати в черзі (кілька мікросекунд), доки комп’ютер/програма завершить те, що пішов робити, а потім повернеться, щоб забрати результати і продовжити роботу з ними.
|
||||
|
||||
Для «синхронного» (на противагу «асинхронному») часто також використовують термін «послідовний», бо комп’ютер/програма слідує всім крокам послідовно, перш ніж перемкнутися на іншу задачу, навіть якщо ці кроки включають очікування.
|
||||
|
||||
### Рівночасність і бургери { #concurrency-and-burgers }
|
||||
|
||||
Ідею **асинхронного** коду, описану вище, інколи також називають **«рівночасністю»**. Вона відрізняється від **«паралелізму»**.
|
||||
|
||||
І рівночасність, і паралелізм стосуються «різних речей, що відбуваються більш-менш одночасно».
|
||||
|
||||
Але деталі між рівночасністю і паралелізмом досить різні.
|
||||
|
||||
Щоб побачити різницю, уявімо таку історію про бургери:
|
||||
|
||||
### Рівночасні бургери { #concurrent-burgers }
|
||||
|
||||
Ви йдете зі своєю симпатією по фастфуд, стаєте в чергу, доки касир приймає замовлення у людей перед вами. 😍
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration">
|
||||
|
||||
Потім ваша черга, ви замовляєте 2 дуже вишукані бургери для вашої симпатії і для себе. 🍔🍔
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration">
|
||||
|
||||
Касир каже щось кухарю на кухні, щоб той знав, що треба приготувати ваші бургери (хоча зараз він готує бургери для попередніх клієнтів).
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration">
|
||||
|
||||
Ви платите. 💸
|
||||
|
||||
Касир дає вам номер вашої черги.
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration">
|
||||
|
||||
Поки ви чекаєте, ви з вашою симпатією обираєте столик, сідаєте і довго розмовляєте (адже ваші бургери дуже вишукані і потребують часу на приготування).
|
||||
|
||||
Сидячи за столиком із вашою симпатією, доки чекаєте бургери, ви можете витратити цей час, милуючись тим, яка ваша симпатія класна, мила і розумна ✨😍✨.
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration">
|
||||
|
||||
Чекаючи і спілкуючись із вашою симпатією, час від часу ви перевіряєте номер на табло біля прилавка, щоб побачити, чи вже ваша черга.
|
||||
|
||||
І от нарешті ваша черга. Ви підходите до прилавка, забираєте бургери і повертаєтеся до столика.
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration">
|
||||
|
||||
Ви з вашою симпатією їсте бургери і гарно проводите час. ✨
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Прекрасні ілюстрації від <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
Уявіть, що в цій історії ви - комп’ютер/програма 🤖.
|
||||
|
||||
Поки ви в черзі, ви просто бездіяльні 😴, чекаєте своєї черги, нічого «продуктивного» не роблячи. Але черга рухається швидко, бо касир лише приймає замовлення (а не готує їх), тож це нормально.
|
||||
|
||||
Коли ж ваша черга, ви виконуєте справді «продуктивну» роботу: переглядаєте меню, вирішуєте, що бажаєте, дізнаєтеся вибір вашої симпатії, платите, перевіряєте, що віддаєте правильну купюру чи картку, що з вас правильно списали кошти, що замовлення містить правильні позиції тощо.
|
||||
|
||||
Але потім, хоча у вас ще немає бургерів, ваша взаємодія з касиром «на паузі» ⏸, бо вам доводиться чекати 🕙, поки бургери будуть готові.
|
||||
|
||||
Втім, відійшовши від прилавка і сівши за столик із номерком, ви можете перемкнути 🔀 увагу на свою симпатію і «попрацювати» ⏯ 🤓 над цим. Тоді ви знову робите щось дуже «продуктивне» - фліртуєте зі своєю симпатією 😍.
|
||||
|
||||
Потім касир 💁 каже «Я закінчив робити бургери», виводячи ваш номер на табло прилавка, але ви не підстрибуєте миттєво, щойно номер змінюється на ваш. Ви знаєте, що ніхто не вкраде ваші бургери, адже у вас є номер вашої черги, а в інших - свій.
|
||||
|
||||
Тож ви чекаєте, поки ваша симпатія завершить історію (завершить поточну роботу ⏯/задачу 🤓), лагідно усміхаєтеся і кажете, що підете за бургерами ⏸.
|
||||
|
||||
Потім ви йдете до прилавка 🔀, до початкової задачі, яку тепер завершено ⏯, забираєте бургери, дякуєте і несете їх до столу. Це завершує той крок/задачу взаємодії з прилавком ⏹. Натомість з’являється нова задача «їсти бургери» 🔀 ⏯, але попередня «отримати бургери» завершена ⏹.
|
||||
|
||||
### Паралельні бургери { #parallel-burgers }
|
||||
|
||||
А тепер уявімо, що це не «рівночасні бургери», а «паралельні бургери».
|
||||
|
||||
Ви йдете зі своєю симпатією по паралельний фастфуд.
|
||||
|
||||
Ви стаєте в чергу, поки кілька (скажімо, 8) касирів, які водночас є кухарями, приймають замовлення у людей перед вами.
|
||||
|
||||
Кожен перед вами чекає, поки його бургери будуть готові, перш ніж відійти від прилавка, тому що кожен з 8 касирів одразу йде і готує бургер, перш ніж приймати наступне замовлення.
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration">
|
||||
|
||||
Нарешті ваша черга, ви замовляєте 2 дуже вишукані бургери для вашої симпатії і для себе.
|
||||
|
||||
Ви платите 💸.
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration">
|
||||
|
||||
Касир іде на кухню.
|
||||
|
||||
Ви чекаєте, стоячи перед прилавком 🕙, щоб ніхто інший не забрав ваші бургери раніше, ніж ви, адже номерків черги немає.
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration">
|
||||
|
||||
Оскільки ви з вашою симпатією зайняті тим, щоб ніхто не став перед вами і не забрав ваші бургери, щойно вони з’являться, ви не можете приділяти увагу своїй симпатії. 😞
|
||||
|
||||
Це «синхронна» робота, ви «синхронізовані» з касиром/кухарем 👨🍳. Вам доводиться чекати 🕙 і бути тут у точний момент, коли касир/кухар 👨🍳 завершить бургери і віддасть їх вам, інакше хтось інший може їх забрати.
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration">
|
||||
|
||||
Потім ваш касир/кухар 👨🍳 нарешті повертається з вашими бургерами після довгого очікування 🕙 перед прилавком.
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration">
|
||||
|
||||
Ви берете бургери і йдете до столика зі своєю симпатією.
|
||||
|
||||
Ви просто їх їсте - і все. ⏹
|
||||
|
||||
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration">
|
||||
|
||||
Багато розмов чи флірту не було, бо більшість часу пішла на очікування 🕙 перед прилавком. 😞
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Прекрасні ілюстрації від <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
///
|
||||
|
||||
---
|
||||
|
||||
У цьому сценарії паралельних бургерів ви - комп’ютер/програма 🤖 з двома процесорами (ви і ваша симпатія), які обидва чекають 🕙 і приділяють увагу ⏯ «очікуванню біля прилавка» 🕙 тривалий час.
|
||||
|
||||
У закладу фастфуду 8 процесорів (касира/кухаря). У той час як у закладі з рівночасними бургерами могло бути лише 2 (один касир і один кухар).
|
||||
|
||||
Та все одно фінальний досвід не найкращий. 😞
|
||||
|
||||
---
|
||||
|
||||
Це була б паралельна історія про бургери. 🍔
|
||||
|
||||
Для більш «реального» прикладу уявіть банк.
|
||||
|
||||
До недавнього часу більшість банків мали кілька касирів 👨💼👨💼👨💼👨💼 і велику чергу 🕙🕙🕙🕙🕙🕙🕙🕙.
|
||||
|
||||
Усі касири робили всю роботу з одним клієнтом за іншим 👨💼⏯.
|
||||
|
||||
І вам доводилося 🕙 довго стояти в черзі, інакше ви втратите свою чергу.
|
||||
|
||||
Ви, напевно, не хотіли б брати свою симпатію 😍 із собою у справи до банку 🏦.
|
||||
|
||||
### Висновок про бургери { #burger-conclusion }
|
||||
|
||||
У цьому сценарії «фастфуд із вашою симпатією», оскільки є багато очікування 🕙, значно доцільніше мати рівночасну систему ⏸🔀⏯.
|
||||
|
||||
Так є у більшості вебзастосунків.
|
||||
|
||||
Багато-багато користувачів, але ваш сервер чекає 🕙 на їхнє не надто гарне з’єднання, щоб вони надіслали свої запити.
|
||||
|
||||
А потім знову чекає 🕙 на повернення відповідей.
|
||||
|
||||
Це «очікування» 🕙 вимірюється у мікросекундах, але все ж, у сумі - це багато очікування в підсумку.
|
||||
|
||||
Ось чому дуже логічно використовувати асинхронний ⏸🔀⏯ код для веб API.
|
||||
|
||||
Такий тип асинхронності зробив NodeJS популярним (хоча NodeJS не є паралельним), і це сила Go як мови програмування.
|
||||
|
||||
І такий самий рівень продуктивності ви отримуєте з **FastAPI**.
|
||||
|
||||
А оскільки можна мати паралелізм і асинхронність одночасно, ви отримуєте вищу продуктивність, ніж більшість протестованих фреймворків NodeJS, і на рівні з Go, який є компільованою мовою, ближчою до C <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(усе завдяки Starlette)</a>.
|
||||
|
||||
### Чи краща рівночасність за паралелізм? { #is-concurrency-better-than-parallelism }
|
||||
|
||||
Ні! Це не мораль історії.
|
||||
|
||||
Рівночасність відрізняється від паралелізму. І вона краща у конкретних сценаріях, що містять багато очікування. Через це зазвичай вона значно краща за паралелізм для розробки вебзастосунків. Але не для всього.
|
||||
|
||||
Щоб урівноважити це, уявімо коротку історію:
|
||||
|
||||
> Ви маєте прибрати великий брудний будинок.
|
||||
|
||||
*Так, це вся історія*.
|
||||
|
||||
---
|
||||
|
||||
Тут немає очікування 🕙 - просто багато роботи, яку треба зробити, у багатьох місцях будинку.
|
||||
|
||||
У вас могли б бути «черги» як у прикладі з бургерами: спочатку вітальня, потім кухня. Але оскільки ви ні на що не чекаєте 🕙, а просто прибираєте, «черги» нічого не змінять.
|
||||
|
||||
Завершення займе той самий час із «чергами» чи без (рівночасність), і ви виконаєте той самий обсяг роботи.
|
||||
|
||||
Але в цьому випадку, якби ви могли привести 8 колишніх касирів/кухарів/тепер прибиральників, і кожен з них (разом із вами) взяв би свою зону будинку для прибирання, ви могли б виконати всю роботу паралельно — з додатковою допомогою — і завершити значно швидше.
|
||||
|
||||
У цьому сценарії кожен з прибиральників (включно з вами) був би процесором, що виконує свою частину роботи.
|
||||
|
||||
І оскільки більшість часу виконання займає реальна робота (а не очікування), а роботу на комп’ютері виконує <abbr title="Central Processing Unit - Центральний процесор">CPU</abbr>, ці проблеми називають «CPU bound».
|
||||
|
||||
---
|
||||
|
||||
Поширені приклади «CPU bound» операцій - це речі, що потребують складної математичної обробки.
|
||||
|
||||
Наприклад:
|
||||
|
||||
- **Обробка аудіо** або **зображень**.
|
||||
- **Комп’ютерний зір**: зображення складається з мільйонів пікселів, кожен піксель має 3 значення/кольори, обробка зазвичай потребує обчислення чогось над цими пікселями, усіма одночасно.
|
||||
- **Машинне навчання**: зазвичай потребує великої кількості множень «матриць» і «векторів». Уявіть величезну таблицю з числами і множення всіх їх разом одночасно.
|
||||
- **Глибоке навчання**: це підгалузь машинного навчання, тож те саме застосовується. Просто тут не одна таблиця чисел для множення, а величезний їх набір, і в багатьох випадках ви використовуєте спеціальний процесор для побудови та/або використання цих моделей.
|
||||
|
||||
### Рівночасність + паралелізм: веб + машинне навчання { #concurrency-parallelism-web-machine-learning }
|
||||
|
||||
З **FastAPI** ви можете скористатися рівночасністю, що дуже поширена у веброзробці (та ж головна принада NodeJS).
|
||||
|
||||
Але ви також можете використати переваги паралелізму і багатопроцесорності (наявність кількох процесів, що працюють паралельно) для навантажень «CPU bound», як у системах машинного навчання.
|
||||
|
||||
Це, плюс простий факт, що Python є основною мовою для **Data Science**, машинного навчання і особливо глибокого навчання, робить FastAPI дуже вдалим вибором для веб API та застосунків Data Science / машинного навчання (серед багатьох інших).
|
||||
|
||||
Щоб побачити, як досягти цього паралелізму у продакшні, див. розділ про [Розгортання](deployment/index.md){.internal-link target=_blank}.
|
||||
|
||||
## `async` і `await` { #async-and-await }
|
||||
|
||||
Сучасні версії Python мають дуже інтуїтивний спосіб визначення асинхронного коду. Це робить його схожим на звичайний «послідовний» код і виконує «очікування» за вас у відповідні моменти.
|
||||
|
||||
Коли є операція, яка вимагатиме очікування перед поверненням результатів і має підтримку цих нових можливостей Python, ви можете написати її так:
|
||||
|
||||
```Python
|
||||
burgers = await get_burgers(2)
|
||||
```
|
||||
|
||||
Ключ тут - `await`. Він каже Python, що потрібно почекати ⏸, поки `get_burgers(2)` завершить свою роботу 🕙, перш ніж зберегти результати в `burgers`. Завдяки цьому Python знатиме, що може піти і зробити щось інше 🔀 ⏯ тим часом (наприклад, прийняти інший запит).
|
||||
|
||||
Щоб `await` працював, він має бути всередині функції, що підтримує цю асинхронність. Для цього просто оголосіть її як `async def`:
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def get_burgers(number: int):
|
||||
# Виконайте деякі асинхронні дії, щоб створити бургери
|
||||
return burgers
|
||||
```
|
||||
|
||||
...замість `def`:
|
||||
|
||||
```Python hl_lines="2"
|
||||
# Це не асинхронно
|
||||
def get_sequential_burgers(number: int):
|
||||
# Виконайте деякі послідовні дії, щоб створити бургери
|
||||
return burgers
|
||||
```
|
||||
|
||||
З `async def` Python знає, що всередині цієї функції він має відслідковувати вирази `await`, і що він може «ставити на паузу» ⏸ виконання цієї функції і йти робити щось інше 🔀, перш ніж повернутися.
|
||||
|
||||
Коли ви хочете викликати функцію, визначену з `async def`, ви маєте «очікувати» її. Тож це не спрацює:
|
||||
|
||||
```Python
|
||||
# Це не спрацює, тому що get_burgers визначено як: async def
|
||||
burgers = get_burgers(2)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Отже, якщо ви використовуєте бібліотеку, яку можна викликати з `await`, вам потрібно створити функцію операції шляху, що її використовує, з `async def`, як тут:
|
||||
|
||||
```Python hl_lines="2-3"
|
||||
@app.get('/burgers')
|
||||
async def read_burgers():
|
||||
burgers = await get_burgers(2)
|
||||
return burgers
|
||||
```
|
||||
|
||||
### Більше технічних деталей { #more-technical-details }
|
||||
|
||||
Ви могли помітити, що `await` можна використовувати лише всередині функцій, визначених з `async def`.
|
||||
|
||||
А водночас функції, визначені з `async def`, потрібно «очікувати». Тож функції з `async def` також можна викликати лише всередині функцій, визначених з `async def`.
|
||||
|
||||
Тож як же викликати першу `async`-функцію - курка чи яйце?
|
||||
|
||||
Якщо ви працюєте з **FastAPI**, вам не потрібно про це турбуватися, адже цією «першою» функцією буде ваша функція операції шляху, і FastAPI знатиме, як учинити правильно.
|
||||
|
||||
Але якщо ви хочете використовувати `async` / `await` без FastAPI, ви також можете це зробити.
|
||||
|
||||
### Пишемо свій власний async-код { #write-your-own-async-code }
|
||||
|
||||
Starlette (і **FastAPI**) базуються на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, що робить їх сумісними як зі стандартною бібліотекою Python <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a>, так і з <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.
|
||||
|
||||
Зокрема, ви можете безпосередньо використовувати <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> для ваших просунутих сценаріїв рівночасності, що потребують складніших патернів у вашому коді.
|
||||
|
||||
І навіть якщо ви не використовували FastAPI, ви могли б писати свої власні async-застосунки з <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, щоб мати високу сумісність і отримати його переваги (наприклад, *структурована рівночасність*).
|
||||
|
||||
Я створив іншу бібліотеку поверх AnyIO, як тонкий шар, щоб дещо покращити анотації типів і отримати кращу **автодопомогу** (autocompletion), **вбудовані помилки** (inline errors) тощо. Вона також має дружній вступ і навчальний посібник, щоб допомогти вам **зрозуміти** і написати **власний async-код**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. Вона буде особливо корисною, якщо вам потрібно **поєднувати async-код зі звичайним** (блокуючим/синхронним) кодом.
|
||||
|
||||
### Інші форми асинхронного коду { #other-forms-of-asynchronous-code }
|
||||
|
||||
Такий стиль використання `async` і `await` відносно новий у мові.
|
||||
|
||||
Але він значно полегшує роботу з асинхронним кодом.
|
||||
|
||||
Такий самий (або майже ідентичний) синтаксис нещодавно з’явився в сучасних версіях JavaScript (у Browser і NodeJS).
|
||||
|
||||
До цього робота з асинхронним кодом була значно складнішою.
|
||||
|
||||
У попередніх версіях Python ви могли використовувати потоки (threads) або <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Але код набагато складніший для розуміння, налагодження і мислення про нього.
|
||||
|
||||
У попередніх версіях NodeJS/Browser JavaScript ви б використовували «callbacks», що призводить до «callback hell».
|
||||
|
||||
## Співпрограми { #coroutines }
|
||||
|
||||
**Співпрограма** - це просто дуже вишукана назва для об’єкта, який повертає функція `async def`. Python знає, що це щось на кшталт функції, яку можна запустити і яка завершиться в певний момент, але яку також можна поставити на паузу ⏸ всередині, коли є `await`.
|
||||
|
||||
Але всю цю функціональність використання асинхронного коду з `async` і `await` часто підсумовують як використання «співпрограм». Це порівняно з головною ключовою особливістю Go - «Goroutines».
|
||||
|
||||
## Висновок { #conclusion }
|
||||
|
||||
Погляньмо на ту саму фразу ще раз:
|
||||
|
||||
> Сучасні версії Python мають підтримку «асинхронного коду» за допомогою так званих «співпрограм», з синтаксисом **`async` і `await`**.
|
||||
|
||||
Тепер це має більше сенсу. ✨
|
||||
|
||||
Усе це приводить у дію FastAPI (через Starlette) і дає йому таку вражаючу продуктивність.
|
||||
|
||||
## Дуже технічні деталі { #very-technical-details }
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Ймовірно, ви можете пропустити це.
|
||||
|
||||
Це дуже технічні деталі про те, як **FastAPI** працює «під капотом».
|
||||
|
||||
Якщо у вас є чимало технічних знань (співпрограми, потоки, блокування тощо) і вам цікаво, як FastAPI обробляє `async def` проти звичайного `def`, - вперед.
|
||||
|
||||
///
|
||||
|
||||
### Функції операції шляху { #path-operation-functions }
|
||||
|
||||
Коли ви оголошуєте функцію операції шляху зі звичайним `def` замість `async def`, вона виконується у зовнішньому пулі потоків (threadpool), який потім «очікується», замість прямого виклику (оскільки прямий виклик блокував би сервер).
|
||||
|
||||
Якщо ви прийшли з іншого async-фреймворку, який не працює так, як описано вище, і звикли визначати тривіальні, лише обчислювальні функції операції шляху зі звичайним `def` заради крихітного виграшу у продуктивності (близько 100 наносекунд), зверніть увагу, що у **FastAPI** ефект буде протилежним. У таких випадках краще використовувати `async def`, якщо тільки ваші функції операції шляху не використовують код, що виконує блокуюче <abbr title="Input/Output - Ввід/Вивід: читання або запис на диск, мережеві комунікації.">I/O</abbr>.
|
||||
|
||||
Втім, у будь-якій ситуації є велика ймовірність, що **FastAPI** [все одно буде швидшим](index.md#performance){.internal-link target=_blank} (або принаймні порівнянним) за ваш попередній фреймворк.
|
||||
|
||||
### Залежності { #dependencies }
|
||||
|
||||
Те саме стосується і [залежностей](tutorial/dependencies/index.md){.internal-link target=_blank}. Якщо залежність є стандартною функцією `def` замість `async def`, вона виконується у зовнішньому пулі потоків.
|
||||
|
||||
### Підзалежності { #sub-dependencies }
|
||||
|
||||
Ви можете мати кілька залежностей і [підзалежностей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, які вимагають одна одну (як параметри визначень функцій). Деякі з них можуть бути створені з `async def`, а деякі - зі звичайним `def`. Все працюватиме, і ті, що створені зі звичайним `def`, будуть викликані у зовнішньому потоці (з пулу потоків), а не «очікувані».
|
||||
|
||||
### Інші допоміжні функції { #other-utility-functions }
|
||||
|
||||
Будь-яка інша допоміжна функція, яку ви викликаєте безпосередньо, може бути створена зі звичайним `def` або `async def`, і FastAPI не впливатиме на спосіб її виклику.
|
||||
|
||||
Це відрізняється від функцій, які FastAPI викликає за вас: функції операції шляху і залежності.
|
||||
|
||||
Якщо ваша допоміжна функція є звичайною функцією з `def`, її буде викликано безпосередньо (як ви написали у своєму коді), не в пулі потоків; якщо функція створена з `async def`, тоді вам слід використовувати `await` при її виклику у вашому коді.
|
||||
|
||||
---
|
||||
|
||||
Знову ж таки, це дуже технічні деталі, які, ймовірно, стануть у пригоді, якщо ви спеціально їх шукали.
|
||||
|
||||
Інакше вам вистачить настанов із розділу вище: <a href="#in-a-hurry">Поспішаєте?</a>.
|
||||
34
docs/uk/docs/benchmarks.md
Normal file
34
docs/uk/docs/benchmarks.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Бенчмарки { #benchmarks }
|
||||
|
||||
Незалежні бенчмарки 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 внутрішньо).
|
||||
|
||||
Але переглядаючи бенчмарки та порівняння, майте на увазі таке.
|
||||
|
||||
## Бенчмарки та швидкість { #benchmarks-and-speed }
|
||||
|
||||
Під час перегляду бенчмарків часто порівнюють кілька інструментів різних типів як рівноцінні.
|
||||
|
||||
Зокрема, разом порівнюють 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 і не може бути швидшою за нього, FastAPI використовує Starlette, тож не може бути швидшою за неї.
|
||||
* FastAPI надає більше можливостей поверх Starlette. Можливості, які майже завжди потрібні під час створення API, як-от валідація та серіалізація даних. І, використовуючи його, ви безкоштовно отримуєте автоматичну документацію (автоматична документація навіть не додає накладних витрат під час роботи застосунку - вона генерується під час запуску).
|
||||
* Якби ви не використовували FastAPI і застосували Starlette безпосередньо (або інший інструмент, наприклад Sanic, Flask, Responder тощо), вам довелося б самостійно реалізувати всю валідацію та серіалізацію даних. Тож ваш кінцевий застосунок усе одно мав би ті самі накладні витрати, ніби він був створений із використанням FastAPI. І в багатьох випадках саме ця валідація та серіалізація даних становить найбільший обсяг коду в застосунках.
|
||||
* Отже, використовуючи FastAPI, ви заощаджуєте час розробки, зменшуєте кількість помилок і рядків коду та, ймовірно, отримуєте таку саму (або кращу) продуктивність, як і без нього (адже інакше вам довелося б реалізувати все це у власному коді).
|
||||
* Якщо ви порівнюєте FastAPI, порівнюйте його з веб-фреймворком (або набором інструментів), який надає валідацію даних, серіалізацію та документацію, наприклад Flask-apispec, NestJS, Molten тощо. Фреймворки з вбудованою автоматичною валідацією даних, серіалізацією та документацією.
|
||||
24
docs/uk/docs/deployment/cloud.md
Normal file
24
docs/uk/docs/deployment/cloud.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Розгортання FastAPI у хмарних постачальників { #deploy-fastapi-on-cloud-providers }
|
||||
|
||||
Ви можете використовувати практично **будь-якого хмарного постачальника**, щоб розгорнути свій застосунок FastAPI.
|
||||
|
||||
У більшості випадків основні хмарні постачальники мають інструкції з розгортання FastAPI у них.
|
||||
|
||||
## FastAPI Cloud { #fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** створено тим самим автором і командою, що стоять за **FastAPI**.
|
||||
|
||||
Воно спрощує процес **створення**, **розгортання** та **доступу** до API з мінімальними зусиллями.
|
||||
|
||||
Воно переносить той самий **досвід розробника** зі створення застосунків із FastAPI на їх **розгортання** у хмарі. 🎉
|
||||
|
||||
FastAPI Cloud є основним спонсором і джерелом фінансування проєктів з відкритим кодом *FastAPI and friends*. ✨
|
||||
|
||||
## Хмарні постачальники - спонсори { #cloud-providers-sponsors }
|
||||
|
||||
Деякі інші хмарні постачальники ✨ [**спонсорують FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ також. 🙇
|
||||
|
||||
Можливо, ви захочете розглянути їх, щоб дотримуватися їхніх інструкцій і спробувати їхні сервіси:
|
||||
|
||||
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a>
|
||||
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a>
|
||||
321
docs/uk/docs/deployment/concepts.md
Normal file
321
docs/uk/docs/deployment/concepts.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# Концепції розгортання { #deployments-concepts }
|
||||
|
||||
Під час розгортання застосунку **FastAPI** (або будь-якого веб-API) є кілька концепцій, які, ймовірно, вас цікавлять, і, спираючись на них, ви зможете знайти **найвідповідніший** спосіб **розгорнути ваш застосунок**.
|
||||
|
||||
Деякі важливі концепції:
|
||||
|
||||
- Безпека - HTTPS
|
||||
- Запуск під час старту
|
||||
- Перезапуски
|
||||
- Реплікація (кількість запущених процесів)
|
||||
- Пам'ять
|
||||
- Попередні кроки перед стартом
|
||||
|
||||
Подивимось, як вони впливають на **розгортання**.
|
||||
|
||||
Зрештою головна мета - **обслуговувати клієнтів вашого API** так, щоб це було **безпечним**, з **мінімумом перерв у роботі**, і щоб **обчислювальні ресурси** (наприклад, віддалені сервери/віртуальні машини) використовувалися якомога ефективніше. 🚀
|
||||
|
||||
Нижче я трохи більше розповім про ці **концепції**, і, сподіваюся, це дасть вам потрібну **інтуїцію**, щоб вирішувати, як розгортати ваш API в дуже різних середовищах, можливо, навіть у **майбутніх**, яких ще не існує.
|
||||
|
||||
Враховуючи ці концепції, ви зможете **оцінювати та проєктувати** найкращий спосіб розгортання **ваших власних API**.
|
||||
|
||||
У наступних розділах я наведу більш **конкретні рецепти** розгортання застосунків FastAPI.
|
||||
|
||||
А поки перегляньмо ці важливі **концептуальні ідеї**. Вони також застосовні до будь-якого іншого типу веб-API. 💡
|
||||
|
||||
## Безпека - HTTPS { #security-https }
|
||||
|
||||
У [попередньому розділі про HTTPS](https.md){.internal-link target=_blank} ми дізналися, як HTTPS забезпечує шифрування для вашого API.
|
||||
|
||||
Ми також бачили, що HTTPS зазвичай надається компонентом, **зовнішнім** щодо вашого серверного застосунку, - **TLS Termination Proxy**.
|
||||
|
||||
І має бути щось, що відповідає за **оновлення сертифікатів HTTPS** - це може бути той самий компонент або інший.
|
||||
|
||||
### Приклади інструментів для HTTPS { #example-tools-for-https }
|
||||
|
||||
Деякі інструменти, які можна використовувати як TLS Termination Proxy:
|
||||
|
||||
- Traefik
|
||||
- Автоматично обробляє оновлення сертифікатів ✨
|
||||
- Caddy
|
||||
- Автоматично обробляє оновлення сертифікатів ✨
|
||||
- Nginx
|
||||
- З зовнішнім компонентом на кшталт Certbot для оновлення сертифікатів
|
||||
- HAProxy
|
||||
- З зовнішнім компонентом на кшталт Certbot для оновлення сертифікатів
|
||||
- Kubernetes з Ingress Controller, наприклад Nginx
|
||||
- З зовнішнім компонентом на кшталт cert-manager для оновлення сертифікатів
|
||||
- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів (див. нижче 👇)
|
||||
|
||||
Ще один варіант - використати **хмарний сервіс**, який зробить більше роботи, зокрема налаштує HTTPS. Можуть бути обмеження або додаткова вартість тощо. Але у такому разі вам не потрібно самостійно налаштовувати TLS Termination Proxy.
|
||||
|
||||
У наступних розділах я покажу кілька конкретних прикладів.
|
||||
|
||||
---
|
||||
|
||||
Далі всі наступні концепції стосуються програми, яка запускає ваш фактичний API (наприклад, Uvicorn).
|
||||
|
||||
## Програма і процес { #program-and-process }
|
||||
|
||||
Ми багато говоритимемо про запущений «процес», тож корисно чітко розуміти, що це означає, і чим відрізняється від слова «програма».
|
||||
|
||||
### Що таке програма { #what-is-a-program }
|
||||
|
||||
Слово **програма** зазвичай вживають для опису багатьох речей:
|
||||
|
||||
- **Код**, який ви пишете, **файли Python**.
|
||||
- **Файл**, який може бути **виконаний** операційною системою, наприклад: `python`, `python.exe` або `uvicorn`.
|
||||
- Конкретна програма під час **виконання** в операційній системі, що використовує CPU та зберігає дані в пам'яті. Це також називають **процесом**.
|
||||
|
||||
### Що таке процес { #what-is-a-process }
|
||||
|
||||
Слово **процес** зазвичай використовують у більш специфічному значенні, маючи на увазі саме те, що виконується в операційній системі (як у попередньому пункті):
|
||||
|
||||
- Конкретна програма під час **виконання** в операційній системі.
|
||||
- Це не про файл і не про код, це **конкретно** про те, що **виконується** та керується операційною системою.
|
||||
- Будь-яка програма, будь-який код **може щось робити** лише під час **виконання**. Тобто коли **процес запущений**.
|
||||
- Процес може бути **завершений** (або «kill») вами чи операційною системою. У цей момент він припиняє виконання і **більше нічого не може робити**.
|
||||
- Кожен застосунок, який працює на вашому комп'ютері, має певний процес за собою: кожна запущена програма, кожне вікно тощо. Зазвичай на комп'ютері одночасно працює **багато процесів**.
|
||||
- **Кілька процесів** **однієї й тієї самої програми** можуть працювати одночасно.
|
||||
|
||||
Якщо ви відкриєте «диспетчер завдань» або «системний монітор» (чи подібні інструменти) в операційній системі, ви побачите багато таких процесів.
|
||||
|
||||
Наприклад, ви, ймовірно, побачите кілька процесів того самого браузера (Firefox, Chrome, Edge тощо). Зазвичай він запускає один процес на вкладку плюс деякі додаткові процеси.
|
||||
|
||||
<img class="shadow" src="/img/deployment/concepts/image01.png">
|
||||
|
||||
---
|
||||
|
||||
Тепер, коли ми знаємо різницю між термінами **процес** і **програма**, продовжимо говорити про розгортання.
|
||||
|
||||
## Запуск під час старту { #running-on-startup }
|
||||
|
||||
У більшості випадків, коли ви створюєте веб-API, ви хочете, щоб він **працював постійно**, без перерв, щоб клієнти завжди мали до нього доступ. Звісно, якщо немає особливих причин запускати його лише в певних ситуаціях. Але зазвичай ви хочете, щоб він постійно працював і був **доступний**.
|
||||
|
||||
### На віддаленому сервері { #in-a-remote-server }
|
||||
|
||||
Коли ви налаштовуєте віддалений сервер (хмарний сервер, віртуальну машину тощо), найпростіше - використовувати `fastapi run` (який використовує Uvicorn) або щось схоже, вручну, так само, як під час локальної розробки.
|
||||
|
||||
І це працюватиме та буде корисним **під час розробки**.
|
||||
|
||||
Але якщо ви втратите з'єднання з сервером, **запущений процес**, найімовірніше, завершиться.
|
||||
|
||||
І якщо сервер буде перезавантажено (наприклад, після оновлень або міграцій у хмарного провайдера), ви, ймовірно, **не помітите цього**. І через це ви навіть не знатимете, що треба вручну перезапустити процес. У результаті ваш API просто залишиться «мертвим». 😱
|
||||
|
||||
### Автоматичний запуск під час старту { #run-automatically-on-startup }
|
||||
|
||||
Загалом ви, напевно, захочете, щоб серверна програма (наприклад, Uvicorn) запускалася автоматично під час старту сервера і без будь-якого **людського втручання**, щоб завжди був запущений процес із вашим API (наприклад, Uvicorn із вашим FastAPI-застосунком).
|
||||
|
||||
### Окрема програма { #separate-program }
|
||||
|
||||
Щоб цього досягти, зазвичай використовують **окрему програму**, яка гарантує запуск вашого застосунку під час старту. І в багатьох випадках вона також забезпечує запуск інших компонентів або застосунків, наприклад бази даних.
|
||||
|
||||
### Приклади інструментів для запуску під час старту { #example-tools-to-run-at-startup }
|
||||
|
||||
Приклади інструментів, які можуть це робити:
|
||||
|
||||
- Docker
|
||||
- Kubernetes
|
||||
- Docker Compose
|
||||
- Docker у режимі Swarm
|
||||
- Systemd
|
||||
- Supervisor
|
||||
- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів
|
||||
- Інші...
|
||||
|
||||
У наступних розділах я наведу більш конкретні приклади.
|
||||
|
||||
## Перезапуски { #restarts }
|
||||
|
||||
Подібно до забезпечення запуску застосунку під час старту системи, ви, ймовірно, також захочете гарантувати його **перезапуск** після збоїв.
|
||||
|
||||
### Ми помиляємося { #we-make-mistakes }
|
||||
|
||||
Ми, люди, постійно робимо **помилки**. Майже завжди у програмному забезпеченні є приховані **помилки**. 🐛
|
||||
|
||||
І ми як розробники постійно покращуємо код, знаходячи ці помилки та додаючи нові можливості (можливо, теж додаючи нові помилки 😅).
|
||||
|
||||
### Невеликі помилки обробляються автоматично { #small-errors-automatically-handled }
|
||||
|
||||
Створюючи веб-API з FastAPI, якщо в нашому коді є помилка, FastAPI зазвичай обмежує її одним запитом, який цю помилку спровокував. 🛡
|
||||
|
||||
Клієнт отримає **500 Internal Server Error** для цього запиту, але застосунок продовжить працювати для наступних запитів замість повного краху.
|
||||
|
||||
### Великі помилки - крахи { #bigger-errors-crashes }
|
||||
|
||||
Втім, бувають випадки, коли ми пишемо код, який **падає весь застосунок**, спричиняючи крах Uvicorn і Python. 💥
|
||||
|
||||
І все ж ви, ймовірно, не захочете, щоб застосунок залишався «мертвим» через помилку в одному місці - ви, напевно, хочете, щоб він **продовжував працювати** принаймні для тих *операцій шляху*, що не зламані.
|
||||
|
||||
### Перезапуск після краху { #restart-after-crash }
|
||||
|
||||
Але в таких випадках із серйозними помилками, що призводять до краху запущеного **процесу**, потрібен зовнішній компонент, відповідальний за **перезапуск** процесу, принаймні кілька разів...
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
...Хоча якщо весь застосунок просто **миттєво падає**, безглуздо перезапускати його безкінечно. Але в таких випадках ви, ймовірно, помітите це під час розробки або принаймні відразу після розгортання.
|
||||
|
||||
Тож зосередьмося на основних випадках, коли у майбутньому він може повністю падати за певних обставин, і тоді все ще має сенс його перезапускати.
|
||||
|
||||
///
|
||||
|
||||
Ймовірно, ви захочете мати відокремлений **зовнішній компонент**, який відповідає за перезапуск застосунку, адже до того моменту сам застосунок з Uvicorn і Python уже впав, і нічого в тому ж коді цієї ж програми зробити не зможе.
|
||||
|
||||
### Приклади інструментів для автоматичного перезапуску { #example-tools-to-restart-automatically }
|
||||
|
||||
У більшості випадків той самий інструмент, який використовується для **запуску програми під час старту**, також використовується для автоматичних **перезапусків**.
|
||||
|
||||
Наприклад, це можуть забезпечувати:
|
||||
|
||||
- Docker
|
||||
- Kubernetes
|
||||
- Docker Compose
|
||||
- Docker у режимі Swarm
|
||||
- Systemd
|
||||
- Supervisor
|
||||
- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів
|
||||
- Інші...
|
||||
|
||||
## Реплікація - процеси та пам'ять { #replication-processes-and-memory }
|
||||
|
||||
У застосунку FastAPI, використовуючи серверну програму, як-от команду `fastapi`, що запускає Uvicorn, один запуск в **одному процесі** може обслуговувати кількох клієнтів рівночасно.
|
||||
|
||||
Але часто ви захочете запускати кілька процесів-працівників одночасно.
|
||||
|
||||
### Кілька процесів - працівники { #multiple-processes-workers }
|
||||
|
||||
Якщо у вас більше клієнтів, ніж може обробити один процес (наприклад, якщо віртуальна машина не надто потужна) і на сервері є **кілька ядер** CPU, тоді ви можете запустити **кілька процесів** із тим самим застосунком паралельно і розподіляти запити між ними.
|
||||
|
||||
Коли ви запускаєте **кілька процесів** того самого програмного забезпечення API, їх зазвичай називають **працівниками** (workers).
|
||||
|
||||
### Процеси-працівники і порти { #worker-processes-and-ports }
|
||||
|
||||
Пам'ятаєте з документації [Про HTTPS](https.md){.internal-link target=_blank}, що на сервері лише один процес може слухати певну комбінацію порту та IP-адреси?
|
||||
|
||||
Це досі так.
|
||||
|
||||
Отже, щоб мати **кілька процесів** одночасно, має бути **єдиний процес, який слухає порт**, і який далі якимось чином передає комунікацію кожному процесу-працівнику.
|
||||
|
||||
### Пам'ять на процес { #memory-per-process }
|
||||
|
||||
Коли програма завантажує щось у пам'ять, наприклад модель машинного навчання в змінну або вміст великого файлу в змінну, все це **споживає частину пам'яті (RAM)** сервера.
|
||||
|
||||
І кілька процесів зазвичай **не діляться пам'яттю**. Це означає, що кожен запущений процес має власні речі, змінні та пам'ять. І якщо у вашому коді споживається багато пам'яті, **кожен процес** споживатиме еквівалентний обсяг пам'яті.
|
||||
|
||||
### Пам'ять сервера { #server-memory }
|
||||
|
||||
Наприклад, якщо ваш код завантажує модель машинного навчання розміром **1 GB**, то при запуску одного процесу з вашим API він споживатиме щонайменше 1 GB RAM. А якщо ви запустите **4 процеси** (4 працівники) - кожен споживатиме 1 GB RAM. Отже, загалом ваш API споживатиме **4 GB RAM**.
|
||||
|
||||
І якщо ваш віддалений сервер або віртуальна машина має лише 3 GB RAM, спроба використати понад 4 GB призведе до проблем. 🚨
|
||||
|
||||
### Кілька процесів - приклад { #multiple-processes-an-example }
|
||||
|
||||
У цьому прикладі є **керівний процес** (Manager Process), який запускає і контролює два **процеси-працівники**.
|
||||
|
||||
Цей керівний процес, імовірно, саме і слухатиме **порт** на IP. І він передаватиме всю комунікацію процесам-працівникам.
|
||||
|
||||
Ці процеси-працівники виконуватимуть ваш застосунок, здійснюватимуть основні обчислення, щоб отримати **запит** і повернути **відповідь**, і завантажуватимуть усе, що ви зберігаєте в змінних у RAM.
|
||||
|
||||
<img src="/img/deployment/concepts/process-ram.drawio.svg">
|
||||
|
||||
Звісно, на тій самій машині, окрім вашого застосунку, зазвичай працюватимуть **інші процеси**.
|
||||
|
||||
Цікавий момент: відсоток **використання CPU** кожним процесом може сильно **варіюватися** з часом, тоді як **пам'ять (RAM)** зазвичай залишається більш-менш **стабільною**.
|
||||
|
||||
Якщо у вас API, що виконує порівняний обсяг обчислень щоразу і у вас багато клієнтів, тоді **використання CPU** також, ймовірно, буде *стабільним* (замість постійних швидких коливань).
|
||||
|
||||
### Приклади інструментів і стратегій реплікації { #examples-of-replication-tools-and-strategies }
|
||||
|
||||
Є кілька підходів для цього, і про конкретні стратегії я розповім у наступних розділах, наприклад, коли говоритимемо про Docker і контейнери.
|
||||
|
||||
Головне обмеження: має бути **єдиний** компонент, що обробляє **порт** на **публічній IP-адресі**. А тоді він має мати спосіб **передавати** комунікацію реплікованим **процесам/працівникам**.
|
||||
|
||||
Ось кілька можливих комбінацій і стратегій:
|
||||
|
||||
- **Uvicorn** з `--workers`
|
||||
- Один **менеджер процесів** Uvicorn слухатиме **IP** і **порт** та запускатиме **кілька процесів-працівників Uvicorn**.
|
||||
- **Kubernetes** та інші розподілені **системи контейнерів**
|
||||
- Щось на рівні **Kubernetes** слухатиме **IP** і **порт**. Реплікація відбуватиметься через **кілька контейнерів**, у кожному з яких запущено **один процес Uvicorn**.
|
||||
- **Хмарні сервіси**, що роблять це за вас
|
||||
- Хмарний сервіс, ймовірно, **забезпечить реплікацію за вас**. Він може дозволяти визначити **процес для запуску** або **образ контейнера** для використання; у будь-якому разі це, найімовірніше, буде **один процес Uvicorn**, а сервіс відповідатиме за його реплікацію.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Не хвилюйтеся, якщо деякі пункти про **контейнери**, Docker чи Kubernetes поки що не дуже зрозумілі.
|
||||
|
||||
Я розповім більше про образи контейнерів, Docker, Kubernetes тощо в майбутньому розділі: [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Попередні кроки перед стартом { #previous-steps-before-starting }
|
||||
|
||||
Є багато випадків, коли потрібно виконати деякі кроки **перед стартом** вашого застосунку.
|
||||
|
||||
Наприклад, ви можете захотіти запустити **міграції бази даних**.
|
||||
|
||||
Але найчастіше ці кроки потрібно виконувати лише **один раз**.
|
||||
|
||||
Отже, ви захочете мати **єдиний процес**, який виконає ці **попередні кроки** перед запуском застосунку.
|
||||
|
||||
І потрібно переконатися, що їх виконує саме один процес навіть якщо потім ви запускаєте **кілька процесів** (кілька працівників) для самого застосунку. Якщо ці кроки виконуватимуться **кількома процесами**, вони **подвоюватимуть** роботу, виконуючи її **паралельно**, і якщо кроки делікатні, як-от міграції бази даних, це може призвести до конфліктів.
|
||||
|
||||
Звісно, бувають випадки, коли немає проблеми запускати попередні кроки кілька разів - тоді все набагато простіше.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Також майте на увазі, що залежно від вашого налаштування інколи **попередні кроки взагалі не потрібні** перед запуском застосунку.
|
||||
|
||||
У такому разі про це можна не турбуватися. 🤷
|
||||
|
||||
///
|
||||
|
||||
### Приклади стратегій попередніх кроків { #examples-of-previous-steps-strategies }
|
||||
|
||||
Це **значною мірою залежить** від способу **розгортання вашої системи** і, ймовірно, буде пов'язано зі способом запуску програм, обробки перезапусків тощо.
|
||||
|
||||
Ось кілька можливих ідей:
|
||||
|
||||
- «Init Container» у Kubernetes, який виконується перед вашим контейнером застосунку
|
||||
- bash-скрипт, який виконує попередні кроки, а потім запускає ваш застосунок
|
||||
- Вам усе одно потрібен спосіб запускати/перезапускати цей bash-скрипт, виявляти помилки тощо.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Я наведу більш конкретні приклади для цього з контейнерами у майбутньому розділі: [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Використання ресурсів { #resource-utilization }
|
||||
|
||||
Ваш сервер(и) - це **ресурс**, який ви можете споживати/**використовувати** вашими програмами: час обчислень на CPU та доступну RAM.
|
||||
|
||||
Скільки системних ресурсів ви хочете споживати/використовувати? Легко подумати «небагато», але насправді ви, ймовірно, захочете споживати **настільки багато, наскільки можливо без краху**.
|
||||
|
||||
Якщо ви платите за 3 сервери, але використовуєте лише трохи їх RAM і CPU, ви, ймовірно, **марнуєте гроші** 💸 і, можливо, **електроенергію серверів** 🌎 тощо.
|
||||
|
||||
У такому разі може бути краще мати лише 2 сервери й використовувати більший відсоток їхніх ресурсів (CPU, пам'ять, диск, пропускну здатність мережі тощо).
|
||||
|
||||
З іншого боку, якщо у вас 2 сервери і ви використовуєте **100% їхнього CPU та RAM**, у певний момент якийсь процес попросить більше пам'яті, і сервер муситиме використати диск як «пам'ять» (що може бути у тисячі разів повільнішим) або навіть **впасти**. Або процесу знадобляться обчислення, і він чекатиме, доки CPU знову звільниться.
|
||||
|
||||
У цьому випадку краще додати **ще один сервер** і запустити частину процесів на ньому, щоб у всіх було **достатньо RAM та часу CPU**.
|
||||
|
||||
Також можлива ситуація, коли з певної причини ви отримаєте **стрибок** використання вашого API. Можливо, він став вірусним або його почали використовувати інші сервіси чи боти. І ви можете захотіти мати додаткові ресурси на випадок таких ситуацій.
|
||||
|
||||
Ви можете встановити **довільний цільовий** рівень, наприклад, **між 50% і 90%** використання ресурсів. Суть у тому, що це, ймовірно, головні речі, які ви захочете вимірювати й використовувати для налаштування розгортань.
|
||||
|
||||
Ви можете використати прості інструменти, як-от `htop`, щоб побачити використання CPU і RAM на сервері або кількість, спожиту кожним процесом. Або складніші засоби моніторингу, розподілені між серверами тощо.
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Тут ви прочитали про основні концепції, які, ймовірно, потрібно тримати в голові, вирішуючи, як розгортати ваш застосунок:
|
||||
|
||||
- Безпека - HTTPS
|
||||
- Запуск під час старту
|
||||
- Перезапуски
|
||||
- Реплікація (кількість запущених процесів)
|
||||
- Пам'ять
|
||||
- Попередні кроки перед стартом
|
||||
|
||||
Розуміння цих ідей і того, як їх застосовувати, має дати вам інтуїцію, необхідну для прийняття рішень під час конфігурування і тонкого налаштування ваших розгортань. 🤓
|
||||
|
||||
У наступних розділах я наведу більше конкретних прикладів можливих стратегій, якими ви можете скористатися. 🚀
|
||||
618
docs/uk/docs/deployment/docker.md
Normal file
618
docs/uk/docs/deployment/docker.md
Normal file
@@ -0,0 +1,618 @@
|
||||
# FastAPI у контейнерах - Docker { #fastapi-in-containers-docker }
|
||||
|
||||
Під час розгортання застосунків FastAPI поширений підхід - збирати образи контейнерів Linux. Зазвичай це робиться за допомогою <a href="https://www.docker.com/" class="external-link" target="_blank">Docker</a>. Потім ви можете розгорнути цей образ контейнера кількома різними способами.
|
||||
|
||||
Використання контейнерів Linux має кілька переваг, зокрема безпека, відтворюваність, простота та інші.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Поспішаєте і вже все це знаєте? Перейдіть до [`Dockerfile` нижче 👇](#build-a-docker-image-for-fastapi).
|
||||
|
||||
///
|
||||
|
||||
<details>
|
||||
<summary>Попередній перегляд Dockerfile 👀</summary>
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.14
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
COPY ./app /code/app
|
||||
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
|
||||
# Якщо запускаєте за представником на кшталт Nginx або Traefik, додайте --proxy-headers
|
||||
# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Що таке контейнер { #what-is-a-container }
|
||||
|
||||
Контейнери (переважно контейнери Linux) - це дуже легкий спосіб упакувати застосунки з усіма їхніми залежностями та потрібними файлами, ізолювавши їх від інших контейнерів (інших застосунків або компонентів) у тій самій системі.
|
||||
|
||||
Контейнери Linux працюють, використовуючи той самий ядро Linux, що й хост (машина, віртуальна машина, хмарний сервер тощо). Це означає, що вони дуже легкі (у порівнянні з повними віртуальними машинами, які емулюють цілу операційну систему).
|
||||
|
||||
Таким чином контейнери споживають мало ресурсів, приблизно як безпосередньо запущені процеси (віртуальна машина споживала б значно більше).
|
||||
|
||||
У контейнерів також є власні ізольовані процеси виконання (зазвичай лише один процес), файлові системи та мережі, що спрощує розгортання, безпеку, розробку тощо.
|
||||
|
||||
## Що таке образ контейнера { #what-is-a-container-image }
|
||||
|
||||
Контейнер запускається з образу контейнера.
|
||||
|
||||
Образ контейнера - це статична версія всіх файлів, змінних оточення та типова команда/програма, яка має бути присутня в контейнері. Тут «статична» означає, що образ контейнера не запущений, він не виконується, це лише упаковані файли та метадані.
|
||||
|
||||
На противагу «образу контейнера», що є збереженим статичним вмістом, «контейнер» зазвичай означає запущений екземпляр, те, що виконується.
|
||||
|
||||
Коли контейнер запущено (запущений з образу контейнера), він може створювати або змінювати файли, змінні оточення тощо. Ці зміни існуватимуть лише в цьому контейнері, але не збережуться в базовому образі контейнера (не будуть записані на диск).
|
||||
|
||||
Образ контейнера можна порівняти з файлом і вмістом програми, наприклад `python` і файлом `main.py`.
|
||||
|
||||
А сам контейнер (на відміну від образу) - це фактично запущений екземпляр образу, порівнянний із процесом. Насправді контейнер працює лише тоді, коли в ньому працює процес (і зазвичай це один процес). Контейнер зупиняється, коли в ньому не працює жоден процес.
|
||||
|
||||
## Образи контейнерів { #container-images }
|
||||
|
||||
Docker був одним з основних інструментів для створення та керування образами контейнерів і контейнерами.
|
||||
|
||||
Існує публічний <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> з готовими офіційними образами для багатьох інструментів, середовищ, баз даних і застосунків.
|
||||
|
||||
Наприклад, є офіційний <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">образ Python</a>.
|
||||
|
||||
І є багато інших образів для різних речей, як-от бази даних, наприклад для:
|
||||
|
||||
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
|
||||
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
|
||||
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
|
||||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a> тощо.
|
||||
|
||||
Використовуючи готовий образ контейнера, дуже легко поєднувати та використовувати різні інструменти. Наприклад, щоб випробувати нову базу даних. У більшості випадків ви можете використати офіційні образи та просто налаштувати їх змінними оточення.
|
||||
|
||||
Таким чином, у багатьох випадках ви зможете навчитися працювати з контейнерами і Docker та повторно використати ці знання з багатьма різними інструментами і компонентами.
|
||||
|
||||
Тобто ви запускатимете кілька контейнерів з різними речами, як-от базу даних, застосунок на Python, вебсервер із фронтендом на React, і з’єднаєте їх через внутрішню мережу.
|
||||
|
||||
Усі системи керування контейнерами (як Docker чи Kubernetes) мають ці мережеві можливості вбудовано.
|
||||
|
||||
## Контейнери і процеси { #containers-and-processes }
|
||||
|
||||
Образ контейнера зазвичай містить у своїх метаданих типову програму або команду, яку слід виконати під час запуску контейнера, і параметри для цієї програми. Дуже схоже на те, що ви б виконали в командному рядку.
|
||||
|
||||
Коли контейнер запускається, він виконає цю команду/програму (хоча ви можете перевизначити її і запустити іншу команду/програму).
|
||||
|
||||
Контейнер працює доти, доки працює головний процес (команда або програма).
|
||||
|
||||
Зазвичай контейнер має один процес, але також можливо запускати підпроцеси з головного процесу, і таким чином у вас може бути кілька процесів у тому самому контейнері.
|
||||
|
||||
Але неможливо мати запущений контейнер без принаймні одного запущеного процесу. Якщо головний процес зупиняється, контейнер зупиняється.
|
||||
|
||||
## Зібрати Docker-образ для FastAPI { #build-a-docker-image-for-fastapi }
|
||||
|
||||
Гаразд, зберімо щось зараз! 🚀
|
||||
|
||||
Я покажу вам, як зібрати образ Docker для FastAPI з нуля на основі офіційного образу Python.
|
||||
|
||||
Це те, що ви захочете робити у більшості випадків, наприклад:
|
||||
|
||||
* Використання Kubernetes або подібних інструментів
|
||||
* Під час запуску на Raspberry Pi
|
||||
* Використання хмарного сервісу, який запустить для вас образ контейнера тощо
|
||||
|
||||
### Вимоги до пакетів { #package-requirements }
|
||||
|
||||
Зазвичай ви маєте вимоги до пакетів для вашого застосунку в окремому файлі.
|
||||
|
||||
Це залежить переважно від інструменту, який ви використовуєте для встановлення цих вимог.
|
||||
|
||||
Найпоширеніший спосіб - мати файл `requirements.txt` з назвами пакетів і їхніми версіями, по одному на рядок.
|
||||
|
||||
Звісно, ви застосуєте ті самі ідеї з [Про версії FastAPI](versions.md){.internal-link target=_blank}, щоб задати діапазони версій.
|
||||
|
||||
Наприклад, ваш `requirements.txt` може виглядати так:
|
||||
|
||||
```
|
||||
fastapi[standard]>=0.113.0,<0.114.0
|
||||
pydantic>=2.7.0,<3.0.0
|
||||
```
|
||||
|
||||
І зазвичай ви встановлюватимете ці залежності пакетів через `pip`, наприклад:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
Successfully installed fastapi pydantic
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Існують інші формати та інструменти для визначення і встановлення залежностей пакетів.
|
||||
|
||||
///
|
||||
|
||||
### Створіть код **FastAPI** { #create-the-fastapi-code }
|
||||
|
||||
* Створіть директорію `app` і перейдіть у неї.
|
||||
* Створіть порожній файл `__init__.py`.
|
||||
* Створіть файл `main.py` з вмістом:
|
||||
|
||||
```Python
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
### Dockerfile { #dockerfile }
|
||||
|
||||
Тепер у тій самій директорії проєкту створіть файл `Dockerfile` з вмістом:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)!
|
||||
FROM python:3.14
|
||||
|
||||
# (2)!
|
||||
WORKDIR /code
|
||||
|
||||
# (3)!
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# (4)!
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (5)!
|
||||
COPY ./app /code/app
|
||||
|
||||
# (6)!
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Почніть з офіційного базового образу Python.
|
||||
|
||||
2. Встановіть поточну робочу директорію в `/code`.
|
||||
|
||||
Саме сюди ми помістимо файл `requirements.txt` і директорію `app`.
|
||||
|
||||
3. Скопіюйте файл з вимогами в директорію `/code`.
|
||||
|
||||
Спочатку скопіюйте лише файл з вимогами, а не решту коду.
|
||||
|
||||
Оскільки цей файл змінюється нечасто, Docker виявить це і використає кеш для цього кроку, що також увімкне кеш і для наступного кроку.
|
||||
|
||||
4. Встановіть залежності пакетів із файлу вимог.
|
||||
|
||||
Опція `--no-cache-dir` каже `pip` не зберігати завантажені пакети локально, адже це потрібно лише тоді, якщо `pip` буде запущено знову для встановлення тих самих пакетів, але в роботі з контейнерами це не так.
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
`--no-cache-dir` стосується лише `pip`, це не має відношення до Docker чи контейнерів.
|
||||
|
||||
///
|
||||
|
||||
Опція `--upgrade` каже `pip` оновити пакети, якщо вони вже встановлені.
|
||||
|
||||
Оскільки попередній крок копіювання файлу може бути виявлений кешем Docker, цей крок також використовуватиме кеш Docker, коли це можливо.
|
||||
|
||||
Використання кешу на цьому кроці збереже вам багато часу під час повторних збірок образу в розробці, замість того щоб завжди завантажувати і встановлювати всі залежності.
|
||||
|
||||
5. Скопіюйте директорію `./app` у директорію `/code`.
|
||||
|
||||
Оскільки тут увесь код, який змінюється найчастіше, кеш Docker не буде легко використаний для цього або будь-яких наступних кроків.
|
||||
|
||||
Тому важливо розмістити це ближче до кінця `Dockerfile`, щоб оптимізувати час збірки образу контейнера.
|
||||
|
||||
6. Встановіть команду для використання `fastapi run`, яка всередині використовує Uvicorn.
|
||||
|
||||
`CMD` приймає список строк, кожна з яких - це те, що ви б набирали в командному рядку, розділене пробілами.
|
||||
|
||||
Ця команда буде виконана з поточної робочої директорії, тієї самої `/code`, яку ви вказали вище через `WORKDIR /code`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Перегляньте, що робить кожен рядок, клацнувши на кожну номерну позначку в коді. 👆
|
||||
|
||||
///
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Обов’язково завжди використовуйте exec form інструкції `CMD`, як пояснено нижче.
|
||||
|
||||
///
|
||||
|
||||
#### Використовуйте `CMD` - exec form { #use-cmd-exec-form }
|
||||
|
||||
Інструкцію Docker <a href="https://docs.docker.com/reference/dockerfile/#cmd" class="external-link" target="_blank">`CMD`</a> можна записати у двох формах:
|
||||
|
||||
✅ Exec form:
|
||||
|
||||
```Dockerfile
|
||||
# ✅ Робіть так
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
⛔️ Shell form:
|
||||
|
||||
```Dockerfile
|
||||
# ⛔️ Не робіть так
|
||||
CMD fastapi run app/main.py --port 80
|
||||
```
|
||||
|
||||
Обов’язково завжди використовуйте exec form, щоб FastAPI міг коректно завершувати роботу та щоб були викликані [події тривалості життя](../advanced/events.md){.internal-link target=_blank}.
|
||||
|
||||
Докладніше про це можна прочитати в <a href="https://docs.docker.com/reference/dockerfile/#shell-and-exec-form" class="external-link" target="_blank">документації Docker про shell та exec form</a>.
|
||||
|
||||
Це може бути особливо помітно при використанні `docker compose`. Див. розділ FAQ Docker Compose для технічних деталей: <a href="https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop" class="external-link" target="_blank">Чому мої сервіси потребують 10 секунд, щоб пересотворитися або зупинитися?</a>.
|
||||
|
||||
#### Структура директорій { #directory-structure }
|
||||
|
||||
Зараз у вас має бути така структура директорій:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ └── main.py
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
#### За представником з термінацією TLS { #behind-a-tls-termination-proxy }
|
||||
|
||||
Якщо ви запускаєте контейнер за представником з термінацією TLS (балансувальником навантаження), наприклад Nginx або Traefik, додайте опцію `--proxy-headers`. Це скаже Uvicorn (через CLI FastAPI) довіряти заголовкам, що надсилаються цим представником, які вказують, що застосунок працює за HTTPS тощо.
|
||||
|
||||
```Dockerfile
|
||||
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]
|
||||
```
|
||||
|
||||
#### Кеш Docker { #docker-cache }
|
||||
|
||||
У цьому `Dockerfile` є важливий трюк: спочатку ми копіюємо лише файл із залежностями, а не решту коду. Ось чому.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
```
|
||||
|
||||
Docker та інші інструменти збирають ці образи контейнерів інкрементально, додаючи один шар поверх іншого, починаючи з верхньої частини `Dockerfile` і додаючи будь-які файли, створені кожною інструкцією в `Dockerfile`.
|
||||
|
||||
Docker та подібні інструменти також використовують внутрішній кеш під час збірки образу. Якщо файл не змінювався з моменту останньої збірки, тоді він повторно використає той самий шар, створений востаннє, замість копіювання файлу знову та створення нового шару з нуля.
|
||||
|
||||
Просте уникнення копіювання файлів не обов’язково суттєво покращує ситуацію, але оскільки для цього кроку використано кеш, він може використати кеш і для наступного кроку. Наприклад, він може використати кеш для інструкції, яка встановлює залежності:
|
||||
|
||||
```Dockerfile
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
```
|
||||
|
||||
Файл із вимогами до пакетів змінюватиметься нечасто. Отже, копіюючи лише цей файл, Docker зможе використати кеш для цього кроку.
|
||||
|
||||
А потім Docker зможе використати кеш і для наступного кроку, який завантажує та встановлює ці залежності. І саме тут ми заощаджуємо багато часу. ✨ ...і уникаємо нудного очікування. 😪😆
|
||||
|
||||
Завантаження і встановлення залежностей пакетів може займати хвилини, але використання кешу займе максимум секунди.
|
||||
|
||||
І оскільки ви збиратимете образ контейнера знову і знову під час розробки, щоб перевіряти, що зміни у вашому коді працюють, це заощадить багато накопиченого часу.
|
||||
|
||||
Потім, ближче до кінця `Dockerfile`, ми копіюємо весь код. Оскільки це те, що змінюється найчастіше, ми розміщуємо це ближче до кінця, адже майже завжди все після цього кроку не зможе використати кеш.
|
||||
|
||||
```Dockerfile
|
||||
COPY ./app /code/app
|
||||
```
|
||||
|
||||
### Зберіть Docker-образ { #build-the-docker-image }
|
||||
|
||||
Тепер, коли всі файли на місці, зберімо образ контейнера.
|
||||
|
||||
* Перейдіть у директорію проєкту (де ваш `Dockerfile`, який містить директорію `app`).
|
||||
* Зберіть ваш образ FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ docker build -t myimage .
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу на `.` в кінці. Це еквівалент `./`. Воно каже Docker, яку директорію використовувати для збірки образу контейнера.
|
||||
|
||||
У цьому випадку - це поточна директорія (`.`).
|
||||
|
||||
///
|
||||
|
||||
### Запустіть Docker-контейнер { #start-the-docker-container }
|
||||
|
||||
* Запустіть контейнер на основі вашого образу:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ docker run -d --name mycontainer -p 80:80 myimage
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Перевірте { #check-it }
|
||||
|
||||
Ви маєте змогу перевірити це за URL вашого Docker-контейнера, наприклад: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> або <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (або еквівалент, використовуючи ваш Docker-хост).
|
||||
|
||||
Ви побачите щось таке:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
## Інтерактивна документація API { #interactive-api-docs }
|
||||
|
||||
Тепер ви можете перейти на <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> або <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (або еквівалент, використовуючи ваш Docker-хост).
|
||||
|
||||
Ви побачите автоматичну інтерактивну документацію API (надається <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
|
||||
|
||||

|
||||
|
||||
## Альтернативна документація API { #alternative-api-docs }
|
||||
|
||||
Також ви можете перейти на <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> або <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (або еквівалент, використовуючи ваш Docker-хост).
|
||||
|
||||
Ви побачите альтернативну автоматичну документацію (надається <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
|
||||
|
||||

|
||||
|
||||
## Збірка Docker-образу з FastAPI в одному файлі { #build-a-docker-image-with-a-single-file-fastapi }
|
||||
|
||||
Якщо ваш FastAPI - це один файл, наприклад `main.py` без директорії `./app`, структура файлів може виглядати так:
|
||||
|
||||
```
|
||||
.
|
||||
├── Dockerfile
|
||||
├── main.py
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
Тоді вам потрібно лише змінити відповідні шляхи для копіювання файлу всередині `Dockerfile`:
|
||||
|
||||
```{ .dockerfile .annotate hl_lines="10 13" }
|
||||
FROM python:3.14
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (1)!
|
||||
COPY ./main.py /code/
|
||||
|
||||
# (2)!
|
||||
CMD ["fastapi", "run", "main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
1. Скопіюйте файл `main.py` безпосередньо у директорію `/code` (без будь-якої директорії `./app`).
|
||||
|
||||
2. Використовуйте `fastapi run`, щоб обслуговувати ваш застосунок з одного файлу `main.py`.
|
||||
|
||||
Коли ви передаєте файл у `fastapi run`, воно автоматично визначить, що це один файл, а не частина пакета, і знатиме, як імпортувати його та запустити ваш застосунок FastAPI. 😎
|
||||
|
||||
## Концепції розгортання { #deployment-concepts }
|
||||
|
||||
Поговорімо знову про деякі з тих самих [Концепцій розгортання](concepts.md){.internal-link target=_blank} у термінах контейнерів.
|
||||
|
||||
Контейнери - це переважно інструмент для спрощення процесу збирання та розгортання застосунку, але вони не нав’язують конкретний підхід до обробки цих концепцій розгортання, і існує кілька можливих стратегій.
|
||||
|
||||
Гарна новина полягає в тому, що для кожної стратегії є спосіб покрити всі концепції розгортання. 🎉
|
||||
|
||||
Розгляньмо ці концепції розгортання в контексті контейнерів:
|
||||
|
||||
* HTTPS
|
||||
* Автозапуск
|
||||
* Перезапуски
|
||||
* Реплікація (кількість запущених процесів)
|
||||
* Пам’ять
|
||||
* Попередні кроки перед запуском
|
||||
|
||||
## HTTPS { #https }
|
||||
|
||||
Якщо зосередитись лише на образі контейнера для застосунку FastAPI (а згодом на запущеному контейнері), HTTPS зазвичай обробляється зовнішнім іншим інструментом.
|
||||
|
||||
Це може бути інший контейнер, наприклад з <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>, що обробляє HTTPS і автоматичне отримання сертифікатів.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Traefik має інтеграції з Docker, Kubernetes та іншими, тож налаштувати і сконфігурувати HTTPS для ваших контейнерів з ним дуже просто.
|
||||
|
||||
///
|
||||
|
||||
Альтернативно, HTTPS може оброблятись хмарним провайдером як один з їхніх сервісів (при цьому застосунок усе ще працює в контейнері).
|
||||
|
||||
## Автозапуск і перезапуски { #running-on-startup-and-restarts }
|
||||
|
||||
Зазвичай інший інструмент відповідає за запуск і виконання вашого контейнера.
|
||||
|
||||
Це може бути безпосередньо Docker, Docker Compose, Kubernetes, хмарний сервіс тощо.
|
||||
|
||||
У більшості (або всіх) випадків є проста опція, щоб увімкнути запуск контейнера при старті системи та перезапуски у разі збоїв. Наприклад, у Docker це опція командного рядка `--restart`.
|
||||
|
||||
Без використання контейнерів змусити застосунки запускатися при старті системи та з перезапусками може бути клопітно і складно. Але під час роботи з контейнерами у більшості випадків ця функціональність вбудована за замовчуванням. ✨
|
||||
|
||||
## Реплікація - кількість процесів { #replication-number-of-processes }
|
||||
|
||||
Якщо у вас є <dfn title="Група машин, налаштованих бути з'єднаними та працювати разом певним чином.">кластер</dfn> машин із Kubernetes, Docker Swarm Mode, Nomad або іншою подібною складною системою для керування розподіленими контейнерами на кількох машинах, тоді ви, ймовірно, захочете обробляти реплікацію на рівні кластера замість використання менеджера процесів (як-от Uvicorn з працівниками) у кожному контейнері.
|
||||
|
||||
Одна з таких розподілених систем керування контейнерами, як-от Kubernetes, зазвичай має інтегровані способи обробляти реплікацію контейнерів, підтримуючи водночас балансування навантаження для вхідних запитів. Усе це - на рівні кластера.
|
||||
|
||||
У таких випадках ви, ймовірно, захочете зібрати Docker-образ з нуля, як [пояснено вище](#dockerfile), встановивши ваші залежності і запустивши один процес Uvicorn замість використання кількох працівників Uvicorn.
|
||||
|
||||
### Балансувальник навантаження { #load-balancer }
|
||||
|
||||
При використанні контейнерів зазвичай є якийсь компонент, що слухає на головному порту. Це може бути інший контейнер, який також є представником з термінацією TLS для обробки HTTPS, або подібний інструмент.
|
||||
|
||||
Оскільки цей компонент приймає навантаження запитів і розподіляє його між працівниками (сподіваємось) збалансовано, його також часто називають балансувальником навантаження.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Той самий компонент представника з термінацією TLS, що використовується для HTTPS, швидше за все, також буде балансувальником навантаження.
|
||||
|
||||
///
|
||||
|
||||
І під час роботи з контейнерами та сама система, яку ви використовуєте для їх запуску і керування ними, вже матиме внутрішні інструменти для передавання мережевої комунікації (наприклад, HTTP-запитів) від цього балансувальника навантаження (який також може бути представником з термінацією TLS) до контейнерів із вашим застосунком.
|
||||
|
||||
### Один балансувальник навантаження - кілька контейнерів-працівників { #one-load-balancer-multiple-worker-containers }
|
||||
|
||||
Під час роботи з Kubernetes або подібними розподіленими системами керування контейнерами використання їхніх внутрішніх мережевих механізмів дозволяє єдиному балансувальнику навантаження, що слухає на головному порту, передавати комунікацію (запити) до кількох контейнерів, у яких запущено ваш застосунок.
|
||||
|
||||
Кожен з цих контейнерів із вашим застосунком зазвичай має лише один процес (наприклад, процес Uvicorn, що запускає ваш застосунок FastAPI). Усі вони будуть ідентичними контейнерами, які запускають те саме, але кожен зі своїм процесом, пам’яттю тощо. Таким чином ви використаєте переваги паралелізму на різних ядрах процесора або навіть на різних машинах.
|
||||
|
||||
А розподілена система контейнерів із балансувальником навантаження розподілятиме запити між кожним із контейнерів із вашим застосунком по черзі. Тож кожен запит може оброблятися одним із кількох реплікованих контейнерів, що запускають ваш застосунок.
|
||||
|
||||
І зазвичай цей балансувальник навантаження зможе обробляти запити, які йдуть до інших застосунків у вашому кластері (наприклад, до іншого домену або під іншим префіксом шляху URL), і передаватиме комунікацію до відповідних контейнерів для того іншого застосунку, що працює у вашому кластері.
|
||||
|
||||
### Один процес на контейнер { #one-process-per-container }
|
||||
|
||||
У такому сценарії ви, ймовірно, захочете мати один (Uvicorn) процес на контейнер, адже ви вже обробляєте реплікацію на рівні кластера.
|
||||
|
||||
Тобто в цьому випадку ви не захочете мати кількох працівників у контейнері, наприклад через опцію командного рядка `--workers`. Ви захочете мати лише один процес Uvicorn на контейнер (але, ймовірно, кілька контейнерів).
|
||||
|
||||
Наявність іншого менеджера процесів всередині контейнера (як це було б із кількома працівниками) лише додасть зайвої складності, яку, найімовірніше, ви вже вирішуєте на рівні кластера.
|
||||
|
||||
### Контейнери з кількома процесами та особливі випадки { #containers-with-multiple-processes-and-special-cases }
|
||||
|
||||
Звісно, є особливі випадки, коли ви можете захотіти мати контейнер із кількома процесами-працівниками Uvicorn всередині.
|
||||
|
||||
У таких випадках ви можете використати опцію командного рядка `--workers`, щоб задати кількість працівників, яких потрібно запустити:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
FROM python:3.14
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
COPY ./app /code/app
|
||||
|
||||
# (1)!
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"]
|
||||
```
|
||||
|
||||
1. Тут ми використовуємо опцію командного рядка `--workers`, щоб встановити кількість працівників 4.
|
||||
|
||||
Ось кілька прикладів, коли це може мати сенс:
|
||||
|
||||
#### Простий застосунок { #a-simple-app }
|
||||
|
||||
Ви можете захотіти менеджер процесів у контейнері, якщо ваш застосунок достатньо простий, щоб запускати його на одному сервері, а не на кластері.
|
||||
|
||||
#### Docker Compose { #docker-compose }
|
||||
|
||||
Ви можете розгортати на одному сервері (не в кластері) за допомогою Docker Compose, тож у вас не буде простого способу керувати реплікацією контейнерів (у Docker Compose), зберігаючи спільну мережу та балансування навантаження.
|
||||
|
||||
Тоді ви можете захотіти мати один контейнер із менеджером процесів, що запускає кілька процесів-працівників всередині.
|
||||
|
||||
---
|
||||
|
||||
Головна думка: це не правила, викарбувані в камені, яких потрібно сліпо дотримуватися. Ви можете використати ці ідеї, щоб оцінити власний кейс і вирішити, який підхід найкращий для вашої системи, розглядаючи, як керувати такими концепціями:
|
||||
|
||||
* Безпека - HTTPS
|
||||
* Автозапуск
|
||||
* Перезапуски
|
||||
* Реплікація (кількість запущених процесів)
|
||||
* Пам’ять
|
||||
* Попередні кроки перед запуском
|
||||
|
||||
## Пам’ять { #memory }
|
||||
|
||||
Якщо ви запускаєте один процес на контейнер, ви матимете більш-менш чітко визначений, стабільний і обмежений обсяг пам’яті, що споживається кожним із цих контейнерів (їх може бути більше одного, якщо вони репліковані).
|
||||
|
||||
Потім ви можете встановити ті самі ліміти та вимоги до пам’яті у ваших конфігураціях для системи керування контейнерами (наприклад, у Kubernetes). Таким чином вона зможе реплікувати контейнери на доступних машинах, враховуючи обсяг пам’яті, потрібний їм, і обсяг доступної пам’яті на машинах у кластері.
|
||||
|
||||
Якщо ваш застосунок простий, імовірно, це не буде проблемою, і вам може не знадобитися задавати жорсткі ліміти пам’яті. Але якщо ви використовуєте багато пам’яті (наприклад, із моделями машинного навчання), вам слід перевірити, скільки пам’яті ви споживаєте, і відкоригувати кількість контейнерів, що запускаються на кожній машині (і, можливо, додати більше машин у ваш кластер).
|
||||
|
||||
Якщо ви запускаєте кілька процесів на контейнер, вам потрібно переконатися, що кількість запущених процесів не споживає більше пам’яті, ніж доступно.
|
||||
|
||||
## Попередні кроки перед запуском і контейнери { #previous-steps-before-starting-and-containers }
|
||||
|
||||
Якщо ви використовуєте контейнери (наприклад, Docker, Kubernetes), то є два основні підходи.
|
||||
|
||||
### Кілька контейнерів { #multiple-containers }
|
||||
|
||||
Якщо у вас кілька контейнерів, імовірно кожен запускає один процес (наприклад, у кластері Kubernetes), тоді ви, ймовірно, захочете мати окремий контейнер, який виконає попередні кроки в одному контейнері, запустивши один процес, перед запуском реплікованих контейнерів-працівників.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Якщо ви використовуєте Kubernetes, це, ймовірно, буде <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>.
|
||||
|
||||
///
|
||||
|
||||
Якщо у вашому випадку немає проблеми запускати ці попередні кроки кілька разів паралельно (наприклад, якщо ви не виконуєте міграції бази даних, а лише перевіряєте, чи база вже готова), тоді ви також можете просто помістити їх у кожен контейнер безпосередньо перед запуском головного процесу.
|
||||
|
||||
### Один контейнер { #single-container }
|
||||
|
||||
Якщо у вас просте налаштування з одним контейнером, який потім запускає кілька процесів-працівників (або теж лише один процес), тоді ви можете виконати ці попередні кроки в тому ж контейнері безпосередньо перед запуском процесу із застосунком.
|
||||
|
||||
### Базовий образ Docker { #base-docker-image }
|
||||
|
||||
Колись існував офіційний образ Docker для FastAPI: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>. Але зараз він застарілий. ⛔️
|
||||
|
||||
Ймовірно, вам не слід використовувати цей базовий образ Docker (або будь-який інший подібний).
|
||||
|
||||
Якщо ви використовуєте Kubernetes (або інші) і вже налаштовуєте реплікацію на рівні кластера з кількома контейнерами. У таких випадках краще зібрати образ з нуля, як описано вище: [Зібрати Docker-образ для FastAPI](#build-a-docker-image-for-fastapi).
|
||||
|
||||
А якщо вам потрібно мати кілька працівників, ви можете просто використати опцію командного рядка `--workers`.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Цей образ Docker було створено тоді, коли Uvicorn не підтримував керування та перезапуск «мертвих» працівників, тому потрібно було використовувати Gunicorn з Uvicorn, що додавало чимало складності лише для того, щоб Gunicorn керував та перезапускав процеси-працівники Uvicorn.
|
||||
|
||||
Але тепер, коли Uvicorn (і команда `fastapi`) підтримують використання `--workers`, немає причин використовувати базовий образ Docker замість того, щоб зібрати власний (це приблизно та сама кількість коду 😅).
|
||||
|
||||
///
|
||||
|
||||
## Розгорнути образ контейнера { #deploy-the-container-image }
|
||||
|
||||
Після отримання образу контейнера (Docker) є кілька способів його розгорнути.
|
||||
|
||||
Наприклад:
|
||||
|
||||
* З Docker Compose на одному сервері
|
||||
* З кластером Kubernetes
|
||||
* З кластером Docker Swarm Mode
|
||||
* З іншим інструментом, як-от Nomad
|
||||
* З хмарним сервісом, який бере ваш образ контейнера і розгортає його
|
||||
|
||||
## Образ Docker з `uv` { #docker-image-with-uv }
|
||||
|
||||
Якщо ви використовуєте <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a> для встановлення та керування вашим проєктом, ви можете скористатися їхнім <a href="https://docs.astral.sh/uv/guides/integration/docker/" class="external-link" target="_blank">посібником Docker для uv</a>.
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Використовуючи системи контейнерів (наприклад, з Docker і Kubernetes), досить просто обробляти всі концепції розгортання:
|
||||
|
||||
* HTTPS
|
||||
* Автозапуск
|
||||
* Перезапуски
|
||||
* Реплікація (кількість запущених процесів)
|
||||
* Пам’ять
|
||||
* Попередні кроки перед запуском
|
||||
|
||||
У більшості випадків ви, ймовірно, не захочете використовувати будь-який базовий образ, а натомість зібрати образ контейнера з нуля на основі офіційного образу Python для Docker.
|
||||
|
||||
Дотримуючись порядку інструкцій у `Dockerfile` і використовуючи кеш Docker, ви можете мінімізувати час збірки, щоб максимізувати свою продуктивність (і уникнути нудьги). 😎
|
||||
65
docs/uk/docs/deployment/fastapicloud.md
Normal file
65
docs/uk/docs/deployment/fastapicloud.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# FastAPI Cloud { #fastapi-cloud }
|
||||
|
||||
Ви можете розгорнути свій застосунок FastAPI на <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a> однією командою, приєднуйтесь до списку очікування, якщо ще ні. 🚀
|
||||
|
||||
## Вхід { #login }
|
||||
|
||||
Переконайтеся, що у вас вже є обліковий запис **FastAPI Cloud** (ми запросили вас зі списку очікування 😉).
|
||||
|
||||
Потім увійдіть:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi login
|
||||
|
||||
You are logged in to FastAPI Cloud 🚀
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Розгортання { #deploy }
|
||||
|
||||
Тепер розгорніть свій застосунок однією командою:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi deploy
|
||||
|
||||
Deploying to FastAPI Cloud...
|
||||
|
||||
✅ Deployment successful!
|
||||
|
||||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Ось і все! Тепер ви можете отримати доступ до свого застосунку за цим URL. ✨
|
||||
|
||||
## Про FastAPI Cloud { #about-fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** створено тим самим автором і командою, що стоїть за **FastAPI**.
|
||||
|
||||
Він спрощує процес **створення**, **розгортання** та **доступу** до API з мінімальними зусиллями.
|
||||
|
||||
Він переносить той самий **досвід розробника**, що і під час створення застосунків з FastAPI, на їх **розгортання** у хмарі. 🎉
|
||||
|
||||
Він також подбає про більшість речей, які вам потрібні під час розгортання застосунку, як-от:
|
||||
|
||||
- HTTPS
|
||||
- реплікація з автомасштабуванням на основі запитів
|
||||
- тощо
|
||||
|
||||
FastAPI Cloud - основний спонсор і джерело фінансування для відкритих проєктів *«FastAPI та друзі»*. ✨
|
||||
|
||||
## Розгортання в інших хмарних провайдерів { #deploy-to-other-cloud-providers }
|
||||
|
||||
FastAPI є відкритим кодом і базується на стандартах. Ви можете розгортати застосунки FastAPI в будь-якого хмарного провайдера за вашим вибором.
|
||||
|
||||
Дотримуйтесь інструкцій вашого хмарного провайдера, щоб розгортати застосунки FastAPI у них. 🤓
|
||||
|
||||
## Розгортання на вашому сервері { #deploy-your-own-server }
|
||||
|
||||
Пізніше в цьому розділі **Розгортання** я також навчу вас усім деталям, щоб ви розуміли, що відбувається, що потрібно зробити і як розгортати застосунки FastAPI самостійно, зокрема на власних серверах. 🤓
|
||||
231
docs/uk/docs/deployment/https.md
Normal file
231
docs/uk/docs/deployment/https.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Про HTTPS { #about-https }
|
||||
|
||||
Легко припустити, що HTTPS - це щось, що просто «увімкнено» або ні.
|
||||
|
||||
Але все значно складніше.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви поспішаєте або це неважливо для вас, переходьте до наступних розділів для покрокових інструкцій із налаштування всього різними техніками.
|
||||
|
||||
///
|
||||
|
||||
Щоб вивчити основи HTTPS з точки зору споживача, перегляньте <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>.
|
||||
|
||||
Тепер, з точки зору розробника, ось кілька речей, які варто пам'ятати, розмірковуючи про HTTPS:
|
||||
|
||||
* Для HTTPS сервер має мати «сертифікати», видані третьою стороною.
|
||||
* Насправді ці сертифікати «отримуються» у третьої сторони, а не «генеруються».
|
||||
* Сертифікати мають строк дії.
|
||||
* Їхній строк дії спливає.
|
||||
* І тоді їх потрібно поновити, знову отримавши у третьої сторони.
|
||||
* Шифрування з'єднання відбувається на рівні TCP.
|
||||
* Це один шар нижче від HTTP.
|
||||
* Тож обробка сертифіката та шифрування виконується до HTTP.
|
||||
* TCP не знає про «домени». Лише про IP-адреси.
|
||||
* Інформація про конкретний домен, який запитується, міститься в даних HTTP.
|
||||
* Сертифікати HTTPS «засвідчують» певний домен, але протокол і шифрування працюють на рівні TCP, до того як відомо, з яким доменом маємо справу.
|
||||
* Типово це означало б, що на одну IP-адресу можна мати лише один сертифікат HTTPS.
|
||||
* Неважливо, наскільки великий ваш сервер або наскільки малий кожен застосунок на ньому.
|
||||
* Однак для цього є рішення.
|
||||
* Є розширення протоколу TLS (який обробляє шифрування на рівні TCP, до HTTP), що називається <a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Server Name Indication - Ідентифікація імені сервера">SNI</abbr></a>.
|
||||
* Це розширення SNI дозволяє одному серверу (з однією IP-адресою) мати кілька сертифікатів HTTPS і обслуговувати кілька доменів/застосунків по HTTPS.
|
||||
* Щоб це працювало, один-єдиний компонент (програма), що працює на сервері та слухає публічну IP-адресу, має мати всі сертифікати HTTPS на сервері.
|
||||
* Після отримання захищеного з'єднання протокол обміну все ще HTTP.
|
||||
* Вміст зашифровано, хоча він надсилається протоколом HTTP.
|
||||
|
||||
Поширена практика мати одну програму/HTTP-сервер, що працює на сервері (машині, хості тощо) і керує всіма частинами HTTPS: приймає зашифровані HTTPS-запити, надсилає розшифровані HTTP-запити до фактичного HTTP-застосунку, що працює на тому ж сервері (у нашому випадку застосунок FastAPI), отримує HTTP-відповідь від застосунку, шифрує її за допомогою відповідного сертифіката HTTPS і надсилає її назад клієнту через HTTPS. Такий сервер часто називають <a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank"><strong>TLS Termination Proxy</strong></a>.
|
||||
|
||||
Деякі варіанти, які ви можете використати як TLS Termination Proxy:
|
||||
|
||||
* Traefik (також може виконувати поновлення сертифікатів)
|
||||
* Caddy (також може виконувати поновлення сертифікатів)
|
||||
* Nginx
|
||||
* HAProxy
|
||||
|
||||
## Let's Encrypt { #lets-encrypt }
|
||||
|
||||
До Let's Encrypt ці сертифікати HTTPS продавалися довіреними третіми сторонами.
|
||||
|
||||
Процес отримання одного з таких сертифікатів був громіздким, вимагав чимало паперової роботи, а самі сертифікати були доволі дорогими.
|
||||
|
||||
Але потім з'явився проєкт <a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>.
|
||||
|
||||
Це проєкт Linux Foundation. Він надає сертифікати HTTPS безкоштовно, в автоматизований спосіб. Ці сертифікати використовують усі стандартні криптографічні механізми безпеки і є короткостроковими (близько 3 місяців), тож безпека насправді краща завдяки зменшеній тривалості життя.
|
||||
|
||||
Домени безпечно перевіряються, а сертифікати генеруються автоматично. Це також дозволяє автоматизувати поновлення цих сертифікатів.
|
||||
|
||||
Ідея полягає в автоматизації отримання та поновлення цих сертифікатів, щоб ви могли мати безпечний HTTPS безкоштовно та назавжди.
|
||||
|
||||
## HTTPS для розробників { #https-for-developers }
|
||||
|
||||
Ось приклад того, як може виглядати HTTPS API, крок за кроком, зосереджуючись переважно на ідеях, важливих для розробників.
|
||||
|
||||
### Доменне ім'я { #domain-name }
|
||||
|
||||
Ймовірно, все почнеться з того, що ви придбаєте якесь доменне ім'я. Потім ви налаштуєте його на сервері DNS (можливо, у вашого ж хмарного провайдера).
|
||||
|
||||
Ви, скоріш за все, отримаєте хмарний сервер (віртуальну машину) або щось подібне, і він матиме <dfn title="З часом не змінюється. Не динамічний.">фіксовану</dfn> публічну IP-адресу.
|
||||
|
||||
На сервері(ах) DNS ви налаштуєте запис («`A record`»), щоб спрямувати ваш домен на публічну IP-адресу вашого сервера.
|
||||
|
||||
Ймовірно, ви зробите це лише один раз, уперше, коли все налаштовуватимете.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Частина про доменне ім'я значно раніше за HTTPS, але оскільки все залежить від домену та IP-адреси, варто згадати це тут.
|
||||
|
||||
///
|
||||
|
||||
### DNS { #dns }
|
||||
|
||||
Тепер зосередьмося на всіх власне частинах HTTPS.
|
||||
|
||||
Спочатку браузер звернеться до серверів DNS, щоб дізнатися, яка IP-адреса для домену, у цьому випадку `someapp.example.com`.
|
||||
|
||||
Сервери DNS повідомлять браузеру використати конкретну IP-адресу. Це буде публічна IP-адреса, яку використовує ваш сервер і яку ви налаштували на серверах DNS.
|
||||
|
||||
<img src="/img/deployment/https/https01.drawio.svg">
|
||||
|
||||
### Початок TLS рукостискання { #tls-handshake-start }
|
||||
|
||||
Потім браузер зв'яжеться з цією IP-адресою на порту 443 (порт HTTPS).
|
||||
|
||||
Перша частина комунікації - це просто встановлення з'єднання між клієнтом і сервером та узгодження криптографічних ключів тощо.
|
||||
|
||||
<img src="/img/deployment/https/https02.drawio.svg">
|
||||
|
||||
Ця взаємодія між клієнтом і сервером для встановлення з'єднання TLS називається TLS рукостисканням.
|
||||
|
||||
### TLS із розширенням SNI { #tls-with-sni-extension }
|
||||
|
||||
Лише один процес на сервері може слухати конкретний порт на конкретній IP-адресі. Інші процеси можуть слухати інші порти на тій самій IP-адресі, але лише один для кожної комбінації IP-адреси та порту.
|
||||
|
||||
TLS (HTTPS) за замовчуванням використовує конкретний порт `443`. Отже, це порт, який нам потрібен.
|
||||
|
||||
Оскільки лише один процес може слухати цей порт, процесом, що робитиме це, буде TLS Termination Proxy.
|
||||
|
||||
TLS Termination Proxy матиме доступ до одного або кількох сертифікатів TLS (сертифікатів HTTPS).
|
||||
|
||||
Використовуючи обговорене вище розширення SNI, TLS Termination Proxy перевірить, який із наявних сертифікатів TLS (HTTPS) слід використати для цього з'єднання, обравши той, що відповідає домену, очікуваному клієнтом.
|
||||
|
||||
У цьому випадку він використає сертифікат для `someapp.example.com`.
|
||||
|
||||
<img src="/img/deployment/https/https03.drawio.svg">
|
||||
|
||||
Клієнт уже довіряє сутності, яка видала цей сертифікат TLS (у цьому випадку Let's Encrypt, але про це згодом), тож він може перевірити, що сертифікат дійсний.
|
||||
|
||||
Потім, використовуючи сертифікат, клієнт і TLS Termination Proxy узгоджують, як шифрувати решту TCP-комунікації. На цьому частина TLS рукостискання завершується.
|
||||
|
||||
Після цього клієнт і сервер мають зашифроване TCP-з'єднання - саме це надає TLS. І тоді вони можуть використати це з'єднання, щоб почати власне HTTP-комунікацію.
|
||||
|
||||
І це і є HTTPS: це звичайний HTTP усередині захищеного TLS-з'єднання замість чистого (незашифрованого) TCP-з'єднання.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу, що шифрування комунікації відбувається на рівні TCP, а не на рівні HTTP.
|
||||
|
||||
///
|
||||
|
||||
### HTTPS-запит { #https-request }
|
||||
|
||||
Тепер, коли клієнт і сервер (конкретно браузер і TLS Termination Proxy) мають зашифроване TCP-з'єднання, вони можуть почати HTTP-комунікацію.
|
||||
|
||||
Отже, клієнт надсилає HTTPS-запит. Це просто HTTP-запит через зашифроване TLS-з'єднання.
|
||||
|
||||
<img src="/img/deployment/https/https04.drawio.svg">
|
||||
|
||||
### Розшифрування запиту { #decrypt-the-request }
|
||||
|
||||
TLS Termination Proxy використає узгоджене шифрування, щоб розшифрувати запит, і передасть звичайний (розшифрований) HTTP-запит процесу, що запускає застосунок (наприклад, процесу з Uvicorn, який запускає застосунок FastAPI).
|
||||
|
||||
<img src="/img/deployment/https/https05.drawio.svg">
|
||||
|
||||
### HTTP-відповідь { #http-response }
|
||||
|
||||
Застосунок обробить запит і надішле звичайну (незашифровану) HTTP-відповідь TLS Termination Proxy.
|
||||
|
||||
<img src="/img/deployment/https/https06.drawio.svg">
|
||||
|
||||
### HTTPS-відповідь { #https-response }
|
||||
|
||||
Потім TLS Termination Proxy зашифрує відповідь, використовуючи попередньо узгоджену криптографію (що почалася із сертифіката для `someapp.example.com`), і надішле її назад у браузер.
|
||||
|
||||
Далі браузер перевірить, що відповідь дійсна й зашифрована правильним криптографічним ключем тощо. Потім він розшифрує відповідь і обробить її.
|
||||
|
||||
<img src="/img/deployment/https/https07.drawio.svg">
|
||||
|
||||
Клієнт (браузер) знатиме, що відповідь надходить від правильного сервера, тому що використовується узгоджена раніше криптографія з використанням сертифіката HTTPS.
|
||||
|
||||
### Кілька застосунків { #multiple-applications }
|
||||
|
||||
На тому самому сервері (або серверах) може бути кілька застосунків, наприклад інші програми API або база даних.
|
||||
|
||||
Лише один процес може обробляти конкретну IP-адресу і порт (TLS Termination Proxy у нашому прикладі), але інші застосунки/процеси також можуть працювати на сервері(ах), доки вони не намагаються використати ту саму комбінацію публічної IP-адреси й порту.
|
||||
|
||||
<img src="/img/deployment/https/https08.drawio.svg">
|
||||
|
||||
Таким чином, TLS Termination Proxy може обробляти HTTPS і сертифікати для кількох доменів, для кількох застосунків, а потім передавати запити до відповідного застосунку в кожному випадку.
|
||||
|
||||
### Поновлення сертифіката { #certificate-renewal }
|
||||
|
||||
У певний момент у майбутньому строк дії кожного сертифіката спливе (приблизно через 3 місяці після його отримання).
|
||||
|
||||
Потім інша програма (в деяких випадках це інша програма, а в деяких - той самий TLS Termination Proxy) зв'яжеться з Let's Encrypt і поновить сертифікат(и).
|
||||
|
||||
<img src="/img/deployment/https/https.drawio.svg">
|
||||
|
||||
Сертифікати TLS пов'язані з доменним іменем, а не з IP-адресою.
|
||||
|
||||
Тому, щоб поновити сертифікати, програма поновлення має довести авторитету (Let's Encrypt), що вона справді «володіє» і контролює цей домен.
|
||||
|
||||
Щоб зробити це й задовольнити різні потреби застосунків, є кілька способів. Деякі популярні:
|
||||
|
||||
* Змінити деякі записи DNS.
|
||||
* Для цього програма поновлення має підтримувати API провайдера DNS, тож залежно від того, якого провайдера DNS ви використовуєте, це може бути або не бути варіантом.
|
||||
* Запуститися як сервер (принаймні під час процесу отримання сертифіката) на публічній IP-адресі, пов'язаній із доменом.
|
||||
* Як ми казали вище, лише один процес може слухати конкретну IP-адресу та порт.
|
||||
* Це одна з причин, чому дуже зручно, коли той самий TLS Termination Proxy також займається процесом поновлення сертифікатів.
|
||||
* Інакше вам, можливо, доведеться на мить зупинити TLS Termination Proxy, запустити програму поновлення, щоб отримати сертифікати, потім налаштувати їх у TLS Termination Proxy і перезапустити TLS Termination Proxy. Це неідеально, оскільки ваші застосунки будуть недоступні під час вимкнення TLS Termination Proxy.
|
||||
|
||||
Увесь цей процес поновлення, паралельно з обслуговуванням застосунку, - одна з головних причин, чому ви можете захотіти мати окрему систему для обробки HTTPS за допомогою TLS Termination Proxy замість того, щоб просто використовувати сертифікати TLS безпосередньо з сервером застосунку (наприклад, Uvicorn).
|
||||
|
||||
## Направлені заголовки проксі { #proxy-forwarded-headers }
|
||||
|
||||
Коли ви використовуєте проксі для обробки HTTPS, ваш сервер застосунку (наприклад, Uvicorn через FastAPI CLI) нічого не знає про процес HTTPS, він спілкується звичайним HTTP із TLS Termination Proxy.
|
||||
|
||||
Цей проксі зазвичай динамічно встановлює деякі HTTP-заголовки перед передачею запиту серверу застосунку, щоб дати йому знати, що запит направляється проксі.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Заголовки проксі:
|
||||
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
|
||||
|
||||
///
|
||||
|
||||
Втім, оскільки сервер застосунку не знає, що він стоїть за довіреним проксі, за замовчуванням він не довірятиме цим заголовкам.
|
||||
|
||||
Але ви можете налаштувати сервер застосунку, щоб довіряти направленим заголовкам, надісланим проксі. Якщо ви використовуєте FastAPI CLI, ви можете скористатися курсивною *опцією CLI* `--forwarded-allow-ips`, щоб повідомити, з яких IP-адрес слід довіряти цим направленим заголовкам.
|
||||
|
||||
Наприклад, якщо сервер застосунку отримує комунікацію лише від довіреного проксі, ви можете встановити `--forwarded-allow-ips="*"`, щоб довіряти всім вхідним IP-адресам, оскільки він отримуватиме запити лише з тієї IP-адреси, яку використовує проксі.
|
||||
|
||||
Так застосунок зможе знати свою публічну URL-адресу, чи використовує він HTTPS, домен тощо.
|
||||
|
||||
Це буде корисно, наприклад, для коректної обробки перенаправлень.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ви можете дізнатися більше про це в документації [За проксі - Увімкнути направлені заголовки проксі](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank}
|
||||
|
||||
///
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Наявність HTTPS дуже важлива і в більшості випадків критична. Більшість зусиль, які вам як розробнику доведеться докласти навколо HTTPS, полягають лише в розумінні цих концепцій і того, як вони працюють.
|
||||
|
||||
Але як тільки ви знаєте базову інформацію про HTTPS для розробників, ви можете легко комбінувати й налаштовувати різні інструменти, щоб керувати всім просто.
|
||||
|
||||
У деяких наступних розділах я покажу кілька конкретних прикладів налаштування HTTPS для застосунків FastAPI. 🔒
|
||||
23
docs/uk/docs/deployment/index.md
Normal file
23
docs/uk/docs/deployment/index.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Розгортання { #deployment }
|
||||
|
||||
Розгортання застосунку **FastAPI** відносно просте.
|
||||
|
||||
## Що означає розгортання { #what-does-deployment-mean }
|
||||
|
||||
Розгорнути застосунок - це виконати необхідні кроки, щоб зробити його доступним для користувачів.
|
||||
|
||||
Для **веб API** це зазвичай означає розміщення його на **віддаленій машині** з **серверною програмою**, що забезпечує хорошу продуктивність, стабільність тощо, щоб ваші **користувачі** могли **отримувати доступ** до застосунку ефективно та без перерв чи проблем.
|
||||
|
||||
Це відрізняється від етапів **розробки**, коли ви постійно змінюєте код, ламаєте й виправляєте його, зупиняєте та перезапускаєте сервер розробки тощо.
|
||||
|
||||
## Стратегії розгортання { #deployment-strategies }
|
||||
|
||||
Існує кілька способів зробити це залежно від вашого конкретного випадку використання та інструментів, які ви використовуєте.
|
||||
|
||||
Ви можете розгорнути сервер самостійно, використовуючи комбінацію інструментів, можете скористатися **хмарним сервісом**, який виконує частину роботи за вас, або обрати інші варіанти.
|
||||
|
||||
Наприклад, ми, команда, що стоїть за FastAPI, створили <a href="https://fastapicloud.com" class="external-link" target="_blank">**FastAPI Cloud**</a>, щоб зробити розгортання застосунків FastAPI у хмарі якомога простішим і з тим самим досвідом розробки, що й під час роботи з FastAPI.
|
||||
|
||||
Я покажу вам кілька основних концепцій, про які, ймовірно, варто пам'ятати під час розгортання **FastAPI**-застосунку (хоча більшість із них стосується будь-яких інших типів веб-застосунків).
|
||||
|
||||
У наступних розділах ви побачите більше деталей, на які варто зважати, та деякі техніки, як це зробити. ✨
|
||||
157
docs/uk/docs/deployment/manually.md
Normal file
157
docs/uk/docs/deployment/manually.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Запустіть сервер вручну { #run-a-server-manually }
|
||||
|
||||
## Використовуйте команду `fastapi run` { #use-the-fastapi-run-command }
|
||||
|
||||
Коротко: використовуйте `fastapi run`, щоб запустити ваш застосунок FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
|
||||
|
||||
Searching for package file structure from directories
|
||||
with <font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
|
||||
the following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
|
||||
to quit<b>)</b>
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Це спрацює в більшості випадків. 😎
|
||||
|
||||
Цю команду можна використати, наприклад, щоб запустити ваш застосунок FastAPI у контейнері, на сервері тощо.
|
||||
|
||||
## Сервери ASGI { #asgi-servers }
|
||||
|
||||
Розгляньмо деталі.
|
||||
|
||||
FastAPI використовує стандарт для побудови Python вебфреймворків і серверів під назвою <abbr title="Asynchronous Server Gateway Interface - Асинхронний шлюзовий інтерфейс сервера">ASGI</abbr>. FastAPI - це ASGI вебфреймворк.
|
||||
|
||||
Головне, що потрібно, щоб запустити застосунок **FastAPI** (або будь-який інший ASGI-застосунок) на віддаленій серверній машині, - це програма ASGI-сервера на кшталт **Uvicorn**; саме вона постачається за замовчуванням у команді `fastapi`.
|
||||
|
||||
Є кілька альтернатив, зокрема:
|
||||
|
||||
* <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a>: високопродуктивний ASGI-сервер.
|
||||
* <a href="https://hypercorn.readthedocs.io/" class="external-link" target="_blank">Hypercorn</a>: ASGI-сервер, сумісний з HTTP/2 і Trio, серед інших можливостей.
|
||||
* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>: ASGI-сервер, створений для Django Channels.
|
||||
* <a href="https://github.com/emmett-framework/granian" class="external-link" target="_blank">Granian</a>: Rust HTTP-сервер для Python-застосунків.
|
||||
* <a href="https://unit.nginx.org/howto/fastapi/" class="external-link" target="_blank">NGINX Unit</a>: NGINX Unit - легке й універсальне середовище виконання вебзастосунків.
|
||||
|
||||
## Серверна машина і серверна програма { #server-machine-and-server-program }
|
||||
|
||||
Є невелика деталь щодо назв, яку варто пам'ятати. 💡
|
||||
|
||||
Слово «**сервер**» зазвичай означає і віддалений/хмарний комп'ютер (фізична або віртуальна машина), і програму, що працює на цій машині (наприклад, Uvicorn).
|
||||
|
||||
Майте на увазі, що коли ви бачите слово «сервер» загалом, воно може стосуватися будь-якого з цих двох значень.
|
||||
|
||||
Коли йдеться про віддалену машину, її часто називають «сервер», а також «машина», «VM» (віртуальна машина), «вузол». Усе це означає різновиди віддаленої машини, зазвичай з Linux, на якій ви запускаєте програми.
|
||||
|
||||
## Встановіть серверну програму { #install-the-server-program }
|
||||
|
||||
Після встановлення FastAPI ви отримуєте продакшн-сервер Uvicorn і можете запускати його командою `fastapi run`.
|
||||
|
||||
Але ви також можете встановити ASGI-сервер вручну.
|
||||
|
||||
Переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його, після чого можете встановити серверну програму.
|
||||
|
||||
Наприклад, щоб установити Uvicorn:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "uvicorn[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Подібний процес застосовується до будь-якої іншої ASGI-серверної програми.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Додавши `standard`, Uvicorn встановить і використовуватиме деякі рекомендовані додаткові залежності.
|
||||
|
||||
Зокрема `uvloop` - високопродуктивну заміну «без змін у коді» для `asyncio`, що суттєво підвищує рівночасність і продуктивність.
|
||||
|
||||
Якщо ви встановлюєте FastAPI через `pip install "fastapi[standard]"`, ви вже отримаєте і `uvicorn[standard]`.
|
||||
|
||||
///
|
||||
|
||||
## Запустіть серверну програму { #run-the-server-program }
|
||||
|
||||
Якщо ви встановили ASGI-сервер вручну, зазвичай потрібно передати рядок імпорту в спеціальному форматі, щоб він імпортував ваш застосунок FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --host 0.0.0.0 --port 80
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Команда `uvicorn main:app` означає:
|
||||
|
||||
* `main`: файл `main.py` (Python «модуль»).
|
||||
* `app`: об'єкт, створений у `main.py` рядком `app = FastAPI()`.
|
||||
|
||||
Це еквівалентно:
|
||||
|
||||
```Python
|
||||
from main import app
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
Кожна альтернативна ASGI-серверна програма матиме подібну команду; читайте більше в їхній документації.
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Uvicorn та інші сервери підтримують опцію `--reload`, корисну під час розробки.
|
||||
|
||||
Опція `--reload` споживає значно більше ресурсів, є менш стабільною тощо.
|
||||
|
||||
Вона дуже допомагає під час розробки, але її не слід використовувати в продакшні.
|
||||
|
||||
///
|
||||
|
||||
## Концепції розгортання { #deployment-concepts }
|
||||
|
||||
Ці приклади запускають серверну програму (наприклад, Uvicorn), піднімаючи один процес, що слухає всі IP (`0.0.0.0`) на визначеному порту (наприклад, `80`).
|
||||
|
||||
Це базова ідея. Але, ймовірно, вам знадобиться подбати ще про таке:
|
||||
|
||||
* Безпека - HTTPS
|
||||
* Автозапуск
|
||||
* Перезапуски
|
||||
* Реплікація (кількість запущених процесів)
|
||||
* Пам'ять
|
||||
* Попередні кроки перед стартом
|
||||
|
||||
У наступних розділах я розповім більше про кожну з цих концепцій, як про них думати, і наведу конкретні приклади та стратегії для їх опрацювання. 🚀
|
||||
139
docs/uk/docs/deployment/server-workers.md
Normal file
139
docs/uk/docs/deployment/server-workers.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Працівники сервера - Uvicorn з працівниками { #server-workers-uvicorn-with-workers }
|
||||
|
||||
Повернімося до попередніх концепцій розгортання:
|
||||
|
||||
- Безпека - HTTPS
|
||||
- Запуск під час старту
|
||||
- Перезапуски
|
||||
- Реплікація (кількість процесів, що виконуються)
|
||||
- Пам'ять
|
||||
- Попередні кроки перед запуском
|
||||
|
||||
До цього моменту, проходячи всі навчальні посібники в документації, ви, ймовірно, запускали серверну програму, наприклад, використовуючи команду `fastapi`, яка запускає Uvicorn у вигляді одного процесу.
|
||||
|
||||
Під час розгортання застосунків ви, найімовірніше, захочете мати реплікацію процесів, щоб використовувати кілька ядер і обробляти більше запитів.
|
||||
|
||||
Як ви бачили в попередньому розділі про [Концепції розгортання](concepts.md){.internal-link target=_blank}, існує кілька стратегій, які можна використовувати.
|
||||
|
||||
Тут я покажу, як використовувати Uvicorn із процесами-працівниками за допомогою команди `fastapi` або безпосередньо команди `uvicorn`.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Якщо ви використовуєте контейнери, наприклад з Docker або Kubernetes, я розповім про це більше в наступному розділі: [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank}.
|
||||
|
||||
Зокрема, під час запуску в Kubernetes вам, найімовірніше, не варто використовувати працівників, натомість запускати один процес Uvicorn на контейнер. Але про це я розповім пізніше в тому розділі.
|
||||
|
||||
///
|
||||
|
||||
## Кілька працівників { #multiple-workers }
|
||||
|
||||
Ви можете запустити кілька працівників за допомогою параметра командного рядка `--workers`:
|
||||
|
||||
//// tab | `fastapi`
|
||||
|
||||
Якщо ви використовуєте команду `fastapi`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
|
||||
|
||||
Searching for package file structure from directories with
|
||||
<font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
|
||||
following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to
|
||||
quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | `uvicorn`
|
||||
|
||||
Якщо ви віддаєте перевагу використовувати команду `uvicorn` безпосередньо:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
|
||||
<font color="#A6E22E">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8080</b> (Press CTRL+C to quit)
|
||||
<font color="#A6E22E">INFO</font>: Started parent process [<font color="#A1EFE4"><b>27365</b></font>]
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27368</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27369</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27370</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27367</font>]
|
||||
<font color="#A6E22E">INFO</font>: Waiting for application startup.
|
||||
<font color="#A6E22E">INFO</font>: Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
Єдина нова опція тут — `--workers`, яка вказує Uvicorn запустити 4 процеси-працівники.
|
||||
|
||||
Також ви можете побачити, що виводиться PID кожного процесу: `27365` для батьківського процесу (це менеджер процесів) і по одному для кожного процесу-працівника: `27368`, `27369`, `27370` і `27367`.
|
||||
|
||||
## Концепції розгортання { #deployment-concepts }
|
||||
|
||||
Тут ви побачили, як використовувати кілька працівників, щоб паралелізувати виконання застосунку, використати кілька ядер процесора та обслуговувати більше запитів.
|
||||
|
||||
Із наведеного вище списку концепцій розгортання, використання працівників головним чином допоможе з частиною про реплікацію і трохи з перезапусками, але про інше все ще треба подбати:
|
||||
|
||||
- Безпека - HTTPS
|
||||
- Запуск під час старту
|
||||
- ***Перезапуски***
|
||||
- Реплікація (кількість процесів, що виконуються)
|
||||
- Пам'ять
|
||||
- Попередні кроки перед запуском
|
||||
|
||||
## Контейнери і Docker { #containers-and-docker }
|
||||
|
||||
У наступному розділі про [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank} я поясню кілька стратегій, які ви можете використати для інших концепцій розгортання.
|
||||
|
||||
Я покажу, як побудувати власний образ з нуля для запуску одного процесу Uvicorn. Це простий процес і, ймовірно, саме те, що потрібно при використанні розподіленої системи керування контейнерами, такої як Kubernetes.
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Ви можете використовувати кілька процесів-працівників за допомогою параметра CLI `--workers` у командах `fastapi` або `uvicorn`, щоб скористатися перевагами багатоядерних процесорів і запускати кілька процесів паралельно.
|
||||
|
||||
Ви можете застосувати ці інструменти та ідеї, якщо налаштовуєте власну систему розгортання і самостійно дбаєте про інші концепції розгортання.
|
||||
|
||||
Перегляньте наступний розділ, щоб дізнатися про FastAPI з контейнерами (наприклад Docker і Kubernetes). Ви побачите, що ці інструменти також мають прості способи вирішити інші концепції розгортання. ✨
|
||||
93
docs/uk/docs/deployment/versions.md
Normal file
93
docs/uk/docs/deployment/versions.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Про версії FastAPI { #about-fastapi-versions }
|
||||
|
||||
**FastAPI** уже використовується у продакшні в багатьох застосунках і системах. Покриття тестами підтримується на рівні 100%. Але розробка все ще рухається швидко.
|
||||
|
||||
Нові можливості додаються часто, помилки регулярно виправляються, а код постійно поліпшується.
|
||||
|
||||
Тому поточні версії все ще `0.x.x`, це відображає те, що кожна версія потенційно може містити несумісні зміни. Це відповідає правилам <a href="https://semver.org/" class="external-link" target="_blank">Семантичного версіонування</a>.
|
||||
|
||||
Ви можете створювати продакшн-застосунки з **FastAPI** вже зараз (і, ймовірно, робите це вже певний час), просто переконайтеся, що ви використовуєте версію, яка коректно працює з рештою вашого коду.
|
||||
|
||||
## Зафіксуйте версію `fastapi` { #pin-your-fastapi-version }
|
||||
|
||||
Перше, що слід зробити, - «зафіксувати» версію **FastAPI**, яку ви використовуєте, до конкретної останньої версії, що, як ви знаєте, правильно працює для вашого застосунку.
|
||||
|
||||
Наприклад, припустімо, що у вашому застосунку використовується версія `0.112.0`.
|
||||
|
||||
Якщо ви використовуєте файл `requirements.txt`, ви можете вказати версію так:
|
||||
|
||||
```txt
|
||||
fastapi[standard]==0.112.0
|
||||
```
|
||||
|
||||
це означатиме, що ви використовуватимете саме версію `0.112.0`.
|
||||
|
||||
Або ви також можете зафіксувати так:
|
||||
|
||||
```txt
|
||||
fastapi[standard]>=0.112.0,<0.113.0
|
||||
```
|
||||
|
||||
це означатиме, що ви використовуватимете версії `0.112.0` і вище, але менше `0.113.0`, наприклад, версія `0.112.2` все ще буде прийнятною.
|
||||
|
||||
Якщо ви використовуєте інший інструмент для керування встановленнями, як-от `uv`, Poetry, Pipenv чи інші, у всіх них є спосіб визначати конкретні версії для ваших пакетів.
|
||||
|
||||
## Доступні версії { #available-versions }
|
||||
|
||||
Ви можете переглянути доступні версії (наприклад, щоб перевірити поточну останню) в [Примітках до випусків](../release-notes.md){.internal-link target=_blank}.
|
||||
|
||||
## Про версії { #about-versions }
|
||||
|
||||
Відповідно до правил Семантичного версіонування, будь-яка версія нижче `1.0.0` потенційно може містити несумісні зміни.
|
||||
|
||||
FastAPI також дотримується правила, що будь-яка зміна «PATCH»-версії - це виправлення помилок і зміни без порушень сумісності.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
«PATCH» - це останнє число, наприклад, у `0.2.3` PATCH-версія - це `3`.
|
||||
|
||||
///
|
||||
|
||||
Тож ви можете зафіксувати версію так:
|
||||
|
||||
```txt
|
||||
fastapi>=0.45.0,<0.46.0
|
||||
```
|
||||
|
||||
Несумісні зміни та нові можливості додаються у «MINOR»-версіях.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
«MINOR» - це число посередині, наприклад, у `0.2.3` MINOR-версія - це `2`.
|
||||
|
||||
///
|
||||
|
||||
## Оновлення версій FastAPI { #upgrading-the-fastapi-versions }
|
||||
|
||||
Ви повинні додати тести для вашого застосунку.
|
||||
|
||||
З **FastAPI** це дуже легко (завдяки Starlette), перегляньте документацію: [Тестування](../tutorial/testing.md){.internal-link target=_blank}
|
||||
|
||||
Після того як у вас є тести, ви можете оновити версію **FastAPI** до новішої і переконатися, що весь ваш код працює правильно, запустивши тести.
|
||||
|
||||
Якщо все працює або після внесення необхідних змін і проходження всіх тестів, зафіксуйте вашу версію `fastapi` на цій новій версії.
|
||||
|
||||
## Про Starlette { #about-starlette }
|
||||
|
||||
Не варто фіксувати версію `starlette`.
|
||||
|
||||
Різні версії **FastAPI** використовуватимуть конкретну новішу версію Starlette.
|
||||
|
||||
Отже, просто дозвольте **FastAPI** використовувати відповідну версію Starlette.
|
||||
|
||||
## Про Pydantic { #about-pydantic }
|
||||
|
||||
Pydantic включає тести для **FastAPI** разом із власними тестами, тому нові версії Pydantic (вище `1.0.0`) завжди сумісні з FastAPI.
|
||||
|
||||
Ви можете зафіксувати Pydantic на будь-якій версії вище `1.0.0`, яка вам підходить.
|
||||
|
||||
Наприклад:
|
||||
|
||||
```txt
|
||||
pydantic>=2.7.0,<3.0.0
|
||||
```
|
||||
298
docs/uk/docs/environment-variables.md
Normal file
298
docs/uk/docs/environment-variables.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# Змінні оточення { #environment-variables }
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви вже знаєте, що таке «змінні оточення» і як їх використовувати, можете пропустити цей розділ.
|
||||
|
||||
///
|
||||
|
||||
Змінна оточення (також відома як «env var») - це змінна, що існує поза кодом Python, в операційній системі, і може бути прочитана вашим кодом Python (а також іншими програмами).
|
||||
|
||||
Змінні оточення корисні для роботи з налаштуваннями застосунку, як частина встановлення Python тощо.
|
||||
|
||||
## Створення і використання змінних оточення { #create-and-use-env-vars }
|
||||
|
||||
Ви можете створювати і використовувати змінні оточення в оболонці (терміналі) без участі Python:
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Ви можете створити змінну оточення MY_NAME командою
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// Потім можна використати її з іншими програмами, наприклад
|
||||
$ echo "Hello $MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Створіть змінну оточення MY_NAME
|
||||
$ $Env:MY_NAME = "Wade Wilson"
|
||||
|
||||
// Використайте її з іншими програмами, наприклад
|
||||
$ echo "Hello $Env:MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
## Читання змінних оточення в Python { #read-env-vars-in-python }
|
||||
|
||||
Ви також можете створити змінні оточення поза Python, у терміналі (або будь-яким іншим способом), а потім зчитати їх у Python.
|
||||
|
||||
Наприклад, у вас може бути файл `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> - це значення за замовчуванням, яке буде повернено.
|
||||
|
||||
Якщо його не вказано, за замовчуванням це `None`. Тут ми надаємо `"World"` як значення за замовчуванням.
|
||||
|
||||
///
|
||||
|
||||
Потім ви можете запустити цю програму Python:
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Тут ми ще не встановлюємо змінну оточення
|
||||
$ python main.py
|
||||
|
||||
// Оскільки ми не встановили змінну оточення, отримуємо значення за замовчуванням
|
||||
|
||||
Hello World from Python
|
||||
|
||||
// Але якщо спочатку створимо змінну оточення
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// А потім знову викличемо програму
|
||||
$ python main.py
|
||||
|
||||
// Тепер вона може прочитати змінну оточення
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Тут ми ще не встановлюємо змінну оточення
|
||||
$ python main.py
|
||||
|
||||
// Оскільки ми не встановили змінну оточення, отримуємо значення за замовчуванням
|
||||
|
||||
Hello World from Python
|
||||
|
||||
// Але якщо спочатку створимо змінну оточення
|
||||
$ $Env:MY_NAME = "Wade Wilson"
|
||||
|
||||
// А потім знову викличемо програму
|
||||
$ python main.py
|
||||
|
||||
// Тепер вона може прочитати змінну оточення
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
Оскільки змінні оточення можна встановлювати поза кодом, але читати в коді, і їх не потрібно зберігати (фіксувати у `git`) разом з іншими файлами, їх часто використовують для конфігурацій або налаштувань.
|
||||
|
||||
Ви також можете створити змінну оточення лише для конкретного запуску програми, вона буде доступна тільки цій програмі і лише на час її виконання.
|
||||
|
||||
Щоб зробити це, створіть її безпосередньо перед командою запуску програми, в тому самому рядку:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Створіть змінну оточення MY_NAME безпосередньо в цьому виклику програми
|
||||
$ MY_NAME="Wade Wilson" python main.py
|
||||
|
||||
// Тепер вона може прочитати змінну оточення
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
|
||||
// Після цього змінна оточення більше не існує
|
||||
$ 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>.
|
||||
|
||||
///
|
||||
|
||||
## Типи і перевірка { #types-and-validation }
|
||||
|
||||
Ці змінні оточення можуть містити лише текстові строки, оскільки вони зовнішні щодо Python і мають бути сумісними з іншими програмами та рештою системи (і навіть з різними операційними системами, як-от Linux, Windows, macOS).
|
||||
|
||||
Це означає, що будь-яке значення, прочитане в Python зі змінної оточення, буде `str`, а будь-яке перетворення до іншого типу або будь-яка перевірка має виконуватися в коді.
|
||||
|
||||
Ви дізнаєтеся більше про використання змінних оточення для роботи з налаштуваннями застосунку в розділі [Просунутий посібник користувача - Налаштування і змінні оточення](./advanced/settings.md){.internal-link target=_blank}.
|
||||
|
||||
## Змінна оточення `PATH` { #path-environment-variable }
|
||||
|
||||
Є спеціальна змінна оточення `PATH`, яку використовують операційні системи (Linux, macOS, Windows) для пошуку програм для запуску.
|
||||
|
||||
Значення змінної `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` у першому каталозі цього списку.
|
||||
|
||||
Якщо знайде, вона використає її. Інакше продовжить пошук в інших каталогах.
|
||||
|
||||
### Встановлення Python і оновлення `PATH` { #installing-python-and-updating-the-path }
|
||||
|
||||
Під час встановлення Python вас можуть запитати, чи хочете ви оновити змінну оточення `PATH`.
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
Припустімо, ви встановлюєте Python і він опиняється в каталозі `/opt/custompython/bin`.
|
||||
|
||||
Якщо ви погодитеся оновити змінну оточення `PATH`, інсталятор додасть `/opt/custompython/bin` до змінної `PATH`.
|
||||
|
||||
Це може виглядати так:
|
||||
|
||||
```plaintext
|
||||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
|
||||
```
|
||||
|
||||
Тепер, коли ви введете `python` у терміналі, система знайде програму Python у `/opt/custompython/bin` (останній каталог) і використає саме її.
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
Припустімо, ви встановлюєте Python і він опиняється в каталозі `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` у терміналі, система знайде програму Python у `C:\opt\custompython\bin` (останній каталог) і використає саме її.
|
||||
|
||||
////
|
||||
|
||||
Отже, якщо ви введете:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
Система знайде програму `python` у `/opt/custompython/bin` і запустить її.
|
||||
|
||||
Це приблизно еквівалентно введенню:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ /opt/custompython/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
Система знайде програму `python` у `C:\opt\custompython\bin\python` і запустить її.
|
||||
|
||||
Це приблизно еквівалентно введенню:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ C:\opt\custompython\bin\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
Ця інформація стане у пригоді під час вивчення [Віртуальних середовищ](virtual-environments.md){.internal-link target=_blank}.
|
||||
|
||||
## Висновок { #conclusion }
|
||||
|
||||
Тепер ви маєте базове розуміння того, що таке змінні оточення і як їх використовувати в Python.
|
||||
|
||||
Також можна прочитати більше у <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Вікіпедії про змінну оточення</a>.
|
||||
|
||||
У багатьох випадках не одразу очевидно, як змінні оточення будуть корисними та застосовними. Але вони постійно з’являються в різних сценаріях під час розробки, тож варто про них знати.
|
||||
|
||||
Наприклад, вам знадобиться ця інформація в наступному розділі про [Віртуальні середовища](virtual-environments.md).
|
||||
256
docs/uk/docs/help-fastapi.md
Normal file
256
docs/uk/docs/help-fastapi.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Допоможіть FastAPI - отримайте допомогу { #help-fastapi-get-help }
|
||||
|
||||
Вам подобається **FastAPI**?
|
||||
|
||||
Хочете допомогти FastAPI, іншим користувачам та автору?
|
||||
|
||||
Або ви хочете отримати допомогу щодо **FastAPI**?
|
||||
|
||||
Є дуже прості способи допомогти (деякі потребують лише одного-двох кліків).
|
||||
|
||||
І є кілька способів отримати допомогу.
|
||||
|
||||
## Підпишіться на розсилку { #subscribe-to-the-newsletter }
|
||||
|
||||
Ви можете підписатися на (нечасту) розсилку [**FastAPI and friends**](newsletter.md){.internal-link target=_blank}, щоб бути в курсі:
|
||||
|
||||
* Новин про FastAPI та друзів 🚀
|
||||
* Посібників 📝
|
||||
* Можливостей ✨
|
||||
* Несумісних змін 🚨
|
||||
* Порад і трюків ✅
|
||||
|
||||
## Стежте за FastAPI в X (Twitter) { #follow-fastapi-on-x-twitter }
|
||||
|
||||
<a href="https://x.com/fastapi" class="external-link" target="_blank">Стежте за @fastapi в **X (Twitter)**</a>, щоб отримувати найсвіжіші новини про **FastAPI**. 🐦
|
||||
|
||||
## Додайте зірочку **FastAPI** на GitHub { #star-fastapi-in-github }
|
||||
|
||||
Ви можете «поставити зірочку» FastAPI на GitHub (натиснувши кнопку зірочки у верхньому правому куті): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. ⭐️
|
||||
|
||||
Додавши зірочку, іншим користувачам буде легше його знайти і побачити, що він уже був корисним для інших.
|
||||
|
||||
## Стежте за випусками в репозиторії GitHub { #watch-the-github-repository-for-releases }
|
||||
|
||||
Ви можете «спостерігати» за FastAPI на GitHub (натиснувши кнопку «watch» у верхньому правому куті): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
|
||||
Там ви можете вибрати «Releases only».
|
||||
|
||||
Зробивши це, ви отримуватимете сповіщення (на вашу електронну пошту) щоразу, коли вийде новий реліз (нова версія) **FastAPI** з виправленнями помилок і новими можливостями.
|
||||
|
||||
## Зв'яжіться з автором { #connect-with-the-author }
|
||||
|
||||
Ви можете зв'язатися зі <a href="https://tiangolo.com" class="external-link" target="_blank">мною (Sebastián Ramírez / `tiangolo`)</a>, автором.
|
||||
|
||||
Ви можете:
|
||||
|
||||
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">Стежити за мною на **GitHub**</a>.
|
||||
* Подивитися інші Open Source-проєкти, які я створив і які можуть вам допомогти.
|
||||
* Стежити, щоб бачити, коли я створюю новий Open Source-проєкт.
|
||||
* <a href="https://x.com/tiangolo" class="external-link" target="_blank">Стежити за мною в **X (Twitter)**</a> або <a href="https://fosstodon.org/@tiangolo" class="external-link" target="_blank">Mastodon</a>.
|
||||
* Розкажіть мені, як ви використовуєте FastAPI (мені дуже приємно це чути).
|
||||
* Дізнаватися, коли я роблю оголошення або випускаю нові інструменти.
|
||||
* Також ви можете <a href="https://x.com/fastapi" class="external-link" target="_blank">стежити за @fastapi в X (Twitter)</a> (окремий акаунт).
|
||||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">Стежити за мною в **LinkedIn**</a>.
|
||||
* Дізнаватися, коли я роблю оголошення або випускаю нові інструменти (хоча X (Twitter) я використовую частіше 🤷♂).
|
||||
* Читати, що я пишу (або стежити за мною) на <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> або <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a>.
|
||||
* Читати інші ідеї, статті та про інструменти, які я створив.
|
||||
* Стежити, щоб читати нове, коли я щось публікую.
|
||||
|
||||
## Твітніть про **FastAPI** { #tweet-about-fastapi }
|
||||
|
||||
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">Твітніть про **FastAPI**</a> і дайте мені та іншим знати, чому він вам подобається. 🎉
|
||||
|
||||
Мені дуже подобається дізнаватися, як використовують **FastAPI**, що вам у ньому сподобалося, у якому проєкті/компанії ви його застосовуєте тощо.
|
||||
|
||||
## Голосуйте за FastAPI { #vote-for-fastapi }
|
||||
|
||||
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">Проголосуйте за **FastAPI** на Slant</a>.
|
||||
* <a href="https://alternativeto.net/software/fastapi/about/" class="external-link" target="_blank">Проголосуйте за **FastAPI** на AlternativeTo</a>.
|
||||
* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">Повідомте, що ви використовуєте **FastAPI**, на StackShare</a>.
|
||||
|
||||
## Допомагайте іншим з питаннями на GitHub { #help-others-with-questions-in-github }
|
||||
|
||||
Ви можете спробувати допомагати іншим з їхніми питаннями у:
|
||||
|
||||
* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
|
||||
* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
|
||||
|
||||
У багатьох випадках ви вже можете знати відповідь на ці питання. 🤓
|
||||
|
||||
Якщо ви багато допомагаєте людям із їхніми питаннями, ви станете офіційним [Експертом FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉
|
||||
|
||||
Пам'ятайте, найважливіше: намагайтеся бути добрими. Люди приходять зі своєю фрустрацією і часто питають не найкращим чином, але постарайтеся бути якомога доброзичливішими. 🤗
|
||||
|
||||
Ідея в тому, щоб спільнота **FastAPI** була доброзичливою та гостинною. Водночас не терпіть булінг чи неповажну поведінку щодо інших. Ми маємо піклуватися одне про одного.
|
||||
|
||||
---
|
||||
|
||||
Ось як допомагати іншим із питаннями (у discussions або issues):
|
||||
|
||||
### Зрозумійте питання { #understand-the-question }
|
||||
|
||||
* Перевірте, чи розумієте ви **мету** та варіант використання людини, яка питає.
|
||||
|
||||
* Потім перевірте, чи **чітке** саме питання (переважна більшість — це питання).
|
||||
|
||||
* Часто запит стосується уявного рішення користувача, але може бути **краще**. Якщо ви краще зрозумієте проблему та варіант використання, зможете запропонувати кращу **альтернативу**.
|
||||
|
||||
* Якщо ви не розумієте питання, попросіть більше **подробиць**.
|
||||
|
||||
### Відтворіть проблему { #reproduce-the-problem }
|
||||
|
||||
У більшості випадків і запитань мова йде про **оригінальний код** людини.
|
||||
|
||||
Часто вони наводять лише фрагмент коду, але цього недостатньо, щоб **відтворити проблему**.
|
||||
|
||||
* Ви можете попросити надати <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">мінімальний, відтворюваний приклад</a>, який ви зможете **скопіювати-вставити** і запустити локально, щоб побачити ту саму помилку або поведінку, яку бачать вони, або краще зрозуміти їхній варіант використання.
|
||||
|
||||
* Якщо ви дуже щедрі, можете спробувати **створити такий приклад** самостійно, лише на основі опису проблеми. Просто майте на увазі, що це може зайняти багато часу, і краще спочатку попросити їх уточнити проблему.
|
||||
|
||||
### Запропонуйте рішення { #suggest-solutions }
|
||||
|
||||
* Після того як ви змогли зрозуміти питання, дайте можливу **відповідь**.
|
||||
|
||||
* Часто краще зрозуміти їхню **первинну проблему або варіант використання**, адже може бути кращий спосіб її вирішити, ніж те, що вони намагаються зробити.
|
||||
|
||||
### Попросіть закрити { #ask-to-close }
|
||||
|
||||
Якщо вони відповіли, дуже ймовірно, що ви розв'язали їхню проблему. Вітаю, **ви герой**! 🦸
|
||||
|
||||
* Тепер, якщо це вирішило їхню проблему, ви можете попросити:
|
||||
|
||||
* У GitHub Discussions: позначити коментар як **відповідь**.
|
||||
* У GitHub Issues: **закрити** issue.
|
||||
|
||||
## Стежте за репозиторієм GitHub { #watch-the-github-repository }
|
||||
|
||||
Ви можете «спостерігати» за FastAPI на GitHub (натиснувши кнопку «watch» у верхньому правому куті): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
|
||||
Якщо вибрати «Watching» замість «Releases only», ви отримуватимете сповіщення, коли хтось створює нове issue або питання. Ви також можете вказати, що хочете отримувати сповіщення лише про нові issues, або discussions, або PR тощо.
|
||||
|
||||
Тоді ви зможете спробувати допомогти їм вирішити ці питання.
|
||||
|
||||
## Ставте питання { #ask-questions }
|
||||
|
||||
Ви можете <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">створити нове питання</a> у репозиторії GitHub, наприклад, щоб:
|
||||
|
||||
* Поставити **питання** або запитати про **проблему**.
|
||||
* Запропонувати нову **можливість**.
|
||||
|
||||
**Примітка**: якщо ви це зробите, я попрошу вас також допомагати іншим. 😉
|
||||
|
||||
## Переглядайте запити на витяг { #review-pull-requests }
|
||||
|
||||
Ви можете допомогти мені переглядати запити на витяг інших.
|
||||
|
||||
І знову, будь ласка, намагайтеся бути якомога доброзичливішими. 🤗
|
||||
|
||||
---
|
||||
|
||||
Ось що слід пам'ятати і як переглядати запит на витяг:
|
||||
|
||||
### Зрозумійте проблему { #understand-the-problem }
|
||||
|
||||
* Спочатку переконайтеся, що ви **розумієте проблему**, яку намагається вирішити запит на витяг. Може бути довша дискусія у GitHub Discussion або issue.
|
||||
|
||||
* Також є велика ймовірність, що запит на витяг насправді не потрібен, бо проблему можна розв'язати **іншим способом**. Тоді ви можете про це запропонувати або запитати.
|
||||
|
||||
### Не переймайтеся стилем { #dont-worry-about-style }
|
||||
|
||||
* Не надто хвилюйтеся про стиль повідомлень фіксацій, я зроблю squash and merge, налаштувавши фіксацію вручну.
|
||||
|
||||
* Також не переймайтеся правилами стилю, вже є автоматизовані інструменти, що це перевіряють.
|
||||
|
||||
А якщо будуть інші вимоги щодо стилю чи узгодженості, я попрошу про це безпосередньо або додам зверху фіксації з потрібними змінами.
|
||||
|
||||
### Перевірте код { #check-the-code }
|
||||
|
||||
* Перегляньте та прочитайте код, оцініть, чи він має сенс, **запустіть його локально** і перевірте, чи справді він розв'язує проблему.
|
||||
|
||||
* Потім залиште **коментар**, що ви це зробили, так я знатиму, що ви справді перевірили.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
На жаль, я не можу просто довіряти PR, які мають кілька схвалень.
|
||||
|
||||
Бувало не раз, що PR мали 3, 5 або більше схвалень, мабуть тому, що опис привабливий, але коли я перевіряв PR, вони виявлялися зламаними, мали помилку або не розв'язували заявлену проблему. 😅
|
||||
|
||||
Тож дуже важливо, щоб ви справді прочитали і запустили код та повідомили в коментарях, що ви це зробили. 🤓
|
||||
|
||||
///
|
||||
|
||||
* Якщо PR можна якось спростити, ви можете про це попросити, але немає потреби бути надто прискіпливими, адже може бути багато суб'єктивних точок зору (і в мене також є своя 🙈), тож краще зосередитися на фундаментальних речах.
|
||||
|
||||
### Тести { #tests }
|
||||
|
||||
* Допоможіть перевірити, що PR має **тести**.
|
||||
|
||||
* Переконайтеся, що тести **падають** до PR. 🚨
|
||||
|
||||
* Потім перевірте, що тести **проходять** після PR. ✅
|
||||
|
||||
* Багато PRів не мають тестів, ви можете **нагадати** додати тести або навіть **запропонувати** деякі тести самі. Це одна з речей, яка найбільше забирає часу, і ви дуже допоможете.
|
||||
|
||||
* Потім також прокоментуйте, що саме ви спробували, так я знатиму, що ви це перевірили. 🤓
|
||||
|
||||
## Створіть запит на витяг { #create-a-pull-request }
|
||||
|
||||
Ви можете [зробити внесок](contributing.md){.internal-link target=_blank} у вихідний код із запитами на витяг, наприклад:
|
||||
|
||||
* Щоб виправити описку, знайдену в документації.
|
||||
* Щоб поділитися статтею, відео або подкастом про FastAPI, який ви створили або знайшли, <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">відредагувавши цей файл</a>.
|
||||
* Обов'язково додайте ваше посилання на початок відповідного розділу.
|
||||
* Щоб допомогти [перекласти документацію](contributing.md#translations){.internal-link target=_blank} вашою мовою.
|
||||
* Ви також можете допомогти з переглядом перекладів, створених іншими.
|
||||
* Щоб запропонувати нові розділи документації.
|
||||
* Щоб виправити наявну проблему/помилку.
|
||||
* Обов'язково додайте тести.
|
||||
* Щоб додати нову можливість.
|
||||
* Обов'язково додайте тести.
|
||||
* Обов'язково додайте документацію, якщо це доречно.
|
||||
|
||||
## Допоможіть підтримувати FastAPI { #help-maintain-fastapi }
|
||||
|
||||
Допоможіть мені підтримувати **FastAPI**! 🤓
|
||||
|
||||
Роботи багато, і більшу її частину можете зробити **ВИ**.
|
||||
|
||||
Основні завдання, які ви можете виконувати вже зараз:
|
||||
|
||||
* [Допомагайте іншим з питаннями на GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (див. розділ вище).
|
||||
* [Переглядайте запити на витяг](#review-pull-requests){.internal-link target=_blank} (див. розділ вище).
|
||||
|
||||
Ці два завдання **найбільше споживають час**. Це основна робота з підтримки FastAPI.
|
||||
|
||||
Якщо ви можете допомогти мені з цим, **ви допомагаєте підтримувати FastAPI** і гарантуєте, що він **рухатиметься швидше й краще**. 🚀
|
||||
|
||||
## Долучайтеся до чату { #join-the-chat }
|
||||
|
||||
Долучайтеся до 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">серверу чату Discord</a> 👥 і спілкуйтеся з іншими в спільноті FastAPI.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Для запитань ставте їх у <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, там значно вища ймовірність, що вам допоможуть [Експерти FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}.
|
||||
|
||||
Використовуйте чат лише для інших загальних розмов.
|
||||
|
||||
///
|
||||
|
||||
### Не використовуйте чат для запитань { #dont-use-the-chat-for-questions }
|
||||
|
||||
Майте на увазі, що оскільки чати дозволяють більше «вільної розмови», легко ставити надто загальні питання, на які складніше відповісти, тож ви можете не отримати відповідей.
|
||||
|
||||
У GitHub шаблон підкаже вам, як написати правильне питання, щоб ви легше отримали хорошу відповідь, або навіть вирішили проблему самостійно ще до запиту. І в GitHub я можу гарантувати, що завжди на все відповім, навіть якщо це займе трохи часу. Особисто я не можу робити це в чатах. 😅
|
||||
|
||||
Розмови в чатах також не так просто шукати, як у GitHub, тож питання та відповіді можуть загубитися. І лише ті, що в GitHub, зараховуються, щоб стати [Експертом FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, тож швидше за все ви отримаєте більше уваги саме в GitHub.
|
||||
|
||||
З іншого боку, у чатах є тисячі користувачів, тож дуже ймовірно, що ви майже завжди знайдете там співрозмовника. 😄
|
||||
|
||||
## Спонсоруйте автора { #sponsor-the-author }
|
||||
|
||||
Якщо ваш **продукт/компанія** залежить від **FastAPI** або пов'язана з ним і ви хочете охопити його користувачів, ви можете спонсорувати автора (мене) через <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub sponsors</a>. Залежно від рівня ви можете отримати додаткові переваги, наприклад значок у документації. 🎁
|
||||
|
||||
---
|
||||
|
||||
Дякую! 🚀
|
||||
79
docs/uk/docs/history-design-future.md
Normal file
79
docs/uk/docs/history-design-future.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Історія, проєктування і майбутнє { #history-design-and-future }
|
||||
|
||||
Деякий час тому <a href="https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920" class="external-link" target="_blank">користувач **FastAPI** запитав</a>:
|
||||
|
||||
> Яка історія цього проєкту? Здається, він нізвідки за кілька тижнів став чудовим [...]
|
||||
|
||||
Ось трохи цієї історії.
|
||||
|
||||
## Альтернативи { #alternatives }
|
||||
|
||||
Я створював API зі складними вимогами протягом кількох років (машинне навчання, розподілені системи, асинхронні завдання, бази даних NoSQL тощо), очолюючи кілька команд розробників.
|
||||
|
||||
У межах цього мені довелося досліджувати, тестувати й використовувати багато альтернатив.
|
||||
|
||||
Історія **FastAPI** значною мірою - це історія його попередників.
|
||||
|
||||
Як сказано в розділі [Альтернативи](alternatives.md){.internal-link target=_blank}:
|
||||
|
||||
<blockquote markdown="1">
|
||||
|
||||
**FastAPI** не існував би без попередньої роботи інших.
|
||||
|
||||
Було створено багато інструментів, які надихнули на його створення.
|
||||
|
||||
Я роками уникав створення нового фреймворку. Спочатку я намагався вирішити всі можливості, які покриває **FastAPI**, використовуючи різні фреймворки, плагіни та інструменти.
|
||||
|
||||
Але в певний момент не залишилося іншого варіанту, окрім створити щось, що надає всі ці можливості, узявши найкращі ідеї з попередніх інструментів і поєднавши їх якнайкраще, використовуючи можливості самої мови, яких раніше взагалі не було (підказки типів Python 3.6+).
|
||||
|
||||
</blockquote>
|
||||
|
||||
## Дослідження { #investigation }
|
||||
|
||||
Використовуючи всі попередні альтернативи, я мав змогу повчитися в кожної, узяти ідеї й поєднати їх якнайкраще для себе та команд розробників, з якими працював.
|
||||
|
||||
Наприклад, було очевидно, що в ідеалі все має ґрунтуватися на стандартних підказках типів Python.
|
||||
|
||||
Також найкращим підходом було використовувати вже наявні стандарти.
|
||||
|
||||
Тож, ще до початку написання коду **FastAPI**, я провів кілька місяців, вивчаючи специфікації OpenAPI, Схеми JSON, OAuth2 тощо. Розуміючи їхні взаємозв'язки, перетини та відмінності.
|
||||
|
||||
## Проєктування { #design }
|
||||
|
||||
Потім я приділив час проєктуванню «API» для розробника, яке я хотів мати як користувач (як розробник, що використовує FastAPI).
|
||||
|
||||
Я протестував кілька ідей у найпопулярніших Python-редакторах: PyCharm, VS Code, редакторах на основі Jedi.
|
||||
|
||||
За даними <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" class="external-link" target="_blank">Python Developer Survey</a>, це охоплює близько 80% користувачів.
|
||||
|
||||
Це означає, що **FastAPI** спеціально тестувався з редакторами, якими користуються 80% розробників Python. І оскільки більшість інших редакторів працюють подібно, усі ці переваги мають працювати практично у всіх редакторах.
|
||||
|
||||
Так я зміг знайти найкращі способи максимально зменшити дублювання коду, забезпечити автодоповнення всюди, перевірки типів і помилок тощо.
|
||||
|
||||
І все це так, щоб надати найкращий досвід розробки для всіх розробників.
|
||||
|
||||
## Вимоги { #requirements }
|
||||
|
||||
Після перевірки кількох альтернатив я вирішив використовувати <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">**Pydantic**</a> через його переваги.
|
||||
|
||||
Потім я зробив внески до нього, щоб зробити його повністю сумісним із Схемою JSON, додати підтримку різних способів оголошення обмежень і поліпшити підтримку редакторів (перевірки типів, автодоповнення) на основі тестів у кількох редакторах.
|
||||
|
||||
Під час розробки я також зробив внески до <a href="https://www.starlette.dev/" class="external-link" target="_blank">**Starlette**</a>, іншої ключової залежності.
|
||||
|
||||
## Розробка { #development }
|
||||
|
||||
Коли я взявся безпосередньо за створення **FastAPI**, більшість складових уже були на місцях: дизайн визначено, вимоги та інструменти підготовлено, знання про стандарти й специфікації - чіткі та свіжі.
|
||||
|
||||
## Майбутнє { #future }
|
||||
|
||||
На цей момент уже зрозуміло, що **FastAPI** зі своїми ідеями корисний для багатьох.
|
||||
|
||||
Його обирають замість попередніх альтернатив, бо він краще відповідає багатьом сценаріям використання.
|
||||
|
||||
Багато розробників і команд уже залежать від **FastAPI** у своїх проєктах (включно зі мною та моєю командою).
|
||||
|
||||
Але попереду ще багато покращень і можливостей.
|
||||
|
||||
**FastAPI** має велике майбутнє.
|
||||
|
||||
І [ваша допомога](help-fastapi.md){.internal-link target=_blank} дуже цінується.
|
||||
17
docs/uk/docs/how-to/authentication-error-status-code.md
Normal file
17
docs/uk/docs/how-to/authentication-error-status-code.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Використовуйте старі коди статусу помилки автентифікації 403 { #use-old-403-authentication-error-status-codes }
|
||||
|
||||
До версії FastAPI `0.122.0`, коли інтегровані засоби безпеки повертали клієнту помилку після невдалої автентифікації, вони використовували HTTP код статусу `403 Forbidden`.
|
||||
|
||||
Починаючи з версії FastAPI `0.122.0`, вони використовують більш доречний HTTP код статусу `401 Unauthorized` і повертають змістовний заголовок `WWW-Authenticate` у відповіді, відповідно до специфікацій HTTP, <a href="https://datatracker.ietf.org/doc/html/rfc7235#section-3.1" class="external-link" target="_blank">RFC 7235</a>, <a href="https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized" class="external-link" target="_blank">RFC 9110</a>.
|
||||
|
||||
Але якщо з якоїсь причини ваші клієнти залежать від старої поведінки, ви можете повернутися до неї, переписавши метод `make_not_authenticated_error` у ваших класах безпеки.
|
||||
|
||||
Наприклад, ви можете створити підклас `HTTPBearer`, який повертатиме помилку `403 Forbidden` замість типового `401 Unauthorized`:
|
||||
|
||||
{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу, що функція повертає екземпляр винятку, вона не породжує його. Породження відбувається в іншій частині внутрішнього коду.
|
||||
|
||||
///
|
||||
56
docs/uk/docs/how-to/conditional-openapi.md
Normal file
56
docs/uk/docs/how-to/conditional-openapi.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Умовний OpenAPI { #conditional-openapi }
|
||||
|
||||
Якщо потрібно, ви можете використовувати налаштування і змінні оточення, щоб умовно налаштовувати OpenAPI залежно від середовища, а також повністю вимикати його.
|
||||
|
||||
## Про безпеку, API та документацію { #about-security-apis-and-docs }
|
||||
|
||||
Приховування інтерфейсів документації у продукційному середовищі *не має* бути способом захисту вашого API.
|
||||
|
||||
Це не додає жодної додаткової безпеки вашому API, *операції шляху* й надалі будуть доступні там, де вони є.
|
||||
|
||||
Якщо у вашому коді є вразливість, вона залишиться.
|
||||
|
||||
Приховування документації лише ускладнює розуміння того, як взаємодіяти з вашим API, і може ускладнити для вас його налагодження у продакшні. Це можна вважати просто формою <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">Безпека через неясність</a>.
|
||||
|
||||
Якщо ви хочете захистити ваш API, є кілька кращих дій, які ви можете зробити, наприклад:
|
||||
|
||||
- Переконайтеся, що у вас добре визначені моделі Pydantic для тіл запитів і відповідей.
|
||||
- Налаштуйте потрібні дозволи та ролі за допомогою залежностей.
|
||||
- Ніколи не зберігайте паролі у відкритому вигляді, лише хеші паролів.
|
||||
- Реалізуйте та використовуйте відомі криптографічні інструменти, як-от pwdlib і токени JWT.
|
||||
- Додайте більш детальний контроль дозволів із областями OAuth2 там, де це потрібно.
|
||||
- ...тощо.
|
||||
|
||||
Втім, у вас може бути дуже специфічний випадок використання, коли справді потрібно вимкнути документацію API для певного середовища (наприклад, для продакшну) або залежно від конфігурацій зі змінних оточення.
|
||||
|
||||
## Умовний OpenAPI з налаштувань і змінних оточення { #conditional-openapi-from-settings-and-env-vars }
|
||||
|
||||
Ви можете легко використати ті самі налаштування Pydantic, щоб налаштувати згенерований OpenAPI та інтерфейси документації.
|
||||
|
||||
Наприклад:
|
||||
|
||||
{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *}
|
||||
|
||||
Тут ми оголошуємо налаштування `openapi_url` з тим самим значенням за замовчуванням `"/openapi.json"`.
|
||||
|
||||
Потім ми використовуємо його під час створення застосунку `FastAPI`.
|
||||
|
||||
Далі ви можете вимкнути OpenAPI (включно з інтерфейсами документації), встановивши змінну оточення `OPENAPI_URL` у пусту строку, наприклад:
|
||||
|
||||
<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>
|
||||
|
||||
Потім, якщо ви перейдете за URL `/openapi.json`, `/docs` або `/redoc`, ви просто отримаєте помилку `404 Not Found` на кшталт:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Not Found"
|
||||
}
|
||||
```
|
||||
70
docs/uk/docs/how-to/configure-swagger-ui.md
Normal file
70
docs/uk/docs/how-to/configure-swagger-ui.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Налаштуйте Swagger UI { #configure-swagger-ui }
|
||||
|
||||
Ви можете налаштувати додаткові <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">параметри Swagger UI</a>.
|
||||
|
||||
Щоб їх налаштувати, передайте аргумент `swagger_ui_parameters` під час створення об’єкта додатка `FastAPI()` або до функції `get_swagger_ui_html()`.
|
||||
|
||||
`swagger_ui_parameters` отримує словник із налаштуваннями, що передаються безпосередньо до Swagger UI.
|
||||
|
||||
FastAPI перетворює ці налаштування на **JSON**, щоб зробити їх сумісними з JavaScript, оскільки саме це потрібно Swagger UI.
|
||||
|
||||
## Вимкніть підсвітку синтаксису { #disable-syntax-highlighting }
|
||||
|
||||
Наприклад, ви можете вимкнути підсвітку синтаксису в Swagger UI.
|
||||
|
||||
Без змін у налаштуваннях підсвітка синтаксису увімкнена за замовчуванням:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image02.png">
|
||||
|
||||
Але ви можете вимкнути її, встановивши `syntaxHighlight` у `False`:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *}
|
||||
|
||||
...після цього Swagger UI більше не показуватиме підсвітку синтаксису:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image03.png">
|
||||
|
||||
## Змініть тему { #change-the-theme }
|
||||
|
||||
Так само ви можете задати тему підсвітки синтаксису ключем `"syntaxHighlight.theme"` (зверніть увагу, що посередині є крапка):
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *}
|
||||
|
||||
Це налаштування змінить колірну тему підсвітки синтаксису:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image04.png">
|
||||
|
||||
## Змініть параметри Swagger UI за замовчуванням { #change-default-swagger-ui-parameters }
|
||||
|
||||
FastAPI містить деякі параметри конфігурації за замовчуванням, що підходять для більшості випадків.
|
||||
|
||||
Вони включають такі типові налаштування:
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
|
||||
|
||||
Ви можете переписати будь-яке з них, задавши інше значення в аргументі `swagger_ui_parameters`.
|
||||
|
||||
Наприклад, щоб вимкнути `deepLinking`, ви можете передати такі налаштування до `swagger_ui_parameters`:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *}
|
||||
|
||||
## Інші параметри Swagger UI { #other-swagger-ui-parameters }
|
||||
|
||||
Щоб побачити всі можливі налаштування, які ви можете використовувати, прочитайте офіційну <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">документацію щодо параметрів Swagger UI</a>.
|
||||
|
||||
## Налаштування лише для JavaScript { #javascript-only-settings }
|
||||
|
||||
Swagger UI також дозволяє інші налаштування як об’єкти, що є тільки для **JavaScript** (наприклад, функції JavaScript).
|
||||
|
||||
FastAPI також включає такі налаштування `presets`, що є лише для JavaScript:
|
||||
|
||||
```JavaScript
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
]
|
||||
```
|
||||
|
||||
Це об’єкти **JavaScript**, а не строки, тому ви не можете передати їх безпосередньо з коду Python.
|
||||
|
||||
Якщо вам потрібно використати такі налаштування лише для JavaScript, скористайтеся одним із методів вище. Повністю перепишіть операцію шляху Swagger UI та вручну напишіть потрібний JavaScript.
|
||||
185
docs/uk/docs/how-to/custom-docs-ui-assets.md
Normal file
185
docs/uk/docs/how-to/custom-docs-ui-assets.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Користувацькі статичні ресурси інтерфейсу документації (самохостинг) { #custom-docs-ui-static-assets-self-hosting }
|
||||
|
||||
Документація API використовує **Swagger UI** і **ReDoc**, і кожному з них потрібні файли JavaScript та CSS.
|
||||
|
||||
Типово ці файли віддаються з <abbr title="Content Delivery Network - Мережа доставки контенту: Сервіс, зазвичай складений з кількох серверів, що надає статичні файли, як-от JavaScript і CSS. Його зазвичай використовують, щоб віддавати ці файли з сервера, ближчого до клієнта, покращуючи продуктивність.">CDN</abbr>.
|
||||
|
||||
Але це можна налаштувати: ви можете вказати конкретний CDN або віддавати файли самостійно.
|
||||
|
||||
## Власний CDN для JavaScript і CSS { #custom-cdn-for-javascript-and-css }
|
||||
|
||||
Припустімо, що ви хочете використовувати інший <abbr title="Content Delivery Network - Мережа доставки контенту">CDN</abbr>, наприклад `https://unpkg.com/`.
|
||||
|
||||
Це може бути корисно, якщо, наприклад, ви живете в країні, що обмежує деякі URL-адреси.
|
||||
|
||||
### Вимкніть автоматичну документацію { #disable-the-automatic-docs }
|
||||
|
||||
Перший крок - вимкнути автоматичну документацію, адже типово вона використовує стандартний CDN.
|
||||
|
||||
Щоб їх вимкнути, встановіть їхні URL у `None` під час створення вашого застосунку `FastAPI`:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *}
|
||||
|
||||
### Додайте користувацьку документацію { #include-the-custom-docs }
|
||||
|
||||
Тепер ви можете створити *операції шляху* для користувацької документації.
|
||||
|
||||
Ви можете перевикористати внутрішні функції FastAPI для створення HTML-сторінок документації і передати їм потрібні аргументи:
|
||||
|
||||
- `openapi_url`: URL, за яким HTML-сторінка документації зможе отримати схему OpenAPI для вашого API. Тут можна використати атрибут `app.openapi_url`.
|
||||
- `title`: заголовок вашого API.
|
||||
- `oauth2_redirect_url`: тут можна використати `app.swagger_ui_oauth2_redirect_url`, щоб узяти значення за замовчуванням.
|
||||
- `swagger_js_url`: URL, за яким HTML для Swagger UI зможе отримати файл **JavaScript**. Це URL вашого користувацького CDN.
|
||||
- `swagger_css_url`: URL, за яким HTML для Swagger UI зможе отримати файл **CSS**. Це URL вашого користувацького CDN.
|
||||
|
||||
Аналогічно для ReDoc...
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
*Операція шляху* для `swagger_ui_redirect` - це допоміжний маршрут, коли ви використовуєте OAuth2.
|
||||
|
||||
Якщо ви інтегруєте ваш API з провайдером OAuth2, ви зможете автентифікуватися і повернутися до документації API з отриманими обліковими даними. І взаємодіяти з ним, використовуючи реальну автентифікацію OAuth2.
|
||||
|
||||
Swagger UI впорається з цим «за лаштунками», але йому потрібен цей «redirect» хелпер.
|
||||
|
||||
///
|
||||
|
||||
### Створіть операцію шляху для перевірки { #create-a-path-operation-to-test-it }
|
||||
|
||||
Тепер, щоб перевірити, що все працює, створіть *операцію шляху*:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *}
|
||||
|
||||
### Перевірте { #test-it }
|
||||
|
||||
Тепер ви маєте змогу відкрити документацію за <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> і перезавантажити сторінку, вона завантажить ці ресурси з нового CDN.
|
||||
|
||||
## Самохостинг JavaScript і CSS для документації { #self-hosting-javascript-and-css-for-docs }
|
||||
|
||||
Самохостинг JavaScript і CSS може бути корисним, якщо, наприклад, ваш застосунок має працювати офлайн, без доступу до Інтернету, або в локальній мережі.
|
||||
|
||||
Тут ви побачите, як віддавати ці файли самостійно, у тому самому застосунку FastAPI, і налаштувати документацію на їх використання.
|
||||
|
||||
### Структура файлів проєкту { #project-file-structure }
|
||||
|
||||
Припустімо, що структура файлів вашого проєкту виглядає так:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
```
|
||||
|
||||
Тепер створіть каталог для збереження цих статичних файлів.
|
||||
|
||||
Нова структура файлів може виглядати так:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static/
|
||||
```
|
||||
|
||||
### Завантажте файли { #download-the-files }
|
||||
|
||||
Завантажте статичні файли, потрібні для документації, і помістіть їх у каталог `static/`.
|
||||
|
||||
Ви, ймовірно, можете натиснути правою кнопкою на кожному посиланні і вибрати опцію на кшталт «Зберегти посилання як...».
|
||||
|
||||
**Swagger UI** використовує файли:
|
||||
|
||||
- <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a>
|
||||
- <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a>
|
||||
|
||||
А **ReDoc** використовує файл:
|
||||
|
||||
- <a href="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a>
|
||||
|
||||
Після цього ваша структура файлів може виглядати так:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static
|
||||
├── redoc.standalone.js
|
||||
├── swagger-ui-bundle.js
|
||||
└── swagger-ui.css
|
||||
```
|
||||
|
||||
### Обслуговуйте статичні файли { #serve-the-static-files }
|
||||
|
||||
- Імпортуйте `StaticFiles`.
|
||||
- Змонтуйте екземпляр `StaticFiles()` у певному шляху.
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *}
|
||||
|
||||
### Перевірте статичні файли { #test-the-static-files }
|
||||
|
||||
Запустіть ваш застосунок і перейдіть до <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>.
|
||||
|
||||
Ви маєте побачити дуже довгий файл JavaScript для **ReDoc**.
|
||||
|
||||
Він може починатися приблизно так:
|
||||
|
||||
```JavaScript
|
||||
/*! Для інформації про ліцензію див. redoc.standalone.js.LICENSE.txt */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
|
||||
...
|
||||
```
|
||||
|
||||
Це підтверджує, що ви можете віддавати статичні файли з вашого застосунку і що ви розмістили статичні файли для документації у правильному місці.
|
||||
|
||||
Тепер ми можемо налаштувати застосунок на використання цих статичних файлів для документації.
|
||||
|
||||
### Вимкніть автоматичну документацію для статичних файлів { #disable-the-automatic-docs-for-static-files }
|
||||
|
||||
Як і при використанні користувацького CDN, першим кроком є вимкнення автоматичної документації, оскільки типово вона використовує CDN.
|
||||
|
||||
Щоб їх вимкнути, встановіть їхні URL у `None` під час створення вашого застосунку `FastAPI`:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *}
|
||||
|
||||
### Додайте користувацьку документацію для статичних файлів { #include-the-custom-docs-for-static-files }
|
||||
|
||||
Аналогічно користувацькому CDN, тепер ви можете створити *операції шляху* для користувацької документації.
|
||||
|
||||
Знову ж, ви можете перевикористати внутрішні функції FastAPI для створення HTML-сторінок документації і передати їм потрібні аргументи:
|
||||
|
||||
- `openapi_url`: URL, за яким HTML-сторінка документації зможе отримати схему OpenAPI для вашого API. Тут можна використати атрибут `app.openapi_url`.
|
||||
- `title`: заголовок вашого API.
|
||||
- `oauth2_redirect_url`: тут можна використати `app.swagger_ui_oauth2_redirect_url`, щоб узяти значення за замовчуванням.
|
||||
- `swagger_js_url`: URL, за яким HTML для Swagger UI зможе отримати файл **JavaScript**. **Це той файл, який тепер віддає ваш власний застосунок**.
|
||||
- `swagger_css_url`: URL, за яким HTML для Swagger UI зможе отримати файл **CSS**. **Це той файл, який тепер віддає ваш власний застосунок**.
|
||||
|
||||
Аналогічно для ReDoc...
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
*Операція шляху* для `swagger_ui_redirect` - це допоміжний маршрут, коли ви використовуєте OAuth2.
|
||||
|
||||
Якщо ви інтегруєте ваш API з провайдером OAuth2, ви зможете автентифікуватися і повернутися до документації API з отриманими обліковими даними. І взаємодіяти з ним, використовуючи реальну автентифікацію OAuth2.
|
||||
|
||||
Swagger UI впорається з цим «за лаштунками», але йому потрібен цей «redirect» хелпер.
|
||||
|
||||
///
|
||||
|
||||
### Створіть операцію шляху для перевірки статичних файлів { #create-a-path-operation-to-test-static-files }
|
||||
|
||||
Тепер, щоб перевірити, що все працює, створіть *операцію шляху*:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *}
|
||||
|
||||
### Перевірте UI зі статичними файлами { #test-static-files-ui }
|
||||
|
||||
Тепер ви маєте змогу вимкнути WiFi, відкрити документацію за <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> і перезавантажити сторінку.
|
||||
|
||||
І навіть без Інтернету ви зможете побачити документацію для вашого API і взаємодіяти з ним.
|
||||
109
docs/uk/docs/how-to/custom-request-and-route.md
Normal file
109
docs/uk/docs/how-to/custom-request-and-route.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Користувацькі класи Request та APIRoute { #custom-request-and-apiroute-class }
|
||||
|
||||
У деяких випадках ви можете захотіти перевизначити логіку, яку використовують класи `Request` та `APIRoute`.
|
||||
|
||||
Зокрема, це може бути доброю альтернативою логіці в проміжному програмному забезпеченні.
|
||||
|
||||
Наприклад, якщо потрібно прочитати або змінити тіло запиту до того, як його обробить ваш застосунок.
|
||||
|
||||
/// danger | Обережно
|
||||
|
||||
Це «просунута» можливість.
|
||||
|
||||
Якщо ви тільки починаєте працювати з **FastAPI**, можливо, варто пропустити цей розділ.
|
||||
|
||||
///
|
||||
|
||||
## Випадки використання { #use-cases }
|
||||
|
||||
Деякі варіанти використання:
|
||||
|
||||
- Перетворення не-JSON тіл запитів на JSON (наприклад, <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>).
|
||||
- Розпакування тіл запитів, стиснених gzip.
|
||||
- Автоматичне логування всіх тіл запитів.
|
||||
|
||||
## Обробка користувацьких кодувань тіла запиту { #handling-custom-request-body-encodings }
|
||||
|
||||
Розгляньмо, як використати користувацький підклас `Request` для розпакування gzip-запитів.
|
||||
|
||||
А також підклас `APIRoute`, щоб застосувати цей користувацький клас запиту.
|
||||
|
||||
### Створіть користувацький клас `GzipRequest` { #create-a-custom-gziprequest-class }
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Це навчальний приклад, щоб продемонструвати принцип роботи. Якщо вам потрібна підтримка Gzip, скористайтеся вбудованим [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
Спочатку створимо клас `GzipRequest`, який перевизначить метод `Request.body()`, щоб розпаковувати тіло за наявності відповідного заголовка.
|
||||
|
||||
Якщо в заголовку немає `gzip`, він не намагатиметься розпаковувати тіло.
|
||||
|
||||
Таким чином один і той самий клас маршруту зможе обробляти як стиснені gzip, так і нестиснені запити.
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
|
||||
|
||||
### Створіть користувацький клас `GzipRoute` { #create-a-custom-gziproute-class }
|
||||
|
||||
Далі створимо користувацький підклас `fastapi.routing.APIRoute`, який використовуватиме `GzipRequest`.
|
||||
|
||||
Цього разу він перевизначить метод `APIRoute.get_route_handler()`.
|
||||
|
||||
Цей метод повертає функцію. І саме ця функція прийме запит і поверне відповідь.
|
||||
|
||||
Тут ми використовуємо її, щоб створити `GzipRequest` з початкового запиту.
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
У `Request` є атрибут `request.scope` - це просто Python `dict`, що містить метадані, пов'язані із запитом.
|
||||
|
||||
Також `Request` має `request.receive` - це функція для «отримання» тіла запиту.
|
||||
|
||||
`scope` `dict` і функція `receive` є частиною специфікації ASGI.
|
||||
|
||||
І саме ці дві сутності - `scope` та `receive` - потрібні для створення нового екземпляра `Request`.
|
||||
|
||||
Щоб дізнатися більше про `Request`, перегляньте <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">документацію Starlette про запити</a>.
|
||||
|
||||
///
|
||||
|
||||
Єдине, що робить інакше функція, повернена `GzipRequest.get_route_handler`, - перетворює `Request` на `GzipRequest`.
|
||||
|
||||
Завдяки цьому наш `GzipRequest` подбає про розпакування даних (за потреби) перед передаванням їх у наші *операції шляху*.
|
||||
|
||||
Після цього вся логіка обробки залишається тією самою.
|
||||
|
||||
А завдяки змінам у `GzipRequest.body` тіло запиту за потреби буде автоматично розпаковане під час завантаження **FastAPI**.
|
||||
|
||||
## Доступ до тіла запиту в обробнику виключень { #accessing-the-request-body-in-an-exception-handler }
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Щоб розв’язати це саме завдання, скоріш за все, простіше використати `body` у користувацькому обробнику `RequestValidationError` ([Обробка помилок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}).
|
||||
|
||||
Але цей приклад усе ще корисний і показує, як взаємодіяти з внутрішніми компонентами.
|
||||
|
||||
///
|
||||
|
||||
Ми також можемо скористатися цим підходом, щоб отримати доступ до тіла запиту в обробнику виключень.
|
||||
|
||||
Усе, що потрібно, - обробити запит усередині блоку `try`/`except`:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
|
||||
|
||||
Якщо станеться виключення, екземпляр `Request` усе ще буде у видимості, тож ми зможемо прочитати й використати тіло запиту під час обробки помилки:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
|
||||
|
||||
## Користувацький клас `APIRoute` у маршрутизаторі { #custom-apiroute-class-in-a-router }
|
||||
|
||||
Можна також встановити параметр `route_class` у `APIRouter`:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
|
||||
|
||||
У цьому прикладі *операції шляху* в `router` використовуватимуть користувацький клас `TimedRoute` і матимуть додатковий заголовок відповіді `X-Response-Time` із часом, витраченим на формування відповіді:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
|
||||
80
docs/uk/docs/how-to/extending-openapi.md
Normal file
80
docs/uk/docs/how-to/extending-openapi.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Розширення OpenAPI { #extending-openapi }
|
||||
|
||||
У деяких випадках вам може знадобитися змінити згенеровану схему OpenAPI.
|
||||
|
||||
У цьому розділі ви побачите як це зробити.
|
||||
|
||||
## Звичайний процес { #the-normal-process }
|
||||
|
||||
Звичайний (типовий) процес такий.
|
||||
|
||||
Застосунок `FastAPI` (екземпляр) має метод `.openapi()`, який має повертати схему OpenAPI.
|
||||
|
||||
Під час створення об'єкта застосунку реєструється *операція шляху* для `/openapi.json` (або для того значення, яке ви встановили в `openapi_url`).
|
||||
|
||||
Вона просто повертає відповідь JSON з результатом методу `.openapi()` застосунку.
|
||||
|
||||
Типово метод `.openapi()` перевіряє властивість `.openapi_schema`, і якщо там вже є дані, повертає їх.
|
||||
|
||||
Якщо ні, він генерує їх за допомогою утилітарної функції `fastapi.openapi.utils.get_openapi`.
|
||||
|
||||
Функція `get_openapi()` приймає такі параметри:
|
||||
|
||||
- `title`: Заголовок OpenAPI, показується в документації.
|
||||
- `version`: Версія вашого API, напр. `2.5.0`.
|
||||
- `openapi_version`: Версія специфікації OpenAPI, що використовується. Типово остання: `3.1.0`.
|
||||
- `summary`: Короткий підсумок API.
|
||||
- `description`: Опис вашого API; може містити markdown і буде показаний у документації.
|
||||
- `routes`: Список маршрутів, це кожна з зареєстрованих *операцій шляху*. Їх беруть з `app.routes`.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Параметр `summary` доступний в OpenAPI 3.1.0 і вище, підтримується FastAPI 0.99.0 і вище.
|
||||
|
||||
///
|
||||
|
||||
## Переписування типових значень { #overriding-the-defaults }
|
||||
|
||||
Використовуючи наведене вище, ви можете скористатися тією ж утилітарною функцією для генерації схеми OpenAPI і переписати потрібні частини.
|
||||
|
||||
Наприклад, додаймо <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">розширення OpenAPI для ReDoc для додавання власного логотипа</a>.
|
||||
|
||||
### Звичайний **FastAPI** { #normal-fastapi }
|
||||
|
||||
Спочатку напишіть ваш застосунок **FastAPI** як зазвичай:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *}
|
||||
|
||||
### Згенерувати схему OpenAPI { #generate-the-openapi-schema }
|
||||
|
||||
Далі використайте ту ж утилітарну функцію для генерації схеми OpenAPI всередині функції `custom_openapi()`:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *}
|
||||
|
||||
### Змінити схему OpenAPI { #modify-the-openapi-schema }
|
||||
|
||||
Тепер можна додати розширення ReDoc, додавши власний `x-logo` до «об'єкта» `info` у схемі OpenAPI:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *}
|
||||
|
||||
### Кешувати схему OpenAPI { #cache-the-openapi-schema }
|
||||
|
||||
Ви можете використовувати властивість `.openapi_schema` як «кеш» для збереження згенерованої схеми.
|
||||
|
||||
Так вашому застосунку не доведеться щоразу генерувати схему, коли користувач відкриває документацію вашого API.
|
||||
|
||||
Вона буде згенерована лише один раз, а потім та сама закешована схема використовуватиметься для наступних запитів.
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *}
|
||||
|
||||
### Переписати метод { #override-the-method }
|
||||
|
||||
Тепер ви можете замінити метод `.openapi()` вашою новою функцією.
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *}
|
||||
|
||||
### Перевірте { #check-it }
|
||||
|
||||
Коли ви перейдете за адресою <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>, побачите, що використовується ваш власний логотип (у цьому прикладі логотип **FastAPI**):
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image01.png">
|
||||
39
docs/uk/docs/how-to/general.md
Normal file
39
docs/uk/docs/how-to/general.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Загальне - Як зробити - Рецепти { #general-how-to-recipes }
|
||||
|
||||
Нижче наведено кілька вказівок до інших розділів документації для загальних або частих питань.
|
||||
|
||||
## Фільтрування даних - Безпека { #filter-data-security }
|
||||
|
||||
Щоб гарантувати, що ви не повертаєте більше даних, ніж слід, прочитайте документацію [Навчальний посібник - Модель відповіді - Тип повернення](../tutorial/response-model.md){.internal-link target=_blank}.
|
||||
|
||||
## Мітки документації - OpenAPI { #documentation-tags-openapi }
|
||||
|
||||
Щоб додати мітки до ваших *операцій шляху* та згрупувати їх в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Мітки](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}.
|
||||
|
||||
## Короткий опис і опис - OpenAPI { #documentation-summary-and-description-openapi }
|
||||
|
||||
Щоб додати короткий опис і опис до ваших *операцій шляху* і показати їх в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Короткий опис і опис](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}.
|
||||
|
||||
## Опис відповіді в документації - OpenAPI { #documentation-response-description-openapi }
|
||||
|
||||
Щоб визначити опис відповіді, що відображається в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Опис відповіді](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}.
|
||||
|
||||
## Позначити застарілою *операцію шляху* - OpenAPI { #documentation-deprecate-a-path-operation-openapi }
|
||||
|
||||
Щоб позначити *операцію шляху* як застарілу і показати це в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Позначення як застаріле](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
|
||||
|
||||
## Перетворити будь-які дані на сумісні з JSON { #convert-any-data-to-json-compatible }
|
||||
|
||||
Щоб перетворити будь-які дані на сумісні з JSON, прочитайте документацію [Навчальний посібник - Кодувальник, сумісний з JSON](../tutorial/encoder.md){.internal-link target=_blank}.
|
||||
|
||||
## Метадані OpenAPI - Документація { #openapi-metadata-docs }
|
||||
|
||||
Щоб додати метадані до вашої схеми OpenAPI, зокрема ліцензію, версію, контактні дані тощо, прочитайте документацію [Навчальний посібник - Метадані та URL документації](../tutorial/metadata.md){.internal-link target=_blank}.
|
||||
|
||||
## Власний URL OpenAPI { #openapi-custom-url }
|
||||
|
||||
Щоб налаштувати URL OpenAPI (або прибрати його), прочитайте документацію [Навчальний посібник - Метадані та URL документації](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}.
|
||||
|
||||
## URL документації OpenAPI { #openapi-docs-urls }
|
||||
|
||||
Щоб оновити URL, які використовуються для автоматично згенерованих інтерфейсів користувача документації, прочитайте документацію [Навчальний посібник - Метадані та URL документації](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
|
||||
60
docs/uk/docs/how-to/graphql.md
Normal file
60
docs/uk/docs/how-to/graphql.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# GraphQL { #graphql }
|
||||
|
||||
Оскільки FastAPI базується на стандарті ASGI, дуже просто інтегрувати будь-яку бібліотеку GraphQL, сумісну з ASGI.
|
||||
|
||||
Ви можете поєднувати звичайні *операції шляху* FastAPI з GraphQL в одному застосунку.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
GraphQL розв’язує деякі дуже специфічні сценарії використання.
|
||||
|
||||
Порівняно зі звичайними веб-API він має переваги та недоліки.
|
||||
|
||||
Переконайтеся, що переваги для вашого випадку використання переважають недоліки. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Бібліотеки GraphQL { #graphql-libraries }
|
||||
|
||||
Ось деякі бібліотеки GraphQL з підтримкою ASGI. Ви можете використовувати їх із FastAPI:
|
||||
|
||||
* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> 🍓
|
||||
* З <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">документацією для FastAPI</a>
|
||||
* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a>
|
||||
* З <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">документацією для FastAPI</a>
|
||||
* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a>
|
||||
* З <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> для інтеграції з ASGI
|
||||
* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>
|
||||
* З <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>
|
||||
|
||||
## GraphQL зі Strawberry { #graphql-with-strawberry }
|
||||
|
||||
Якщо вам потрібен або ви хочете використовувати GraphQL, <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> - рекомендована бібліотека, адже її дизайн найближчий до дизайну FastAPI; усе базується на анотаціях типів.
|
||||
|
||||
Залежно від вашого сценарію використання ви можете надати перевагу іншій бібліотеці, але якби ви запитали мене, я, ймовірно, порадив би спробувати Strawberry.
|
||||
|
||||
Ось невеликий приклад того, як інтегрувати Strawberry з FastAPI:
|
||||
|
||||
{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *}
|
||||
|
||||
Більше про Strawberry ви можете дізнатися в <a href="https://strawberry.rocks/" class="external-link" target="_blank">документації Strawberry</a>.
|
||||
|
||||
І також <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">документацію про Strawberry з FastAPI</a>.
|
||||
|
||||
## Застарілий `GraphQLApp` зі Starlette { #older-graphqlapp-from-starlette }
|
||||
|
||||
Попередні версії Starlette містили клас `GraphQLApp` для інтеграції з <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>.
|
||||
|
||||
Його вилучено з Starlette як застарілий, але якщо у вас є код, що його використовував, ви можете легко мігрувати на <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>, який покриває той самий сценарій використання та має майже ідентичний інтерфейс.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо вам потрібен GraphQL, я все ж рекомендую звернути увагу на <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a>, адже він базується на анотаціях типів, а не на власних класах і типах.
|
||||
|
||||
///
|
||||
|
||||
## Дізнайтеся більше { #learn-more }
|
||||
|
||||
Ви можете дізнатися більше про GraphQL в <a href="https://graphql.org/" class="external-link" target="_blank">офіційній документації GraphQL</a>.
|
||||
|
||||
Також ви можете почитати більше про кожну з цих бібліотек за наведеними посиланнями.
|
||||
13
docs/uk/docs/how-to/index.md
Normal file
13
docs/uk/docs/how-to/index.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Як зробити - Рецепти { #how-to-recipes }
|
||||
|
||||
Тут ви побачите різні рецепти або керівництва «як зробити» з **різних тем**.
|
||||
|
||||
Більшість із цих ідей є більш-менш **незалежними**, і в більшості випадків вам слід вивчати їх лише тоді, коли вони безпосередньо стосуються **вашого проєкту**.
|
||||
|
||||
Якщо щось здається цікавим і корисним для вашого проєкту, сміливо перевірте це, інакше, ймовірно, просто пропустіть.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви хочете **вивчити FastAPI** у структурований спосіб (рекомендується), натомість прочитайте [Навчальний посібник - Посібник користувача](../tutorial/index.md){.internal-link target=_blank} розділ за розділом.
|
||||
|
||||
///
|
||||
135
docs/uk/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
Normal file
135
docs/uk/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Перехід з Pydantic v1 на Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 }
|
||||
|
||||
Якщо у вас стара програма FastAPI, можливо, ви використовуєте Pydantic версії 1.
|
||||
|
||||
FastAPI версії 0.100.0 підтримував як Pydantic v1, так і v2. Використовувалася та версія, яку ви встановили.
|
||||
|
||||
FastAPI версії 0.119.0 запровадив часткову підтримку Pydantic v1 всередині Pydantic v2 (як `pydantic.v1`), щоб спростити перехід на v2.
|
||||
|
||||
FastAPI 0.126.0 припинив підтримку Pydantic v1, водночас ще певний час підтримував `pydantic.v1`.
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Команда Pydantic припинила підтримку Pydantic v1 для останніх версій Python, починаючи з Python 3.14.
|
||||
|
||||
Це стосується і `pydantic.v1`, який більше не підтримується в Python 3.14 і новіших.
|
||||
|
||||
Якщо ви хочете використовувати найновіші можливості Python, вам потрібно переконатися, що ви використовуєте Pydantic v2.
|
||||
|
||||
///
|
||||
|
||||
Якщо у вас стара програма FastAPI з Pydantic v1, нижче я покажу, як мігрувати на Pydantic v2, а також можливості FastAPI 0.119.0, які допоможуть з поступовою міграцією.
|
||||
|
||||
## Офіційний посібник { #official-guide }
|
||||
|
||||
У Pydantic є офіційний <a href="https://docs.pydantic.dev/latest/migration/" class="external-link" target="_blank">Посібник з міграції</a> з v1 на v2.
|
||||
|
||||
Там описано, що змінилося, як перевірки тепер стали коректнішими та суворішими, можливі застереження тощо.
|
||||
|
||||
Прочитайте його, щоб краще зрозуміти зміни.
|
||||
|
||||
## Тести { #tests }
|
||||
|
||||
Переконайтеся, що у вашій програмі є [тести](../tutorial/testing.md){.internal-link target=_blank} і що ви запускаєте їх у системі безперервної інтеграції (CI).
|
||||
|
||||
Так ви зможете виконати оновлення і впевнитися, що все працює як очікується.
|
||||
|
||||
## `bump-pydantic` { #bump-pydantic }
|
||||
|
||||
У багатьох випадках, якщо ви використовуєте звичайні моделі Pydantic без налаштувань, більшу частину процесу міграції з Pydantic v1 на Pydantic v2 можна автоматизувати.
|
||||
|
||||
Ви можете скористатися <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a> від тієї ж команди Pydantic.
|
||||
|
||||
Цей інструмент допоможе автоматично змінити більшість коду, який потрібно змінити.
|
||||
|
||||
Після цього запустіть тести й перевірте, чи все працює. Якщо так - ви все завершили. 😎
|
||||
|
||||
## Pydantic v1 у v2 { #pydantic-v1-in-v2 }
|
||||
|
||||
Pydantic v2 містить усе з Pydantic v1 як підмодуль `pydantic.v1`. Але це більше не підтримується у версіях Python вище 3.13.
|
||||
|
||||
Це означає, що ви можете встановити останню версію Pydantic v2 та імпортувати і використовувати старі компоненти Pydantic v1 з цього підмодуля, ніби у вас встановлено старий Pydantic v1.
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
|
||||
|
||||
### Підтримка FastAPI для Pydantic v1 у v2 { #fastapi-support-for-pydantic-v1-in-v2 }
|
||||
|
||||
Починаючи з FastAPI 0.119.0, також є часткова підтримка Pydantic v1 всередині Pydantic v2, щоб спростити перехід на v2.
|
||||
|
||||
Тож ви можете оновити Pydantic до останньої версії 2 і змінити імпорти на використання підмодуля `pydantic.v1`, і в багатьох випадках усе просто запрацює.
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Майте на увазі, що оскільки команда Pydantic більше не підтримує Pydantic v1 у нових версіях Python, починаючи з Python 3.14, використання `pydantic.v1` також не підтримується в Python 3.14 і новіших.
|
||||
|
||||
///
|
||||
|
||||
### Pydantic v1 і v2 в одній програмі { #pydantic-v1-and-v2-on-the-same-app }
|
||||
|
||||
Pydantic не підтримує ситуацію, коли модель Pydantic v2 має власні поля, визначені як моделі Pydantic v1, або навпаки.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "❌ Not Supported"
|
||||
direction TB
|
||||
subgraph V2["Pydantic v2 Model"]
|
||||
V1Field["Pydantic v1 Model"]
|
||||
end
|
||||
subgraph V1["Pydantic v1 Model"]
|
||||
V2Field["Pydantic v2 Model"]
|
||||
end
|
||||
end
|
||||
|
||||
style V2 fill:#f9fff3
|
||||
style V1 fill:#fff6f0
|
||||
style V1Field fill:#fff6f0
|
||||
style V2Field fill:#f9fff3
|
||||
```
|
||||
|
||||
...але в одній програмі ви можете мати окремі моделі на Pydantic v1 і v2.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "✅ Supported"
|
||||
direction TB
|
||||
subgraph V2["Pydantic v2 Model"]
|
||||
V2Field["Pydantic v2 Model"]
|
||||
end
|
||||
subgraph V1["Pydantic v1 Model"]
|
||||
V1Field["Pydantic v1 Model"]
|
||||
end
|
||||
end
|
||||
|
||||
style V2 fill:#f9fff3
|
||||
style V1 fill:#fff6f0
|
||||
style V1Field fill:#fff6f0
|
||||
style V2Field fill:#f9fff3
|
||||
```
|
||||
|
||||
У деяких випадках можна навіть використовувати моделі і Pydantic v1, і v2 в одній операції шляху у вашій програмі FastAPI:
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
|
||||
|
||||
У наведеному вище прикладі вхідна модель - це модель Pydantic v1, а вихідна модель (визначена як `response_model=ItemV2`) - модель Pydantic v2.
|
||||
|
||||
### Параметри Pydantic v1 { #pydantic-v1-parameters }
|
||||
|
||||
Якщо вам потрібно використовувати деякі специфічні для FastAPI інструменти для параметрів, як-от `Body`, `Query`, `Form` тощо, з моделями Pydantic v1, ви можете імпортувати їх з `fastapi.temp_pydantic_v1_params`, поки завершуєте міграцію на Pydantic v2:
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
|
||||
|
||||
### Покрокова міграція { #migrate-in-steps }
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Спершу спробуйте `bump-pydantic`: якщо ваші тести проходять і все працює - ви впоралися однією командою. ✨
|
||||
|
||||
///
|
||||
|
||||
Якщо `bump-pydantic` не підходить для вашого випадку, скористайтеся підтримкою одночасно Pydantic v1 і v2 в одній програмі, щоб виконати поступову міграцію на Pydantic v2.
|
||||
|
||||
Спочатку ви можете оновити Pydantic до останньої версії 2 і змінити імпорти на `pydantic.v1` для всіх ваших моделей.
|
||||
|
||||
Потім починайте переносити моделі з Pydantic v1 на v2 групами, поступовими кроками. 🚶
|
||||
101
docs/uk/docs/how-to/separate-openapi-schemas.md
Normal file
101
docs/uk/docs/how-to/separate-openapi-schemas.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Окремі схеми OpenAPI для введення та виведення, чи ні { #separate-openapi-schemas-for-input-and-output-or-not }
|
||||
|
||||
Відколи вийшов **Pydantic v2**, згенерований OpenAPI став трохи точнішим і більш коректним, ніж раніше. 😎
|
||||
|
||||
Насправді подекуди буде навіть **дві схеми JSON** в OpenAPI для тієї самої моделі Pydantic: для введення та для виведення - залежно від наявності значень за замовчуванням.
|
||||
|
||||
Розгляньмо, як це працює, і як це змінити за потреби.
|
||||
|
||||
## Моделі Pydantic для введення та виведення { #pydantic-models-for-input-and-output }
|
||||
|
||||
Припустімо, у вас є модель Pydantic зі значеннями за замовчуванням, наприклад:
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
|
||||
|
||||
### Модель для введення { #model-for-input }
|
||||
|
||||
Якщо ви використовуєте цю модель як введення, як тут:
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *}
|
||||
|
||||
…тоді поле `description` не буде обов'язковим, адже воно має значення за замовчуванням `None`.
|
||||
|
||||
### Модель для введення в документації { #input-model-in-docs }
|
||||
|
||||
У документації ви побачите, що поле `description` не має **червоної зірочки** - воно не позначене як обов'язкове:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image01.png">
|
||||
</div>
|
||||
|
||||
### Модель для виведення { #model-for-output }
|
||||
|
||||
Але якщо ви використовуєте цю саму модель для виведення, як тут:
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *}
|
||||
|
||||
…тоді, оскільки `description` має значення за замовчуванням, якщо ви нічого не повернете для цього поля, воно все одно матиме це **значення за замовчуванням**.
|
||||
|
||||
### Модель для даних відповіді при виведенні { #model-for-output-response-data }
|
||||
|
||||
Якщо ви скористаєтеся документацією та перевірите відповідь, навіть попри те, що код нічого не додав в одне з полів `description`, JSON-відповідь містить значення за замовчуванням (`null`):
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image02.png">
|
||||
</div>
|
||||
|
||||
Це означає, що воно **завжди матиме значення** - просто іноді це значення може бути `None` (або `null` у JSON).
|
||||
|
||||
Отже, клієнтам, які використовують ваш API, не потрібно перевіряти, чи існує значення - вони можуть **припустити, що поле завжди буде присутнє**, але в окремих випадках воно матиме значення за замовчуванням `None`.
|
||||
|
||||
У OpenAPI це описується тим, що поле позначається як **обов'язкове**, адже воно завжди присутнє.
|
||||
|
||||
Тому Схема JSON для моделі може відрізнятися залежно від того, чи використовується вона для **введення або виведення**:
|
||||
- для **введення** `description` не буде обов'язковим
|
||||
- для **виведення** воно буде **обов'язковим** (і можливо `None`, або в термінах JSON - `null`)
|
||||
|
||||
### Модель для виведення в документації { #model-for-output-in-docs }
|
||||
|
||||
У документації ви також можете перевірити модель для виведення: **і** `name`, і `description` позначені як **обов'язкові** червоною зірочкою:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image03.png">
|
||||
</div>
|
||||
|
||||
### Модель для введення та виведення в документації { #model-for-input-and-output-in-docs }
|
||||
|
||||
Якщо відкрити всі наявні Схеми (схеми JSON) в OpenAPI, то ви побачите дві: `Item-Input` і `Item-Output`.
|
||||
|
||||
Для `Item-Input` поле `description` **не є обов'язковим** - червоної зірочки немає.
|
||||
|
||||
А для `Item-Output` `description` **є обов'язковим** - є червона зірочка.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image04.png">
|
||||
</div>
|
||||
|
||||
Завдяки цій можливості у **Pydantic v2** ваша документація API стає більш **точною**, а якщо у вас є згенеровані клієнти та SDK, вони також будуть точнішими - з кращим **досвідом розробника** та узгодженістю. 🎉
|
||||
|
||||
## Не розділяти схеми { #do-not-separate-schemas }
|
||||
|
||||
Втім іноді може знадобитися мати **одну й ту саму схему для введення та виведення**.
|
||||
|
||||
Основний випадок - коли у вас вже є згенерований клієнтський код/SDK, і ви поки не бажаєте оновлювати весь такий згенерований код/SDK. Ймовірно, ви зробите це пізніше, але не зараз.
|
||||
|
||||
У такому разі ви можете вимкнути цю можливість у **FastAPI** параметром `separate_input_output_schemas=False`.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Підтримку `separate_input_output_schemas` додано у FastAPI `0.102.0`. 🤓
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *}
|
||||
|
||||
### Одна схема для моделей введення та виведення в документації { #same-schema-for-input-and-output-models-in-docs }
|
||||
|
||||
Тепер для цієї моделі буде лише одна спільна схема для введення та виведення - тільки `Item`, і в ній `description` буде **не обов'язковим**:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
|
||||
</div>
|
||||
7
docs/uk/docs/how-to/testing-database.md
Normal file
7
docs/uk/docs/how-to/testing-database.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Тестування бази даних { #testing-a-database }
|
||||
|
||||
Ви можете ознайомитися з базами даних, SQL і SQLModel у <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документації SQLModel</a>. 🤓
|
||||
|
||||
Є невеликий <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">навчальний посібник про використання SQLModel з FastAPI</a>. ✨
|
||||
|
||||
Цей навчальний посібник містить розділ про <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">тестування баз даних SQL</a>. 😎
|
||||
28
docs/uk/docs/project-generation.md
Normal file
28
docs/uk/docs/project-generation.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Шаблон Full Stack FastAPI { #full-stack-fastapi-template }
|
||||
|
||||
Шаблони, хоча зазвичай постачаються з певним налаштуванням, спроєктовані бути гнучкими та налаштовуваними. Це дає змогу змінювати їх і адаптувати до вимог вашого проєкту, що робить їх чудовою відправною точкою. 🏁
|
||||
|
||||
Ви можете використати цей шаблон для старту, адже в ньому вже виконано значну частину початкового налаштування, безпеки, роботи з базою даних і деяких кінцевих точок API.
|
||||
|
||||
Репозиторій GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Шаблон Full Stack FastAPI</a>
|
||||
|
||||
## Шаблон Full Stack FastAPI - стек технологій і можливості { #full-stack-fastapi-template-technology-stack-and-features }
|
||||
|
||||
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/uk) для бекенд API на Python.
|
||||
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) для взаємодії з SQL-базою даних у Python (ORM).
|
||||
- 🔍 [Pydantic](https://docs.pydantic.dev), який використовується FastAPI, для перевірки даних і керування налаштуваннями.
|
||||
- 💾 [PostgreSQL](https://www.postgresql.org) як SQL-база даних.
|
||||
- 🚀 [React](https://react.dev) для фронтенду.
|
||||
- 💃 Використання TypeScript, хуків, Vite та інших частин сучасного фронтенд-стеку.
|
||||
- 🎨 [Tailwind CSS](https://tailwindcss.com) і [shadcn/ui](https://ui.shadcn.com) для фронтенд-компонентів.
|
||||
- 🤖 Автоматично згенерований фронтенд-клієнт.
|
||||
- 🧪 [Playwright](https://playwright.dev) для End-to-End тестування.
|
||||
- 🦇 Підтримка темного режиму.
|
||||
- 🐋 [Docker Compose](https://www.docker.com) для розробки та продакшену.
|
||||
- 🔒 Безпечне хешування паролів за замовчуванням.
|
||||
- 🔑 Автентифікація JWT (JSON Web Token).
|
||||
- 📫 Відновлення пароля на основі електронної пошти.
|
||||
- ✅ Тести з [Pytest](https://pytest.org).
|
||||
- 📞 [Traefik](https://traefik.io) як зворотний представник / балансувальник навантаження.
|
||||
- 🚢 Інструкції з розгортання з Docker Compose, включно з налаштуванням фронтенд-представника Traefik для автоматичних HTTPS-сертифікатів.
|
||||
- 🏭 CI (безперервна інтеграція) і CD (безперервне розгортання) на базі GitHub Actions.
|
||||
3
docs/uk/docs/resources/index.md
Normal file
3
docs/uk/docs/resources/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Ресурси { #resources }
|
||||
|
||||
Додаткові ресурси, зовнішні посилання та інше. ✈️
|
||||
11
docs/uk/docs/translation-banner.md
Normal file
11
docs/uk/docs/translation-banner.md
Normal file
@@ -0,0 +1,11 @@
|
||||
/// details | 🌐 Переклад ШІ та людьми
|
||||
|
||||
Цей переклад виконано ШІ під керівництвом людей. 🤝
|
||||
|
||||
Можливі помилки через неправильне розуміння початкового змісту або неприродні формулювання тощо. 🤖
|
||||
|
||||
Ви можете покращити цей переклад, [допомігши нам краще спрямовувати AI LLM](https://fastapi.tiangolo.com/uk/contributing/#translations).
|
||||
|
||||
[Англійська версія](ENGLISH_VERSION_URL)
|
||||
|
||||
///
|
||||
504
docs/uk/docs/tutorial/bigger-applications.md
Normal file
504
docs/uk/docs/tutorial/bigger-applications.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# Більші застосунки - кілька файлів { #bigger-applications-multiple-files }
|
||||
|
||||
Якщо ви створюєте застосунок або веб-API, рідко вдається вмістити все в один файл.
|
||||
|
||||
**FastAPI** надає зручний інструмент для структурування вашого застосунку, зберігаючи всю гнучкість.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Якщо ви прийшли з Flask, це еквівалент «Blueprints» у Flask.
|
||||
|
||||
///
|
||||
|
||||
## Приклад структури файлів { #an-example-file-structure }
|
||||
|
||||
Припустімо, у вас така структура файлів:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ ├── dependencies.py
|
||||
│ └── routers
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── items.py
|
||||
│ │ └── users.py
|
||||
│ └── internal
|
||||
│ ├── __init__.py
|
||||
│ └── admin.py
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Тут кілька файлів `__init__.py`: по одному в кожному каталозі та підкаталозі.
|
||||
|
||||
Саме це дозволяє імпортувати код з одного файлу в інший.
|
||||
|
||||
Наприклад, у `app/main.py` ви можете мати рядок:
|
||||
|
||||
```
|
||||
from app.routers import items
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
* Каталог `app` містить усе. І він має порожній файл `app/__init__.py`, тож це «пакет Python» (збірка «модулів Python»): `app`.
|
||||
* Він містить файл `app/main.py`. Оскільки він усередині пакета Python (каталог з файлом `__init__.py`), це «модуль» цього пакета: `app.main`.
|
||||
* Є також файл `app/dependencies.py`, так само як `app/main.py`, це «модуль»: `app.dependencies`.
|
||||
* Є підкаталог `app/routers/` з іншим файлом `__init__.py`, отже це «підпакет Python»: `app.routers`.
|
||||
* Файл `app/routers/items.py` знаходиться в пакеті `app/routers/`, отже це підмодуль: `app.routers.items`.
|
||||
* Так само і `app/routers/users.py`, це інший підмодуль: `app.routers.users`.
|
||||
* Є також підкаталог `app/internal/` з іншим файлом `__init__.py`, отже це інший «підпакет Python»: `app.internal`.
|
||||
* І файл `app/internal/admin.py` - ще один підмодуль: `app.internal.admin`.
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
|
||||
Та сама структура файлів з коментарями:
|
||||
|
||||
```bash
|
||||
.
|
||||
├── app # «app» - це пакет Python
|
||||
│ ├── __init__.py # цей файл робить «app» «пакетом Python»
|
||||
│ ├── main.py # модуль «main», напр. import app.main
|
||||
│ ├── dependencies.py # модуль «dependencies», напр. import app.dependencies
|
||||
│ └── routers # «routers» - це «підпакет Python»
|
||||
│ │ ├── __init__.py # робить «routers» «підпакетом Python»
|
||||
│ │ ├── items.py # підмодуль «items», напр. import app.routers.items
|
||||
│ │ └── users.py # підмодуль «users», напр. import app.routers.users
|
||||
│ └── internal # «internal» - це «підпакет Python»
|
||||
│ ├── __init__.py # робить «internal» «підпакетом Python»
|
||||
│ └── admin.py # підмодуль «admin», напр. import app.internal.admin
|
||||
```
|
||||
|
||||
## `APIRouter` { #apirouter }
|
||||
|
||||
Припустімо, файл, присвячений обробці лише користувачів, - це підмодуль у `/app/routers/users.py`.
|
||||
|
||||
Ви хочете мати *операції шляху*, пов'язані з користувачами, відокремлено від решти коду, щоб зберегти порядок.
|
||||
|
||||
Але це все одно частина того самого застосунку/веб-API **FastAPI** (це частина того самого «пакета Python»).
|
||||
|
||||
Ви можете створювати *операції шляху* для цього модуля, використовуючи `APIRouter`.
|
||||
|
||||
### Імпортуйте `APIRouter` { #import-apirouter }
|
||||
|
||||
Імпортуйте його та створіть «екземпляр» так само, як ви б робили з класом `FastAPI`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *}
|
||||
|
||||
### *Операції шляху* з `APIRouter` { #path-operations-with-apirouter }
|
||||
|
||||
Потім використовуйте його для оголошення *операцій шляху*.
|
||||
|
||||
Використовуйте його так само, як і клас `FastAPI`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
|
||||
|
||||
Можете думати про `APIRouter` як про «міні `FastAPI`».
|
||||
|
||||
Підтримуються ті самі опції.
|
||||
|
||||
Ті самі `parameters`, `responses`, `dependencies`, `tags` тощо.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
У цьому прикладі змінна називається `router`, але ви можете назвати її як завгодно.
|
||||
|
||||
///
|
||||
|
||||
Ми включимо цей `APIRouter` у основний застосунок `FastAPI`, але спочатку розгляньмо залежності та інший `APIRouter`.
|
||||
|
||||
## Залежності { #dependencies }
|
||||
|
||||
Бачимо, що нам знадобляться кілька залежностей, які використовуються в різних місцях застосунку.
|
||||
|
||||
Тож помістимо їх у власний модуль `dependencies` (`app/dependencies.py`).
|
||||
|
||||
Тепер використаємо просту залежність для читання користувацького заголовка `X-Token`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ми використовуємо вигаданий заголовок, щоб спростити приклад.
|
||||
|
||||
Але в реальних випадках ви отримаєте кращі результати, використовуючи інтегровані [засоби безпеки](security/index.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Інший модуль з `APIRouter` { #another-module-with-apirouter }
|
||||
|
||||
Припустімо, у вас також є кінцеві точки для обробки «items» у модулі `app/routers/items.py`.
|
||||
|
||||
У вас є *операції шляху* для:
|
||||
|
||||
* `/items/`
|
||||
* `/items/{item_id}`
|
||||
|
||||
Структура така сама, як у `app/routers/users.py`.
|
||||
|
||||
Але ми хочемо бути розумнішими й трохи спростити код.
|
||||
|
||||
Ми знаємо, що всі *операції шляху* в цьому модулі мають однакові:
|
||||
|
||||
* Префікс шляху `prefix`: `/items`.
|
||||
* `tags`: (лише одна мітка: `items`).
|
||||
* Додаткові `responses`.
|
||||
* `dependencies`: усім потрібна залежність `X-Token`, яку ми створили.
|
||||
|
||||
Тож замість додавання цього до кожної *операції шляху*, ми можемо додати це до `APIRouter`.
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
|
||||
|
||||
Оскільки шлях кожної *операції шляху* має починатися з `/`, як у:
|
||||
|
||||
```Python hl_lines="1"
|
||||
@router.get("/{item_id}")
|
||||
async def read_item(item_id: str):
|
||||
...
|
||||
```
|
||||
|
||||
...префікс не має містити кінцевий `/`.
|
||||
|
||||
Отже, у цьому випадку префікс - це `/items`.
|
||||
|
||||
Ми також можемо додати список `tags` та додаткові `responses`, які застосовуватимуться до всіх *операцій шляху*, включених у цей router.
|
||||
|
||||
І ми можемо додати список `dependencies`, які буде додано до всіх *операцій шляху* у router і які виконуватимуться/вирішуватимуться для кожного запиту до них.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Зверніть увагу, що так само як і для [залежностей у декораторах *операцій шляху*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, жодне значення не буде передано вашій *функції операції шляху*.
|
||||
|
||||
///
|
||||
|
||||
У підсумку шляхи предметів тепер:
|
||||
|
||||
* `/items/`
|
||||
* `/items/{item_id}`
|
||||
|
||||
...як ми і планували.
|
||||
|
||||
* Вони будуть позначені списком міток, що містить один рядок `"items"`.
|
||||
* Ці «мітки» особливо корисні для автоматичної інтерактивної документації (за допомогою OpenAPI).
|
||||
* Усі вони включатимуть наперед визначені `responses`.
|
||||
* Для всіх цих *операцій шляху* список `dependencies` буде оцінений/виконаний перед ними.
|
||||
* Якщо ви також оголосите залежності в конкретній *операції шляху*, **вони також будуть виконані**.
|
||||
* Спочатку виконуються залежності router'а, потім [`dependencies` у декораторі](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, а потім звичайні параметричні залежності.
|
||||
* Ви також можете додати [`Security` залежності з `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Наявність `dependencies` у `APIRouter` можна використати, наприклад, щоб вимагати автентифікацію для всієї групи *операцій шляху*. Навіть якщо залежності не додані до кожної з них окремо.
|
||||
|
||||
///
|
||||
|
||||
/// check | Перевірте
|
||||
|
||||
Параметри `prefix`, `tags`, `responses` і `dependencies` - це (як і в багатьох інших випадках) просто можливість **FastAPI**, яка допомагає уникати дублювання коду.
|
||||
|
||||
///
|
||||
|
||||
### Імпортуйте залежності { #import-the-dependencies }
|
||||
|
||||
Цей код живе в модулі `app.routers.items`, у файлі `app/routers/items.py`.
|
||||
|
||||
І нам потрібно отримати функцію залежності з модуля `app.dependencies`, файлу `app/dependencies.py`.
|
||||
|
||||
Тож ми використовуємо відносний імпорт з `..` для залежностей:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *}
|
||||
|
||||
#### Як працюють відносні імпорти { #how-relative-imports-work }
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви досконало знаєте, як працюють імпорти, перейдіть до наступного розділу нижче.
|
||||
|
||||
///
|
||||
|
||||
Одна крапка `.`, як у:
|
||||
|
||||
```Python
|
||||
from .dependencies import get_token_header
|
||||
```
|
||||
|
||||
означає:
|
||||
|
||||
* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/routers/items.py`) (каталог `app/routers/`)...
|
||||
* знайти модуль `dependencies` (уявний файл `app/routers/dependencies.py`)...
|
||||
* і з нього імпортувати функцію `get_token_header`.
|
||||
|
||||
Але такого файлу не існує, наші залежності у файлі `app/dependencies.py`.
|
||||
|
||||
Згадайте, як виглядає структура нашого застосунку/файлів:
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
|
||||
---
|
||||
|
||||
Дві крапки `..`, як у:
|
||||
|
||||
```Python
|
||||
from ..dependencies import get_token_header
|
||||
```
|
||||
|
||||
означають:
|
||||
|
||||
* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/routers/items.py`) (каталог `app/routers/`)...
|
||||
* перейти до батьківського пакета (каталог `app/`)...
|
||||
* і там знайти модуль `dependencies` (файл `app/dependencies.py`)...
|
||||
* і з нього імпортувати функцію `get_token_header`.
|
||||
|
||||
Це працює правильно! 🎉
|
||||
|
||||
---
|
||||
|
||||
Так само, якби ми використали три крапки `...`, як у:
|
||||
|
||||
```Python
|
||||
from ...dependencies import get_token_header
|
||||
```
|
||||
|
||||
це б означало:
|
||||
|
||||
* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/routers/items.py`) (каталог `app/routers/`)...
|
||||
* перейти до батьківського пакета (каталог `app/`)...
|
||||
* потім перейти до батьківського пакета від того (немає батьківського пакета, `app` - верхній рівень 😱)...
|
||||
* і там знайти модуль `dependencies` (файл `app/dependencies.py`)...
|
||||
* і з нього імпортувати функцію `get_token_header`.
|
||||
|
||||
Це б посилалося на якийсь пакет вище за `app/` з власним файлом `__init__.py` тощо. Але в нас такого немає. Тож у нашому прикладі це спричинить помилку. 🚨
|
||||
|
||||
Але тепер ви знаєте, як це працює, тож можете використовувати відносні імпорти у власних застосунках, незалежно від їхньої складності. 🤓
|
||||
|
||||
### Додайте користувацькі `tags`, `responses` і `dependencies` { #add-some-custom-tags-responses-and-dependencies }
|
||||
|
||||
Ми не додаємо префікс `/items` ані `tags=["items"]` до кожної *операції шляху*, бо додали їх до `APIRouter`.
|
||||
|
||||
Але ми все ще можемо додати _ще_ `tags`, які будуть застосовані до конкретної *операції шляху*, а також додаткові `responses`, специфічні для цієї *операції шляху*:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Остання операція шляху матиме комбінацію міток: `["items", "custom"]`.
|
||||
|
||||
І вона також матиме в документації обидві відповіді: одну для `404` і одну для `403`.
|
||||
|
||||
///
|
||||
|
||||
## Основний `FastAPI` { #the-main-fastapi }
|
||||
|
||||
Тепер розгляньмо модуль `app/main.py`.
|
||||
|
||||
Тут ви імпортуєте і використовуєте клас `FastAPI`.
|
||||
|
||||
Це буде головний файл вашого застосунку, який усе поєднує.
|
||||
|
||||
І оскільки більшість вашої логіки тепер житиме у власних модулях, головний файл буде досить простим.
|
||||
|
||||
### Імпортуйте `FastAPI` { #import-fastapi }
|
||||
|
||||
Імпортуйте та створіть клас `FastAPI`, як зазвичай.
|
||||
|
||||
І ми навіть можемо оголосити [глобальні залежності](dependencies/global-dependencies.md){.internal-link target=_blank}, які будуть поєднані із залежностями кожного `APIRouter`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *}
|
||||
|
||||
### Імпортуйте `APIRouter` { #import-the-apirouter }
|
||||
|
||||
Тепер імпортуємо інші підмодулі, що мають `APIRouter`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *}
|
||||
|
||||
Оскільки файли `app/routers/users.py` та `app/routers/items.py` - це підмодулі, що є частиною того самого пакета Python `app`, ми можемо використати одну крапку `.` для «відносних імпортів».
|
||||
|
||||
### Як працює імпорт { #how-the-importing-works }
|
||||
|
||||
Розділ:
|
||||
|
||||
```Python
|
||||
from .routers import items, users
|
||||
```
|
||||
|
||||
означає:
|
||||
|
||||
* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/main.py`) (каталог `app/`)...
|
||||
* знайти підпакет `routers` (каталог `app/routers/`)...
|
||||
* і з нього імпортувати підмодулі `items` (файл `app/routers/items.py`) і `users` (файл `app/routers/users.py`)...
|
||||
|
||||
Модуль `items` матиме змінну `router` (`items.router`). Це та сама, що ми створили у файлі `app/routers/items.py`, це об’єкт `APIRouter`.
|
||||
|
||||
Потім ми робимо те саме для модуля `users`.
|
||||
|
||||
Ми також могли б імпортувати їх так:
|
||||
|
||||
```Python
|
||||
from app.routers import items, users
|
||||
```
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Перша версія - «відносний імпорт»:
|
||||
|
||||
```Python
|
||||
from .routers import items, users
|
||||
```
|
||||
|
||||
Друга версія - «абсолютний імпорт»:
|
||||
|
||||
```Python
|
||||
from app.routers import items, users
|
||||
```
|
||||
|
||||
Щоб дізнатися більше про пакети й модулі Python, прочитайте <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">офіційну документацію Python про модулі</a>.
|
||||
|
||||
///
|
||||
|
||||
### Уникайте колізій імен { #avoid-name-collisions }
|
||||
|
||||
Ми імпортуємо підмодуль `items` напряму, замість імпорту лише його змінної `router`.
|
||||
|
||||
Це тому, що в підмодулі `users` також є змінна з назвою `router`.
|
||||
|
||||
Якби ми імпортували один за одним, як:
|
||||
|
||||
```Python
|
||||
from .routers.items import router
|
||||
from .routers.users import router
|
||||
```
|
||||
|
||||
`router` з `users` перезаписав би той, що з `items`, і ми не змогли б використовувати їх одночасно.
|
||||
|
||||
Щоб мати змогу використовувати обидва в одному файлі, ми імпортуємо підмодулі напряму:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *}
|
||||
|
||||
### Додайте `APIRouter` для `users` і `items` { #include-the-apirouters-for-users-and-items }
|
||||
|
||||
Тепер додаймо `router` з підмодулів `users` і `items`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *}
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
`users.router` містить `APIRouter` у файлі `app/routers/users.py`.
|
||||
|
||||
А `items.router` містить `APIRouter` у файлі `app/routers/items.py`.
|
||||
|
||||
///
|
||||
|
||||
За допомогою `app.include_router()` ми можемо додати кожен `APIRouter` до основного застосунку `FastAPI`.
|
||||
|
||||
Це включить усі маршрути з цього router'а як частину застосунку.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Фактично, всередині для кожної *операції шляху*, оголошеної в `APIRouter`, буде створена окрема *операція шляху*.
|
||||
|
||||
Тобто за лаштунками все працюватиме так, ніби це один і той самий застосунок.
|
||||
|
||||
///
|
||||
|
||||
/// check | Перевірте
|
||||
|
||||
Вам не потрібно перейматися продуктивністю під час включення router'ів.
|
||||
|
||||
Це займе мікросекунди і відбуватиметься лише під час запуску.
|
||||
|
||||
Тож це не вплине на продуктивність. ⚡
|
||||
|
||||
///
|
||||
|
||||
### Додайте `APIRouter` з власними `prefix`, `tags`, `responses` і `dependencies` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
|
||||
|
||||
Уявімо, що ваша організація надала вам файл `app/internal/admin.py`.
|
||||
|
||||
Він містить `APIRouter` з кількома адміністративними *операціями шляху*, які організація спільно використовує між кількома проєктами.
|
||||
|
||||
Для цього прикладу він буде дуже простим. Але припустімо, що оскільки його спільно використовують з іншими проєктами організації, ми не можемо модифікувати його та додавати `prefix`, `dependencies`, `tags` тощо прямо до `APIRouter`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *}
|
||||
|
||||
Але ми все одно хочемо встановити користувацький `prefix` під час включення `APIRouter`, щоб усі його *операції шляху* починалися з `/admin`, хочемо захистити його за допомогою `dependencies`, які вже є в цьому проєкті, і хочемо додати `tags` та `responses`.
|
||||
|
||||
Ми можемо оголосити все це, не змінюючи оригінальний `APIRouter`, передавши ці параметри до `app.include_router()`:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *}
|
||||
|
||||
Таким чином, вихідний `APIRouter` залишиться без змін, тож ми все ще зможемо спільно використовувати той самий файл `app/internal/admin.py` з іншими проєктами в організації.
|
||||
|
||||
У результаті в нашому застосунку кожна з *операцій шляху* з модуля `admin` матиме:
|
||||
|
||||
* Префікс `/admin`.
|
||||
* Мітку `admin`.
|
||||
* Залежність `get_token_header`.
|
||||
* Відповідь `418`. 🍵
|
||||
|
||||
Але це вплине лише на цей `APIRouter` у нашому застосунку, а не на будь-який інший код, який його використовує.
|
||||
|
||||
Наприклад, інші проєкти можуть використовувати той самий `APIRouter` з іншим методом автентифікації.
|
||||
|
||||
### Додайте *операцію шляху* { #include-a-path-operation }
|
||||
|
||||
Ми також можемо додавати *операції шляху* безпосередньо до застосунку `FastAPI`.
|
||||
|
||||
Тут ми це робимо... просто щоб показати, що так можна 🤷:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *}
|
||||
|
||||
і це працюватиме коректно разом з усіма іншими *операціями шляху*, доданими через `app.include_router()`.
|
||||
|
||||
/// info | Дуже технічні деталі
|
||||
|
||||
Примітка: це дуже технічна деталь, яку ви, ймовірно, можете просто пропустити.
|
||||
|
||||
---
|
||||
|
||||
`APIRouter` не «монтуються», вони не ізольовані від решти застосунку.
|
||||
|
||||
Це тому, що ми хочемо включати їхні *операції шляху* в схему OpenAPI та інтерфейси користувача.
|
||||
|
||||
Оскільки ми не можемо просто ізолювати їх і «змонтувати» незалежно від решти, *операції шляху* «клонуються» (створюються заново), а не включаються безпосередньо.
|
||||
|
||||
///
|
||||
|
||||
## Перевірте автоматичну документацію API { #check-the-automatic-api-docs }
|
||||
|
||||
Тепер запустіть ваш застосунок:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev app/main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
І відкрийте документацію за адресою <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Ви побачите автоматичну документацію API, що включає шляхи з усіх підмодулів, з правильними шляхами (і префіксами) та правильними мітками:
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/image01.png">
|
||||
|
||||
## Включайте той самий router кілька разів з різними `prefix` { #include-the-same-router-multiple-times-with-different-prefix }
|
||||
|
||||
Ви також можете використовувати `.include_router()` кілька разів з одним і тим самим router'ом, але з різними префіксами.
|
||||
|
||||
Це може бути корисно, наприклад, щоб публікувати той самий API під різними префіксами, наприклад `/api/v1` і `/api/latest`.
|
||||
|
||||
Це просунуте використання, яке вам може й не знадобитися, але воно є на випадок, якщо потрібно.
|
||||
|
||||
## Включіть один `APIRouter` до іншого { #include-an-apirouter-in-another }
|
||||
|
||||
Так само як ви можете включити `APIRouter` у застосунок `FastAPI`, ви можете включити `APIRouter` в інший `APIRouter`, використовуючи:
|
||||
|
||||
```Python
|
||||
router.include_router(other_router)
|
||||
```
|
||||
|
||||
Переконайтеся, що ви робите це до включення `router` в застосунок `FastAPI`, щоб *операції шляху* з `other_router` також були включені.
|
||||
288
docs/uk/docs/tutorial/dependencies/classes-as-dependencies.md
Normal file
288
docs/uk/docs/tutorial/dependencies/classes-as-dependencies.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# Класи як залежності { #classes-as-dependencies }
|
||||
|
||||
Перш ніж заглибитися у систему **впровадження залежностей**, оновімо попередній приклад.
|
||||
|
||||
## `dict` з попереднього прикладу { #a-dict-from-the-previous-example }
|
||||
|
||||
У попередньому прикладі ми повертали `dict` із нашого «залежного»:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
Але тоді ми отримуємо `dict` у параметрі `commons` функції операції шляху.
|
||||
|
||||
І ми знаємо, що редактори не можуть надати багато підтримки (наприклад, автодоповнення) для `dict`, адже вони не знають їхніх ключів і типів значень.
|
||||
|
||||
Можна зробити краще…
|
||||
|
||||
## Що робить об’єкт залежністю { #what-makes-a-dependency }
|
||||
|
||||
Дотепер ви бачили залежності, оголошені як функції.
|
||||
|
||||
Але це не єдиний спосіб оголошувати залежності (хоча, ймовірно, найпоширеніший).
|
||||
|
||||
Ключовий момент у тому, що залежність має бути «викликаємим».
|
||||
|
||||
«Викликаємий» у Python - це будь-що, що Python може «викликати», як функцію.
|
||||
|
||||
Отже, якщо у вас є об’єкт `something` (який може й не бути функцією) і ви можете «викликати» його (виконати) так:
|
||||
|
||||
```Python
|
||||
something()
|
||||
```
|
||||
|
||||
або
|
||||
|
||||
```Python
|
||||
something(some_argument, some_keyword_argument="foo")
|
||||
```
|
||||
|
||||
тоді це «викликаємий».
|
||||
|
||||
## Класи як залежності { #classes-as-dependencies_1 }
|
||||
|
||||
Ви могли помітити, що для створення екземпляра класу Python ви використовуєте той самий синтаксис.
|
||||
|
||||
Наприклад:
|
||||
|
||||
```Python
|
||||
class Cat:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
|
||||
fluffy = Cat(name="Mr Fluffy")
|
||||
```
|
||||
|
||||
У цьому випадку `fluffy` - екземпляр класу `Cat`.
|
||||
|
||||
А для створення `fluffy` ви «викликаєте» `Cat`.
|
||||
|
||||
Отже, клас Python також є **викликаємим**.
|
||||
|
||||
Тож у **FastAPI** ви можете використовувати клас Python як залежність.
|
||||
|
||||
Насправді **FastAPI** перевіряє, що це «викликаємий» об’єкт (функція, клас чи щось інше) і які параметри в нього оголошені.
|
||||
|
||||
Якщо ви передаєте «викликаємий» як залежність у **FastAPI**, він проаналізує параметри цього об’єкта і обробить їх так само, як параметри для функції операції шляху. Включно з підзалежностями.
|
||||
|
||||
Це також стосується викликаємих без жодних параметрів. Так само, як і для функцій операцій шляху без параметрів.
|
||||
|
||||
Тоді ми можемо змінити залежність `common_parameters` вище на клас `CommonQueryParams`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *}
|
||||
|
||||
Зверніть увагу на метод `__init__`, який використовують для створення екземпляра класу:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *}
|
||||
|
||||
…він має ті самі параметри, що й наш попередній `common_parameters`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *}
|
||||
|
||||
Саме ці параметри **FastAPI** використає, щоб «розв’язати» залежність.
|
||||
|
||||
В обох випадках буде:
|
||||
|
||||
- Необов’язковий параметр запиту `q`, який є `str`.
|
||||
- Параметр запиту `skip`, який є `int`, зі значенням за замовчуванням `0`.
|
||||
- Параметр запиту `limit`, який є `int`, зі значенням за замовчуванням `100`.
|
||||
|
||||
В обох випадках дані будуть перетворені, перевірені й задокументовані в схемі OpenAPI тощо.
|
||||
|
||||
## Використання { #use-it }
|
||||
|
||||
Тепер ви можете оголосити залежність, використовуючи цей клас.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
|
||||
|
||||
**FastAPI** викликає клас `CommonQueryParams`. Це створює «екземпляр» цього класу, і цей екземпляр буде передано як параметр `commons` у вашу функцію.
|
||||
|
||||
## Анотація типів проти `Depends` { #type-annotation-vs-depends }
|
||||
|
||||
Зверніть увагу, що вище ми двічі пишемо `CommonQueryParams`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Надавайте перевагу варіанту з `Annotated`, якщо це можливо.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Останній `CommonQueryParams` у:
|
||||
|
||||
```Python
|
||||
... Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
- це те, що **FastAPI** фактично використає, щоб дізнатися, яка залежність.
|
||||
|
||||
Саме з нього **FastAPI** витягне оголошені параметри і саме його **FastAPI** буде викликати.
|
||||
|
||||
---
|
||||
|
||||
У цьому випадку перший `CommonQueryParams` у:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, ...
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Надавайте перевагу варіанту з `Annotated`, якщо це можливо.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams ...
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
…не має жодного особливого значення для **FastAPI**. FastAPI не використовуватиме його для перетворення даних, перевірки тощо (адже для цього використовується `Depends(CommonQueryParams)`).
|
||||
|
||||
Насправді ви могли б написати просто:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[Any, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Надавайте перевагу варіанту з `Annotated`, якщо це можливо.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
…як у:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *}
|
||||
|
||||
Але оголошувати тип рекомендується - так ваш редактор знатиме, що буде передано в параметр `commons`, і зможе допомагати з автодоповненням, перевірками типів тощо:
|
||||
|
||||
<img src="/img/tutorial/dependencies/image02.png">
|
||||
|
||||
## Скорочення { #shortcut }
|
||||
|
||||
Але ви бачите, що тут маємо деяке дублювання коду - `CommonQueryParams` пишемо двічі:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Надавайте перевагу варіанту з `Annotated`, якщо це можливо.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
**FastAPI** надає скорочення для таких випадків, коли залежність - це саме клас, який **FastAPI** «викличе», щоб створити екземпляр цього класу.
|
||||
|
||||
Для таких випадків ви можете зробити так:
|
||||
|
||||
Замість того щоб писати:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Надавайте перевагу варіанту з `Annotated`, якщо це можливо.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
…напишіть:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends()]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Надавайте перевагу варіанту з `Annotated`, якщо це можливо.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends()
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
Ви оголошуєте залежність як тип параметра і використовуєте `Depends()` без параметрів, замість того щоб вдруге писати повний клас усередині `Depends(CommonQueryParams)`.
|
||||
|
||||
Той самий приклад виглядатиме так:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *}
|
||||
|
||||
…і **FastAPI** знатиме, що робити.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо це здається заплутанішим, ніж корисним, просто не використовуйте це - воно не є обов’язковим.
|
||||
|
||||
Це лише скорочення. Адже **FastAPI** дбає про мінімізацію дублювання коду.
|
||||
|
||||
///
|
||||
@@ -0,0 +1,69 @@
|
||||
# Залежності в декораторах операцій шляху { #dependencies-in-path-operation-decorators }
|
||||
|
||||
Іноді вам насправді не потрібне значення, яке повертає залежність, у вашій *функції операції шляху*.
|
||||
|
||||
Або залежність узагалі не повертає значення.
|
||||
|
||||
Але її все одно потрібно виконати/опрацювати.
|
||||
|
||||
Для таких випадків, замість оголошення параметра *функції операції шляху* з `Depends`, ви можете додати `list` `dependencies` до *декоратора операції шляху*.
|
||||
|
||||
## Додайте `dependencies` до *декоратора операції шляху* { #add-dependencies-to-the-path-operation-decorator }
|
||||
|
||||
*Декоратор операції шляху* приймає необов'язковий аргумент `dependencies`.
|
||||
|
||||
Це має бути `list` з `Depends()`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *}
|
||||
|
||||
Ці залежності будуть виконані/оброблені так само, як звичайні залежності. Але їхні значення (якщо щось повертають) не передаватимуться у вашу *функцію операції шляху*.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Деякі редактори перевіряють невикористані параметри функцій і показують їх як помилки.
|
||||
|
||||
Використовуючи ці `dependencies` у *декораторі операції шляху*, ви можете гарантувати їх виконання та водночас уникнути помилок редактора/інструментів.
|
||||
|
||||
Це також може допомогти уникнути плутанини для нових розробників, які бачать невикористаний параметр у вашому коді й можуть вирішити, що він зайвий.
|
||||
|
||||
///
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
У цьому прикладі ми використовуємо вигадані власні заголовки `X-Key` і `X-Token`.
|
||||
|
||||
Але в реальних випадках, під час впровадження безпеки, ви отримаєте більше користі, використовуючи вбудовані [Інструменти безпеки (наступний розділ)](../security/index.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Помилки залежностей і значення, що повертаються { #dependencies-errors-and-return-values }
|
||||
|
||||
Ви можете використовувати ті самі *функції* залежностей, що й зазвичай.
|
||||
|
||||
### Вимоги залежностей { #dependency-requirements }
|
||||
|
||||
Вони можуть оголошувати вимоги до запиту (наприклад, заголовки) або інші підзалежності:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *}
|
||||
|
||||
### Підіймати винятки { #raise-exceptions }
|
||||
|
||||
Ці залежності можуть `raise` винятки, так само як і звичайні залежності:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *}
|
||||
|
||||
### Значення, що повертаються { #return-values }
|
||||
|
||||
Вони можуть повертати значення або ні - ці значення не використовуватимуться.
|
||||
|
||||
Отже, ви можете перевикористати звичайну залежність (яка повертає значення), яку вже застосовуєте деінде, і навіть якщо значення не буде використано, залежність буде виконана:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *}
|
||||
|
||||
## Залежності для групи операцій шляху { #dependencies-for-a-group-of-path-operations }
|
||||
|
||||
Далі, читаючи про структурування великих застосунків ([Більші застосунки - декілька файлів](../../tutorial/bigger-applications.md){.internal-link target=_blank}), можливо з кількома файлами, ви дізнаєтеся, як оголосити один параметр `dependencies` для групи *операцій шляху*.
|
||||
|
||||
## Глобальні залежності { #global-dependencies }
|
||||
|
||||
Далі ми побачимо, як додати залежності до всього застосунку `FastAPI`, щоб вони застосовувалися до кожної *операції шляху*.
|
||||
289
docs/uk/docs/tutorial/dependencies/dependencies-with-yield.md
Normal file
289
docs/uk/docs/tutorial/dependencies/dependencies-with-yield.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Залежності з yield { #dependencies-with-yield }
|
||||
|
||||
FastAPI підтримує залежності, які виконують деякі <dfn title='інколи також називається «exit code», «cleanup code», «teardown code», «closing code», «context manager exit code» тощо'>додаткові кроки після завершення</dfn>.
|
||||
|
||||
Щоб це зробити, використовуйте `yield` замість `return` і напишіть додаткові кроки (код) після нього.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Переконайтесь, що ви використовуєте `yield` лише один раз на залежність.
|
||||
|
||||
///
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Будь-яка функція, яку можна використовувати з:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> або
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
буде придатною як залежність у **FastAPI**.
|
||||
|
||||
Насправді FastAPI використовує ці два декоратори внутрішньо.
|
||||
|
||||
///
|
||||
|
||||
## Залежність бази даних з `yield` { #a-database-dependency-with-yield }
|
||||
|
||||
Наприклад, ви можете використати це, щоб створити сесію бази даних і закрити її після завершення.
|
||||
|
||||
Перед створенням відповіді виконується лише код до і включно з оператором `yield`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *}
|
||||
|
||||
Значення, передане `yield`, впроваджується в *операції шляху* та інші залежності:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *}
|
||||
|
||||
Код після оператора `yield` виконується після відповіді:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Можете використовувати як `async`, так і звичайні функції.
|
||||
|
||||
**FastAPI** зробить усе правильно з кожною з них, так само як і зі звичайними залежностями.
|
||||
|
||||
///
|
||||
|
||||
## Залежність з `yield` та `try` { #a-dependency-with-yield-and-try }
|
||||
|
||||
Якщо ви використовуєте блок `try` в залежності з `yield`, ви отримаєте будь-який виняток, який був згенерований під час використання залежності.
|
||||
|
||||
Наприклад, якщо якийсь код десь посередині, в іншій залежності або в *операції шляху*, зробив «rollback» транзакції бази даних або створив будь-який інший виняток, ви отримаєте цей виняток у своїй залежності.
|
||||
|
||||
Тож ви можете обробити цей конкретний виняток усередині залежності за допомогою `except SomeException`.
|
||||
|
||||
Так само ви можете використовувати `finally`, щоб гарантувати виконання завершальних кроків незалежно від того, був виняток чи ні.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *}
|
||||
|
||||
## Підзалежності з `yield` { #sub-dependencies-with-yield }
|
||||
|
||||
Ви можете мати підзалежності та «дерева» підзалежностей будь-якого розміру і форми, і будь-яка або всі з них можуть використовувати `yield`.
|
||||
|
||||
**FastAPI** гарантує, що «exit code» у кожній залежності з `yield` буде виконано в правильному порядку.
|
||||
|
||||
Наприклад, `dependency_c` може залежати від `dependency_b`, а `dependency_b` - від `dependency_a`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *}
|
||||
|
||||
І всі вони можуть використовувати `yield`.
|
||||
|
||||
У цьому випадку `dependency_c`, щоб виконати свій завершальний код, потребує, щоб значення з `dependency_b` (тут `dep_b`) все ще було доступним.
|
||||
|
||||
І, у свою чергу, `dependency_b` потребує, щоб значення з `dependency_a` (тут `dep_a`) було доступним для свого завершального коду.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *}
|
||||
|
||||
Так само ви можете мати деякі залежності з `yield`, а інші - з `return`, і частина з них може залежати від інших.
|
||||
|
||||
І ви можете мати одну залежність, яка вимагає кілька інших залежностей з `yield` тощо.
|
||||
|
||||
Ви можете мати будь-які комбінації залежностей, які вам потрібні.
|
||||
|
||||
**FastAPI** подбає, щоб усе виконувалося в правильному порядку.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Це працює завдяки <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">Менеджерам контексту</a> Python.
|
||||
|
||||
**FastAPI** використовує їх внутрішньо, щоб досягти цього.
|
||||
|
||||
///
|
||||
|
||||
## Залежності з `yield` та `HTTPException` { #dependencies-with-yield-and-httpexception }
|
||||
|
||||
Ви бачили, що можна використовувати залежності з `yield` і мати блоки `try`, які намагаються виконати деякий код, а потім запускають завершальний код після `finally`.
|
||||
|
||||
Також можна використовувати `except`, щоб перехопити згенерований виняток і щось із ним зробити.
|
||||
|
||||
Наприклад, ви можете підняти інший виняток, як-от `HTTPException`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Це доволі просунута техніка, і в більшості випадків вона вам не знадобиться, адже ви можете піднімати винятки (включно з `HTTPException`) всередині іншого коду вашого застосунку, наприклад, у *функції операції шляху*.
|
||||
|
||||
Але вона є, якщо вам це потрібно. 🤓
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *}
|
||||
|
||||
Якщо ви хочете перехоплювати винятки та створювати на їх основі користувацьку відповідь, створіть [Користувацький обробник винятків](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
|
||||
|
||||
## Залежності з `yield` та `except` { #dependencies-with-yield-and-except }
|
||||
|
||||
Якщо ви перехоплюєте виняток за допомогою `except` у залежності з `yield` і не піднімаєте його знову (або не піднімаєте новий виняток), FastAPI не зможе помітити, що стався виняток, так само як це було б у звичайному Python:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *}
|
||||
|
||||
У цьому випадку клієнт побачить відповідь *HTTP 500 Internal Server Error*, як і має бути, з огляду на те, що ми не піднімаємо `HTTPException` або подібний виняток, але на сервері **не буде жодних логів** чи інших ознак того, що це була за помилка. 😱
|
||||
|
||||
### Завжди використовуйте `raise` у залежностях з `yield` та `except` { #always-raise-in-dependencies-with-yield-and-except }
|
||||
|
||||
Якщо ви перехоплюєте виняток у залежності з `yield`, якщо тільки ви не піднімаєте інший `HTTPException` або подібний, **вам слід повторно підняти початковий виняток**.
|
||||
|
||||
Ви можете повторно підняти той самий виняток, використовуючи `raise`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *}
|
||||
|
||||
Тепер клієнт отримає ту саму відповідь *HTTP 500 Internal Server Error*, але сервер матиме наш користувацький `InternalError` у логах. 😎
|
||||
|
||||
## Виконання залежностей з `yield` { #execution-of-dependencies-with-yield }
|
||||
|
||||
Послідовність виконання приблизно така, як на цій діаграмі. Час тече зверху вниз. І кожна колонка - це одна з частин, що взаємодіють або виконують код.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant client as Client
|
||||
participant handler as Exception handler
|
||||
participant dep as Dep with yield
|
||||
participant operation as Path Operation
|
||||
participant tasks as Background tasks
|
||||
|
||||
Note over client,operation: Can raise exceptions, including HTTPException
|
||||
client ->> dep: Start request
|
||||
Note over dep: Run code up to yield
|
||||
opt raise Exception
|
||||
dep -->> handler: Raise Exception
|
||||
handler -->> client: HTTP error response
|
||||
end
|
||||
dep ->> operation: Run dependency, e.g. DB session
|
||||
opt raise
|
||||
operation -->> dep: Raise Exception (e.g. HTTPException)
|
||||
opt handle
|
||||
dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
|
||||
end
|
||||
handler -->> client: HTTP error response
|
||||
end
|
||||
|
||||
operation ->> client: Return response to client
|
||||
Note over client,operation: Response is already sent, can't change it anymore
|
||||
opt Tasks
|
||||
operation -->> tasks: Send background tasks
|
||||
end
|
||||
opt Raise other exception
|
||||
tasks -->> tasks: Handle exceptions in the background task code
|
||||
end
|
||||
```
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Лише **одна відповідь** буде надіслана клієнту. Це може бути одна з помилкових відповідей або відповідь від *операції шляху*.
|
||||
|
||||
Після відправлення однієї з цих відповідей іншу відправити не можна.
|
||||
|
||||
///
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Якщо ви піднімаєте будь-який виняток у коді з *функції операції шляху*, він буде переданий у залежності з `yield`, включно з `HTTPException`. У більшості випадків ви захочете повторно підняти той самий виняток або новий із залежності з `yield`, щоб переконатися, що його коректно оброблено.
|
||||
|
||||
///
|
||||
|
||||
## Ранній вихід і `scope` { #early-exit-and-scope }
|
||||
|
||||
Зазвичай завершальний код залежностей з `yield` виконується **після того**, як відповідь надіслано клієнту.
|
||||
|
||||
Але якщо ви знаєте, що вам не потрібно використовувати залежність після повернення з *функції операції шляху*, ви можете використати `Depends(scope="function")`, щоб сказати FastAPI, що слід закрити залежність після повернення з *функції операції шляху*, але **до** надсилання **відповіді**.
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *}
|
||||
|
||||
`Depends()` приймає параметр `scope`, який може бути:
|
||||
|
||||
* `"function"`: запустити залежність перед *функцією операції шляху*, що обробляє запит, завершити залежність після завершення *функції операції шляху*, але **до** того, як відповідь буде відправлена клієнту. Тобто функція залежності буде виконуватися **навколо** *функції операції **шляху***.
|
||||
* `"request"`: запустити залежність перед *функцією операції шляху*, що обробляє запит (подібно до `"function"`), але завершити **після** того, як відповідь буде відправлена клієнту. Тобто функція залежності буде виконуватися **навколо** циклу **запиту** та відповіді.
|
||||
|
||||
Якщо не вказано, і залежність має `yield`, за замовчуванням `scope` дорівнює `"request"`.
|
||||
|
||||
### `scope` для підзалежностей { #scope-for-sub-dependencies }
|
||||
|
||||
Коли ви оголошуєте залежність із `scope="request"` (за замовчуванням), будь-яка підзалежність також має мати `scope` рівний `"request"`.
|
||||
|
||||
Але залежність з `scope` рівним `"function"` може мати залежності з `scope` `"function"` і `scope` `"request"`.
|
||||
|
||||
Це тому, що будь-яка залежність має бути здатною виконати свій завершальний код раніше за підзалежності, оскільки вона може все ще потребувати їх під час свого завершального коду.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant client as Client
|
||||
participant dep_req as Dep scope="request"
|
||||
participant dep_func as Dep scope="function"
|
||||
participant operation as Path Operation
|
||||
|
||||
client ->> dep_req: Start request
|
||||
Note over dep_req: Run code up to yield
|
||||
dep_req ->> dep_func: Pass dependency
|
||||
Note over dep_func: Run code up to yield
|
||||
dep_func ->> operation: Run path operation with dependency
|
||||
operation ->> dep_func: Return from path operation
|
||||
Note over dep_func: Run code after yield
|
||||
Note over dep_func: ✅ Dependency closed
|
||||
dep_func ->> client: Send response to client
|
||||
Note over client: Response sent
|
||||
Note over dep_req: Run code after yield
|
||||
Note over dep_req: ✅ Dependency closed
|
||||
```
|
||||
|
||||
## Залежності з `yield`, `HTTPException`, `except` і фоновими задачами { #dependencies-with-yield-httpexception-except-and-background-tasks }
|
||||
|
||||
Залежності з `yield` еволюціонували з часом, щоб покрити різні сценарії та виправити деякі проблеми.
|
||||
|
||||
Якщо ви хочете дізнатися, що змінювалося в різних версіях FastAPI, прочитайте про це в просунутому посібнику користувача: [Розширені залежності - Залежності з `yield`, `HTTPException`, `except` і фоновими задачами](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
|
||||
## Менеджери контексту { #context-managers }
|
||||
|
||||
### Що таке «Менеджери контексту» { #what-are-context-managers }
|
||||
|
||||
«Менеджери контексту» - це будь-які Python-об'єкти, які можна використовувати в операторі `with`.
|
||||
|
||||
Наприклад, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">можна використати `with`, щоб прочитати файл</a>:
|
||||
|
||||
```Python
|
||||
with open("./somefile.txt") as f:
|
||||
contents = f.read()
|
||||
print(contents)
|
||||
```
|
||||
|
||||
Під капотом `open("./somefile.txt")` створює об'єкт, який називається «Менеджер контексту».
|
||||
|
||||
Коли блок `with` завершується, він гарантує закриття файлу, навіть якщо були винятки.
|
||||
|
||||
Коли ви створюєте залежність з `yield`, **FastAPI** внутрішньо створить для неї менеджер контексту й поєднає його з іншими пов'язаними інструментами.
|
||||
|
||||
### Використання менеджерів контексту в залежностях з `yield` { #using-context-managers-in-dependencies-with-yield }
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Це, загалом, «просунута» ідея.
|
||||
|
||||
Якщо ви тільки починаєте з **FastAPI**, можливо, варто наразі пропустити це.
|
||||
|
||||
///
|
||||
|
||||
У Python ви можете створювати Менеджери контексту, <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">створивши клас із двома методами: `__enter__()` і `__exit__()`</a>.
|
||||
|
||||
Ви також можете використовувати їх усередині залежностей **FastAPI** з `yield`, використовуючи
|
||||
`with` або `async with` у середині функції залежності:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial010_py310.py hl[1:9,13] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Інший спосіб створити менеджер контексту:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> або
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
використовуючи їх для декорування функції з одним `yield`.
|
||||
|
||||
Саме це **FastAPI** використовує внутрішньо для залежностей з `yield`.
|
||||
|
||||
Але вам не потрібно використовувати ці декоратори для залежностей FastAPI (і не варто).
|
||||
|
||||
FastAPI зробить це за вас внутрішньо.
|
||||
|
||||
///
|
||||
15
docs/uk/docs/tutorial/dependencies/global-dependencies.md
Normal file
15
docs/uk/docs/tutorial/dependencies/global-dependencies.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Глобальні залежності { #global-dependencies }
|
||||
|
||||
Для деяких типів застосунків ви можете захотіти додати залежності до всього застосунку.
|
||||
|
||||
Подібно до того, як ви можете [додавати `dependencies` до *декораторів операцій шляху*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, ви можете додати їх до застосунку `FastAPI`.
|
||||
|
||||
У такому разі вони будуть застосовані до всіх *операцій шляху* в застосунку:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py310.py hl[17] *}
|
||||
|
||||
Усі ідеї з розділу про [додавання `dependencies` до *декораторів операцій шляху*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} так само застосовні, але в цьому випадку - до всіх *операцій шляху* в застосунку.
|
||||
|
||||
## Залежності для груп *операцій шляху* { #dependencies-for-groups-of-path-operations }
|
||||
|
||||
Пізніше, читаючи про структуру більших застосунків ([Більші застосунки - кілька файлів](../../tutorial/bigger-applications.md){.internal-link target=_blank}), можливо з кількома файлами, ви дізнаєтеся, як оголосити єдиний параметр `dependencies` для групи *операцій шляху*.
|
||||
250
docs/uk/docs/tutorial/dependencies/index.md
Normal file
250
docs/uk/docs/tutorial/dependencies/index.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Залежності { #dependencies }
|
||||
|
||||
**FastAPI** має дуже потужну, але інтуїтивну систему **<dfn title="також відомо як: компоненти, ресурси, провайдери, сервіси, інжектовані об'єкти">Впровадження залежностей</dfn>**.
|
||||
|
||||
Вона створена так, щоб бути дуже простою у використанні та щоб полегшити будь-якому розробнику інтеграцію інших компонентів з **FastAPI**.
|
||||
|
||||
## Що таке «Впровадження залежностей» { #what-is-dependency-injection }
|
||||
|
||||
У програмуванні **«Впровадження залежностей»** означає, що існує спосіб для вашого коду (у цьому випадку ваших *функцій операцій шляху*) задекларувати речі, які йому потрібні для роботи: «залежності».
|
||||
|
||||
А потім ця система (у цьому випадку **FastAPI**) подбає про все необхідне, щоб надати вашому коду ці потрібні залежності («інжектувати» залежності).
|
||||
|
||||
Це дуже корисно, коли вам потрібно:
|
||||
|
||||
* Мати спільну логіку (одна й та сама логіка знову і знову).
|
||||
* Ділитися з’єднаннями з базою даних.
|
||||
* Примусово застосовувати безпеку, автентифікацію, вимоги до ролей тощо.
|
||||
* І багато іншого...
|
||||
|
||||
Все це з мінімізацією дублювання коду.
|
||||
|
||||
## Перші кроки { #first-steps }
|
||||
|
||||
Розгляньмо дуже простий приклад. Він буде таким простим, що поки що не дуже корисним.
|
||||
|
||||
Але так ми зможемо зосередитися на тому, як працює система **Впровадження залежностей**.
|
||||
|
||||
### Створіть залежність або «залежний» { #create-a-dependency-or-dependable }
|
||||
|
||||
Спочатку зосередьмося на залежності.
|
||||
|
||||
Це просто функція, яка може приймати ті самі параметри, що й *функція операції шляху*:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
|
||||
|
||||
Ось і все.
|
||||
|
||||
**2 рядки**.
|
||||
|
||||
І вона має ту саму форму та структуру, що й усі ваші *функції операцій шляху*.
|
||||
|
||||
Можете думати про неї як про *функцію операції шляху* без «декоратора» (без `@app.get("/some-path")`).
|
||||
|
||||
І вона може повертати будь-що.
|
||||
|
||||
У цьому випадку ця залежність очікує:
|
||||
|
||||
* Необов’язковий параметр запиту `q` типу `str`.
|
||||
* Необов’язковий параметр запиту `skip` типу `int`, за замовчуванням `0`.
|
||||
* Необов’язковий параметр запиту `limit` типу `int`, за замовчуванням `100`.
|
||||
|
||||
Потім вона просто повертає `dict`, що містить ці значення.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
FastAPI додав підтримку `Annotated` (і почав її рекомендувати) у версії 0.95.0.
|
||||
|
||||
Якщо у вас старіша версія, ви отримаєте помилки при спробі використати `Annotated`.
|
||||
|
||||
Переконайтеся, що ви [Оновіть версію FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} щонайменше до 0.95.1 перед використанням `Annotated`.
|
||||
|
||||
///
|
||||
|
||||
### Імпортуйте `Depends` { #import-depends }
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
### Оголосіть залежність у «залежному» { #declare-the-dependency-in-the-dependant }
|
||||
|
||||
Так само, як ви використовуєте `Body`, `Query` тощо з параметрами вашої *функції операції шляху*, використовуйте `Depends` з новим параметром:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
|
||||
|
||||
Хоча ви використовуєте `Depends` у параметрах вашої функції так само, як `Body`, `Query` тощо, `Depends` працює трохи інакше.
|
||||
|
||||
Ви передаєте в `Depends` лише один параметр.
|
||||
|
||||
Цей параметр має бути чимось на кшталт функції.
|
||||
|
||||
Ви її не викликаєте безпосередньо (не додавайте дужки в кінці), ви просто передаєте її як параметр у `Depends()`.
|
||||
|
||||
І ця функція приймає параметри так само, як і *функції операцій шляху*.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
У наступному розділі ви побачите, які ще «речі», окрім функцій, можна використовувати як залежності.
|
||||
|
||||
///
|
||||
|
||||
Щоразу, коли надходить новий запит, **FastAPI** подбає про:
|
||||
|
||||
* Виклик вашої функції-залежності («залежного») з правильними параметрами.
|
||||
* Отримання результату з вашої функції.
|
||||
* Присвоєння цього результату параметру у вашій *функції операції шляху*.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
common_parameters(["common_parameters"])
|
||||
read_items["/items/"]
|
||||
read_users["/users/"]
|
||||
|
||||
common_parameters --> read_items
|
||||
common_parameters --> read_users
|
||||
```
|
||||
|
||||
Таким чином ви пишете спільний код один раз, а **FastAPI** подбає про його виклик для ваших *операцій шляху*.
|
||||
|
||||
/// check | Перевірте
|
||||
|
||||
Зверніть увагу, що вам не потрібно створювати спеціальний клас і передавати його кудись у **FastAPI**, щоб «зареєструвати» його чи щось подібне.
|
||||
|
||||
Ви просто передаєте його в `Depends`, і **FastAPI** знає, що робити далі.
|
||||
|
||||
///
|
||||
|
||||
## Спільне використання залежностей `Annotated` { #share-annotated-dependencies }
|
||||
|
||||
У наведених вище прикладах видно невелике **дублювання коду**.
|
||||
|
||||
Коли вам потрібно використати залежність `common_parameters()`, доводиться писати весь параметр з анотацією типу та `Depends()`:
|
||||
|
||||
```Python
|
||||
commons: Annotated[dict, Depends(common_parameters)]
|
||||
```
|
||||
|
||||
Але оскільки ми використовуємо `Annotated`, ми можемо зберегти це значення `Annotated` у змінній і використовувати його в кількох місцях:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Це просто стандартний Python, це називається «псевдонім типу» і насправді не є специфічним для **FastAPI**.
|
||||
|
||||
Але оскільки **FastAPI** базується на стандартах Python, включно з `Annotated`, ви можете використати цей трюк у своєму коді. 😎
|
||||
|
||||
///
|
||||
|
||||
Залежності продовжать працювати як очікується, і **найкраще** те, що **інформація про типи буде збережена**, а це означає, що ваш редактор зможе й надалі надавати **автозаповнення**, **помилки в рядку** тощо. Те саме і для інших інструментів, як-от `mypy`.
|
||||
|
||||
Це буде особливо корисно у **великій кодовій базі**, де ви використовуєте **одні й ті самі залежності** знову і знову в **багатьох *операціях шляху***.
|
||||
|
||||
## Бути `async` чи не бути `async` { #to-async-or-not-to-async }
|
||||
|
||||
Оскільки залежності також викликатимуться **FastAPI** (так само, як і ваші *функції операцій шляху*), під час визначення ваших функцій діють ті самі правила.
|
||||
|
||||
Ви можете використовувати `async def` або звичайний `def`.
|
||||
|
||||
І ви можете оголошувати залежності з `async def` всередині звичайних *функцій операцій шляху* з `def`, або залежності з `def` всередині *функцій операцій шляху* з `async def` тощо.
|
||||
|
||||
Це не має значення. **FastAPI** знатиме, що робити.
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Якщо ви не впевнені, перегляньте розділ [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} про `async` і `await` у документації.
|
||||
|
||||
///
|
||||
|
||||
## Інтегровано з OpenAPI { #integrated-with-openapi }
|
||||
|
||||
Усі декларації запитів, перевірки та вимоги ваших залежностей (і субзалежностей) будуть інтегровані в ту саму схему OpenAPI.
|
||||
|
||||
Тож інтерактивна документація також міститиме всю інформацію з цих залежностей:
|
||||
|
||||
<img src="/img/tutorial/dependencies/image01.png">
|
||||
|
||||
## Просте використання { #simple-usage }
|
||||
|
||||
Якщо придивитися, *функції операцій шляху* оголошуються для використання щоразу, коли збігаються *шлях* та *операція*, а потім **FastAPI** подбає про виклик функції з правильними параметрами, витягуючи дані із запиту.
|
||||
|
||||
Насправді всі (або більшість) вебфреймворків працюють так само.
|
||||
|
||||
Ви ніколи не викликаєте ці функції безпосередньо. Їх викликає ваш фреймворк (у цьому випадку **FastAPI**).
|
||||
|
||||
За допомогою системи впровадження залежностей ви також можете вказати **FastAPI**, що ваша *функція операції шляху* також «залежить» від чогось, що має бути виконано до вашої *функції операції шляху*, і **FastAPI** подбає про виконання цього та «інжектування» результатів.
|
||||
|
||||
Інші поширені терміни для цієї самої ідеї «впровадження залежностей»:
|
||||
|
||||
* ресурси
|
||||
* провайдери
|
||||
* сервіси
|
||||
* інжектовані об’єкти
|
||||
* компоненти
|
||||
|
||||
## Плагіни **FastAPI** { #fastapi-plug-ins }
|
||||
|
||||
Інтеграції та «плагіни» можна будувати за допомогою системи **Впровадження залежностей**. Але насправді **немає потреби створювати «плагіни»**, оскільки, використовуючи залежності, можна оголосити безмежну кількість інтеграцій та взаємодій, які стають доступними для ваших *функцій операцій шляху*.
|
||||
|
||||
І залежності можна створювати дуже просто та інтуїтивно, що дозволяє вам просто імпортувати потрібні пакунки Python і інтегрувати їх з вашими функціями API за кілька рядків коду, буквально.
|
||||
|
||||
Ви побачите приклади цього в наступних розділах, про реляційні та NoSQL бази даних, безпеку тощо.
|
||||
|
||||
## Сумісність **FastAPI** { #fastapi-compatibility }
|
||||
|
||||
Простота системи впровадження залежностей робить **FastAPI** сумісним з:
|
||||
|
||||
* усіма реляційними базами даних
|
||||
* NoSQL базами даних
|
||||
* зовнішніми пакунками
|
||||
* зовнішніми API
|
||||
* системами автентифікації та авторизації
|
||||
* системами моніторингу використання API
|
||||
* системами інжекції даних у відповідь
|
||||
* тощо.
|
||||
|
||||
## Просто і потужно { #simple-and-powerful }
|
||||
|
||||
Хоча ієрархічна система впровадження залежностей дуже проста у визначенні та використанні, вона все ще дуже потужна.
|
||||
|
||||
Ви можете визначати залежності, які своєю чергою можуть визначати власні залежності.
|
||||
|
||||
Зрештою будується ієрархічне дерево залежностей, і система **Впровадження залежностей** подбає про розв’язання всіх цих залежностей (і їхніх субзалежностей) та надання (інжектування) результатів на кожному кроці.
|
||||
|
||||
Наприклад, припустімо, у вас є 4 кінцеві точки API (*операції шляху*):
|
||||
|
||||
* `/items/public/`
|
||||
* `/items/private/`
|
||||
* `/users/{user_id}/activate`
|
||||
* `/items/pro/`
|
||||
|
||||
тоді ви могли б додати різні вимоги до дозволів для кожної з них лише за допомогою залежностей і субзалежностей:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
current_user(["current_user"])
|
||||
active_user(["active_user"])
|
||||
admin_user(["admin_user"])
|
||||
paying_user(["paying_user"])
|
||||
|
||||
public["/items/public/"]
|
||||
private["/items/private/"]
|
||||
activate_user["/users/{user_id}/activate"]
|
||||
pro_items["/items/pro/"]
|
||||
|
||||
current_user --> active_user
|
||||
active_user --> admin_user
|
||||
active_user --> paying_user
|
||||
|
||||
current_user --> public
|
||||
active_user --> private
|
||||
admin_user --> activate_user
|
||||
paying_user --> pro_items
|
||||
```
|
||||
|
||||
## Інтегровано з **OpenAPI** { #integrated-with-openapi_1 }
|
||||
|
||||
Усі ці залежності, декларуючи свої вимоги, також додають параметри, перевірки тощо до ваших *операцій шляху*.
|
||||
|
||||
**FastAPI** подбає про додавання всього цього до схеми OpenAPI, щоб це відображалося в інтерактивних системах документації.
|
||||
105
docs/uk/docs/tutorial/dependencies/sub-dependencies.md
Normal file
105
docs/uk/docs/tutorial/dependencies/sub-dependencies.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Підзалежності { #sub-dependencies }
|
||||
|
||||
Ви можете створювати залежності, які мають підзалежності.
|
||||
|
||||
Вони можуть бути настільки глибокими, наскільки потрібно.
|
||||
|
||||
FastAPI подбає про їх розв'язання.
|
||||
|
||||
## Перша залежність «dependable» { #first-dependency-dependable }
|
||||
|
||||
Можна створити першу залежність («dependable») так:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *}
|
||||
|
||||
Вона оголошує необов'язковий параметр запиту `q` типу `str`, а потім просто повертає його.
|
||||
|
||||
Це досить просто (не дуже корисно), але допоможе зосередитися на тому, як працюють підзалежності.
|
||||
|
||||
## Друга залежність, «dependable» і «dependant» { #second-dependency-dependable-and-dependant }
|
||||
|
||||
Далі ви можете створити іншу функцію залежності («dependable»), яка водночас оголошує власну залежність (тож вона також є «dependant»):
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
|
||||
|
||||
Зосередьмося на оголошених параметрах:
|
||||
|
||||
- Хоча ця функція сама є залежністю («dependable»), вона також оголошує іншу залежність (вона «залежить» від чогось).
|
||||
- Вона залежить від `query_extractor` і присвоює значення, яке він повертає, параметру `q`.
|
||||
- Вона також оголошує необов'язкове кукі `last_query` типу `str`.
|
||||
- Якщо користувач не надав параметр запиту `q`, ми використовуємо останній запит, який зберегли раніше в кукі.
|
||||
|
||||
## Використання залежності { #use-the-dependency }
|
||||
|
||||
Потім ми можемо використати залежність так:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *}
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Зверніть увагу, що ми оголошуємо лише одну залежність у функції операції шляху — `query_or_cookie_extractor`.
|
||||
|
||||
Але FastAPI знатиме, що спочатку треба розв'язати `query_extractor`, щоб передати його результат у `query_or_cookie_extractor` під час виклику.
|
||||
|
||||
///
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
query_extractor(["query_extractor"])
|
||||
query_or_cookie_extractor(["query_or_cookie_extractor"])
|
||||
|
||||
read_query["/items/"]
|
||||
|
||||
query_extractor --> query_or_cookie_extractor --> read_query
|
||||
```
|
||||
|
||||
## Використання тієї ж залежності кілька разів { #using-the-same-dependency-multiple-times }
|
||||
|
||||
Якщо одна з ваших залежностей оголошена кілька разів для однієї операції шляху, наприклад, кілька залежностей мають спільну підзалежність, FastAPI знатиме, що цю підзалежність потрібно викликати лише один раз на запит.
|
||||
|
||||
І він збереже повернуте значення у <dfn title="Утиліта/система для збереження обчислених/згенерованих значень, щоб повторно використовувати їх замість повторного обчислення.">«кеш»</dfn> і передасть його всім «dependants», яким воно потрібне в цьому конкретному запиті, замість того щоб викликати залежність кілька разів для одного й того ж запиту.
|
||||
|
||||
У просунутому сценарії, коли ви знаєте, що залежність має викликатися на кожному кроці (можливо, кілька разів) у межах того самого запиту замість використання «кешованого» значення, ви можете встановити параметр `use_cache=False` при використанні `Depends`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
|
||||
return {"fresh_value": fresh_value}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ без Annotated
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Надавайте перевагу версії з `Annotated`, якщо це можливо.
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
|
||||
return {"fresh_value": fresh_value}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Попри всі модні терміни, система впровадження залежностей досить проста.
|
||||
|
||||
Це просто функції, які виглядають так само, як функції операцій шляху.
|
||||
|
||||
Втім вона дуже потужна і дозволяє оголошувати довільно глибоко вкладені «графи» залежностей (дерева).
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Усе це може здаватися не надто корисним на простих прикладах.
|
||||
|
||||
Але ви побачите, наскільки це корисно, у розділах про **безпеку**.
|
||||
|
||||
І також побачите, скільки коду це вам заощадить.
|
||||
|
||||
///
|
||||
211
docs/uk/docs/tutorial/extra-models.md
Normal file
211
docs/uk/docs/tutorial/extra-models.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Додаткові моделі { #extra-models }
|
||||
|
||||
Продовжуючи попередній приклад, часто потрібно мати більше ніж одну пов’язану модель.
|
||||
|
||||
Особливо це стосується моделей користувача, тому що:
|
||||
|
||||
* **вхідна модель** повинна мати пароль.
|
||||
* **вихідна модель** не повинна містити пароль.
|
||||
* **модель бази даних**, ймовірно, повинна містити хеш пароля.
|
||||
|
||||
/// danger | Обережно
|
||||
|
||||
Ніколи не зберігайте паролі користувачів у відкритому вигляді. Завжди зберігайте «безпечний хеш», який потім можна перевірити.
|
||||
|
||||
Якщо ви ще не знаєте, що таке «хеш пароля», ви дізнаєтесь у [розділах про безпеку](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Кілька моделей { #multiple-models }
|
||||
|
||||
Ось загальна ідея того, як можуть виглядати моделі з їхніми полями пароля та місцями використання:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
|
||||
|
||||
### Про `**user_in.model_dump()` { #about-user-in-model-dump }
|
||||
|
||||
#### `.model_dump()` у Pydantic { #pydantics-model-dump }
|
||||
|
||||
`user_in` - це модель Pydantic класу `UserIn`.
|
||||
|
||||
Моделі Pydantic мають метод `.model_dump()`, який повертає `dict` з даними моделі.
|
||||
|
||||
Отже, якщо ми створимо об’єкт Pydantic `user_in` так:
|
||||
|
||||
```Python
|
||||
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
|
||||
```
|
||||
|
||||
і викличемо:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.model_dump()
|
||||
```
|
||||
|
||||
тепер ми маємо `dict` з даними у змінній `user_dict` (це `dict`, а не об’єкт моделі Pydantic).
|
||||
|
||||
А якщо викликати:
|
||||
|
||||
```Python
|
||||
print(user_dict)
|
||||
```
|
||||
|
||||
ми отримаємо Python `dict` з:
|
||||
|
||||
```Python
|
||||
{
|
||||
'username': 'john',
|
||||
'password': 'secret',
|
||||
'email': 'john.doe@example.com',
|
||||
'full_name': None,
|
||||
}
|
||||
```
|
||||
|
||||
#### Розпакування `dict` { #unpacking-a-dict }
|
||||
|
||||
Якщо взяти `dict`, наприклад `user_dict`, і передати його у функцію (або клас) як `**user_dict`, Python «розпакує» його. Ключі та значення `user_dict` будуть передані безпосередньо як іменовані аргументи.
|
||||
|
||||
Отже, продовжуючи з `user_dict` вище, запис:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
дасть еквівалентний результат:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username="john",
|
||||
password="secret",
|
||||
email="john.doe@example.com",
|
||||
full_name=None,
|
||||
)
|
||||
```
|
||||
|
||||
А точніше, використовуючи безпосередньо `user_dict`, з будь-яким його вмістом у майбутньому:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
password = user_dict["password"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
)
|
||||
```
|
||||
|
||||
#### Модель Pydantic зі вмісту іншої { #a-pydantic-model-from-the-contents-of-another }
|
||||
|
||||
Як у прикладі вище ми отримали `user_dict` з `user_in.model_dump()`, цей код:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.model_dump()
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
буде еквівалентним:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.model_dump())
|
||||
```
|
||||
|
||||
...тому що `user_in.model_dump()` повертає `dict`, а ми змушуємо Python «розпакувати» його, передаючи в `UserInDB` з префіксом `**`.
|
||||
|
||||
Тож ми отримуємо модель Pydantic з даних іншої моделі Pydantic.
|
||||
|
||||
#### Розпакування `dict` і додаткові ключові аргументи { #unpacking-a-dict-and-extra-keywords }
|
||||
|
||||
Додаючи додатковий іменований аргумент `hashed_password=hashed_password`, як тут:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
|
||||
```
|
||||
|
||||
...у підсумку це дорівнює:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
password = user_dict["password"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
hashed_password = hashed_password,
|
||||
)
|
||||
```
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Додаткові допоміжні функції `fake_password_hasher` і `fake_save_user` лише демонструють можливий потік даних і, звісно, не забезпечують реальної безпеки.
|
||||
|
||||
///
|
||||
|
||||
## Зменшення дублювання { #reduce-duplication }
|
||||
|
||||
Зменшення дублювання коду - одна з ключових ідей у **FastAPI**.
|
||||
|
||||
Адже дублювання коду підвищує ймовірність помилок, проблем безпеки, розсинхронізації коду (коли ви оновлюєте в одному місці, але не в інших) тощо.
|
||||
|
||||
І ці моделі спільно використовують багато даних та дублюють назви і типи атрибутів.
|
||||
|
||||
Можна зробити краще.
|
||||
|
||||
Можна оголосити модель `UserBase`, яка буде базовою для інших моделей. Потім створити підкласи цієї моделі, що наслідуватимуть її атрибути (оголошення типів, валідацію тощо).
|
||||
|
||||
Уся конвертація даних, валідація, документація тощо працюватимуть як зазвичай.
|
||||
|
||||
Таким чином, ми оголошуємо лише відмінності між моделями (з відкритим `password`, з `hashed_password` і без пароля):
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
|
||||
|
||||
## `Union` або `anyOf` { #union-or-anyof }
|
||||
|
||||
Ви можете оголосити відповідь як `Union` двох або більше типів - це означає, що відповідь може бути будь-якого з них.
|
||||
|
||||
В OpenAPI це буде визначено як `anyOf`.
|
||||
|
||||
Для цього використайте стандартну підказку типу Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Під час визначення <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a> спочатку вказуйте найконкретніший тип, а потім менш конкретний. У прикладі нижче більш конкретний `PlaneItem` стоїть перед `CarItem` у `Union[PlaneItem, CarItem]`.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
|
||||
|
||||
### `Union` у Python 3.10 { #union-in-python-3-10 }
|
||||
|
||||
У цьому прикладі ми передаємо `Union[PlaneItem, CarItem]` як значення аргументу `response_model`.
|
||||
|
||||
Оскільки ми передаємо його як значення аргументу, а не в анотації типу, потрібно використовувати `Union` навіть у Python 3.10.
|
||||
|
||||
Якби це була анотація типу, можна було б використати вертикальну риску, наприклад:
|
||||
|
||||
```Python
|
||||
some_variable: PlaneItem | CarItem
|
||||
```
|
||||
|
||||
Але якщо записати це як присвоєння `response_model=PlaneItem | CarItem`, отримаємо помилку, тому що Python спробує виконати невалідну операцію між `PlaneItem` і `CarItem`, замість того щоб трактувати це як анотацію типу.
|
||||
|
||||
## Список моделей { #list-of-models }
|
||||
|
||||
Аналогічно можна оголошувати відповіді як списки об’єктів.
|
||||
|
||||
Для цього використайте стандартний Python `list`:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *}
|
||||
|
||||
## Відповідь з довільним `dict` { #response-with-arbitrary-dict }
|
||||
|
||||
Також можна оголосити відповідь, використовуючи звичайний довільний `dict`, вказавши лише типи ключів і значень, без моделі Pydantic.
|
||||
|
||||
Це корисно, якщо ви заздалегідь не знаєте допустимі назви полів/атрибутів (які були б потрібні для моделі Pydantic).
|
||||
|
||||
У такому разі можна використати `dict`:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *}
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Використовуйте кілька моделей Pydantic і вільно наслідуйте для кожного випадку.
|
||||
|
||||
Не обов’язково мати одну модель даних на сутність, якщо ця сутність може мати різні «стани». Як у випадку сутності користувача зі станами: з `password`, з `password_hash` і без пароля.
|
||||
107
docs/uk/docs/tutorial/path-operation-configuration.md
Normal file
107
docs/uk/docs/tutorial/path-operation-configuration.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Налаштування операції шляху { #path-operation-configuration }
|
||||
|
||||
Є кілька параметрів, які ви можете передати вашому «декоратору операції шляху» для налаштування.
|
||||
|
||||
/// warning | Попередження
|
||||
|
||||
Зверніть увагу, що ці параметри передаються безпосередньо «декоратору операції шляху», а не вашій «функції операції шляху».
|
||||
|
||||
///
|
||||
|
||||
## Код статусу відповіді { #response-status-code }
|
||||
|
||||
Ви можете визначити (HTTP) `status_code`, який буде використано у відповіді вашої «операції шляху».
|
||||
|
||||
Можна передати безпосередньо цілий код, наприклад `404`.
|
||||
|
||||
Якщо ви не пам'ятаєте призначення числових кодів, скористайтеся скороченими константами в `status`:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
|
||||
|
||||
Цей код статусу буде використано у відповіді та додано до схеми OpenAPI.
|
||||
|
||||
/// note | Технічні деталі
|
||||
|
||||
Ви також можете використати `from starlette import status`.
|
||||
|
||||
FastAPI надає той самий `starlette.status` як `fastapi.status` для вашої зручності як розробника. Але він походить безпосередньо зі Starlette.
|
||||
|
||||
///
|
||||
|
||||
## Мітки { #tags }
|
||||
|
||||
Ви можете додати мітки до вашої «операції шляху», передайте параметр `tags` зі `list` із `str` (зазвичай лише один `str`):
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *}
|
||||
|
||||
Вони будуть додані до схеми OpenAPI та використані інтерфейсами автоматичної документації:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image01.png">
|
||||
|
||||
### Мітки з переліками { #tags-with-enums }
|
||||
|
||||
У великому застосунку ви можете накопичити багато міток і захочете переконатися, що завжди використовуєте ту саму мітку для пов'язаних «операцій шляху».
|
||||
|
||||
У таких випадках має сенс зберігати мітки в `Enum`.
|
||||
|
||||
FastAPI підтримує це так само, як і зі звичайними строками:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *}
|
||||
|
||||
## Короткий опис і опис { #summary-and-description }
|
||||
|
||||
Ви можете додати `summary` і `description`:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *}
|
||||
|
||||
## Опис зі строки документації { #description-from-docstring }
|
||||
|
||||
Оскільки описи зазвичай довгі та займають кілька рядків, ви можете оголосити опис «операції шляху» у <dfn title="багаторядкова строка як перший вираз усередині функції (не прив'язаний до жодної змінної), використовується для документації">строці документації</dfn> функції, і FastAPI прочитає його звідти.
|
||||
|
||||
Ви можете писати <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> у строці документації, його буде інтерпретовано та показано коректно (з урахуванням відступів у строці документації).
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *}
|
||||
|
||||
Його буде використано в інтерактивній документації:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image02.png">
|
||||
|
||||
## Опис відповіді { #response-description }
|
||||
|
||||
Ви можете вказати опис відповіді параметром `response_description`:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *}
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Зверніть увагу, що `response_description` стосується саме відповіді, а `description` стосується «операції шляху» загалом.
|
||||
|
||||
///
|
||||
|
||||
/// check | Перевірте
|
||||
|
||||
OpenAPI визначає, що кожна «операція шляху» потребує опису відповіді.
|
||||
|
||||
Тому, якщо ви його не надасте, FastAPI автоматично згенерує «Successful response».
|
||||
|
||||
///
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image03.png">
|
||||
|
||||
## Позначити операцію шляху як застарілу { #deprecate-a-path-operation }
|
||||
|
||||
Якщо потрібно позначити «операцію шляху» як <dfn title="застарілий, не рекомендується використовувати">застарілу</dfn>, але не видаляючи її, передайте параметр `deprecated`:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *}
|
||||
|
||||
У інтерактивній документації вона буде чітко позначена як застаріла:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image04.png">
|
||||
|
||||
Подивіться, як виглядають застарілі та незастарілі «операції шляху»:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image05.png">
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Ви можете легко налаштовувати та додавати метадані до ваших «операцій шляху», передаючи параметри «декораторам операцій шляху».
|
||||
203
docs/uk/docs/tutorial/security/first-steps.md
Normal file
203
docs/uk/docs/tutorial/security/first-steps.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Безпека - перші кроки { #security-first-steps }
|
||||
|
||||
Уявімо, що ваш **backend** API працює на певному домені.
|
||||
|
||||
А **frontend** - на іншому домені або в іншому шляху того ж домену (або у мобільному застосунку).
|
||||
|
||||
І ви хочете, щоб frontend міг автентифікуватися в backend, використовуючи ім'я користувача та пароль.
|
||||
|
||||
Ми можемо використати **OAuth2**, щоб збудувати це з **FastAPI**.
|
||||
|
||||
Але заощадимо вам час на читання всієї довгої специфікації, щоб знайти лише потрібні дрібниці.
|
||||
|
||||
Скористаймося інструментами, які надає **FastAPI**, щоб обробляти безпеку.
|
||||
|
||||
## Як це виглядає { #how-it-looks }
|
||||
|
||||
Спочатку просто запустімо код і подивімося, як він працює, а потім повернемося, щоб розібратися, що відбувається.
|
||||
|
||||
## Створіть `main.py` { #create-main-py }
|
||||
|
||||
Скопіюйте приклад у файл `main.py`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py310.py *}
|
||||
|
||||
## Запустіть { #run-it }
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Пакет <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> автоматично встановлюється з **FastAPI**, коли ви виконуєте команду `pip install "fastapi[standard]"`.
|
||||
|
||||
Однак, якщо ви використовуєте команду `pip install fastapi`, пакет `python-multipart` за замовчуванням не включено.
|
||||
|
||||
Щоб встановити його вручну, переконайтеся, що ви створили [віртуальне оточення](../../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
Це тому, що **OAuth2** використовує «form data» для надсилання `username` та `password`.
|
||||
|
||||
///
|
||||
|
||||
Запустіть приклад:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Перевірте { #check-it }
|
||||
|
||||
Перейдіть до інтерактивної документації: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Ви побачите щось подібне:
|
||||
|
||||
<img src="/img/tutorial/security/image01.png">
|
||||
|
||||
/// check | Кнопка Authorize!
|
||||
|
||||
У вас уже є нова блискуча кнопка «Authorize».
|
||||
|
||||
А ваша *операція шляху* має маленький замок у правому верхньому куті, на який можна натиснути.
|
||||
|
||||
///
|
||||
|
||||
Якщо натиснути її, з’явиться маленька форма авторизації, щоб ввести `username` і `password` (та інші необов’язкові поля):
|
||||
|
||||
<img src="/img/tutorial/security/image02.png">
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Неважливо, що ви введете у форму, поки що це не спрацює. Але ми скоро це доробимо.
|
||||
|
||||
///
|
||||
|
||||
Звісно, це не frontend для кінцевих користувачів, але це чудовий інструмент для інтерактивної документації всього вашого API.
|
||||
|
||||
Ним може користуватися команда frontend (якою можете бути і ви самі).
|
||||
|
||||
Ним можуть користуватися сторонні застосунки та системи.
|
||||
|
||||
І ним також можете користуватися ви самі, щоб налагоджувати, перевіряти та тестувати той самий застосунок.
|
||||
|
||||
## Потік паролю { #the-password-flow }
|
||||
|
||||
Тепер повернімося трохи назад і розберімося, що це все таке.
|
||||
|
||||
`password` «flow» - це один зі способів («flows»), визначених в OAuth2, для обробки безпеки та автентифікації.
|
||||
|
||||
OAuth2 був спроєктований так, щоб backend або API могли бути незалежними від сервера, який автентифікує користувача.
|
||||
|
||||
Але в нашому випадку той самий застосунок **FastAPI** оброблятиме і API, і автентифікацію.
|
||||
|
||||
Тож розгляньмо це у спрощеному вигляді:
|
||||
|
||||
- Користувач вводить `username` і `password` у frontend і натискає `Enter`.
|
||||
- Frontend (у браузері користувача) надсилає ці `username` і `password` на специфічну URL-адресу нашого API (оголошену як `tokenUrl="token"`).
|
||||
- API перевіряє ці `username` і `password` та повертає «токен» (ми ще нічого з цього не реалізували).
|
||||
- «Токен» - це просто строка з деяким вмістом, який ми можемо пізніше використати, щоб перевірити цього користувача.
|
||||
- Зазвичай токен налаштований на завершення строку дії через певний час.
|
||||
- Тож користувачу доведеться знову увійти пізніше.
|
||||
- І якщо токен викрадуть, ризик менший. Це не як постійний ключ, який працюватиме завжди (у більшості випадків).
|
||||
- Frontend тимчасово зберігає цей токен десь.
|
||||
- Користувач клікає у frontend, щоб перейти до іншого розділу вебзастосунку.
|
||||
- Frontend має отримати ще дані з API.
|
||||
- Але для того конкретного кінцевого пункту потрібна автентифікація.
|
||||
- Тож, щоб автентифікуватися в нашому API, він надсилає заголовок `Authorization` зі значенням `Bearer ` плюс токен.
|
||||
- Якщо токен містить `foobar`, вміст заголовка `Authorization` буде: `Bearer foobar`.
|
||||
|
||||
## `OAuth2PasswordBearer` у **FastAPI** { #fastapis-oauth2passwordbearer }
|
||||
|
||||
**FastAPI** надає кілька інструментів на різних рівнях абстракції, щоб реалізувати ці функції безпеки.
|
||||
|
||||
У цьому прикладі ми використаємо **OAuth2** з потоком **Password**, використовуючи токен **Bearer**. Це робиться за допомогою класу `OAuth2PasswordBearer`.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
«Bearer»-токен - не єдиний варіант.
|
||||
|
||||
Але це найкращий для нашого сценарію.
|
||||
|
||||
І, можливо, найкращий для більшості сценаріїв, якщо ви не експерт з OAuth2 і точно не знаєте, чому інша опція краще відповідає вашим потребам.
|
||||
|
||||
У такому разі **FastAPI** теж надає інструменти, щоб це збудувати.
|
||||
|
||||
///
|
||||
|
||||
Коли ми створюємо екземпляр класу `OAuth2PasswordBearer`, ми передаємо параметр `tokenUrl`. Цей параметр містить URL, який клієнт (frontend у браузері користувача) використовуватиме, щоб надіслати `username` і `password` для отримання токена.
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Тут `tokenUrl="token"` відноситься до відносної URL-адреси `token`, яку ми ще не створили. Оскільки це відносна URL-адреса, вона еквівалентна `./token`.
|
||||
|
||||
Тому, якщо ваш API розміщений на `https://example.com/`, це буде `https://example.com/token`. А якщо на `https://example.com/api/v1/`, тоді це буде `https://example.com/api/v1/token`.
|
||||
|
||||
Використання відносної URL-адреси важливе, щоб ваша програма продовжувала працювати навіть у просунутому сценарії, як-от [Behind a Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
Цей параметр не створює той endpoint / *операцію шляху*, а декларує, що URL `/token` буде тим, який клієнт має використовувати, щоб отримати токен. Ця інформація використовується в OpenAPI, а потім - у системах інтерактивної документації API.
|
||||
|
||||
Незабаром ми також створимо фактичну операцію шляху.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Якщо ви дуже строгий «Pythonista», вам може не подобатися стиль імені параметра `tokenUrl` замість `token_url`.
|
||||
|
||||
Це тому, що використано ту саму назву, що і в специфікації OpenAPI. Тож якщо вам потрібно буде дослідити ці схеми безпеки глибше, ви зможете просто скопіювати та вставити її, щоб знайти більше інформації.
|
||||
|
||||
///
|
||||
|
||||
Змінна `oauth2_scheme` - це екземпляр `OAuth2PasswordBearer`, але це також і «викликаємий» об’єкт.
|
||||
|
||||
Його можна викликати так:
|
||||
|
||||
```Python
|
||||
oauth2_scheme(some, parameters)
|
||||
```
|
||||
|
||||
Тож його можна використовувати з `Depends`.
|
||||
|
||||
### Використання { #use-it }
|
||||
|
||||
Тепер ви можете передати `oauth2_scheme` як залежність через `Depends`.
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *}
|
||||
|
||||
Ця залежність надасть `str`, який буде присвоєний параметру `token` *функції операції шляху*.
|
||||
|
||||
**FastAPI** знатиме, що може використати цю залежність, щоб визначити «схему безпеки» в схемі OpenAPI (і в автоматичній документації API).
|
||||
|
||||
/// info | Технічні деталі
|
||||
|
||||
**FastAPI** знатиме, що може використати клас `OAuth2PasswordBearer` (оголошений у залежності), щоб визначити схему безпеки в OpenAPI, тому що він наслідує `fastapi.security.oauth2.OAuth2`, який своєю чергою наслідує `fastapi.security.base.SecurityBase`.
|
||||
|
||||
Усі утиліти безпеки, які інтегруються з OpenAPI (і автоматичною документацією API), наслідують `SecurityBase`. Так **FastAPI** розуміє, як інтегрувати їх в OpenAPI.
|
||||
|
||||
///
|
||||
|
||||
## Що відбувається { #what-it-does }
|
||||
|
||||
Вона шукатиме в запиті заголовок `Authorization`, перевірить, чи його значення - це `Bearer ` плюс деякий токен, і поверне токен як `str`.
|
||||
|
||||
Якщо заголовка `Authorization` немає або значення не містить токена `Bearer `, вона одразу відповість помилкою зі статус-кодом 401 (`UNAUTHORIZED`).
|
||||
|
||||
Вам навіть не потрібно перевіряти, чи існує токен, щоб повернути помилку. Ви можете бути певні: якщо ваша функція виконується, у параметрі токена буде `str`.
|
||||
|
||||
Ви вже можете спробувати це в інтерактивній документації:
|
||||
|
||||
<img src="/img/tutorial/security/image03.png">
|
||||
|
||||
Ми ще не перевіряємо дійсність токена, але це вже початок.
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Отже, лише 3-4 додатковими рядками ви вже маєте деяку примітивну форму безпеки.
|
||||
105
docs/uk/docs/tutorial/security/get-current-user.md
Normal file
105
docs/uk/docs/tutorial/security/get-current-user.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Отримати поточного користувача { #get-current-user }
|
||||
|
||||
У попередньому розділі система безпеки (яка базується на системі впровадження залежностей) передавала функції операції шляху `token` як `str`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *}
|
||||
|
||||
Але це ще не дуже корисно.
|
||||
|
||||
Зробімо так, щоб вона повертала поточного користувача.
|
||||
|
||||
## Створити модель користувача { #create-a-user-model }
|
||||
|
||||
Спочатку створімо модель користувача в Pydantic.
|
||||
|
||||
Так само, як ми використовуємо Pydantic для оголошення тіл, ми можемо використовувати його будь-де:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
|
||||
|
||||
## Створити залежність `get_current_user` { #create-a-get-current-user-dependency }
|
||||
|
||||
Створімо залежність `get_current_user`.
|
||||
|
||||
Пам'ятаєте, що залежності можуть мати підзалежності?
|
||||
|
||||
`get_current_user` матиме залежність із тим самим `oauth2_scheme`, який ми створили раніше.
|
||||
|
||||
Так само, як ми робили раніше безпосередньо в операції шляху, наша нова залежність `get_current_user` отримає `token` як `str` від підзалежності `oauth2_scheme`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
|
||||
|
||||
## Отримати користувача { #get-the-user }
|
||||
|
||||
`get_current_user` використає (фальшиву) утилітну функцію, яку ми створили, що приймає `token` як `str` і повертає нашу Pydantic-модель `User`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
|
||||
|
||||
## Впровадити поточного користувача { #inject-the-current-user }
|
||||
|
||||
Тепер ми можемо використати той самий `Depends` з нашим `get_current_user` в операції шляху:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
|
||||
|
||||
Зверніть увагу, що ми оголошуємо тип `current_user` як Pydantic-модель `User`.
|
||||
|
||||
Це допоможе всередині функції з автодоповненням і перевірками типів.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Можливо, ви пам'ятаєте, що тіла запиту також оголошуються моделями Pydantic.
|
||||
|
||||
Тут **FastAPI** не заплутається, тому що ви використовуєте `Depends`.
|
||||
|
||||
///
|
||||
|
||||
/// check | Перевірте
|
||||
|
||||
Те, як спроєктована ця система залежностей, дозволяє мати різні залежності (різні «залежні»), які всі повертають модель `User`.
|
||||
|
||||
Ми не обмежені наявністю лише однієї залежності, що може повертати такі дані.
|
||||
|
||||
///
|
||||
|
||||
## Інші моделі { #other-models }
|
||||
|
||||
Тепер ви можете отримувати поточного користувача безпосередньо у функціях операцій шляху та працювати з механізмами безпеки на рівні **впровадження залежностей**, використовуючи `Depends`.
|
||||
|
||||
І ви можете використовувати будь-яку модель або дані для вимог безпеки (у цьому випадку Pydantic-модель `User`).
|
||||
|
||||
Але ви не обмежені використанням якоїсь конкретної модели даних, класу чи типу.
|
||||
|
||||
Хочете мати id та email і не мати жодного username у вашій моделі? Без проблем. Ви можете використовувати ті самі інструменти.
|
||||
|
||||
Хочете мати просто `str`? Або лише `dict`? Або безпосередньо екземпляр класу моделі бази даних? Усе працює так само.
|
||||
|
||||
У вашій програмі насправді входять не користувачі, а роботи, боти чи інші системи, що мають лише токен доступу? Знову ж, усе працює так само.
|
||||
|
||||
Просто використовуйте будь-який тип моделі, будь-який клас, будь-яку базу даних, які потрібні вашій програмі. **FastAPI** подбає про це завдяки системі впровадження залежностей.
|
||||
|
||||
## Розмір коду { #code-size }
|
||||
|
||||
Цей приклад може здаватися багатослівним. Майте на увазі, що ми змішуємо безпеку, моделі даних, утилітні функції та операції шляху в одному файлі.
|
||||
|
||||
Але ось ключовий момент.
|
||||
|
||||
Речі, пов'язані з безпекою та впровадженням залежностей, пишуться один раз.
|
||||
|
||||
І ви можете зробити це настільки складним, наскільки потрібно. І все одно мати це написаним лише один раз, в одному місці. З усією гнучкістю.
|
||||
|
||||
Зате ви можете мати тисячі кінцевих точок (операцій шляху), що використовують одну й ту саму систему безпеки.
|
||||
|
||||
І всі вони (або будь-яка їхня частина, яку ви захочете) можуть скористатися повторним використанням цих залежностей або будь-яких інших, які ви створите.
|
||||
|
||||
І всі ці тисячі операцій шляху можуть бути всього у 3 рядки:
|
||||
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Тепер ви можете отримувати поточного користувача безпосередньо у вашій функції операції шляху.
|
||||
|
||||
Ми вже на півдорозі.
|
||||
|
||||
Потрібно лише додати операцію шляху, щоб користувач/клієнт міг фактично надіслати `username` і `password`.
|
||||
|
||||
Далі саме це.
|
||||
277
docs/uk/docs/tutorial/security/oauth2-jwt.md
Normal file
277
docs/uk/docs/tutorial/security/oauth2-jwt.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# OAuth2 з паролем (і хешуванням), Bearer з токенами JWT { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
|
||||
|
||||
Тепер, коли ми маємо весь потік безпеки, зробімо застосунок справді захищеним, використовуючи токени <abbr title="JSON Web Tokens - Токени JSON Web">JWT</abbr> і безпечне хешування паролів.
|
||||
|
||||
Цей код ви можете реально використовувати у своєму застосунку, зберігати хеші паролів у своїй базі даних тощо.
|
||||
|
||||
Ми почнемо з того місця, де зупинилися в попередньому розділі, і розширимо його.
|
||||
|
||||
## Про JWT { #about-jwt }
|
||||
|
||||
JWT означає «JSON Web Tokens».
|
||||
|
||||
Це стандарт кодування об'єкта JSON у довгий щільний рядок без пробілів. Він виглядає так:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
```
|
||||
|
||||
Він не зашифрований, тому кожен може відновити інформацію з вмісту.
|
||||
|
||||
Але він підписаний. Тож коли ви отримуєте токен, який ви видали, ви можете перевірити, що справді його видали ви.
|
||||
|
||||
Таким чином, ви можете створити токен із терміном дії, наприклад, 1 тиждень. І коли користувач повернеться наступного дня з токеном, ви знатимете, що користувач досі увійшов у вашу систему.
|
||||
|
||||
Через тиждень токен завершить термін дії, і користувач не буде авторизований та має знову увійти, щоб отримати новий токен. І якщо користувач (або третя сторона) намагатиметься змінити токен, щоб змінити термін дії, ви це виявите, бо підписи не співпадатимуть.
|
||||
|
||||
Якщо хочете «погратися» з токенами JWT і побачити, як вони працюють, перегляньте <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
|
||||
|
||||
## Встановіть `PyJWT` { #install-pyjwt }
|
||||
|
||||
Нам потрібно встановити `PyJWT`, щоб створювати та перевіряти токени JWT у Python.
|
||||
|
||||
Переконайтеся, що ви створили [віртуальне оточення](../../virtual-environments.md){.internal-link target=_blank}, активували його і тоді встановіть `pyjwt`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pyjwt
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Якщо ви плануєте використовувати алгоритми цифрового підпису на кшталт RSA або ECDSA, слід встановити залежність криптобібліотеки `pyjwt[crypto]`.
|
||||
|
||||
Докладніше про це можна прочитати у <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">документації з встановлення PyJWT</a>.
|
||||
|
||||
///
|
||||
|
||||
## Хешування паролів { #password-hashing }
|
||||
|
||||
«Хешування» означає перетворення деякого вмісту (у цьому випадку пароля) на послідовність байтів (просто строку), що виглядає як нісенітниця.
|
||||
|
||||
Кожного разу, коли ви передаєте точно той самий вміст (точно той самий пароль), ви отримуєте точно ту саму «нісенітницю».
|
||||
|
||||
Але не можна перетворити цю «нісенітницю» назад у пароль.
|
||||
|
||||
### Навіщо використовувати хешування паролів { #why-use-password-hashing }
|
||||
|
||||
Якщо вашу базу даних вкрадуть, зловмисник не матиме відкритих паролів ваших користувачів, а лише хеші.
|
||||
|
||||
Тож зловмисник не зможе спробувати використати цей пароль в іншій системі (оскільки багато користувачів використовують той самий пароль всюди, це було б небезпечно).
|
||||
|
||||
## Встановіть `pwdlib` { #install-pwdlib }
|
||||
|
||||
pwdlib - це чудовий пакет Python для роботи з хешами паролів.
|
||||
|
||||
Він підтримує багато безпечних алгоритмів хешування та утиліт для роботи з ними.
|
||||
|
||||
Рекомендований алгоритм - «Argon2».
|
||||
|
||||
Переконайтеся, що ви створили [віртуальне оточення](../../virtual-environments.md){.internal-link target=_blank}, активували його і тоді встановіть pwdlib з Argon2:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "pwdlib[argon2]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
З `pwdlib` ви навіть можете налаштувати його так, щоб він умів читати паролі, створені **Django**, плагіном безпеки **Flask** або багатьма іншими.
|
||||
|
||||
Тож ви зможете, наприклад, спільно використовувати ті самі дані з застосунку Django в базі даних із застосунком FastAPI. Або поступово мігрувати застосунок Django, використовуючи ту саму базу даних.
|
||||
|
||||
І ваші користувачі зможуть входити як із вашого застосунку Django, так і з вашого застосунку **FastAPI** одночасно.
|
||||
|
||||
///
|
||||
|
||||
## Хешування і перевірка паролів { #hash-and-verify-the-passwords }
|
||||
|
||||
Імпортуйте потрібні інструменти з `pwdlib`.
|
||||
|
||||
Створіть екземпляр PasswordHash з рекомендованими налаштуваннями - він буде використаний для хешування та перевірки паролів.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
pwdlib також підтримує алгоритм хешування bcrypt, але не включає застарілі алгоритми - для роботи із застарілими хешами рекомендується використовувати бібліотеку passlib.
|
||||
|
||||
Наприклад, ви можете використати її для читання і перевірки паролів, згенерованих іншою системою (наприклад, Django), але хешувати будь-які нові паролі іншим алгоритмом, таким як Argon2 або Bcrypt.
|
||||
|
||||
І бути сумісними з усіма ними одночасно.
|
||||
|
||||
///
|
||||
|
||||
Створіть утилітарну функцію для хешування пароля, що надходить від користувача.
|
||||
|
||||
І ще одну утиліту для перевірки, чи отриманий пароль відповідає збереженому хешу.
|
||||
|
||||
І ще одну - для автентифікації та повернення користувача.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *}
|
||||
|
||||
Коли `authenticate_user` викликається з ім'ям користувача, якого немає в базі даних, ми все одно запускаємо `verify_password` для «підставного» хешу.
|
||||
|
||||
Це забезпечує приблизно однаковий час відповіді кінцевої точки незалежно від того, чи є ім'я користувача дійсним, запобігаючи **атакам за часом**, які могли б бути використані для перелічення наявних імен користувачів.
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Якщо ви перевірите нову (фальшиву) базу даних `fake_users_db`, побачите, як зараз виглядає хеш пароля: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`.
|
||||
|
||||
///
|
||||
|
||||
## Опрацювання токенів JWT { #handle-jwt-tokens }
|
||||
|
||||
Імпортуйте встановлені модулі.
|
||||
|
||||
Створіть випадковий секретний ключ, який буде використано для підписання токенів JWT.
|
||||
|
||||
Щоб згенерувати безпечний випадковий секретний ключ, використайте команду:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ openssl rand -hex 32
|
||||
|
||||
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
І скопіюйте вивід у змінну `SECRET_KEY` (не використовуйте той, що в прикладі).
|
||||
|
||||
Створіть змінну `ALGORITHM` з алгоритмом для підписання токена JWT і встановіть її в `"HS256"`.
|
||||
|
||||
Створіть змінну для терміну дії токена.
|
||||
|
||||
Визначте модель Pydantic, яка буде використана в кінцевій точці токена для відповіді.
|
||||
|
||||
Створіть утилітарну функцію для генерації нового токена доступу.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *}
|
||||
|
||||
## Оновіть залежності { #update-the-dependencies }
|
||||
|
||||
Оновіть `get_current_user`, щоб отримувати той самий токен, що й раніше, але цього разу - токен JWT.
|
||||
|
||||
Декодуйте отриманий токен, перевірте його та поверніть поточного користувача.
|
||||
|
||||
Якщо токен недійсний, одразу поверніть помилку HTTP.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *}
|
||||
|
||||
## Оновіть операцію шляху `/token` { #update-the-token-path-operation }
|
||||
|
||||
Створіть `timedelta` з часом життя токена.
|
||||
|
||||
Створіть справжній токен доступу JWT і поверніть його.
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *}
|
||||
|
||||
### Технічні деталі про «subject» `sub` у JWT { #technical-details-about-the-jwt-subject-sub }
|
||||
|
||||
Специфікація JWT каже, що існує ключ `sub` із суб'єктом токена.
|
||||
|
||||
Використовувати його не обов'язково, але саме туди зазвичай поміщають ідентифікатор користувача, тож ми використовуємо його тут.
|
||||
|
||||
JWT може використовуватися й для інших речей, окрім ідентифікації користувача та надання йому можливості безпосередньо виконувати операції з вашою API.
|
||||
|
||||
Наприклад, ви можете ідентифікувати «автомобіль» або «допис у блозі».
|
||||
|
||||
Тоді ви можете додати дозволи щодо цієї сутності, як-от «керувати» (для автомобіля) або «редагувати» (для допису).
|
||||
|
||||
І потім ви можете видати цей токен JWT користувачу (або боту), і він зможе виконувати ці дії (керувати автомобілем або редагувати допис), навіть не маючи облікового запису - лише з токеном JWT, який ваша API для цього згенерувала.
|
||||
|
||||
Використовуючи ці ідеї, JWT можна застосовувати у значно складніших сценаріях.
|
||||
|
||||
У таких випадках кілька сутностей можуть мати однакові ідентифікатори, скажімо `foo` (користувач `foo`, автомобіль `foo` і допис `foo`).
|
||||
|
||||
Щоб уникнути колізій ідентифікаторів, під час створення токена JWT для користувача ви можете додати префікс до значення ключа `sub`, наприклад `username:`. Отже, у цьому прикладі значення `sub` могло б бути: `username:johndoe`.
|
||||
|
||||
Важливо пам'ятати, що ключ `sub` має містити унікальний ідентифікатор у межах усього застосунку і має бути строкою.
|
||||
|
||||
## Перевірте { #check-it }
|
||||
|
||||
Запустіть сервер і перейдіть до документації: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
Ви побачите такий інтерфейс користувача:
|
||||
|
||||
<img src="/img/tutorial/security/image07.png">
|
||||
|
||||
Авторизуйте застосунок так само, як раніше.
|
||||
|
||||
Використайте облікові дані:
|
||||
|
||||
Username: `johndoe`
|
||||
Password: `secret`
|
||||
|
||||
/// check | Перевірте
|
||||
|
||||
Зверніть увагу, що ніде в коді немає відкритого пароля "`secret`", ми маємо лише хешовану версію.
|
||||
|
||||
///
|
||||
|
||||
<img src="/img/tutorial/security/image08.png">
|
||||
|
||||
Викличте кінцеву точку `/users/me/`, ви отримаєте відповідь:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image09.png">
|
||||
|
||||
Якщо відкриєте інструменти розробника, ви побачите, що у відправлених даних є лише токен, пароль надсилається тільки в першому запиті для автентифікації користувача та отримання токена доступу, але не надсилається далі:
|
||||
|
||||
<img src="/img/tutorial/security/image10.png">
|
||||
|
||||
/// note | Примітка
|
||||
|
||||
Зверніть увагу на заголовок `Authorization` зі значенням, що починається з `Bearer `.
|
||||
|
||||
///
|
||||
|
||||
## Просунуте використання зі `scopes` { #advanced-usage-with-scopes }
|
||||
|
||||
OAuth2 має поняття «scopes».
|
||||
|
||||
Ви можете використовувати їх, щоб додати конкретний набір дозволів до токена JWT.
|
||||
|
||||
Потім ви можете видати цей токен користувачу напряму або третій стороні для взаємодії з вашою API із набором обмежень.
|
||||
|
||||
Ви можете дізнатися, як їх використовувати і як вони інтегровані з **FastAPI** пізніше у **просунутому посібнику користувача**.
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Маючи все, що ви бачили досі, ви можете налаштувати захищений застосунок **FastAPI**, використовуючи стандарти на кшталт OAuth2 і JWT.
|
||||
|
||||
Майже в будь-якому фреймворку опрацювання безпеки дуже швидко стає досить складною темою.
|
||||
|
||||
Багато пакетів, що сильно це спрощують, змушені йти на численні компроміси з моделлю даних, базою даних і доступними можливостями. Дехто з цих пакетів, які надто все спрощують, насправді мають приховані вади безпеки.
|
||||
|
||||
---
|
||||
|
||||
**FastAPI** не йде на жодні компроміси з будь-якою базою даних, моделлю даних чи інструментом.
|
||||
|
||||
Він дає вам усю гнучкість, щоб обрати ті, які найкраще підходять вашому проєкту.
|
||||
|
||||
І ви можете напряму використовувати добре підтримувані та широко застосовувані пакети на кшталт `pwdlib` і `PyJWT`, адже **FastAPI** не вимагає жодних складних механізмів для інтеграції зовнішніх пакетів.
|
||||
|
||||
Водночас він надає інструменти, щоб максимально спростити процес без компромісів у гнучкості, надійності чи безпеці.
|
||||
|
||||
І ви можете використовувати та впроваджувати безпечні стандартні протоколи, як-от OAuth2, відносно простим способом.
|
||||
|
||||
У **просунутому посібнику користувача** ви можете дізнатися більше про те, як використовувати «scopes» в OAuth2 для більш детальної системи дозволів, дотримуючись тих самих стандартів. OAuth2 зі scopes - це механізм, який використовують багато великих провайдерів автентифікації, як-от Facebook, Google, GitHub, Microsoft, X (Twitter) тощо, щоб авторизувати сторонні застосунки на взаємодію з їхніми API від імені користувачів.
|
||||
289
docs/uk/docs/tutorial/security/simple-oauth2.md
Normal file
289
docs/uk/docs/tutorial/security/simple-oauth2.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Простий OAuth2 з паролем і Bearer { #simple-oauth2-with-password-and-bearer }
|
||||
|
||||
Тепер продовжимо з попереднього розділу і додамо відсутні частини, щоб отримати повний потік безпеки.
|
||||
|
||||
## Отримайте `username` і `password` { #get-the-username-and-password }
|
||||
|
||||
Ми використаємо утиліти безпеки **FastAPI**, щоб отримати `username` і `password`.
|
||||
|
||||
OAuth2 визначає, що під час використання «потоку паролю» (який ми використовуємо) клієнт/користувач має надіслати поля `username` і `password` як дані форми.
|
||||
|
||||
І специфікація каже, що поля мають називатися саме так. Тому `user-name` або `email` не підійдуть.
|
||||
|
||||
Але не хвилюйтеся, у фронтенді ви можете відображати це так, як забажаєте, для кінцевих користувачів.
|
||||
|
||||
І ваші моделі бази даних можуть використовувати будь-які інші назви.
|
||||
|
||||
Але для *операції шляху* входу ми маємо використовувати саме ці назви, щоб бути сумісними зі специфікацією (і мати змогу, наприклад, користуватися вбудованою системою документації API).
|
||||
|
||||
Специфікація також вказує, що `username` і `password` мають надсилатися як дані форми (тобто без JSON).
|
||||
|
||||
### `scope` { #scope }
|
||||
|
||||
Специфікація також каже, що клієнт може надіслати інше поле форми «`scope`».
|
||||
|
||||
Назва поля форми — `scope` (в однині), але насправді це довга строка зі «scopes», розділеними пробілами.
|
||||
|
||||
Кожен «scope» — це просто строка (без пробілів).
|
||||
|
||||
Їх зазвичай використовують для оголошення конкретних прав доступу, наприклад:
|
||||
|
||||
- `users:read` або `users:write` — поширені приклади.
|
||||
- `instagram_basic` використовується Facebook / Instagram.
|
||||
- `https://www.googleapis.com/auth/drive` використовується Google.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
У OAuth2 «scope» — це просто строка, що оголошує конкретний потрібний дозвіл.
|
||||
|
||||
Не має значення, чи містить вона інші символи на кшталт `:` або чи це URL.
|
||||
|
||||
Ці деталі залежать від реалізації.
|
||||
|
||||
Для OAuth2 це просто строки.
|
||||
|
||||
///
|
||||
|
||||
## Код для отримання `username` і `password` { #code-to-get-the-username-and-password }
|
||||
|
||||
Тепер скористаймося утилітами, наданими **FastAPI**, щоб обробити це.
|
||||
|
||||
### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
|
||||
|
||||
Спочатку імпортуйте `OAuth2PasswordRequestForm` і використайте його як залежність із `Depends` в *операції шляху* для `/token`:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
|
||||
|
||||
`OAuth2PasswordRequestForm` — це клас залежності, що оголошує тіло форми з:
|
||||
|
||||
- `username`.
|
||||
- `password`.
|
||||
- Необов'язковим полем `scope` як великою строкою, складеною зі строк, розділених пробілами.
|
||||
- Необов'язковим `grant_type`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Специфікація OAuth2 насправді вимагає поле `grant_type` із фіксованим значенням `password`, але `OAuth2PasswordRequestForm` цього не примушує.
|
||||
|
||||
Якщо вам потрібно це примусити, використовуйте `OAuth2PasswordRequestFormStrict` замість `OAuth2PasswordRequestForm`.
|
||||
|
||||
///
|
||||
|
||||
- Необов'язковим `client_id` (для нашого прикладу не потрібно).
|
||||
- Необов'язковим `client_secret` (для нашого прикладу не потрібно).
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
`OAuth2PasswordRequestForm` — не спеціальний клас для **FastAPI**, як `OAuth2PasswordBearer`.
|
||||
|
||||
`OAuth2PasswordBearer` робить так, що **FastAPI** знає, що це схема безпеки. Тому її додають таким чином до OpenAPI.
|
||||
|
||||
Але `OAuth2PasswordRequestForm` — це просто клас залежності, який ви могли б написати самі, або ви могли б напряму оголосити параметри `Form`.
|
||||
|
||||
Оскільки це типова задача, **FastAPI** надає його безпосередньо, щоб спростити роботу.
|
||||
|
||||
///
|
||||
|
||||
### Використовуйте дані форми { #use-the-form-data }
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Екземпляр класу залежності `OAuth2PasswordRequestForm` не матиме атрибута `scope` з довгою строкою, розділеною пробілами, натомість він матиме атрибут `scopes` зі справжнім списком строк для кожного надісланого scope.
|
||||
|
||||
У цьому прикладі ми не використовуємо `scopes`, але ця можливість є, якщо знадобиться.
|
||||
|
||||
///
|
||||
|
||||
Тепер отримайте дані користувача з (фальшивої) бази даних, використовуючи `username` з поля форми.
|
||||
|
||||
Якщо такого користувача немає, повертаємо помилку «Incorrect username or password».
|
||||
|
||||
Для помилки використовуємо виняток `HTTPException`:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
|
||||
|
||||
### Перевірте пароль { #check-the-password }
|
||||
|
||||
На цьому етапі ми маємо дані користувача з нашої бази даних, але ще не перевірили пароль.
|
||||
|
||||
Спершу розмістимо ці дані в Pydantic-моделі `UserInDB`.
|
||||
|
||||
Ви ніколи не повинні зберігати паролі у відкритому вигляді, тож ми використаємо (фальшиву) систему хешування паролів.
|
||||
|
||||
Якщо паролі не збігаються, повертаємо ту саму помилку.
|
||||
|
||||
#### Хешування паролів { #password-hashing }
|
||||
|
||||
«Хешування» означає: перетворення деякого вмісту (у цьому випадку пароля) у послідовність байтів (просто строку), яка виглядає як нісенітниця.
|
||||
|
||||
Кожного разу, коли ви передаєте точно той самий вміст (точно той самий пароль), ви отримуєте точно ту саму «нісенітницю».
|
||||
|
||||
Але ви не можете перетворити цю «нісенітницю» назад у пароль.
|
||||
|
||||
##### Навіщо використовувати хешування паролів { #why-use-password-hashing }
|
||||
|
||||
Якщо вашу базу даних вкрадуть, зловмисник не матиме паролів користувачів у відкритому вигляді, лише хеші.
|
||||
|
||||
Тож зловмисник не зможе спробувати використати ті самі паролі в іншій системі (оскільки багато користувачів використовують той самий пароль всюди, це було б небезпечно).
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
|
||||
|
||||
#### Про `**user_dict` { #about-user-dict }
|
||||
|
||||
`UserInDB(**user_dict)` означає:
|
||||
|
||||
Передати ключі та значення з `user_dict` безпосередньо як аргументи ключ-значення, еквівалентно до:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
disabled = user_dict["disabled"],
|
||||
hashed_password = user_dict["hashed_password"],
|
||||
)
|
||||
```
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Для повнішого пояснення `**user_dict` перегляньте [документацію для **Додаткових моделей**](../extra-models.md#about-user-in-dict){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
## Поверніть токен { #return-the-token }
|
||||
|
||||
Відповідь точки входу `token` має бути об'єктом JSON.
|
||||
|
||||
Вона повинна містити `token_type`. У нашому випадку, оскільки ми використовуємо токени «Bearer», тип токена має бути «`bearer`».
|
||||
|
||||
Також має бути `access_token` зі строкою, що містить наш токен доступу.
|
||||
|
||||
Для цього простого прикладу ми зробимо повністю небезпечно і повернемо той самий `username` як токен.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
У наступному розділі ви побачите справді безпечну реалізацію з хешуванням паролів і токенами <abbr title="JSON Web Tokens - Токени JSON Web">JWT</abbr>.
|
||||
|
||||
А поки зосередьмося на конкретних деталях, які нам потрібні.
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Згідно зі специфікацією, ви маєте повертати JSON з `access_token` і `token_type`, як у цьому прикладі.
|
||||
|
||||
Це те, що ви маєте зробити самостійно у своєму коді, і переконатися, що використовуєте саме ці ключі JSON.
|
||||
|
||||
Це майже єдине, що ви маєте зробити правильно самі, щоб відповідати специфікації.
|
||||
|
||||
Решту **FastAPI** зробить за вас.
|
||||
|
||||
///
|
||||
|
||||
## Оновіть залежності { #update-the-dependencies }
|
||||
|
||||
Тепер оновимо наші залежності.
|
||||
|
||||
Ми хочемо отримати `current_user` лише якщо цей користувач активний.
|
||||
|
||||
Тому створимо додаткову залежність `get_current_active_user`, яка своєю чергою використовує як залежність `get_current_user`.
|
||||
|
||||
Обидві ці залежності просто повернуть HTTP-помилку, якщо користувача не існує або він неактивний.
|
||||
|
||||
Отже, у нашій кінцевій точці ми отримаємо користувача лише якщо він існує, був правильно автентифікований і є активним:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Додатковий заголовок `WWW-Authenticate` зі значенням `Bearer`, який ми тут повертаємо, також є частиною специфікації.
|
||||
|
||||
Будь-який HTTP (помилка) зі статус-кодом 401 «UNAUTHORIZED» також має повертати заголовок `WWW-Authenticate`.
|
||||
|
||||
У випадку токенів носія (наш випадок) значенням цього заголовка має бути `Bearer`.
|
||||
|
||||
Фактично ви можете пропустити цей додатковий заголовок, і все одно працюватиме.
|
||||
|
||||
Але він наданий тут, щоб відповідати специфікаціям.
|
||||
|
||||
Також можуть існувати інструменти, які очікують і використовують його (зараз або в майбутньому), і це може бути корисно вам або вашим користувачам, зараз або в майбутньому.
|
||||
|
||||
У цьому користь стандартів...
|
||||
|
||||
///
|
||||
|
||||
## Подивіться в дії { #see-it-in-action }
|
||||
|
||||
Відкрийте інтерактивну документацію: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
### Автентифікація { #authenticate }
|
||||
|
||||
Натисніть кнопку «Authorize».
|
||||
|
||||
Використайте облікові дані:
|
||||
|
||||
Користувач: `johndoe`
|
||||
|
||||
Пароль: `secret`
|
||||
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
Після автентифікації в системі ви побачите таке:
|
||||
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### Отримайте власні дані користувача { #get-your-own-user-data }
|
||||
|
||||
Тепер використайте операцію `GET` зі шляхом `/users/me`.
|
||||
|
||||
Ви отримаєте дані свого користувача, наприклад:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false,
|
||||
"hashed_password": "fakehashedsecret"
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
Якщо натиснути значок замка й вийти з системи, а потім знову спробувати ту саму операцію, ви отримаєте помилку HTTP 401 такого вигляду:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
### Неактивний користувач { #inactive-user }
|
||||
|
||||
Тепер спробуйте з неактивним користувачем, автентифікуйтеся з:
|
||||
|
||||
Користувач: `alice`
|
||||
|
||||
Пароль: `secret2`
|
||||
|
||||
І спробуйте використати операцію `GET` зі шляхом `/users/me`.
|
||||
|
||||
Ви отримаєте помилку «Inactive user», наприклад:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Inactive user"
|
||||
}
|
||||
```
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Тепер у вас є інструменти для реалізації повної системи безпеки на основі `username` і `password` для вашого API.
|
||||
|
||||
Використовуючи ці інструменти, ви можете зробити систему безпеки сумісною з будь-якою базою даних і з будь-якою моделлю користувача чи даних.
|
||||
|
||||
Єдина відсутня деталь у тому, що наразі це ще не «безпечна» реалізація.
|
||||
|
||||
У наступному розділі ви побачите, як використовувати безпечну бібліотеку для хешування паролів і токени <abbr title="JSON Web Tokens - Токени JSON Web">JWT</abbr>.
|
||||
357
docs/uk/docs/tutorial/sql-databases.md
Normal file
357
docs/uk/docs/tutorial/sql-databases.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# SQL (реляційні) бази даних { #sql-relational-databases }
|
||||
|
||||
**FastAPI** не вимагає від вас використовувати SQL (реляційну) базу даних. Але ви можете скористатися будь-якою базою даних, яку забажаєте.
|
||||
|
||||
Тут ми розглянемо приклад з <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>.
|
||||
|
||||
**SQLModel** побудовано поверх <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> та Pydantic. Її створив той самий автор, що і **FastAPI**, як ідеальну пару для застосунків FastAPI, яким потрібні **SQL бази даних**.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ви можете використовувати будь-яку іншу бібліотеку для SQL або NoSQL баз (інколи їх називають <abbr title="Object Relational Mapper - Об'єктно-реляційний відображувач: складний термін для бібліотеки, де деякі класи представляють таблиці SQL, а екземпляри представляють рядки в цих таблицях">"ORMs"</abbr>), FastAPI нічого не нав’язує. 😎
|
||||
|
||||
///
|
||||
|
||||
Оскільки SQLModel базується на SQLAlchemy, ви можете легко використовувати будь-яку базу даних, яку підтримує SQLAlchemy (а отже й SQLModel), наприклад:
|
||||
|
||||
* PostgreSQL
|
||||
* MySQL
|
||||
* SQLite
|
||||
* Oracle
|
||||
* Microsoft SQL Server тощо
|
||||
|
||||
У цьому прикладі ми використаємо **SQLite**, оскільки вона зберігається в одному файлі, а Python має вбудовану підтримку. Тож ви можете скопіювати цей приклад і запустити «як є».
|
||||
|
||||
Пізніше, для вашого продакшн-застосунку, ви можете перейти на сервер бази даних, наприклад **PostgreSQL**.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Існує офіційний генератор проєкту з **FastAPI** та **PostgreSQL**, включно з фронтендом та іншими інструментами: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
|
||||
|
||||
///
|
||||
|
||||
Це дуже простий і короткий навчальний посібник. Якщо ви хочете вивчити бази даних загалом, SQL або більш просунуті можливості, зверніться до <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документації SQLModel</a>.
|
||||
|
||||
## Встановіть `SQLModel` { #install-sqlmodel }
|
||||
|
||||
Спочатку переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили `sqlmodel`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install sqlmodel
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Створіть застосунок з однією моделлю { #create-the-app-with-a-single-model }
|
||||
|
||||
Спершу створимо найпростішу версію застосунку з однією моделлю **SQLModel**.
|
||||
|
||||
Потім нижче покращимо безпеку і гнучкість за допомогою кількох моделей. 🤓
|
||||
|
||||
### Створіть моделі { #create-models }
|
||||
|
||||
Імпортуйте `SQLModel` і створіть модель бази даних:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}
|
||||
|
||||
Клас `Hero` дуже схожий на модель Pydantic (фактично, під капотом це і є модель Pydantic).
|
||||
|
||||
Є кілька відмінностей:
|
||||
|
||||
* `table=True` каже SQLModel, що це «таблична модель» - вона має представляти **таблицю** в SQL базі даних, а не просто «модель даних» (як будь-який інший звичайний клас Pydantic).
|
||||
|
||||
* `Field(primary_key=True)` каже SQLModel, що `id` - це **первинний ключ** у SQL базі даних (більше про первинні ключі в SQL див. у документації SQLModel).
|
||||
|
||||
Примітка: Ми використовуємо `int | None` для поля первинного ключа, щоб у Python-коді можна було створити об’єкт без `id` (`id=None`), припускаючи, що база даних згенерує його під час збереження. SQLModel розуміє, що `id` надасть база даних, і визначає стовпець як ненульовий `INTEGER` у схемі бази даних. Докладніше див. <a href="https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id" class="external-link" target="_blank">документацію SQLModel про первинні ключі</a>.
|
||||
|
||||
* `Field(index=True)` каже SQLModel створити **SQL-індекс** для цього стовпця, що дозволить швидше виконувати пошук у базі даних під час читання даних, відфільтрованих за цим стовпцем.
|
||||
|
||||
SQLModel знатиме, що оголошене як `str` стане SQL-стовпцем типу `TEXT` (або `VARCHAR`, залежно від бази).
|
||||
|
||||
### Створіть рушій { #create-an-engine }
|
||||
|
||||
`engine` SQLModel (під капотом це `engine` SQLAlchemy) - це те, що **утримує з’єднання** з базою даних.
|
||||
|
||||
У вас має бути **єдиний об’єкт `engine`** для всього коду, що під’єднується до тієї самої бази.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
|
||||
|
||||
Використання `check_same_thread=False` дозволяє FastAPI використовувати ту саму базу SQLite у різних потоках. Це необхідно, адже **один запит** може використати **понад один потік** (наприклад, у залежностях).
|
||||
|
||||
Не переймайтеся, завдяки структурі коду ми далі забезпечимо **одну сесію SQLModel на запит**, власне цього і прагне `check_same_thread`.
|
||||
|
||||
### Створіть таблиці { #create-the-tables }
|
||||
|
||||
Додамо функцію, яка використовує `SQLModel.metadata.create_all(engine)`, щоб **створити таблиці** для всіх «табличних моделей».
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
|
||||
|
||||
### Створіть залежність сесії { #create-a-session-dependency }
|
||||
|
||||
**`Session`** зберігає **об’єкти в пам’яті** та відстежує зміни у даних, а потім **використовує `engine`** для взаємодії з базою даних.
|
||||
|
||||
Ми створимо **залежність** FastAPI з `yield`, яка надаватиме нову `Session` для кожного запиту. Це й гарантує, що ми використовуємо одну сесію на запит. 🤓
|
||||
|
||||
Далі створимо залежність `Annotated` - `SessionDep`, щоб спростити решту коду, який її використовуватиме.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
|
||||
|
||||
### Створюйте таблиці під час запуску { #create-database-tables-on-startup }
|
||||
|
||||
Ми створимо таблиці бази під час запуску застосунку.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}
|
||||
|
||||
Тут ми створюємо таблиці на події запуску застосунку.
|
||||
|
||||
У продакшні ви, ймовірно, використаєте міграційний скрипт, який виконується перед стартом застосунку. 🤓
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
SQLModel матиме утиліти міграцій-обгортки над Alembic, але поки що ви можете використовувати <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> напряму.
|
||||
|
||||
///
|
||||
|
||||
### Створіть героя { #create-a-hero }
|
||||
|
||||
Оскільки кожна модель SQLModel також є моделлю Pydantic, ви можете використовувати її в тих самих **анотаціях типів**, що і моделі Pydantic.
|
||||
|
||||
Наприклад, якщо ви оголосите параметр типу `Hero`, він буде прочитаний з **JSON-тіла**.
|
||||
|
||||
Так само ви можете оголосити її як **тип, що повертається** функції, і форма даних з’явиться в автоматичному UI документації API.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
|
||||
|
||||
Тут ми використовуємо залежність `SessionDep` (`Session`), щоб додати нового `Hero` до екземпляра `Session`, закомітити зміни до бази, оновити дані в `hero`, а потім повернути його.
|
||||
|
||||
### Читання героїв { #read-heroes }
|
||||
|
||||
Ми можемо **читати** `Hero` з бази даних, використовуючи `select()`. Ми можемо додати `limit` і `offset` для пагінації результатів.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
|
||||
|
||||
### Читання одного героя { #read-one-hero }
|
||||
|
||||
Ми можемо **прочитати** одного `Hero`.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
|
||||
|
||||
### Видалення героя { #delete-a-hero }
|
||||
|
||||
Ми також можемо **видалити** `Hero`.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
|
||||
|
||||
### Запуск застосунку { #run-the-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>
|
||||
|
||||
Потім перейдіть до інтерфейсу `/docs`, ви побачите, що **FastAPI** використовує ці **моделі** для **документування** API, а також для **серіалізації** та **валідації** даних.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image01.png">
|
||||
</div>
|
||||
|
||||
## Оновіть застосунок кількома моделями { #update-the-app-with-multiple-models }
|
||||
|
||||
Тепер трохи **відрефакторимо** застосунок, щоб підвищити **безпеку** та **гнучкість**.
|
||||
|
||||
Якщо подивитися на попередній варіант, у UI видно, що наразі клієнт може сам обирати `id` для створюваного `Hero`. 😱
|
||||
|
||||
Цього допускати не можна - вони можуть перезаписати `id`, який уже призначений у БД. Визначення `id` має виконувати **бекенд** або **база даних**, **а не клієнт**.
|
||||
|
||||
Крім того, ми створюємо для героя `secret_name`, але наразі повертаємо його всюди - це не дуже «секретно»... 😅
|
||||
|
||||
Виправимо це, додавши кілька **додаткових моделей**. Ось де SQLModel засяє. ✨
|
||||
|
||||
### Створіть кілька моделей { #create-multiple-models }
|
||||
|
||||
У **SQLModel** будь-який клас моделі з `table=True` - це **таблична модель**.
|
||||
|
||||
А будь-який клас моделі без `table=True` - це **модель даних**; це, по суті, просто моделі Pydantic (з парою невеликих доповнень). 🤓
|
||||
|
||||
З SQLModel ми можемо використовувати **успадкування**, щоб **уникати дублювання** полів у всіх випадках.
|
||||
|
||||
#### `HeroBase` - базовий клас { #herobase-the-base-class }
|
||||
|
||||
Почнімо з моделі `HeroBase`, яка містить усі **спільні поля** для всіх моделей:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
|
||||
|
||||
#### `Hero` - «таблична модель» { #hero-the-table-model }
|
||||
|
||||
Потім створімо `Hero`, фактичну «табличну модель», з **додатковими полями**, яких немає в інших моделях завжди:
|
||||
|
||||
* `id`
|
||||
* `secret_name`
|
||||
|
||||
Оскільки `Hero` успадковується від `HeroBase`, вона **також** містить **поля**, оголошені в `HeroBase`, тож усі поля `Hero` такі:
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
|
||||
|
||||
#### `HeroPublic` - публічна «модель даних» { #heropublic-the-public-data-model }
|
||||
|
||||
Далі створимо модель `HeroPublic` - це та, що буде **повертатися** клієнтам API.
|
||||
|
||||
Вона має ті самі поля, що й `HeroBase`, отже не включатиме `secret_name`.
|
||||
|
||||
Нарешті, особистості наших героїв захищено! 🥷
|
||||
|
||||
Вона також перевизначає `id: int`. Роблячи це, ми укладаємо **контракт** з клієнтами API, що `id` завжди буде присутній і буде типу `int` (ніколи не `None`).
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Наявність у моделі відповіді гарантії, що значення завжди доступне і завжди `int` (не `None`), дуже корисна для клієнтів API - вони можуть писати значно простіший код, маючи цю визначеність.
|
||||
|
||||
Також **автоматично згенеровані клієнти** матимуть простіші інтерфейси, тож розробникам, які взаємодіють з вашим API, буде значно зручніше. 😎
|
||||
|
||||
///
|
||||
|
||||
Усі поля в `HeroPublic` такі самі, як у `HeroBase`, з `id`, оголошеним як `int` (не `None`):
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
* `age`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
|
||||
|
||||
#### `HeroCreate` - «модель даних» для створення героя { #herocreate-the-data-model-to-create-a-hero }
|
||||
|
||||
Тепер створимо модель `HeroCreate` - вона **валідуватиме** дані, отримані від клієнтів.
|
||||
|
||||
Вона має ті ж поля, що й `HeroBase`, а також `secret_name`.
|
||||
|
||||
Тепер, коли клієнти **створюють нового героя**, вони надсилають `secret_name`, він зберігається в базі даних, але ці секретні імена не повертаються клієнтам у API.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Так слід поводитися з **паролями**. Приймайте їх, але не повертайте в API.
|
||||
|
||||
Також потрібно **хешувати** значення паролів перед збереженням, **ніколи не зберігайте їх у відкритому вигляді**.
|
||||
|
||||
///
|
||||
|
||||
Поля `HeroCreate`:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
|
||||
|
||||
#### `HeroUpdate` - «модель даних» для оновлення героя { #heroupdate-the-data-model-to-update-a-hero }
|
||||
|
||||
У попередній версії у нас не було способу **оновлювати героя**, але тепер, маючи **кілька моделей**, ми можемо це зробити. 🎉
|
||||
|
||||
«Модель даних» `HeroUpdate` дещо особлива: вона має **всі ті самі поля**, що й для створення нового героя, але всі поля **необов’язкові** (усі мають значення за замовчуванням). Таким чином, під час оновлення героя ви можете надіслати лише ті поля, які хочете змінити.
|
||||
|
||||
Оскільки **поля фактично змінюються** (тип тепер включає `None` і значення за замовчуванням - `None`), нам потрібно **оголосити їх заново**.
|
||||
|
||||
Насправді немає потреби успадковуватися від `HeroBase`, бо ми перевизначаємо всі поля. Я залишу успадкування для послідовності, але це не обов’язково. Це радше питання смаку. 🤷
|
||||
|
||||
Поля `HeroUpdate`:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
|
||||
|
||||
### Створення з `HeroCreate` і повернення `HeroPublic` { #create-with-herocreate-and-return-a-heropublic }
|
||||
|
||||
Тепер, коли в нас **кілька моделей**, ми можемо оновити частини застосунку, що їх використовують.
|
||||
|
||||
У запиті ми отримуємо «модель даних» `HeroCreate`, і з неї створюємо «табличну модель» `Hero`.
|
||||
|
||||
Ця нова «таблична модель» `Hero` міститиме поля, надіслані клієнтом, а також матиме `id`, згенерований базою даних.
|
||||
|
||||
Далі ми повертаємо з функції цю ж «табличну модель» `Hero`. Але оскільки ми оголошуємо `response_model` як «модель даних» `HeroPublic`, **FastAPI** використає `HeroPublic` для валідації та серіалізації даних.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Тепер ми використовуємо `response_model=HeroPublic` замість **анотації типу, що повертається** `-> HeroPublic`, тому що значення, яке ми повертаємо, насправді - не `HeroPublic`.
|
||||
|
||||
Якби ми оголосили `-> HeroPublic`, ваш редактор і лінтер справедливо зауважили б, що ви повертаєте `Hero`, а не `HeroPublic`.
|
||||
|
||||
Оголошуючи це в `response_model`, ми кажемо **FastAPI** зробити свою справу, не втручаючись в анотації типів та підказки від вашого редактора й інших інструментів.
|
||||
|
||||
///
|
||||
|
||||
### Читання героїв з `HeroPublic` { #read-heroes-with-heropublic }
|
||||
|
||||
Можемо зробити те саме, що й раніше, щоб **читати** `Hero`, знову використовуємо `response_model=list[HeroPublic]`, щоб гарантувати коректну валідацію та серіалізацію даних.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
|
||||
|
||||
### Читання одного героя з `HeroPublic` { #read-one-hero-with-heropublic }
|
||||
|
||||
Ми можемо **прочитати** одного героя:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
|
||||
|
||||
### Оновлення героя з `HeroUpdate` { #update-a-hero-with-heroupdate }
|
||||
|
||||
Ми можемо **оновити героя**. Для цього використовуємо HTTP-операцію `PATCH`.
|
||||
|
||||
У коді ми отримуємо `dict` з усіма даними, які надіслав клієнт, - лише з тими даними, які надіслав клієнт, виключаючи будь-які значення, присутні лише як значення за замовчуванням. Для цього використовуємо `exclude_unset=True`. Це головний трюк. 🪄
|
||||
|
||||
Потім використовуємо `hero_db.sqlmodel_update(hero_data)`, щоб оновити `hero_db` даними з `hero_data`.
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
|
||||
|
||||
### Знову видалення героя { #delete-a-hero-again }
|
||||
|
||||
**Видалення** героя майже не змінилося.
|
||||
|
||||
Ми не задовольнимо бажання відрефакторити все в цьому місці. 😅
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
|
||||
|
||||
### Знову запустіть застосунок { #run-the-app-again }
|
||||
|
||||
Ви можете знову запустити застосунок:
|
||||
|
||||
<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>
|
||||
|
||||
Якщо ви перейдете до UI `/docs`, побачите, що він оновився і більше не очікуватиме отримати `id` від клієнта під час створення героя тощо.
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image02.png">
|
||||
</div>
|
||||
|
||||
## Підсумок { #recap }
|
||||
|
||||
Ви можете використовувати <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> для взаємодії з SQL базою даних і спростити код за допомогою «моделей даних» та «табличних моделей».
|
||||
|
||||
Багато чого ще можна дізнатися в документації **SQLModel**, там є розширений міні-<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">навчальний посібник з використання SQLModel з **FastAPI**</a>. 🚀
|
||||
862
docs/uk/docs/virtual-environments.md
Normal file
862
docs/uk/docs/virtual-environments.md
Normal file
@@ -0,0 +1,862 @@
|
||||
# Віртуальні середовища { #virtual-environments }
|
||||
|
||||
Коли ви працюєте над проєктами Python, вам, імовірно, слід використовувати віртуальне середовище (або схожий механізм), щоб ізолювати пакети, які ви встановлюєте для кожного проєкту.
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
Якщо ви вже знаєте про віртуальні середовища, як їх створювати та використовувати, можете пропустити цей розділ. 🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Віртуальне середовище відрізняється від змінної оточення.
|
||||
|
||||
Змінна оточення - це змінна в системі, яку можуть використовувати програми.
|
||||
|
||||
Віртуальне середовище - це каталог із файлами в ньому.
|
||||
|
||||
///
|
||||
|
||||
/// info | Інформація
|
||||
|
||||
На цій сторінці ви дізнаєтеся, як використовувати віртуальні середовища і як вони працюють.
|
||||
|
||||
Якщо ви готові прийняти інструмент, що керує всім за вас (включно з установленням Python), спробуйте <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
|
||||
///
|
||||
|
||||
## Створіть проєкт { #create-a-project }
|
||||
|
||||
Спочатку створіть каталог для вашого проєкту.
|
||||
|
||||
Зазвичай я створюю каталог з назвою `code` у моєму домашньому каталозі користувача.
|
||||
|
||||
І всередині нього я створюю окремий каталог на кожен проєкт.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Перейдіть до домашнього каталогу
|
||||
$ cd
|
||||
// Створіть каталог для всіх ваших проєктів з кодом
|
||||
$ mkdir code
|
||||
// Перейдіть у цей каталог code
|
||||
$ cd code
|
||||
// Створіть каталог для цього проєкту
|
||||
$ mkdir awesome-project
|
||||
// Перейдіть до каталогу цього проєкту
|
||||
$ cd awesome-project
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Створіть віртуальне середовище { #create-a-virtual-environment }
|
||||
|
||||
Коли ви починаєте працювати над проєктом Python уперше, створіть віртуальне середовище у вашому проєкті **<dfn title="є інші варіанти, це проста настанова">у вашому проєкті</dfn>**.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Це потрібно робити лише один раз на проєкт, не щоразу, коли ви працюєте.
|
||||
|
||||
///
|
||||
|
||||
//// tab | `venv`
|
||||
|
||||
Щоб створити віртуальне середовище, ви можете використати модуль `venv`, який постачається разом із Python.
|
||||
|
||||
<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`.
|
||||
|
||||
///
|
||||
|
||||
## Активуйте віртуальне середовище { #activate-the-virtual-environment }
|
||||
|
||||
Активуйте нове віртуальне середовище, щоб будь-яка команда 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
|
||||
|
||||
Або якщо ви використовуєте Bash для Windows (напр., <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Кожного разу, коли ви встановлюєте новий пакет у це середовище, активуйте середовище знову.
|
||||
|
||||
Це гарантує, що якщо ви використовуєте програму термінала (<abbr title="command line interface - інтерфейс командного рядка">CLI</abbr>), встановлену цим пакетом, ви використовуєте саме ту з вашого віртуального середовища, а не будь-яку іншу, яка може бути встановлена глобально, імовірно з іншою версією, ніж вам потрібно.
|
||||
|
||||
///
|
||||
|
||||
## Перевірте активність віртуального середовища { #check-the-virtual-environment-is-active }
|
||||
|
||||
Перевірте, що віртуальне середовище активне (попередня команда спрацювала).
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Це необов'язково, але це гарний спосіб перевірити, що все працює як очікується і ви використовуєте саме те віртуальне середовище, яке планували.
|
||||
|
||||
///
|
||||
|
||||
//// tab | Linux, macOS, Windows Bash
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ which python
|
||||
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Якщо показано бінарний файл `python` за шляхом `.venv/bin/python` усередині вашого проєкту (у цьому випадку `awesome-project`), тоді все спрацювало. 🎉
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ Get-Command python
|
||||
|
||||
C:\Users\user\code\awesome-project\.venv\Scripts\python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Якщо показано бінарний файл `python` за шляхом `.venv\Scripts\python` усередині вашого проєкту (у цьому випадку `awesome-project`), тоді все спрацювало. 🎉
|
||||
|
||||
////
|
||||
|
||||
## Оновіть `pip` { #upgrade-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>
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Іноді ви можете отримати помилку **`No module named pip`** при спробі оновити pip.
|
||||
|
||||
Якщо це сталося, встановіть і оновіть pip за допомогою команди нижче:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python -m ensurepip --upgrade
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Ця команда встановить pip, якщо він ще не встановлений, і також гарантує, що встановлена версія pip принаймні така ж нова, як доступна в `ensurepip`.
|
||||
|
||||
///
|
||||
|
||||
## Додайте `.gitignore` { #add-gitignore }
|
||||
|
||||
Якщо ви використовуєте Git (варто це робити), додайте файл `.gitignore`, щоб виключити з Git усе у вашому `.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
|
||||
*
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## Встановіть пакети { #install-packages }
|
||||
|
||||
Після активації середовища ви можете встановлювати в нього пакети.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Робіть це один раз під час встановлення або оновлення пакетів, потрібних вашому проєкту.
|
||||
|
||||
Якщо вам потрібно оновити версію або додати новий пакет, ви зробите це знову.
|
||||
|
||||
///
|
||||
|
||||
### Встановіть пакети безпосередньо { #install-packages-directly }
|
||||
|
||||
Якщо ви поспішаєте та не хочете використовувати файл для оголошення вимог вашого проєкту до пакетів, ви можете встановити їх безпосередньо.
|
||||
|
||||
/// 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` { #install-from-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
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## Запустіть вашу програму { #run-your-program }
|
||||
|
||||
Після активації віртуального середовища ви можете запустити вашу програму, і вона використовуватиме Python із вашого віртуального середовища з пакетами, які ви там встановили.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py
|
||||
|
||||
Hello World
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Налаштуйте ваш редактор { #configure-your-editor }
|
||||
|
||||
Ймовірно, ви використовуєте редактор коду, переконайтеся, що ви налаштували його на використання того самого віртуального середовища, яке ви створили (швидше за все, він визначить його автоматично), щоб отримувати автодоповнення та підсвічування помилок.
|
||||
|
||||
Наприклад:
|
||||
|
||||
* <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 | Порада
|
||||
|
||||
Зазвичай це потрібно робити лише один раз, коли ви створюєте віртуальне середовище.
|
||||
|
||||
///
|
||||
|
||||
## Деактивуйте віртуальне середовище { #deactivate-the-virtual-environment }
|
||||
|
||||
Коли ви завершили роботу над проєктом, ви можете деактивувати віртуальне середовище.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ deactivate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Таким чином, коли ви запустите `python`, він не намагатиметься запускатися з того віртуального середовища з установленими там пакетами.
|
||||
|
||||
## Готові до роботи { #ready-to-work }
|
||||
|
||||
Тепер ви готові почати працювати над вашим проєктом.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Хочете зрозуміти, що це все було вище?
|
||||
|
||||
Продовжуйте читати. 👇🤓
|
||||
|
||||
///
|
||||
|
||||
## Навіщо віртуальні середовища { #why-virtual-environments }
|
||||
|
||||
Щоб працювати з FastAPI, вам потрібно встановити <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>.
|
||||
|
||||
Після цього вам потрібно буде встановити FastAPI та інші пакети, які ви хочете використовувати.
|
||||
|
||||
Для встановлення пакетів зазвичай використовують команду `pip`, що постачається з Python (або схожі альтернативи).
|
||||
|
||||
Однак, якщо ви просто користуватиметеся `pip` напряму, пакети встановлюватимуться у ваше глобальне середовище Python (глобальну інсталяцію Python).
|
||||
|
||||
### Проблема { #the-problem }
|
||||
|
||||
То в чому ж проблема встановлення пакетів у глобальне середовище Python?
|
||||
|
||||
З часом ви, вірогідно, писатимете багато різних програм, які залежать від різних пакетів. І деякі з цих ваших проєктів залежатимуть від різних версій одного й того ж пакета. 😱
|
||||
|
||||
Наприклад, ви можете створити проєкт із назвою `philosophers-stone`, ця програма залежить від іншого пакета з назвою `harry`, використовуючи версію `1`. Тож вам потрібно встановити `harry`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
stone(philosophers-stone) -->|requires| harry-1[harry v1]
|
||||
```
|
||||
|
||||
Потім, трохи згодом, ви створюєте інший проєкт із назвою `prisoner-of-azkaban`, і цей проєкт також залежить від `harry`, але йому потрібна версія `harry` `3`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
|
||||
```
|
||||
|
||||
Але тепер проблема в тому, що якщо ви встановлюєте пакети глобально (у глобальне середовище), а не у локальне віртуальне середовище, вам доведеться вибирати, яку версію `harry` встановити.
|
||||
|
||||
Якщо ви хочете запустити `philosophers-stone`, вам спочатку потрібно встановити `harry` версії `1`, наприклад, так:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==1"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
У підсумку у вас буде встановлено `harry` версії `1` у глобальному середовищі Python.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[global env]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) -->|requires| harry-1
|
||||
end
|
||||
```
|
||||
|
||||
Але якщо ви захочете запустити `prisoner-of-azkaban`, вам доведеться видалити `harry` версії `1` і встановити `harry` версії `3` (або просто встановлення версії `3` автоматично видалить версію `1`).
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "harry==3"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
У підсумку у вас буде встановлено `harry` версії `3` у глобальному середовищі Python.
|
||||
|
||||
А якщо ви знову спробуєте запустити `philosophers-stone`, є шанс, що він не працюватиме, тому що йому потрібен `harry` версії `1`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph global[global env]
|
||||
harry-1[<strike>harry v1</strike>]
|
||||
style harry-1 fill:#ccc,stroke-dasharray: 5 5
|
||||
harry-3[harry v3]
|
||||
end
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) -.-x|⛔️| harry-1
|
||||
end
|
||||
subgraph azkaban-project[prisoner-of-azkaban project]
|
||||
azkaban(prisoner-of-azkaban) --> |requires| harry-3
|
||||
end
|
||||
```
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
У пакетах Python дуже поширена практика намагатися якнайкраще уникати несумісних змін у нових версіях, але краще підстрахуватися та встановлювати новіші версії свідомо і тоді, коли ви можете запустити тести, щоб перевірити, що все працює коректно.
|
||||
|
||||
///
|
||||
|
||||
Тепер уявіть те саме з багатьма іншими пакетами, від яких залежать усі ваші проєкти. Це дуже складно керувати. І ви, імовірно, запускатимете деякі проєкти з деякими несумісними версіями пакетів і не розумітимете, чому щось не працює.
|
||||
|
||||
Також, залежно від вашої операційної системи (напр., Linux, Windows, macOS), у ній може бути вже встановлений Python. І в такому разі, імовірно, уже будуть попередньо встановлені деякі пакети з певними версіями, потрібними вашій системі. Якщо ви встановлюєте пакети в глобальне середовище Python, ви можете зламати деякі програми, що постачаються з вашою операційною системою.
|
||||
|
||||
## Де встановлюються пакети { #where-are-packages-installed }
|
||||
|
||||
Коли ви встановлюєте Python, він створює на вашому комп'ютері кілька каталогів із деякими файлами.
|
||||
|
||||
Деякі з цих каталогів відповідають за зберігання всіх пакетів, які ви встановлюєте.
|
||||
|
||||
Коли ви запускаєте:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Не запускайте це зараз, це лише приклад 🤓
|
||||
$ pip install "fastapi[standard]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Це завантажить стиснений файл з кодом FastAPI, зазвичай із <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>.
|
||||
|
||||
Також будуть завантажені файли для інших пакетів, від яких залежить FastAPI.
|
||||
|
||||
Потім усе це буде розпаковано та покладено в каталог на вашому комп'ютері.
|
||||
|
||||
Типово ці завантажені та розпаковані файли будуть покладені в каталог, що постачається з вашою інсталяцією Python, це глобальне середовище.
|
||||
|
||||
## Що таке віртуальні середовища { #what-are-virtual-environments }
|
||||
|
||||
Рішенням проблеми з наявністю всіх пакетів у глобальному середовищі є використання віртуального середовища для кожного проєкту, над яким ви працюєте.
|
||||
|
||||
Віртуальне середовище - це каталог, дуже схожий на глобальний, у якому ви можете встановлювати пакети для конкретного проєкту.
|
||||
|
||||
Таким чином кожен проєкт матиме власне віртуальне середовище (каталог `.venv`) із власними пакетами.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph stone-project[philosophers-stone project]
|
||||
stone(philosophers-stone) --->|requires| harry-1
|
||||
subgraph venv1[.venv]
|
||||
harry-1[harry v1]
|
||||
end
|
||||
end
|
||||
subgraph azkaban-project[prisoner-of-azkaban project]
|
||||
azkaban(prisoner-of-azkaban) --->|requires| harry-3
|
||||
subgraph venv2[.venv]
|
||||
harry-3[harry v3]
|
||||
end
|
||||
end
|
||||
stone-project ~~~ azkaban-project
|
||||
```
|
||||
|
||||
## Що означає активація віртуального середовища { #what-does-activating-a-virtual-environment-mean }
|
||||
|
||||
Коли ви активуєте віртуальне середовище, наприклад так:
|
||||
|
||||
//// 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
|
||||
|
||||
Або якщо ви використовуєте Bash для Windows (напр., <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ source .venv/Scripts/activate
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
Ця команда створить або змінить деякі [Змінні оточення](environment-variables.md){.internal-link target=_blank}, які будуть доступні для наступних команд.
|
||||
|
||||
Однією з цих змінних є змінна `PATH`.
|
||||
|
||||
/// tip | Порада
|
||||
|
||||
Ви можете дізнатися більше про змінну оточення `PATH` у розділі [Змінні оточення](environment-variables.md#path-environment-variable){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
Активація віртуального середовища додає його шлях `.venv/bin` (на Linux і macOS) або `.venv\Scripts` (на Windows) до змінної оточення `PATH`.
|
||||
|
||||
Скажімо, до активації середовища змінна `PATH` виглядала так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
Це означає, що система шукатиме програми в:
|
||||
|
||||
* `/usr/bin`
|
||||
* `/bin`
|
||||
* `/usr/sbin`
|
||||
* `/sbin`
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows
|
||||
|
||||
```plaintext
|
||||
C:\Windows\System32
|
||||
```
|
||||
|
||||
Це означає, що система шукатиме програми в:
|
||||
|
||||
* `C:\Windows\System32`
|
||||
|
||||
////
|
||||
|
||||
Після активації віртуального середовища змінна `PATH` виглядатиме приблизно так:
|
||||
|
||||
//// tab | Linux, macOS
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
Це означає, що система тепер спочатку шукатиме програми в:
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin
|
||||
```
|
||||
|
||||
перед тим, як шукати в інших каталогах.
|
||||
|
||||
Тож коли ви введете `python` у терміналі, система знайде програму Python у
|
||||
|
||||
```plaintext
|
||||
/home/user/code/awesome-project/.venv/bin/python
|
||||
```
|
||||
|
||||
і використає саме її.
|
||||
|
||||
////
|
||||
|
||||
//// 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` (наприклад, з глобального середовища).
|
||||
|
||||
Активація віртуального середовища також змінює ще кілька речей, але це одна з найважливіших.
|
||||
|
||||
## Перевірка віртуального середовища { #checking-a-virtual-environment }
|
||||
|
||||
Коли ви перевіряєте, чи активне віртуальне середовище, наприклад так:
|
||||
|
||||
//// 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` використовується. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Навіщо деактивувати віртуальне середовище { #why-deactivate-a-virtual-environment }
|
||||
|
||||
Наприклад, ви працюєте над проєктом `philosophers-stone`, активували його віртуальне середовище, встановили пакети та працюєте з цим середовищем.
|
||||
|
||||
А потім ви хочете працювати над іншим проєктом `prisoner-of-azkaban`.
|
||||
|
||||
Ви переходите до цього проєкту:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ cd ~/code/prisoner-of-azkaban
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Якщо ви не деактивуєте віртуальне середовище для `philosophers-stone`, коли ви запустите `python` у терміналі, він спробує використовувати Python із `philosophers-stone`.
|
||||
|
||||
<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` він використовуватиме Python із віртуального середовища в `prisoner-of-azkaban`.
|
||||
|
||||
<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>
|
||||
|
||||
## Альтернативи { #alternatives }
|
||||
|
||||
Це простий посібник, щоб ви швидко стартували та зрозуміли, як усе працює «під капотом».
|
||||
|
||||
Існує багато альтернатив керування віртуальними середовищами, залежностями пакетів (вимогами), проєктами.
|
||||
|
||||
Коли будете готові й захочете використовувати інструмент для керування всім проєктом, залежностями пакетів, віртуальними середовищами тощо, я раджу спробувати <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
|
||||
|
||||
`uv` уміє багато чого, зокрема:
|
||||
|
||||
* Встановлювати Python для вас, включно з різними версіями
|
||||
* Керувати віртуальним середовищем ваших проєктів
|
||||
* Встановлювати пакети
|
||||
* Керувати залежностями пакетів і версіями у вашому проєкті
|
||||
* Гарантувати, що у вас є точний набір пакетів і версій для встановлення, включно з їхніми залежностями, щоб ви були певні, що зможете запустити ваш проєкт у продакшені точно так само, як і на вашому комп'ютері під час розробки - це називається блокуванням
|
||||
* І багато іншого
|
||||
|
||||
## Висновок { #conclusion }
|
||||
|
||||
Якщо ви все це прочитали й зрозуміли, тепер ви знаєте значно більше про віртуальні середовища, ніж багато розробників. 🤓
|
||||
|
||||
Знання цих деталей, найімовірніше, стане в пригоді в майбутньому, коли ви налагоджуватимете щось, що виглядає складним, але ви знатимете, як усе працює «під капотом». 😎
|
||||
Reference in New Issue
Block a user