Compare commits

...

1 Commits

Author SHA1 Message Date
github-actions[bot]
15e88aac9b 🌐 Update translations for uk (add-missing) 2026-01-11 17:44:10 +00:00
75 changed files with 11888 additions and 0 deletions

503
docs/uk/docs/_llm-test.md Normal file
View File

@@ -0,0 +1,503 @@
# Тестовий файл LLM { #llm-test-file }
Цей документ перевіряє, чи <abbr title="Large Language Model - Велика мовна модель">LLM</abbr>, який перекладає документацію, розуміє `general_prompt` у `scripts/translate.py` та мовно-специфічний prompt у `docs/{language code}/llm-prompt.md`. Мовно-специфічний prompt додається до `general_prompt`.
Тести, додані тут, побачать усі розробники мовно-специфічних prompt.
Використовуйте так:
* Майте мовно-специфічний prompt — `docs/{language code}/llm-prompt.md`.
* Зробіть «свіжий» переклад цього документа на бажану цільову мову (див., напр., команду `translate-page` у `translate.py`). Це створить переклад у `docs/{language code}/docs/_llm-test.md`.
* Перевірте, чи все гаразд у перекладі.
* За потреби поліпште мовно-специфічний prompt, загальний prompt або англомовний документ.
* Потім вручну виправте решту проблем у перекладі, щоб це був якісний переклад.
* Перекладіть знову, маючи якісний переклад на місці. Ідеальний результат — LLM більше не вносить жодних змін у переклад. Це означає, що загальний prompt і ваш мовно-специфічний prompt — настільки добрі, наскільки це можливо (інколи він усе ж робитиме кілька на вигляд випадкових змін, причина в тому, що <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` у загальному prompt у `scripts/translate.py`.
////
## Лапки { #quotes }
//// tab | Тест
Учора мій друг написав: "If you spell incorrectly correctly, you have spelled it incorrectly". На що я відповів: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'" .
/// note | Примітка
LLM, імовірно, перекладе це неправильно. Цікаво лише, чи збереже він виправлений переклад під час повторного перекладання.
///
////
//// tab | Інформація
Автор prompt може вирішити, чи перетворювати нейтральні лапки на типографські. Можна залишити як є.
Див., наприклад, розділ `### 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
# Print a greeting to the universe
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
// Create a directory "Code"
$ mkdir code
// Switch into that directory
$ cd code
```
...і приклад коду Python...
```Python
wont_work() # This won't work 😱
works(foo="bar") # This works 🎉
```
...і на цьому все.
////
//// tab | Інформація
Код у блоках коду не слід змінювати, за винятком коментарів.
Див. розділ `### Content of code blocks` у загальному prompt у `scripts/translate.py`.
////
## Вкладки та кольорові блоки { #tabs-and-colored-boxes }
//// tab | Тест
/// info | Інформація
Деякий текст
///
/// note | Примітка
Деякий текст
///
/// note | Технічні деталі
Деякий текст
///
/// check
Деякий текст
///
/// tip | Порада
Деякий текст
///
/// warning | Попередження
Деякий текст
///
/// danger | Обережно
Деякий текст
///
////
//// tab | Інформація
Для вкладок і блоків `Info`/`Note`/`Warning`/тощо слід додавати переклад їхньої назви після вертикальної риски (`|`).
Див. розділи `### Special blocks` і `### Tab blocks` у загальному prompt у `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` у загальному prompt у `scripts/translate.py`.
////
## HTML-елементи «abbr» { #html-abbr-elements }
//// tab | Тест
Тут наведено кілька речей, обгорнутих в HTML-елементи «abbr» (деякі вигадані):
### abbr задає повну фразу { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done - Доведення справ до кінця">GTD</abbr>
* <abbr title="less than - менше ніж"><code>lt</code></abbr>
* <abbr title="XML Web Token - XML вебтокен">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - Паралельний Server Gateway Interface">PSGI</abbr>
### abbr задає пояснення { #the-abbr-gives-an-explanation }
* <abbr title="Група машин, які налаштовані бути з’єднаними та працювати разом певним чином.">cluster</abbr>
* <abbr title="Метод машинного навчання, що використовує штучні нейронні мережі з численними прихованими шарами між вхідним і вихідним шарами, формуючи комплексну внутрішню структуру">Deep Learning</abbr>
### abbr задає повну фразу та пояснення { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network - Mozilla Developer Network: документація для розробників, написана людьми з Firefox">MDN</abbr>
* <abbr title="Input/Output - Ввід/вивід: читання або запис на диск, мережеві комунікації.">I/O</abbr>.
////
//// tab | Інформація
Атрибути `title` в елементах `abbr` перекладаються за певними інструкціями.
Переклади можуть додавати власні елементи `abbr`, які LLM не повинен вилучати. Напр., щоб пояснювати англійські слова.
Див. розділ `### HTML abbr elements` у загальному prompt у `scripts/translate.py`.
////
## Заголовки { #headings }
//// tab | Тест
### Розробка вебзастосунку — навчальний посібник { #develop-a-webapp-a-tutorial }
Привіт.
### Підказки типів і -анотації { #type-hints-and-annotations }
Привіт ще раз.
### Супер- і підкласи { #super-and-subclasses }
Привіт ще раз.
////
//// tab | Інформація
Єдине жорстке правило для заголовків — LLM має залишати частину хешу в фігурних дужках без змін, що гарантує, що посилання не зламаються.
Див. розділ `### Headings` у загальному prompt у `scripts/translate.py`.
Для деяких мовно-специфічних інструкцій див., напр., розділ `### Headings` у `docs/de/llm-prompt.md`.
////
## Терміни, що використовуються в документації { #terms-used-in-the-docs }
//// tab | Тест
* ви
* ваш
* напр.
* тощо
* `foo` як `int`
* `bar` як `str`
* `baz` як `list`
* Tutorial — Посібник користувача
* Advanced User Guide
* документація SQLModel
* документація API
* автоматична документація
* Data Science
* Deep Learning
* Machine Learning
* Dependency Injection
* автентифікація HTTP Basic
* HTTP Digest
* формат ISO
* стандарт JSON Schema
* JSON schema
* визначення schema
* Password Flow
* Mobile
* застарілий
* спроєктований
* недійсний
* на льоту
* стандартний
* за замовчуванням
* чутливий до регістру
* нечутливий до регістру
* обслуговувати застосунок
* обслуговувати сторінку
* застосунок
* застосунок
* запит
* відповідь
* відповідь про помилку
* операція шляху
* декоратор операції шляху
* функція операції шляху
* body
* тіло запиту
* тіло відповіді
* JSON body
* form body
* file body
* тіло функції
* параметр
* body-параметр
* path-параметр
* query-параметр
* cookie-параметр
* header-параметр
* form-параметр
* параметр функції
* подія
* подія startup
* запуск сервера
* подія shutdown
* подія lifespan
* обробник
* обробник подій
* обробник винятків
* обробляти
* модель
* модель Pydantic
* модель даних
* модель бази даних
* модель форми
* об’єкт моделі
* клас
* базовий клас
* батьківський клас
* підклас
* дочірній клас
* споріднений клас
* метод класу
* заголовок
* заголовки
* заголовок authorization
* заголовок `Authorization`
* forwarded header
* система dependency injection
* залежність
* dependable
* dependant
* I/O bound
* CPU bound
* concurrency
* parallelism
* multiprocessing
* env var
* змінна оточення
* `PATH`
* змінна `PATH`
* автентифікація
* провайдер автентифікації
* авторизація
* форма авторизації
* провайдер авторизації
* користувач автентифікується
* система автентифікує користувача
* CLI
* інтерфейс командного рядка
* сервер
* клієнт
* хмарний провайдер
* хмарний сервіс
* розробка
* етапи розробки
* dict
* словник
* перелічення
* enum
* елемент enum
* енкодер
* декодер
* кодувати
* декодувати
* виняток
* підняти
* вираз
* інструкція
* frontend
* backend
* обговорення GitHub
* issue GitHub
* продуктивність
* оптимізація продуктивності
* тип повернення
* значення повернення
* безпека
* схема безпеки
* завдання
* фонове завдання
* функція завдання
* шаблон
* рушій шаблонів
* анотація типів
* підказка типів
* server worker
* worker Uvicorn
* Worker Gunicorn
* процес worker
* клас worker
* робоче навантаження
* розгортання
* розгортати
* SDK
* software development kit
* `APIRouter`
* `requirements.txt`
* Bearer Token
* breaking change
* bug
* button
* callable
* code
* commit
* context manager
* coroutine
* сесія бази даних
* диск
* домен
* engine
* fake X
* метод HTTP GET
* item
* бібліотека
* lifespan
* lock
* middleware
* мобільний застосунок
* модуль
* mounting
* мережа
* origin
* override
* payload
* processor
* property
* proxy
* pull request
* query
* RAM
* віддалена машина
* status code
* string
* tag
* вебфреймворк
* wildcard
* повертати
* валідувати
////
//// tab | Інформація
Це неповний і не нормативний список (переважно) технічних термінів, які зустрічаються в документації. Він може бути корисним автору prompt, щоб зрозуміти, для яких термінів LLM потрібна підказка. Наприклад, коли він постійно відкотить якісний переклад до гіршого варіанта. Або коли він має проблеми з відмінюванням/узгодженням терміна вашою мовою.
Див., напр., розділ `### List of English terms and their preferred German translations` у `docs/de/llm-prompt.md`.
////

View File

@@ -0,0 +1,3 @@
# Про { #about }
Про FastAPI, його дизайн, натхнення та інше. 🤓

View File

@@ -0,0 +1,247 @@
# Додаткові відповіді в OpenAPI { #additional-responses-in-openapi }
/// warning | Попередження
Це доволі просунута тема.
Якщо ви тільки починаєте з **FastAPI**, вам це може бути не потрібно.
///
Ви можете оголошувати додаткові відповіді з додатковими кодами статусу, media types, описами тощо.
Ці додаткові відповіді буде включено до схеми OpenAPI, тож вони також з’являться в документації API.
Але для цих додаткових відповідей вам потрібно переконатися, що ви повертаєте `Response`, як-от `JSONResponse`, безпосередньо, із вашим кодом статусу та вмістом.
## Додаткова відповідь із `model` { #additional-response-with-model }
Ви можете передати в *декоратори операції шляху* параметр `responses`.
Він приймає `dict`: ключі — це коди статусу для кожної відповіді (наприклад, `200`), а значення — інші `dict` з інформацією для кожної з них.
Кожен із цих `dict` відповіді може мати ключ `model`, що містить Pydantic model, так само як `response_model`.
**FastAPI** візьме цю модель, згенерує її JSON Schema та включить у правильне місце в OpenAPI.
Наприклад, щоб оголосити ще одну відповідь із кодом статусу `404` і Pydantic model `Message`, ви можете написати:
{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
/// note | Примітка
Пам’ятайте, що потрібно повертати `JSONResponse` безпосередньо.
///
/// info | Інформація
Ключ `model` не є частиною OpenAPI.
**FastAPI** візьме Pydantic model звідти, згенерує JSON Schema і помістить його в правильне місце.
Правильне місце:
* У ключі `content`, значенням якого є інший JSON-об’єкт (`dict`), що містить:
* Ключ із media type, наприклад `application/json`, значенням якого є інший JSON-об’єкт, що містить:
* Ключ `schema`, значенням якого є JSON Schema з моделі — ось правильне місце.
* **FastAPI** додає тут посилання на глобальні JSON Schemas в іншому місці вашого OpenAPI, замість того щоб включати схему безпосередньо. Так інші застосунки та клієнти можуть використовувати ці JSON Schemas напряму, надавати кращі інструменти генерації коду тощо.
///
Згенеровані відповіді в 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"
}
}
}
}
}
}
}
```
## Додаткові media types для основної відповіді { #additional-media-types-for-the-main-response }
Ви можете використати цей самий параметр `responses`, щоб додати різні media types для тієї самої основної відповіді.
Наприклад, ви можете додати додатковий media type `image/png`, оголосивши, що ваша *операція шляху* може повертати JSON-об’єкт (із media type `application/json`) або PNG-зображення:
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
/// note | Примітка
Зверніть увагу, що потрібно повертати зображення безпосередньо через `FileResponse`.
///
/// info | Інформація
Якщо ви явно не вкажете інший media type у параметрі `responses`, FastAPI вважатиме, що відповідь має той самий media type, що й основний клас відповіді (типово `application/json`).
Але якщо ви вказали власний клас відповіді з `None` як media type, FastAPI використає `application/json` для будь-якої додаткової відповіді, що має пов’язану модель.
///
## Об’єднання інформації { #combining-information }
Ви також можете об’єднувати інформацію про відповіді з кількох місць, зокрема з параметрів `response_model`, `status_code` і `responses`.
Ви можете оголосити `response_model`, використовуючи типовий код статусу `200` (або власний, якщо потрібно), а потім оголосити додаткову інформацію для цієї ж відповіді в `responses` — безпосередньо в схемі OpenAPI.
**FastAPI** збереже додаткову інформацію з `responses` і об’єднає її з JSON Schema вашої моделі.
Наприклад, ви можете оголосити відповідь із кодом статусу `404`, яка використовує Pydantic model і має власний `description`.
А також відповідь із кодом статусу `200`, яка використовує ваш `response_model`, але містить власний `example`:
{* ../../docs_src/additional_responses/tutorial003_py39.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 Responses Object</a> — містить `Response Object`.
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI Response Object</a> — ви можете включити будь-що з цього безпосередньо в кожну відповідь у параметрі `responses`, зокрема `description`, `headers`, `content` (усередині нього ви оголошуєте різні media types і JSON Schemas) та `links`.

View 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}.

View File

@@ -0,0 +1,163 @@
# Розширені залежності { #advanced-dependencies }
## Параметризовані залежності { #parameterized-dependencies }
Усі залежності, які ми бачили, — це фіксована функція або клас.
Але можуть бути випадки, коли ви хочете мати змогу задавати параметри для залежності, не оголошуючи багато різних функцій або класів.
Уявімо, що ми хочемо мати залежність, яка перевіряє, чи містить параметр запиту `q` певний фіксований вміст.
Але ми хочемо мати змогу параметризувати цей фіксований вміст.
## «Callable» екземпляр { #a-callable-instance }
У Python є спосіб зробити екземпляр класу «callable».
Не сам клас (який уже є callable), а екземпляр цього класу.
Для цього ми оголошуємо метод `__call__`:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
У цьому випадку саме `__call__` **FastAPI** використовуватиме, щоб перевіряти додаткові параметри та підзалежності, і саме його буде викликано, щоб передати значення параметру у вашій *функції операції шляху* пізніше.
## Параметризуйте екземпляр { #parameterize-the-instance }
А тепер ми можемо використати `__init__`, щоб оголосити параметри екземпляра, які ми зможемо використати для «параметризації» залежності:
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
У цьому випадку **FastAPI** ніколи не чіпатиме й не «цікавитиметься» `__init__` — ми використаємо його безпосередньо у своєму коді.
## Створіть екземпляр { #create-an-instance }
Ми можемо створити екземпляр цього класу так:
{* ../../docs_src/dependencies/tutorial011_an_py39.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_py39.py hl[22] *}
/// tip | Порада
Усе це може здаватися надуманим. І поки що може бути не дуже зрозуміло, у чому користь.
Ці приклади навмисно прості, але показують, як це все працює.
У розділах про безпеку є допоміжні функції, які реалізовані так само.
Якщо ви все це зрозуміли, то вже знаєте, як «під капотом» працюють ті утиліти для безпеки.
///
## Залежності з `yield`, `HTTPException`, `except` і Background Tasks { #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 Discussions</a> з вашим конкретним сценарієм і поясненням, чому вам було б корисне раннє закриття для залежностей з `yield`.
Якщо знайдуться переконливі сценарії для раннього закриття в залежностях з `yield`, я розгляну можливість додати новий спосіб явно вмикати раннє закриття.
### Залежності з `yield` і `except`, технічні деталі { #dependencies-with-yield-and-except-technical-details }
До FastAPI 0.110.0, якщо ви використовували залежність з `yield`, а потім перехоплювали виняток через `except` у цій залежності і не піднімали виняток знову, виняток автоматично піднімався/передавався будь-яким обробникам винятків або обробнику внутрішньої помилки сервера.
У версії 0.110.0 це було змінено, щоб виправити неконтрольоване споживання пам’яті через переспрямовані винятки без обробника (внутрішні помилки сервера), і щоб узгодити поведінку зі звичайним Python-кодом.
### Background Tasks і залежності з `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`.
Наприклад, замість використання тієї самої сесії бази даних, ви створите нову сесію бази даних усередині фонового завдання і отримаєте об’єкти з бази даних, використовуючи цю нову сесію. А потім, замість передачі об’єкта з бази даних як параметра у функцію фонового завдання, ви передасте ID цього об’єкта й повторно отримаєте об’єкт усередині функції фонового завдання.

View File

@@ -0,0 +1,99 @@
# Асинхронні тести { #async-tests }
Ви вже бачили, як тестувати ваші застосунки **FastAPI** за допомогою наданого `TestClient`. До цього моменту ви бачили лише те, як писати синхронні тести, без використання `async`-функцій.
Можливість використовувати асинхронні функції у ваших тестах може бути корисною, наприклад, коли ви асинхронно виконуєте запити до бази даних. Уявіть, що ви хочете протестувати надсилання запитів до вашого застосунку FastAPI, а потім перевірити, що ваш backend успішно записав правильні дані в базу даних, використовуючи async-бібліотеку для роботи з БД.
Подивімося, як це реалізувати.
## 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_py39/main.py *}
Файл `test_main.py` матиме тести для `main.py`, і тепер він може виглядати так:
{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
## Запуск { #run-it }
Ви можете запускати тести як зазвичай, через:
<div class="termy">
```console
$ pytest
---> 100%
```
</div>
## Детальніше { #in-detail }
Маркер `@pytest.mark.anyio` повідомляє pytest, що цю тестову функцію потрібно викликати асинхронно:
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
/// tip | Порада
Зверніть увагу, що тепер тестова функція — це `async def`, а не просто `def`, як раніше при використанні `TestClient`.
///
Потім ми можемо створити `AsyncClient` із застосунком і надсилати до нього async-запити, використовуючи `await`.
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
Це еквівалентно:
```Python
response = client.get('/')
```
...що ми використовували для виконання запитів через `TestClient`.
/// tip | Порада
Зверніть увагу, що з новим `AsyncClient` ми використовуємо async/await — запит є асинхронним.
///
/// warning | Попередження
Якщо ваш застосунок покладається на події життєвого циклу (lifespan events), `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>), пам’ятайте: об’єкти, яким потрібен event loop, слід створювати лише всередині async-функцій, наприклад, у callback `@app.on_event("startup")`.
///

View File

@@ -0,0 +1,466 @@
# За проксі { #behind-a-proxy }
У багатьох ситуаціях перед вашим застосунком FastAPI ви використовуватимете **проксі**, як-от Traefik або Nginx.
Такі проксі можуть обробляти сертифікати HTTPS та інші речі.
## Forwarded-заголовки проксі { #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>
///
### Увімкнення Forwarded-заголовків проксі { #enable-proxy-forwarded-headers }
Ви можете запустити FastAPI CLI з *CLI Option* `--forwarded-allow-ips` і передати IP-адреси, яким слід довіряти для читання цих forwarded-заголовків.
Якщо встановити `--forwarded-allow-ips="*"`, довіра буде до всіх вхідних IP.
Якщо ваш **сервер** знаходиться за довіреним **проксі** і лише проксі звертається до нього, це змусить сервер приймати як «дозволену» ту 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_py39.py hl[6] *}
Якщо клієнт спробує перейти на `/items`, за замовчуванням його буде перенаправлено на `/items/`.
Але до встановлення *CLI Option* `--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}.
///
### Як працюють Forwarded-заголовки проксі { #how-proxy-forwarded-headers-work }
Ось візуальне представлення того, як **проксі** додає forwarded-заголовки між клієнтом і **сервером застосунку**:
```mermaid
sequenceDiagram
participant Client
participant Proxy as Proxy/Load Balancer
participant Server as FastAPI Server
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
Note over Proxy: Proxy adds forwarded headers
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
Proxy->>Client: HTTPS Response
```
**Проксі** перехоплює початковий запит клієнта й додає спеціальні *forwarded*-заголовки (`X-Forwarded-*`) перед тим, як передати запит на **сервер застосунку**.
Ці заголовки зберігають інформацію про початковий запит, яка інакше була б втрачена:
* **X-Forwarded-For**: IP-адреса початкового клієнта
* **X-Forwarded-Proto**: початковий протокол (`https`)
* **X-Forwarded-Host**: початковий хост (`mysuperapp.com`)
Коли **FastAPI CLI** налаштовано з `--forwarded-allow-ips`, він довіряє цим заголовкам і використовує їх, наприклад, щоб генерувати правильні URL в редіректах.
## Проксі зі «зрізаним» префіксом шляху { #proxy-with-a-stripped-path-prefix }
У вас може бути проксі, який додає префікс шляху до вашого застосунку.
У таких випадках ви можете використати `root_path` для налаштування застосунку.
`root_path` — це механізм, наданий специфікацією ASGI (на якій побудований FastAPI через Starlette).
`root_path` використовується для обробки таких специфічних випадків.
Також він використовується внутрішньо під час монтування підзастосунків.
Наявність проксі зі «зрізаним» префіксом шляху в цьому випадку означає, що ви можете оголосити шлях `/app` у своєму коді, але потім додати шар зверху (проксі), який розмістить ваш застосунок **FastAPI** під шляхом на кшталт `/api/v1`.
У цьому разі початковий шлях `/app` фактично обслуговуватиметься за адресою `/api/v1/app`.
Попри те, що весь ваш код написано так, ніби існує лише `/app`.
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *}
І проксі буде **«зрізати»** **префікс шляху** на льоту перед передаванням запиту на сервер застосунку (ймовірно, Uvicorn через FastAPI CLI), залишаючи ваш застосунок переконаним, що його обслуговують на `/app`, тож вам не потрібно оновлювати весь код, додаючи префікс `/api/v1`.
До цього моменту все працюватиме як зазвичай.
Але потім, коли ви відкриєте вбудований UI документації (frontend), він очікуватиме отримати схему OpenAPI за адресою `/openapi.json`, а не `/api/v1/openapi.json`.
Тож frontend (який працює в браузері) спробує звернутися до `/openapi.json` і не зможе отримати схему OpenAPI.
Оскільки для нашого застосунку використовується проксі з префіксом шляху `/api/v1`, frontend має отримувати схему 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, доступних на цій машині/сервері.
///
UI документації також потрібна схема OpenAPI, щоб задекларувати, що цей API `server` розташований на `/api/v1` (за проксі). Наприклад:
```JSON hl_lines="4-8"
{
"openapi": "3.1.0",
// More stuff here
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
// More stuff here
}
}
```
У цьому прикладі «Proxy» може бути чимось на кшталт **Traefik**. А сервером може бути 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_py39.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_py39.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) до нього.
Це демонструє, як Proxy (Traefik) використовує префікс шляху і як сервер (Uvicorn) використовує `root_path` з опції `--root-path`.
### Перевірка UI документації { #check-the-docs-ui }
Але найцікавіше — далі. ✨
«Офіційний» спосіб доступу до застосунку — через проксі з префіксом шляху, який ми визначили. Тож, як і слід очікувати, якщо ви спробуєте 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">
Але якщо ми звернемося до UI документації за «офіційною» 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`, наприклад, якщо хочете, щоб *той самий* UI документації взаємодіяв і зі staging-, і з production-середовищем.
Якщо ви передаєте власний список `servers` і є `root_path` (бо ваш API знаходиться за проксі), **FastAPI** вставить «server» з цим `root_path` на початок списку.
Наприклад:
{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
Згенерує схему OpenAPI на кшталт:
```JSON hl_lines="5-7"
{
"openapi": "3.1.0",
// More stuff here
"servers": [
{
"url": "/api/v1"
},
{
"url": "https://stag.example.com",
"description": "Staging environment"
},
{
"url": "https://prod.example.com",
"description": "Production environment"
}
],
"paths": {
// More stuff here
}
}
```
/// tip | Порада
Зверніть увагу на автоматично згенерований server зі значенням `url` `/api/v1`, узятим з `root_path`.
///
У UI документації за адресою <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> це виглядатиме так:
<img src="/img/tutorial/behind-a-proxy/image03.png">
/// tip | Порада
UI документації взаємодіятиме з тим сервером, який ви виберете.
///
/// note | Технічні деталі
Властивість `servers` у специфікації OpenAPI є необов’язковою.
Якщо ви не вказуєте параметр `servers` і `root_path` дорівнює `/`, властивість `servers` у згенерованій схемі OpenAPI буде повністю пропущена за замовчуванням, що еквівалентно одному server зі значенням `url` `/`.
///
### Вимкнення автоматичного server з `root_path` { #disable-automatic-server-from-root-path }
Якщо ви не хочете, щоб **FastAPI** включав автоматичний server з `root_path`, можете використати параметр `root_path_in_servers=False`:
{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
і тоді він не включатиметься в схему OpenAPI.
## Монтування підзастосунку { #mounting-a-sub-application }
Якщо вам потрібно змонтувати підзастосунок (як описано в [Підзастосунки — монтування](sub-applications.md){.internal-link target=_blank}), водночас використовуючи проксі з `root_path`, ви можете зробити це у звичний спосіб.
FastAPI внутрішньо «розумно» використовує `root_path`, тож усе просто працюватиме. ✨

View File

@@ -0,0 +1,312 @@
# Користувацька відповідь — HTML, Stream, File та інше { #custom-response-html-stream-file-others }
За замовчуванням **FastAPI** повертатиме відповіді, використовуючи `JSONResponse`.
Ви можете перевизначити це, повернувши `Response` напряму, як показано в розділі [Повернути Response напряму](response-directly.md){.internal-link target=_blank}.
Але якщо ви повертаєте `Response` напряму (або будь-який підклас, як-от `JSONResponse`), дані не будуть автоматично конвертовані (навіть якщо ви оголосили `response_model`), і документація не буде автоматично згенерована (наприклад, із включенням конкретного «media type» у HTTP-заголовок `Content-Type` як частини згенерованого OpenAPI).
Втім, ви також можете оголосити `Response`, яку потрібно використовувати (наприклад, будь-який підклас `Response`), у *декораторі операції шляху* через параметр `response_class`.
Вміст, який ви повертаєте з вашої *функції операції шляху*, буде вкладено всередину цього `Response`.
І якщо цей `Response` має JSON media type (`application/json`), як у випадку з `JSONResponse` та `UJSONResponse`, дані, які ви повертаєте, будуть автоматично конвертовані (і відфільтровані) за допомогою будь-якого Pydantic `response_model`, який ви оголосили в *декораторі операції шляху*.
/// note | Примітка
Якщо ви використовуєте клас відповіді без media type, 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 Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}, пояснений у підручнику. Саме це дозволяє вам повертати **довільні об’єкти**, наприклад моделі бази даних.
Але якщо ви впевнені, що вміст, який ви повертаєте, **можна серіалізувати в JSON**, ви можете передати його безпосередньо класу відповіді та уникнути додаткових витрат, які FastAPI матиме, пропускаючи результат через `jsonable_encoder` перед передаванням його класу відповіді.
{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
/// info | Інформація
Параметр `response_class` також буде використано, щоб визначити «media type» відповіді.
У цьому випадку 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_py39.py hl[2,7] *}
/// info | Інформація
Параметр `response_class` також буде використано, щоб визначити «media type» відповіді.
У цьому випадку HTTP-заголовок `Content-Type` буде встановлено в `text/html`.
І це буде задокументовано відповідним чином в OpenAPI.
///
### Повернути `Response` { #return-a-response }
Як показано в [Повернути Response напряму](response-directly.md){.internal-link target=_blank}, ви також можете перевизначити відповідь напряму у вашій *операції шляху*, повернувши її.
Той самий приклад з вище, який повертає `HTMLResponse`, може виглядати так:
{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
/// warning | Попередження
`Response`, повернений напряму вашою *функцією операції шляху*, не буде задокументований в OpenAPI (наприклад, `Content-Type` не буде задокументовано) і не буде видимий в автоматичних інтерактивних документах.
///
/// info | Інформація
Звісно, фактичний заголовок `Content-Type`, status code тощо будуть взяті з об’єкта `Response`, який ви повернули.
///
### Документувати в OpenAPI і перевизначити `Response` { #document-in-openapi-and-override-response }
Якщо ви хочете перевизначити відповідь усередині функції, але водночас задокументувати «media type» в OpenAPI, ви можете використати параметр `response_class` І повернути об’єкт `Response`.
Тоді `response_class` буде використано лише для документування OpenAPI *операції шляху*, а ваш `Response` буде використано як є.
#### Повернути `HTMLResponse` напряму { #return-an-htmlresponse-directly }
Наприклад, це може виглядати так:
{* ../../docs_src/custom_response/tutorial004_py39.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` — HTTP status code типу `int`.
* `headers``dict` зі строк.
* `media_type``str`, що задає media type. Напр. `"text/html"`.
FastAPI (насправді Starlette) автоматично додасть заголовок Content-Length. Також буде додано заголовок Content-Type на основі `media_type` із додаванням charset для текстових типів.
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
### `HTMLResponse` { #htmlresponse }
Приймає текст або байти та повертає HTML-відповідь, як ви читали вище.
### `PlainTextResponse` { #plaintextresponse }
Приймає текст або байти та повертає відповідь звичайним текстом.
{* ../../docs_src/custom_response/tutorial005_py39.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_py39.py hl[2,7] *}
/// tip | Порада
Можливо, `ORJSONResponse` буде швидшою альтернативою.
///
### `RedirectResponse` { #redirectresponse }
Повертає HTTP-перенаправлення. За замовчуванням використовує status code 307 (Temporary Redirect).
Ви можете повернути `RedirectResponse` напряму:
{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
---
Або ви можете використати його в параметрі `response_class`:
{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
Якщо ви зробите так, то з вашої *функції операції шляху* можна повертати URL напряму.
У цьому випадку використаний `status_code` буде стандартним для `RedirectResponse`, тобто `307`.
---
Також можна використати параметр `status_code` у поєднанні з параметром `response_class`:
{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
### `StreamingResponse` { #streamingresponse }
Приймає async generator або звичайний generator/iterator і стрімить тіло відповіді.
{* ../../docs_src/custom_response/tutorial007_py39.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()`), ви можете створити generator function, щоб ітеруватися по цьому file-like об’єкту.
Так вам не потрібно спочатку повністю зчитувати його в пам’ять; ви можете передати цю generator function у `StreamingResponse` і повернути її.
Це охоплює багато бібліотек для роботи з хмарним сховищем, обробки відео та інші.
{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
1. Це generator function. Це «generator function», бо всередині містить інструкції `yield`.
2. Використовуючи блок `with`, ми гарантуємо, що file-like об’єкт буде закрито після завершення generator function. Тобто після того, як вона завершить надсилання відповіді.
3. Цей `yield from` каже функції ітеруватися по об’єкту з назвою `file_like`. А потім для кожної проітерованої частини робити `yield` цієї частини так, ніби вона походить з цієї generator function (`iterfile`).
Отже, це generator function, яка «делегує» роботу генерації чомусь іншому всередині.
Роблячи це таким чином, ми можемо помістити все в блок `with` і так гарантувати, що file-like об’єкт буде закрито після завершення.
/// tip | Порада
Зверніть увагу: оскільки тут ми використовуємо стандартний `open()`, який не підтримує `async` та `await`, ми оголошуємо операцію шляху звичайним `def`.
///
### `FileResponse` { #fileresponse }
Асинхронно стрімить файл як відповідь.
Має інший набір аргументів для створення екземпляра, ніж інші типи відповідей:
* `path` — шлях до файлу, який потрібно стрімити.
* `headers` — будь-які користувацькі заголовки для включення, як словник.
* `media_type` — рядок, що задає media type. Якщо не задано, ім’я файлу або шлях буде використано для визначення media type.
* `filename` — якщо задано, буде включено у відповідь `Content-Disposition`.
Файлові відповіді включатимуть відповідні заголовки `Content-Length`, `Last-Modified` та `ETag`.
{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
Ви також можете використати параметр `response_class`:
{* ../../docs_src/custom_response/tutorial009b_py39.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_py39.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_py39.py hl[2,4] *}
/// tip | Порада
Ви все одно можете перевизначати `response_class` в *операціях шляху*, як і раніше.
///
## Додаткова документація { #additional-documentation }
Ви також можете оголосити media type та багато інших деталей в OpenAPI через `responses`: [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}.

View 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, це гарний прийом, щоб використати їх для побудови web API за допомогою FastAPI. 🤓
///
## Dataclasses у `response_model` { #dataclasses-in-response-model }
Ви також можете використовувати `dataclasses` у параметрі `response_model`:
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
Dataclass буде автоматично перетворено на Pydantic dataclass.
У такий спосіб його схема відображатиметься в інтерфейсі документації API:
<img src="/img/tutorial/dataclasses/image01.png">
## Dataclasses у вкладених структурах даних { #dataclasses-in-nested-data-structures }
Ви також можете поєднувати `dataclasses` з іншими анотаціями типів, щоб створювати вкладені структури даних.
У деяких випадках вам усе ж доведеться використовувати версію `dataclasses` від Pydantic. Наприклад, якщо виникають помилки з автоматично згенерованою документацією API.
У такому разі ви можете просто замінити стандартні `dataclasses` на `pydantic.dataclasses` — це повна (drop-in) заміна:
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
1. Ми все ще імпортуємо `field` зі стандартного `dataclasses`.
2. `pydantic.dataclasses` — це повна (drop-in) заміна для `dataclasses`.
3. Dataclass `Author` містить список dataclasses `Item`.
4. Dataclass `Author` використовується як параметр `response_model`.
5. Ви можете використовувати інші стандартні анотації типів із dataclasses як тіло запиту.
У цьому випадку це список dataclasses `Item`.
6. Тут ми повертаємо словник, який містить `items` — список dataclasses.
FastAPI все ще здатний <abbr title="перетворення даних у формат, який можна передавати">серіалізувати</abbr> дані в JSON.
7. Тут `response_model` використовує анотацію типу — список dataclasses `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`. 🔖

View File

@@ -0,0 +1,165 @@
# Події життєвого циклу { #lifespan-events }
Ви можете визначити логіку (код), яку слід виконати перед **запуском** застосунку. Це означає, що цей код буде виконано **один раз**, **до того**, як застосунок **почне приймати запити**.
Так само ви можете визначити логіку (код), яку слід виконати під час **завершення роботи** застосунку. У цьому разі цей код буде виконано **один раз**, **після** обробки, ймовірно, **багатьох запитів**.
Оскільки цей код виконується до того, як застосунок **почне** приймати запити, і одразу після того, як він **завершить** їх обробку, він охоплює весь **життєвий цикл** застосунку (слово «lifespan» за мить стане важливим).
Це може бути дуже корисно для налаштування **ресурсів**, які потрібні вам для всього застосунку, які є **спільними** для запитів, та/або які потрібно потім **очистити**. Наприклад, пул підключень до бази даних або завантаження спільної моделі машинного навчання.
## Випадок використання { #use-case }
Почнімо з прикладу **випадку використання**, а потім подивімося, як це розв’язати.
Уявімо, що у вас є кілька **моделей машинного навчання**, які ви хочете використовувати для обробки запитів.
Ці самі моделі є спільними для запитів, тобто це не одна модель на запит чи одна на користувача або щось подібне.
Уявімо, що завантаження моделі може **займати досить багато часу**, бо потрібно читати багато **даних із диска**. Тож ви не хочете робити це для кожного запиту.
Ви могли б завантажувати її на верхньому рівні модуля/файлу, але це також означало б, що модель **завантажуватиметься** навіть тоді, коли ви просто запускаєте простий автоматизований тест. Тоді тест був би **повільним**, бо йому довелося б чекати на завантаження моделі перед запуском незалежної частини коду.
Саме це ми й розв’яжемо: завантажимо модель до початку обробки запитів, але лише безпосередньо перед тим, як застосунок почне їх приймати, а не під час завантаження коду.
## Lifespan { #lifespan }
Ви можете визначити цю логіку *startup* і *shutdown* за допомогою параметра `lifespan` застосунку `FastAPI` та «context manager» (за мить покажу, що це таке).
Почнімо з прикладу, а потім розберемо його детально.
Створімо асинхронну функцію `lifespan()` з `yield` ось так:
{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
Тут ми імітуємо дорогу операцію *startup* із завантаженням моделі, додаючи (фейкову) функцію моделі в словник із моделями машинного навчання перед `yield`. Цей код буде виконано **до того**, як застосунок **почне приймати запити**, під час *startup*.
А потім, одразу після `yield`, ми вивантажуємо модель. Цей код буде виконано **після** того, як застосунок **завершить обробку запитів**, безпосередньо перед *shutdown*. Це може, наприклад, звільнити ресурси на кшталт пам’яті або GPU.
/// tip | Порада
`shutdown` відбудеться, коли ви **зупиняєте** застосунок.
Можливо, вам потрібно запустити нову версію, або ви просто втомилися його запускати.
///
### Функція lifespan { #lifespan-function }
Перше, на що слід звернути увагу: ми визначаємо асинхронну функцію з `yield`. Це дуже схоже на залежності з `yield`.
{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
Перша частина функції, до `yield`, буде виконана **до** запуску застосунку.
А частина після `yield` буде виконана **після** того, як застосунок завершить роботу.
### Async Context Manager { #async-context-manager }
Якщо перевірити, функція декорована `@asynccontextmanager`.
Це перетворює функцію на те, що називають «**async context manager**».
{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
**Context manager** у Python — це те, що ви можете використати в операторі `with`, наприклад, `open()` можна використати як context manager:
```Python
with open("file.txt") as file:
file.read()
```
У нових версіях Python також є **async context manager**. Його слід використовувати з `async with`:
```Python
async with lifespan(app):
await do_stuff()
```
Коли ви створюєте context manager або async context manager, як вище, він робить таке: перед входом у блок `with` виконає код до `yield`, а після виходу з блоку `with` виконає код після `yield`.
У нашому прикладі коду вище ми не використовуємо його напряму, але передаємо FastAPI, щоб він використовував його.
Параметр `lifespan` застосунку `FastAPI` приймає **async context manager**, тож ми можемо передати йому наш новий async context manager `lifespan`.
{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
## Альтернативні події (застаріло) { #alternative-events-deprecated }
/// warning | Попередження
Рекомендований спосіб обробляти *startup* і *shutdown* — використовувати параметр `lifespan` застосунку `FastAPI`, як описано вище. Якщо ви надасте параметр `lifespan`, обробники подій `startup` і `shutdown` більше не викликатимуться. Або весь підхід через `lifespan`, або весь підхід через події — не обидва одночасно.
Ймовірно, ви можете пропустити цю частину.
///
Є альтернативний спосіб визначити цю логіку, яка має виконуватися під час *startup* і під час *shutdown*.
Ви можете визначити обробники подій (функції), які потрібно виконати до запуску застосунку або під час завершення його роботи.
Ці функції можна оголошувати як `async def` або як звичайні `def`.
### Подія `startup` { #startup-event }
Щоб додати функцію, яку потрібно запустити перед стартом застосунку, оголосіть її з подією `"startup"`:
{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
У цьому випадку функція-обробник події `startup` ініціалізує елемент `"database"` (це просто `dict`) деякими значеннями.
Ви можете додати більше ніж одну функцію-обробник події.
І ваш застосунок не почне приймати запити, доки не завершаться всі обробники події `startup`.
### Подія `shutdown` { #shutdown-event }
Щоб додати функцію, яку потрібно запустити під час завершення роботи застосунку, оголосіть її з подією `"shutdown"`:
{* ../../docs_src/events/tutorial002_py39.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 }
Є висока ймовірність, що логіка для ваших *startup* і *shutdown* пов’язана: ви можете хотіти щось запустити, а потім завершити, отримати ресурс, а потім звільнити його тощо.
Робити це в розділених функціях, які не ділять між собою логіку або змінні, складніше, бо доведеться зберігати значення в глобальних змінних або застосовувати подібні прийоми.
Тому зараз рекомендується натомість використовувати `lifespan`, як пояснено вище.
## Технічні деталі { #technical-details }
Лише технічна деталь для допитливих ґіків.
Під капотом, у технічній специфікації ASGI, це частина <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>, і вона визначає події з назвами `startup` та `shutdown`.
/// info | Інформація
Докладніше про обробники `lifespan` у Starlette читайте в <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">документації Lifespan у Starlette</a>.
Зокрема, як обробляти стан lifespan, який можна використовувати в інших частинах вашого коду.
///
## Підзастосунки { #sub-applications }
🚨 Майте на увазі, що ці події життєвого циклу (startup і shutdown) виконуватимуться лише для головного застосунку, а не для [Підзастосунків — монтування](sub-applications.md){.internal-link target=_blank}.

View File

@@ -0,0 +1,208 @@
# Генерація SDK { #generating-sdks }
Оскільки **FastAPI** базується на специфікації **OpenAPI**, його API можна описати у стандартному форматі, який розуміє багато інструментів.
Це спрощує генерацію актуальної **документації**, клієнтських бібліотек (<abbr title="Software Development Kits - Набори засобів розробки програмного забезпечення">**SDKs**</abbr>) кількома мовами, а також **тестування** чи **workflow для автоматизації**, які залишаються синхронізованими з вашим кодом.
У цьому посібнику ви дізнаєтеся, як згенерувати **TypeScript SDK** для вашого FastAPI бекенду.
## Open Source генератори 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>
Деякі з цих рішень також можуть бути open source або мати безплатні тарифи, тож ви можете спробувати їх без фінансових зобов’язань. Також доступні інші комерційні генератори SDK, їх можна знайти онлайн.
## Створення TypeScript SDK { #create-a-typescript-sdk }
Почнімо з простого застосунку FastAPI:
{* ../../docs_src/generate_clients/tutorial001_py39.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">
Ви також отримаєте автодоповнення для payload, який потрібно надіслати:
<img src="/img/tutorial/generate-clients/image03.png">
/// tip | Порада
Зверніть увагу на автодоповнення для `name` і `price` — це було визначено в застосунку FastAPI, у моделі `Item`.
///
Також ви матимете inline-помилки для даних, які надсилаєте:
<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_py39.py hl[21,26,34] *}
### Генерація TypeScript-клієнта з тегами { #generate-a-typescript-client-with-tags }
Якщо ви генеруєте клієнт для застосунку FastAPI з використанням тегів, зазвичай клієнтський код також буде розділено відповідно до тегів.
Так ви зможете мати впорядковане й коректно згруповане клієнтське API:
<img src="/img/tutorial/generate-clients/image06.png">
У цьому випадку у вас є:
* `ItemsService`
* `UsersService`
### Назви методів клієнта { #client-method-names }
Зараз згенеровані назви методів на кшталт `createItemItemsPost` виглядають не дуже охайно:
```TypeScript
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
```
...це тому, що генератор клієнта використовує внутрішній **operation ID** OpenAPI для кожної *операції шляху*.
OpenAPI вимагає, щоб кожен operation ID був унікальним серед усіх *операцій шляху*, тож FastAPI використовує **ім’я функції**, **шлях** і **HTTP метод/операцію**, щоб згенерувати цей operation ID — так він може гарантувати унікальність.
Але далі я покажу, як це покращити.
## Власні Operation ID і кращі назви методів { #custom-operation-ids-and-better-method-names }
Ви можете **змінити** спосіб **генерації** цих operation ID, щоб зробити їх простішими та отримати **простішi назви методів** у клієнтах.
У такому разі вам потрібно забезпечити, щоб кожен 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_py39.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 прямо перед генерацією клієнтів — лише щоб зробити назви методів гарнішими та **чистішими**.
Можна завантажити OpenAPI JSON у файл `openapi.json`, а потім **прибрати цей префікс тега** скриптом на кшталт такого:
{* ../../docs_src/generate_clients/tutorial004_py39.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
```
Після генерації нового клієнта ви матимете **чисті назви методів** з усім **автодоповненням**, **inline-помилками** тощо:
<img src="/img/tutorial/generate-clients/image08.png">
## Переваги { #benefits }
Використовуючи автоматично згенеровані клієнти, ви отримуєте **автодоповнення** для:
* Методів.
* Payload запиту в тілі, query-параметрів тощо.
* Payload відповіді.
Також ви матимете **inline-помилки** для всього.
І щоразу, коли ви оновлюєте код бекенду та **перегенеровуєте** фронтенд, нові *операції шляху* будуть доступні як методи, старі — видалені, а будь-які інші зміни відобразяться в згенерованому коді.
Це також означає, що якщо щось змінилося, це автоматично **відобразиться** в клієнтському коді. А якщо ви **збираєте** (build) клієнт, збірка завершиться помилкою, якщо є будь-яка **невідповідність** у використовуваних даних.
Отже, ви зможете **виявляти багато помилок** дуже рано в циклі розробки, замість того щоб чекати, поки помилки проявляться у ваших кінцевих користувачів у production, а потім намагатися відлагодити, де саме проблема.

View 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}.
А наступні розділи припускають, що ви вже прочитали його та знаєте ці основні ідеї.

View File

@@ -0,0 +1,97 @@
# Розширені middleware { #advanced-middleware }
У головному посібнику ви прочитали, як додати [власний Middleware](../tutorial/middleware.md){.internal-link target=_blank} до вашого застосунку.
Також ви прочитали, як обробляти [CORS за допомогою `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}.
У цьому розділі ми розглянемо, як використовувати інші middleware.
## Додавання ASGI middleware { #adding-asgi-middlewares }
Оскільки **FastAPI** базується на Starlette і реалізує специфікацію <abbr title="Asynchronous Server Gateway Interface">ASGI</abbr>, ви можете використовувати будь-який ASGI middleware.
Middleware не обов’язково має бути зроблений спеціально для FastAPI або Starlette, головне — щоб він відповідав специфікації ASGI.
Загалом, ASGI middleware — це класи, які очікують отримати ASGI застосунок як перший аргумент.
Тож у документації сторонніх ASGI middleware вам, імовірно, скажуть зробити щось на кшталт:
```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_py39.py hl[2,6] *}
## `TrustedHostMiddleware` { #trustedhostmiddleware }
Гарантує, що всі вхідні запити мають коректно встановлений заголовок `Host`, щоб захиститися від атак на HTTP Host Header.
{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
Підтримуються такі аргументи:
* `allowed_hosts` — список доменних імен, які слід дозволити як імена хостів. Підтримуються домени з wildcard, як-от `*.example.com`, для зіставлення піддоменів. Щоб дозволити будь-яке ім’я хоста, використайте `allowed_hosts=["*"]` або не додавайте цей middleware.
* `www_redirect` — якщо встановлено в True, запити до версій дозволених хостів без `www` буде перенаправлено на відповідні версії з `www`. За замовчуванням `True`.
Якщо вхідний запит не проходить перевірку, буде надіслано відповідь `400`.
## `GZipMiddleware` { #gzipmiddleware }
Обробляє GZip-відповіді для будь-якого запиту, що містить `"gzip"` у заголовку `Accept-Encoding`.
Middleware оброблятиме як стандартні, так і потокові відповіді.
{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
Підтримуються такі аргументи:
* `minimum_size` — не застосовувати GZip до відповідей, менших за цей мінімальний розмір у байтах. За замовчуванням `500`.
* `compresslevel` — використовується під час стиснення GZip. Це ціле число в діапазоні від 1 до 9. За замовчуванням `9`. Менше значення дає швидше стиснення, але більший розмір файлів, тоді як більше значення — повільніше стиснення, але менший розмір файлів.
## Інші middleware { #other-middlewares }
Існує багато інших ASGI middleware.
Наприклад:
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">`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 List</a>.

View File

@@ -0,0 +1,186 @@
# OpenAPI callbacks { #openapi-callbacks }
Ви можете створити API з *операцією шляху*, яка може запускати запит до *зовнішнього API*, створеного кимось іншим (ймовірно, тим самим розробником, який буде *використовувати* ваше API).
Процес, що відбувається, коли ваш застосунок API викликає *зовнішнє API*, називається «callback». Тому що програмне забезпечення, яке написав зовнішній розробник, надсилає запит до вашого API, а потім ваше API «викликає у відповідь», надсилаючи запит до *зовнішнього API* (яке, ймовірно, створив той самий розробник).
У цьому випадку вам може знадобитися задокументувати, як *має* виглядати це зовнішнє API. Яку *операцію шляху* воно повинно мати, яке тіло має очікувати, яку відповідь має повертати тощо.
## Застосунок із callbacks { #an-app-with-callbacks }
Розгляньмо це на прикладі.
Уявіть, що ви розробляєте застосунок, який дає змогу створювати рахунки.
Ці рахунки матимуть `id`, `title` (необов’язково), `customer` і `total`.
Користувач вашого API (зовнішній розробник) створить рахунок у вашому API за допомогою POST-запиту.
Потім ваше API (уявімо) буде:
* Надсилати рахунок якомусь клієнту зовнішнього розробника.
* Отримувати оплату.
* Надсилати сповіщення назад користувачу API (зовнішньому розробнику).
* Це буде зроблено шляхом надсилання POST-запиту (від *вашого API*) до певного *зовнішнього API*, наданого цим зовнішнім розробником (це і є «callback»).
## Звичайний застосунок **FastAPI** { #the-normal-fastapi-app }
Спочатку подивімося, як виглядатиме звичайний застосунок API до додавання callback.
Він матиме *операцію шляху*, яка отримуватиме тіло `Invoice`, і query-параметр `callback_url`, що міститиме URL для callback.
Ця частина доволі стандартна, більшість коду вам, ймовірно, уже знайома:
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
/// tip | Порада
Query-параметр `callback_url` використовує тип Pydantic <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a>.
///
Єдине нове тут — це `callbacks=invoices_callback_router.routes` як аргумент для *декоратора операції шляху*. Далі подивимося, що це таке.
## Документування callback { #documenting-the-callback }
Фактичний код callback сильно залежатиме від вашого застосунку API.
І, ймовірно, значно відрізнятиметься від застосунку до застосунку.
Це може бути лише один або два рядки коду, наприклад:
```Python
callback_url = "https://example.com/api/v1/invoices/events/"
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
```
Але, можливо, найважливіша частина callback — переконатися, що користувач вашого API (зовнішній розробник) реалізує *зовнішнє API* правильно, відповідно до даних, які *ваше API* надсилатиме в тілі запиту callback тощо.
Тож далі ми додамо код, щоб задокументувати, як має виглядати це *зовнішнє API*, аби приймати callback від *вашого API*.
Ця документація з’явиться в Swagger UI за адресою `/docs` у вашому API і дасть змогу зовнішнім розробникам зрозуміти, як побудувати *зовнішнє API*.
Цей приклад не реалізує сам callback (це може бути просто один рядок коду), лише частину документації.
/// tip | Порада
Фактичний callback — це просто HTTP-запит.
Під час реалізації callback ви можете використати щось на кшталт <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>.
///
## Напишіть код документації для callback { #write-the-callback-documentation-code }
Цей код не виконуватиметься у вашому застосунку, він потрібен лише для *документування* того, як має виглядати *зовнішнє API*.
Але ви вже знаєте, як легко створювати автоматичну документацію для API з **FastAPI**.
Тож ми використаємо ті самі знання, щоб задокументувати, як має виглядати *зовнішнє API*… створивши *операцію(ї) шляху*, які зовнішнє API повинно реалізувати (ті, які викликатиме ваше API).
/// tip | Порада
Під час написання коду для документування callback може бути корисно уявити, що ви — той *зовнішній розробник*. І що зараз ви реалізуєте *зовнішнє API*, а не *ваше API*.
Тимчасово прийнявши цю точку зору (з позиції *зовнішнього розробника*), вам може бути очевидніше, куди розміщувати параметри, Pydantic-модель для тіла, для відповіді тощо для цього *зовнішнього API*.
///
### Створіть callback `APIRouter` { #create-a-callback-apirouter }
Спочатку створіть новий `APIRouter`, який міститиме один або більше callbacks.
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
### Створіть callback *операцію шляху* { #create-the-callback-path-operation }
Щоб створити callback *операцію шляху*, використайте той самий `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*.
### Вираз шляху для callback { #the-callback-path-expression }
Callback-*шлях* може містити <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*.
У цьому випадку це `str`:
```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-запит на `callback_url` (до *зовнішнього API*):
```
https://www.external.org/events/invoices/2expen51ve
```
з JSON-тілом, яке міститиме щось на кшталт:
```JSON
{
"description": "Payment celebration",
"paid": true
}
```
і воно очікуватиме відповідь від цього *зовнішнього API* з JSON-тілом на кшталт:
```JSON
{
"ok": true
}
```
/// tip | Порада
Зверніть увагу: використаний URL для callback містить URL, отриманий як query-параметр у `callback_url` (`https://www.external.org/events`), а також `id` рахунку зсередини JSON-тіла (`2expen51ve`).
///
### Додайте callback router { #add-the-callback-router }
На цьому етапі у вас є потрібні *callback-операції шляху* (ті, які *зовнішній розробник* має реалізувати в *зовнішньому API*) у callback router, який ви створили вище.
Тепер використайте параметр `callbacks` у *декораторі операції шляху вашого API*, щоб передати атрибут `.routes` (це фактично просто `list` маршрутів/*операцій шляху*) з цього callback router:
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
/// tip | Порада
Зверніть увагу, що ви передаєте не сам router (`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">

View File

@@ -0,0 +1,55 @@
# OpenAPI вебхуки { #openapi-webhooks }
Бувають випадки, коли ви хочете повідомити **користувачам** вашого API, що ваш застосунок може викликати *їхній* застосунок (надсилаючи запит) з певними даними, зазвичай щоб **сповістити** про певний **івент**.
Це означає, що замість звичного процесу, коли користувачі надсилають запити до вашого API, **ваше API** (або ваш застосунок) може **надсилати запити до їхньої системи** (до їхнього API, їхнього застосунку).
Зазвичай це називають **webhook**.
## Кроки роботи вебхуків { #webhooks-steps }
Зазвичай процес такий: **ви визначаєте** у своєму коді, яке саме повідомлення ви надсилатимете — **тіло запиту**.
Також ви певним чином визначаєте, у які **моменти** ваш застосунок надсилатиме ці запити або івенти.
А **ваші користувачі** певним чином (наприклад, у вебпанелі керування десь) визначають **URL**, куди ваш застосунок має надсилати ці запити.
Уся **логіка** щодо реєстрації URL для вебхуків і код, який фактично надсилає ці запити, залишається на ваш розсуд. Ви пишете це так, як хочете, у **власному коді**.
## Документування вебхуків за допомогою **FastAPI** та OpenAPI { #documenting-webhooks-with-fastapi-and-openapi }
У **FastAPI**, використовуючи OpenAPI, ви можете визначати назви цих вебхуків, типи HTTP-операцій, які ваш застосунок може надсилати (наприклад, `POST`, `PUT` тощо), і **тіла** запитів, які ваш застосунок надсилатиме.
Це може значно спростити для ваших користувачів **реалізацію їхніх API** для отримання ваших запитів **webhook** — вони навіть можуть згенерувати частину власного коду API автоматично.
/// info | Інформація
Webhooks доступні в OpenAPI 3.1.0 і вище, підтримуються FastAPI `0.99.0` і вище.
///
## Застосунок із вебхуками { #an-app-with-webhooks }
Коли ви створюєте застосунок **FastAPI**, є атрибут `webhooks`, який можна використовувати для визначення *webhooks* так само, як ви визначаєте *операції шляху*, наприклад за допомогою `@app.webhooks.post()`.
{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
Визначені вами webhooks потраплять до схеми **OpenAPI** та автоматичного **інтерфейсу документації**.
/// info | Інформація
Об’єкт `app.webhooks` насправді є просто `APIRouter`, тим самим типом, який ви використовуєте під час структурування застосунку на кілька файлів.
///
Зверніть увагу, що з webhooks ви фактично не оголошуєте *path* (як-от `/items/`) — текст, який ви передаєте, є лише **ідентифікатором** вебхука (назвою івенту). Наприклад, у `@app.webhooks.post("new-subscription")` назва вебхука — `new-subscription`.
Це тому, що очікується, що **ваші користувачі** визначать фактичний **шлях URL**, за яким вони хочуть отримувати запит webhook, якимось іншим способом (наприклад, у вебпанелі керування).
### Перевірте документацію { #check-the-docs }
Тепер ви можете запустити застосунок і перейти на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Ви побачите, що у документації є звичайні *операції шляху*, а також деякі **webhooks**:
<img src="/img/tutorial/openapi-webhooks/image01.png">

View 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_py39.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_py39.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_py39.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 Extra { #openapi-extra }
Коли ви оголошуєте *операцію шляху* у вашому застосунку, **FastAPI** автоматично генерує відповідні метадані про цю *операцію шляху* для включення в схему OpenAPI.
/// note | Технічні деталі
У специфікації OpenAPI це називається <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation Object</a>.
///
Він містить усю інформацію про *операцію шляху* та використовується для генерації автоматичної документації.
Він включає `tags`, `parameters`, `requestBody`, `responses` тощо.
Ця частина схеми OpenAPI, специфічна для *операції шляху*, зазвичай генерується **FastAPI** автоматично, але ви також можете її розширити.
/// tip | Порада
Це низькорівнева точка розширення.
Якщо вам потрібно лише оголосити додаткові відповіді, зручніший спосіб — через [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}.
///
Ви можете розширити схему OpenAPI для *операції шляху* за допомогою параметра `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_py39.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_py39.py hl[19:36, 39:40] *}
У цьому прикладі ми не оголошували жодної моделі Pydantic. Фактично тіло запиту навіть не <abbr title="converted from some plain format, like bytes, into Python objects - перетворення з простого формату, наприклад bytes, у об’єкти Python">parsed</abbr> як JSON — його читають безпосередньо як `bytes`, а функція `magic_data_reader()` відповідала б за його розбір певним способом.
Попри це, ми можемо оголосити очікувану схему для тіла запиту.
### Власний тип вмісту OpenAPI { #custom-openapi-content-type }
Використовуючи цей самий підхід, ви можете застосувати модель Pydantic, щоб визначити JSON Schema, яку потім буде включено до власної секції схеми OpenAPI для *операції шляху*.
І ви можете зробити це навіть тоді, коли тип даних у запиті — не JSON.
Наприклад, у цьому застосунку ми не використовуємо інтегровану функціональність FastAPI для витягування JSON Schema з моделей Pydantic, а також автоматичну валідацію для JSON. Насправді ми оголошуємо тип вмісту запиту як YAML, а не JSON:
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
Попри це, хоча ми не використовуємо стандартну інтегровану функціональність, ми все одно використовуємо модель Pydantic, щоб вручну згенерувати JSON Schema для даних, які ми хочемо отримати в YAML.
Далі ми використовуємо запит безпосередньо й витягаємо тіло як `bytes`. Це означає, що FastAPI навіть не намагатиметься розбирати корисне навантаження запиту як JSON.
А потім у нашому коді ми напряму розбираємо цей YAML-вміст і знову використовуємо ту саму модель Pydantic, щоб валідувати YAML-вміст:
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
/// tip | Порада
Тут ми повторно використовуємо ту саму модель Pydantic.
Але так само ми могли б валідувати це іншим способом.
///

View 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` у вашій *функції операції шляху* (так само, як це можна зробити для cookies і headers).
Після цього ви можете встановити `status_code` у цьому *тимчасовому* об’єкті відповіді.
{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
Потім ви можете повернути будь-який потрібний об’єкт, як зазвичай (наприклад, `dict`, модель бази даних тощо).
І якщо ви оголосили `response_model`, його й надалі буде використано для фільтрації та перетворення об’єкта, який ви повернули.
**FastAPI** використає цю *тимчасову* відповідь, щоб отримати код статусу (а також cookies і headers), і додасть їх до фінальної відповіді, що містить значення, яке ви повернули, відфільтроване через будь-який `response_model`.
Також можна оголосити параметр `Response` у залежностях і встановлювати в них код статусу. Але майте на увазі: спрацює останнє встановлене значення.

View File

@@ -0,0 +1,51 @@
# Cookies у відповіді { #response-cookies }
## Використовуйте параметр `Response` { #use-a-response-parameter }
Ви можете оголосити параметр типу `Response` у вашій *функції операції шляху*.
Після цього ви можете встановлювати cookies в цьому *тимчасовому* об’єкті відповіді.
{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
Далі ви можете повертати будь-який потрібний об’єкт, як зазвичай (наприклад, `dict`, модель бази даних тощо).
І якщо ви оголосили `response_model`, він усе одно використовуватиметься для фільтрації та перетворення об’єкта, який ви повернули.
**FastAPI** використає цю *тимчасову* відповідь, щоб витягнути cookies (а також заголовки та код статусу), і додасть їх до фінальної відповіді, що містить значення, яке ви повернули, відфільтроване згідно з будь-яким `response_model`.
Також ви можете оголосити параметр `Response` у залежностях і встановлювати в них cookies (і заголовки).
## Повертайте `Response` напряму { #return-a-response-directly }
Ви також можете створювати cookies, повертаючи `Response` напряму у вашому коді.
Для цього створіть відповідь, як описано в [Повертайте Response напряму](response-directly.md){.internal-link target=_blank}.
Потім встановіть у ній Cookies і поверніть її:
{* ../../docs_src/response_cookies/tutorial001_py39.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` часто використовують для встановлення заголовків і cookies, **FastAPI** також надає його як `fastapi.Response`.
///
Щоб переглянути всі доступні параметри й опції, ознайомтеся з <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">документацією Starlette</a>.

View File

@@ -0,0 +1,65 @@
# Повертайте відповідь напряму { #return-a-response-directly }
Коли ви створюєте **FastAPI** *операцію шляху*, зазвичай ви можете повертати з неї будь-які дані: `dict`, `list`, модель Pydantic, модель бази даних тощо.
За замовчуванням **FastAPI** автоматично перетворює це значення, що повертається, на JSON за допомогою `jsonable_encoder`, описаного в розділі [JSON-сумісний енкодер](../tutorial/encoder.md){.internal-link target=_blank}.
Потім «за лаштунками» він помістить ці JSON-сумісні дані (наприклад, `dict`) всередину `JSONResponse`, який буде використано для надсилання відповіді клієнту.
Але ви можете повертати `JSONResponse` безпосередньо з ваших *операцій шляху*.
Це може бути корисно, наприклад, щоб повертати власні заголовки або cookies.
## Повернення `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` лише для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі 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_py39.py hl[1,18] *}
## Примітки { #notes }
Коли ви повертаєте `Response` напряму, його дані не валідовуються, не конвертуються (серіалізуються) і не документуються автоматично.
Але ви все одно можете задокументувати це, як описано в [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}.
У наступних розділах ви побачите, як використовувати/оголошувати ці власні `Response`, зберігаючи при цьому автоматичну конвертацію даних, документацію тощо.

View File

@@ -0,0 +1,41 @@
# Заголовки відповіді { #response-headers }
## Використовуйте параметр `Response` { #use-a-response-parameter }
Ви можете оголосити параметр типу `Response` у вашій *функції операції шляху* (так само, як і для cookies).
Після цього ви можете встановлювати заголовки в цьому *тимчасовому* об’єкті відповіді.
{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
Далі ви можете повертати будь-який потрібний об’єкт, як зазвичай (наприклад, `dict`, модель бази даних тощо).
Якщо ви оголосили `response_model`, він усе одно буде використаний, щоб відфільтрувати та перетворити повернений об’єкт.
**FastAPI** використає цю *тимчасову* відповідь, щоб витягти заголовки (а також cookies і код стану), і додасть їх до фінальної відповіді, що містить значення, яке ви повернули, відфільтроване через `response_model`.
Також ви можете оголосити параметр `Response` у залежностях і встановлювати в них заголовки (та cookies).
## Повертайте `Response` напряму { #return-a-response-directly }
Ви також можете додавати заголовки, коли повертаєте `Response` безпосередньо.
Створіть відповідь, як описано в [Повернути Response напряму](response-directly.md){.internal-link target=_blank}, і передайте заголовки як додатковий параметр:
{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
/// note | Технічні деталі
Ви також можете використовувати `from starlette.responses import Response` або `from starlette.responses import JSONResponse`.
**FastAPI** надає ті самі `starlette.responses` як `fastapi.responses` просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
А оскільки `Response` часто використовується для встановлення заголовків і cookies, **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>.

View 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_py39.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_py39.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 { #timing-attacks }
Але що таке «timing attack»?
Уявімо, що зловмисники намагаються вгадати ім’я користувача та пароль.
І вони надсилають запит з іменем користувача `johndoe` і паролем `love123`.
Тоді Python-код у вашому застосунку буде еквівалентний приблизно такому:
```Python
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
```
Але в момент, коли Python порівнює першу літеру `j` у `johndoe` з першою літерою `s` у `stanleyjobson`, він одразу поверне `False`, бо вже знає, що ці два рядки не однакові, і вважає, що «немає потреби витрачати додаткові обчислення на порівняння решти літер». І ваш застосунок скаже: «Incorrect username or password».
Потім зловмисники пробують ім’я користувача `stanleyjobsox` і пароль `love123`.
І код вашого застосунку робить щось на кшталт:
```Python
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
```
Python доведеться порівняти весь фрагмент `stanleyjobso` в `stanleyjobsox` і `stanleyjobson`, перш ніж зрозуміти, що рядки відрізняються. Тож відповідь «Incorrect username or password» займе на кілька мікросекунд більше.
#### Час відповіді допомагає зловмисникам { #the-time-to-answer-helps-the-attackers }
У цей момент, помітивши, що сервер витратив на кілька мікросекунд більше на надсилання відповіді «Incorrect username or password», зловмисники зрозуміють, що вони _в чомусь_ праві — деякі початкові літери збіглися.
І тоді вони можуть повторити спробу, знаючи, що правильне значення, ймовірно, ближче до `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_py39.py hl[26:30] *}

View 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}.
Усі вони ґрунтуються на тих самих концепціях, але дають змогу використовувати додаткові можливості.

View File

@@ -0,0 +1,274 @@
# Області OAuth2 { #oauth2-scopes }
Ви можете використовувати області OAuth2 безпосередньо з **FastAPI** — вони інтегровані так, щоб працювати безшовно.
Це дає змогу мати більш деталізовану систему дозволів, дотримуючись стандарту OAuth2, інтегровану у ваш застосунок OpenAPI (і документацію API).
OAuth2 з областями — це механізм, який використовують багато великих провайдерів автентифікації, як-от Facebook, Google, GitHub, Microsoft, X (Twitter) тощо. Вони застосовують його, щоб надавати користувачам і застосункам конкретні дозволи.
Щоразу, коли ви «входите через» Facebook, Google, GitHub, Microsoft, X (Twitter), цей застосунок використовує OAuth2 з областями.
У цьому розділі ви побачите, як керувати автентифікацією та авторизацією за допомогою того самого OAuth2 з областями у вашому застосунку **FastAPI**.
/// warning | Попередження
Це більш-менш просунутий розділ. Якщо ви лише починаєте, можете його пропустити.
Вам не обов’язково потрібні області OAuth2, і ви можете організувати автентифікацію та авторизацію так, як вам потрібно.
Але OAuth2 з областями можна гарно інтегрувати у ваш API (з OpenAPI) і документацію API.
Водночас ці області, або будь-які інші вимоги безпеки/авторизації, ви все одно застосовуєте так, як потрібно, у вашому коді.
У багатьох випадках OAuth2 з областями може бути надлишковим.
Але якщо ви знаєте, що вам це потрібно, або вам цікаво — читайте далі.
///
## Області OAuth2 та OpenAPI { #oauth2-scopes-and-openapi }
Специфікація OAuth2 визначає «scopes» як список рядків, розділених пробілами.
Вміст кожного з цих рядків може мати будь-який формат, але не повинен містити пробілів.
Ці області представляють «permissions» (дозволи).
В OpenAPI (наприклад, у документації API) ви можете визначати «security schemes».
Коли одна з таких схем безпеки використовує OAuth2, ви також можете оголошувати та використовувати області.
Кожна «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:
{* ../../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 з двома доступними областями: `me` і `items`.
Параметр `scopes` отримує `dict`, де кожна область є ключем, а опис — значенням:
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
Оскільки ми тепер оголошуємо ці області, вони з’являться в документації API, коли ви виконаєте вхід/авторизацію.
І ви зможете вибрати, до яких областей хочете надати доступ: `me` і `items`.
Це той самий механізм, який використовується, коли ви надаєте дозволи під час входу через Facebook, Google, GitHub тощо:
<img src="/img/tutorial/security/image11.png">
## JWT токен з областями { #jwt-token-with-scopes }
Тепер змініть *операцію шляху* токена так, щоб вона повертала запитані області.
Ми й далі використовуємо той самий `OAuth2PasswordRequestForm`. Він має властивість `scopes` з `list` із `str`, з кожною областю, яку він отримав у запиті.
І ми повертаємо області як частину JWT токена.
/// danger | Обережно
Для простоти тут ми просто додаємо області, отримані з запиту, безпосередньо до токена.
Але у вашому застосунку, з міркувань безпеки, слід переконатися, що ви додаєте лише ті області, які користувач справді може мати, або ті, які ви попередньо визначили.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
## Оголошення областей в *операціях шляху* та залежностях { #declare-scopes-in-path-operations-and-dependencies }
Тепер ми оголошуємо, що *операція шляху* для `/users/me/items/` потребує область `items`.
Для цього ми імпортуємо та використовуємо `Security` з `fastapi`.
Ви можете використовувати `Security`, щоб оголошувати залежності (так само, як `Depends`), але `Security` також отримує параметр `scopes` зі списком областей (рядків).
У цьому випадку ми передаємо функцію залежності `get_current_active_user` до `Security` (так само, як зробили б із `Depends`).
Але також передаємо `list` областей — у цьому випадку лише одну: `items` (може бути й більше).
І функція залежності `get_current_active_user` також може оголошувати підзалежності — не лише через `Depends`, а й через `Security`. Оголошуючи власну функцію підзалежності (`get_current_user`) та додаткові вимоги до областей.
У цьому випадку потрібна область `me` (може вимагатися більше ніж одна область).
/// note | Примітка
Вам не обов’язково додавати різні області в різних місцях.
Ми робимо це тут, щоб показати, як **FastAPI** обробляє області, оголошені на різних рівнях.
///
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
/// info | Технічні деталі
`Security` насправді є підкласом `Depends` і має лише один додатковий параметр, який ми побачимо пізніше.
Але використовуючи `Security` замість `Depends`, **FastAPI** знатиме, що може оголошувати області безпеки, використовувати їх внутрішньо та документувати API через OpenAPI.
Але коли ви імпортуєте `Query`, `Path`, `Depends`, `Security` та інші з `fastapi`, то це фактично функції, які повертають спеціальні класи.
///
## Використання `SecurityScopes` { #use-securityscopes }
Тепер оновіть залежність `get_current_user`.
Саме її використовують залежності вище.
Тут ми використовуємо ту саму схему OAuth2, яку створили раніше, оголошуючи її як залежність: `oauth2_scheme`.
Оскільки ця функція залежності сама по собі не має вимог до областей, ми можемо використати `Depends` з `oauth2_scheme` — не потрібно використовувати `Security`, коли не треба вказувати області безпеки.
Ми також оголошуємо спеціальний параметр типу `SecurityScopes`, імпортований з `fastapi.security`.
Клас `SecurityScopes` схожий на `Request` (`Request` використовувався для отримання об’єкта запиту напряму).
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
## Використання `scopes` { #use-the-scopes }
Параметр `security_scopes` матиме тип `SecurityScopes`.
Він матиме властивість `scopes` зі списком, який містить усі області, потрібні для нього самого та для всіх залежностей, що використовують його як підзалежність. Тобто для всіх «dependants»... це може звучати заплутано — нижче це ще раз пояснюється.
Об’єкт `security_scopes` (класу `SecurityScopes`) також надає атрибут `scope_str` з одним рядком, що містить ці області, розділені пробілами (ми будемо це використовувати).
Ми створюємо `HTTPException`, який можемо повторно використовувати (`raise`) у кількох місцях.
У цьому винятку ми включаємо потрібні області (якщо є) у вигляді рядка, розділеного пробілами (використовуючи `scope_str`). Цей рядок з областями ми поміщаємо в заголовок `WWW-Authenticate` (це частина специфікації).
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
## Перевірка `username` і структури даних { #verify-the-username-and-data-shape }
Ми перевіряємо, що отримали `username`, і витягаємо області.
Далі валідуюємо ці дані за допомогою Pydantic-моделі (перехоплюючи виняток `ValidationError`), і якщо під час читання JWT токена або перевірки даних через Pydantic виникає помилка — піднімаємо `HTTPException`, який створили раніше.
Для цього ми оновлюємо Pydantic-модель `TokenData`, додаючи нову властивість `scopes`.
Валідуючи дані через Pydantic, ми можемо переконатися, що маємо, наприклад, рівно `list` із `str` для областей та `str` для `username`.
А не, наприклад, `dict` чи щось інше, що могло б зламати застосунок пізніше і створити ризик безпеки.
Ми також перевіряємо, що існує користувач із таким `username`, і якщо ні — піднімаємо той самий виняток, який створили раніше.
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
## Перевірка `scopes` { #verify-the-scopes }
Тепер ми перевіряємо, що всі області, потрібні для цієї залежності та всіх «dependants» (включно з *операціями шляху*), присутні в областях, наданих у отриманому токені. Інакше піднімаємо `HTTPException`.
Для цього використовуємо `security_scopes.scopes`, що містить `list` усіх цих областей як `str`.
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
## Дерево залежностей та області { #dependency-tree-and-scopes }
Розгляньмо ще раз це дерево залежностей та області.
Оскільки залежність `get_current_active_user` має підзалежність `get_current_user`, область `"me"`, оголошена в `get_current_active_user`, буде включена до списку потрібних областей у `security_scopes.scopes`, переданому в `get_current_user`.
Сама *операція шляху* також оголошує область `"items"`, тож вона теж буде в списку `security_scopes.scopes`, переданому в `get_current_user`.
Ось як виглядає ієрархія залежностей та областей:
* *Операція шляху* `read_own_items` має:
* Потрібні області `["items"]` із залежністю:
* `get_current_active_user`:
* Функція залежності `get_current_active_user` має:
* Потрібні області `["me"]` із залежністю:
* `get_current_user`:
* Функція залежності `get_current_user` має:
* Немає областей, потрібних для неї самої.
* Залежність, що використовує `oauth2_scheme`.
* Параметр `security_scopes` типу `SecurityScopes`:
* Цей параметр `security_scopes` має властивість `scopes` із `list`, який містить усі оголошені вище області, отже:
* `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` у будь-якій точці й у кількох місцях — він не обов’язково має бути в «кореневій» залежності.
Він завжди міститиме області безпеки, оголошені в поточних залежностях `Security`, та у всіх «dependants» для **саме цієї** *операції шляху* і **саме цього** дерева залежностей.
Оскільки `SecurityScopes` міститиме всі області, оголошені «dependants», ви можете використати його, щоб перевіряти наявність потрібних областей у токені в центральній функції залежності, а потім оголошувати різні вимоги до областей у різних *операціях шляху*.
Вони перевірятимуться незалежно для кожної *операції шляху*.
## Перевірте { #check-it }
Якщо ви відкриєте документацію API, ви зможете автентифікуватися та вказати, які області ви хочете авторизувати.
<img src="/img/tutorial/security/image11.png">
Якщо ви не виберете жодної області, ви будете «автентифіковані», але коли спробуєте отримати доступ до `/users/me/` або `/users/me/items/`, отримаєте помилку про недостатні дозволи. Водночас ви все ще зможете отримати доступ до `/status/`.
А якщо ви виберете область `me`, але не область `items`, ви зможете отримати доступ до `/users/me/`, але не до `/users/me/items/`.
Саме так поводитиметься сторонній застосунок, який спробував би отримати доступ до однієї з цих *операцій шляху* з токеном, наданим користувачем, залежно від того, скільки дозволів користувач надав цьому застосунку.
## Про інтеграції зі сторонніми застосунками { #about-third-party-integrations }
У цьому прикладі ми використовуємо OAuth2 «password» flow.
Це доречно, коли ми входимо у власний застосунок — імовірно, через власний frontend.
Тому що ми можемо довіряти йому отримання `username` та `password`, адже ми його контролюємо.
Але якщо ви створюєте OAuth2-застосунок, до якого підключатимуться інші (тобто якщо ви створюєте провайдера автентифікації на кшталт Facebook, Google, GitHub тощо), вам слід використати один з інших flow.
Найпоширеніший — implicit flow.
Найбезпечніший — code flow, але його складніше реалізувати, бо він вимагає більше кроків. Через цю складність багато провайдерів у підсумку рекомендують implicit flow.
/// note | Примітка
Часто кожен провайдер автентифікації називає свої flow по-різному, роблячи це частиною бренду.
Але зрештою вони реалізують той самий стандарт OAuth2.
///
**FastAPI** містить утиліти для всіх цих OAuth2 flow автентифікації в `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`.

View 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>
Він також встановлюється, коли ви встановлюєте extra `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_py39.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_py39.py hl[18:20] *}
### Запустіть сервер { #run-the-server }
Потім ви запускатимете сервер, передаючи конфігурації як змінні середовища. Наприклад, ви можете встановити `ADMIN_EMAIL` і `APP_NAME` так:
<div class="termy">
```console
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
/// tip | Порада
Щоб встановити кілька env vars для однієї команди, просто розділіть їх пробілом і розмістіть усі перед командою.
///
Після цього налаштування `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_py39/config.py *}
А потім використати його у файлі `main.py`:
{* ../../docs_src/settings/app01_py39/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_py39/config.py hl[10] *}
Зверніть увагу, що тепер ми не створюємо екземпляр за замовчуванням `settings = Settings()`.
### Основний файл застосунку { #the-main-app-file }
Тепер ми створюємо залежність, яка повертає новий `config.Settings()`.
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// tip | Порада
Трохи згодом ми обговоримо `@lru_cache`.
Поки що можете вважати `get_settings()` звичайною функцією.
///
Потім ми можемо вимагати її як залежність у *функції операції шляху* і використовувати всюди, де вона потрібна.
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *}
### Налаштування та тестування { #settings-and-testing }
Тоді під час тестування буде дуже легко надати інший об’єкт налаштувань, створивши перевизначення залежності для `get_settings`:
{* ../../docs_src/settings/app02_an_py39/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_py39/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` всередині вашого класу `Settings` у Pydantic і встановлюємо значення як ім’я файлу з 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_py39/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 для кожного запиту, водночас даючи змогу перевизначати це під час тестування.

View File

@@ -0,0 +1,67 @@
# Підзастосунки — монтування { #sub-applications-mounts }
Якщо вам потрібно мати два незалежні застосунки FastAPI з власним незалежним OpenAPI та власними UI документації, ви можете мати головний застосунок і «змонтувати» один (або кілька) підзастосунок(ів).
## Монтування застосунку **FastAPI** { #mounting-a-fastapi-application }
«Монтування» означає додавання повністю «незалежного» застосунку на певному шляху, який потім відповідатиме за обробку всього під цим шляхом, із _операціями шляху_, оголошеними в цьому підзастосунку.
### Застосунок верхнього рівня { #top-level-application }
Спочатку створіть головний, застосунок верхнього рівня **FastAPI** та його *операції шляху*:
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *}
### Підзастосунок { #sub-application }
Далі створіть ваш підзастосунок та його *операції шляху*.
Цей підзастосунок — це просто ще один стандартний застосунок FastAPI, але саме його буде «змонтовано»:
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *}
### Змонтуйте підзастосунок { #mount-the-sub-application }
У вашому застосунку верхнього рівня `app` змонтуйте підзастосунок `subapi`.
У цьому випадку його буде змонтовано за шляхом `/subapi`:
{* ../../docs_src/sub_applications/tutorial001_py39.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`.
Так підзастосунок знатиме, що потрібно використовувати цей префікс шляху для UI документації.
А підзастосунок також може мати власні змонтовані підзастосунки, і все працюватиме коректно, тому що FastAPI автоматично обробляє всі ці `root_path`.
Детальніше про `root_path` і про те, як явно його використовувати, ви дізнаєтеся в розділі [За проксі](behind-a-proxy.md){.internal-link target=_blank}.

View 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`, передайте назву шаблону, об’єкт запиту та словник «context» (контекст) з парами ключ-значення, які будуть доступні всередині шаблону Jinja2.
{* ../../docs_src/templates/tutorial001_py39.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`, взятий зі словника «context» `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>.

View File

@@ -0,0 +1,53 @@
# Тестування залежностей із перевизначеннями { #testing-dependencies-with-overrides }
## Перевизначення залежностей під час тестування { #overriding-dependencies-during-testing }
Є кілька сценаріїв, коли під час тестування може знадобитися перевизначити залежність.
Ви не хочете, щоб виконувалася початкова залежність (а також будь-які її підзалежності).
Натомість ви хочете надати іншу залежність, яка використовуватиметься лише під час тестів (можливо, лише в деяких конкретних тестах) і повертатиме значення, яке можна використати там, де використовувалося значення початкової залежності.
### Випадки використання: зовнішній сервіс { #use-cases-external-service }
Наприклад, у вас може бути зовнішній провайдер автентифікації, якого потрібно викликати.
Ви надсилаєте йому токен, і він повертає автентифікованого користувача.
Цей провайдер може брати плату за кожен запит, а виклик може займати більше часу, ніж якби для тестів ви мали фіксованого mock-користувача.
Імовірно, ви хочете протестувати зовнішнього провайдера один раз, але не обов’язково викликати його для кожного тесту, що виконується.
У такому разі ви можете перевизначити залежність, яка викликає цього провайдера, і використовувати власну залежність, що повертає mock-користувача, лише для ваших тестів.
### Використання атрибута `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 | Порада
Якщо ви хочете перевизначувати залежність лише під час деяких тестів, ви можете встановити перевизначення на початку тесту (всередині функції тесту) і скинути його в кінці (наприкінці функції тесту).
///

View File

@@ -0,0 +1,12 @@
# Тестування подій: `lifespan` і `startup` - `shutdown` { #testing-events-lifespan-and-startup-shutdown }
Коли вам потрібно, щоб `lifespan` виконувався у ваших тестах, ви можете використати `TestClient` з оператором `with`:
{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *}
Ви можете прочитати більше деталей про [«Running lifespan in tests in the official Starlette documentation site.»](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)
Для застарілих подій `startup` і `shutdown` ви можете використати `TestClient` так:
{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *}

View File

@@ -0,0 +1,13 @@
# Тестування WebSockets { #testing-websockets }
Ви можете використовувати той самий `TestClient` для тестування WebSockets.
Для цього використайте `TestClient` в інструкції `with`, підключившись до WebSocket:
{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *}
/// note | Примітка
Докладніше дивіться документацію Starlette щодо <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">тестування WebSockets</a>.
///

View File

@@ -0,0 +1,56 @@
# Безпосереднє використання Request { #using-the-request-directly }
До цього моменту ви оголошували частини запиту, які вам потрібні, разом із їхніми типами.
Отримуючи дані з:
* шляху як параметрів;
* заголовків;
* cookies;
* тощо.
І таким чином **FastAPI** автоматично перевіряє ці дані, перетворює їх і генерує документацію для вашого API.
Але бувають ситуації, коли вам може знадобитися доступ безпосередньо до об’єкта `Request`.
## Деталі про об’єкт `Request` { #details-about-the-request-object }
Оскільки в основі **FastAPI** фактично лежить **Starlette**, із шаром кількох інструментів поверх, за потреби ви можете використовувати об’єкт Starlette <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> безпосередньо.
Це також означає, що якщо ви отримуєте дані безпосередньо з об’єкта `Request` (наприклад, читаєте body), FastAPI не буде їх валідовувати, перетворювати або документувати (через OpenAPI, для автоматичного UI API).
Хоча будь-який інший параметр, оголошений звичним способом (наприклад, body за допомогою Pydantic-моделі), все одно буде валідовано, перетворено, анотовано тощо.
Але є специфічні випадки, коли корисно отримати об’єкт `Request`.
## Використовуйте об’єкт `Request` безпосередньо { #use-the-request-object-directly }
Уявімо, що ви хочете отримати IP-адресу/хост клієнта всередині вашої *функції операції шляху*.
Для цього потрібно отримати доступ до запиту безпосередньо.
{* ../../docs_src/using_request_directly/tutorial001_py39.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.
///

View 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 endpoint.
---
Але для цього прикладу ми використаємо дуже простий HTML-документ з невеликою кількістю JavaScript — усе всередині довгого рядка.
Звісно, це не оптимально, і ви не використовували б це в продакшені.
У продакшені ви б скористалися одним із варіантів вище.
Але це найпростіший спосіб зосередитися на серверній частині WebSockets і мати робочий приклад:
{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *}
## Створення `websocket` { #create-a-websocket }
У вашому застосунку **FastAPI** створіть `websocket`:
{* ../../docs_src/websockets/tutorial001_py39.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_py39.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 endpoints ви можете імпортувати з `fastapi` та використовувати:
* `Depends`
* `Security`
* `Cookie`
* `Header`
* `Path`
* `Query`
Вони працюють так само, як і для інших endpoints 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», який використовується в path.
* «Token», який використовується як query parameter.
/// tip | Порада
Зверніть увагу, що query `token` буде оброблено залежністю.
///
Після цього ви зможете під’єднати WebSocket, а потім надсилати й отримувати повідомлення:
<img src="/img/tutorial/websockets/image05.png">
## Обробка роз’єднань і кількох клієнтів { #handling-disconnections-and-multiple-clients }
Коли WebSocket-з’єднання закрито, `await websocket.receive_text()` підніме виняток `WebSocketDisconnect`, який ви можете перехопити та обробити, як у цьому прикладі.
{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
Щоб спробувати:
* Відкрийте застосунок у кількох вкладках браузера.
* Надсилайте повідомлення з них.
* Потім закрийте одну з вкладок.
Це підніме виняток `WebSocketDisconnect`, і всі інші клієнти отримають повідомлення на кшталт:
```
Client #1596980209979 left the chat
```
/// tip | Порада
Застосунок вище — мінімальний і простий приклад, що демонструє, як обробляти та транслювати повідомлення на кілька WebSocket-з’єднань.
Але майте на увазі, що оскільки все обробляється в пам’яті, в одному списку, це працюватиме лише доки працює процес, і лише з одним процесом.
Якщо вам потрібно щось, що легко інтегрується з FastAPI, але є більш надійним і підтримується Redis, PostgreSQL чи іншими, перегляньте <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>.
///
## Додаткова інформація { #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>.

View File

@@ -0,0 +1,35 @@
# Підключення 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 }
Потрібно імпортувати `WSGIMiddleware`.
Потім обгорнути WSGI-застосунок (наприклад, Flask) цим middleware.
І після цього змонтувати його за певним шляхом.
{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *}
## Перевірка { #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
View 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 підтримують **«асинхронний код»** за допомогою того, що називається **«coroutines»**, із синтаксисом **`async` і `await`**.
Розберімо цю фразу по частинах у розділах нижче:
* **Асинхронний код**
* **`async` і `await`**
* **Coroutines**
## Асинхронний код { #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 значення/кольори; обробка зазвичай вимагає обчислювати щось над цими пікселями — усіма одночасно.
* **Machine Learning**: зазвичай потребує великої кількості множень «матриць» і «векторів». Уявіть величезну електронну таблицю з числами та множення їх усіх одночасно.
* **Deep Learning**: це підгалузь Machine Learning, тож діє те саме. Просто це не одна таблиця чисел для множення, а величезний набір таких таблиць, і в багатьох випадках ви використовуєте спеціальний процесор для побудови та/або використання цих моделей.
### Конкурентність + паралелізм: веб + Machine Learning { #concurrency-parallelism-web-machine-learning }
З **FastAPI** ви можете скористатися конкурентністю, яка дуже типова для веброзробки (це та сама головна перевага NodeJS).
Але ви також можете використовувати переваги паралелізму та multiprocessing (коли кілька процесів працюють паралельно) для **CPU bound** навантажень, як у системах Machine Learning.
Це, плюс простий факт, що Python — головна мова для **Data Science**, Machine Learning і особливо Deep Learning, робить FastAPI дуже вдалим вибором для веб-API та застосунків у Data Science / Machine Learning (серед багатьох інших).
Щоб побачити, як досягти цього паралелізму у production, дивіться розділ про [Розгортання](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):
# Do some asynchronous stuff to create the burgers
return burgers
```
...замість `def`:
```Python hl_lines="2"
# This is not asynchronous
def get_sequential_burgers(number: int):
# Do some sequential stuff to create the burgers
return burgers
```
З `async def` Python знає, що всередині цієї функції треба враховувати вирази `await`, і що він може «призупинити» ⏸ виконання цієї функції та піти зробити щось інше 🔀, перш ніж повернутися.
Коли ви хочете викликати функцію `async def`, вам треба її «await». Тож це не працюватиме:
```Python
# This won't work, because get_burgers was defined with: 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`, потрібно «await». Тож функції з `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>, щоб мати високу сумісність і отримати його переваги (наприклад, *structured concurrency*).
Я створив ще одну бібліотеку поверх AnyIO як тонкий шар, щоб трохи покращити type annotations і отримати кращі **autocompletion**, **inline errors** тощо. Вона також має дружній вступ і навчальний посібник, щоб допомогти вам **зрозуміти** та писати **власний async-код**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. Вона буде особливо корисною, якщо вам потрібно **поєднувати async-код зі звичайним** (blocking/synchronous) кодом.
### Інші форми асинхронного коду { #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 { #coroutines }
**Coroutine** — це просто дуже «пишний» термін для того, що повертає функція `async def`. Python знає, що це щось на кшталт функції, яку можна запустити й яка колись завершиться, але яка також може бути призупинена ⏸ всередині, коли є `await`.
Але всю цю функціональність використання асинхронного коду з `async` і `await` часто узагальнюють як використання «coroutines». Це можна порівняти з головною фішкою Go — «Goroutines».
## Підсумок { #conclusion }
Подивімося на ту саму фразу з вище:
> Сучасні версії Python підтримують **«асинхронний код»** за допомогою того, що називається **«coroutines»**, із синтаксисом **`async` і `await`**.
Тепер це має бути зрозумілішим. ✨
Усе це — те, що «живить» FastAPI (через Starlette) і дає йому таку вражаючу продуктивність.
## Дуже технічні деталі { #very-technical-details }
/// warning | Попередження
Ймовірно, ви можете пропустити це.
Це дуже технічні деталі того, як **FastAPI** працює всередині.
Якщо ви маєте достатньо технічних знань (coroutines, threads, blocking тощо) і вам цікаво, як FastAPI обробляє `async def` проти звичайного `def`, продовжуйте.
///
### Функції операцій шляху { #path-operation-functions }
Коли ви оголошуєте *функцію операції шляху* звичайним `def` замість `async def`, вона виконується в зовнішньому threadpool, і вже його «await», замість прямого виклику (бо це заблокувало б сервер).
Якщо ви переходите з іншого 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`, вона виконується в зовнішньому threadpool.
### Підзалежності { #sub-dependencies }
Ви можете мати кілька залежностей і [підзалежностей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, які потребують одна одну (як параметри у визначеннях функцій); деякі з них можуть бути створені через `async def`, а деякі — через звичайний `def`. Це все одно працюватиме, а ті, що створені через звичайний `def`, будуть викликані в зовнішньому потоці (із threadpool), замість того щоб їх «await».
### Інші допоміжні функції { #other-utility-functions }
Будь-яка інша допоміжна функція, яку ви викликаєте безпосередньо, може бути створена як звичайним `def`, так і `async def`, і FastAPI не впливатиме на те, як ви її викликаєте.
Це відрізняється від функцій, які FastAPI викликає за вас: *функцій операцій шляху* та залежностей.
Якщо ваша допоміжна функція — звичайна з `def`, вона буде викликана напряму (як ви написали у своєму коді), не в threadpool; якщо функція створена через `async def`, тоді під час виклику у вашому коді вам слід зробити `await`.
---
Знову ж таки, це дуже технічні деталі, які, ймовірно, корисні, якщо ви спеціально їх шукали.
Інакше вам має вистачити рекомендацій із розділу вище: <a href="#in-a-hurry">Поспішаєте?</a>.

View 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 тощо — фреймворками з інтегрованими автоматичними валідацією даних, серіалізацією та документацією.

View 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 — основний спонсор і джерело фінансування для open source проєктів *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>

View 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 }
Слово **процес** зазвичай використовується більш конкретно — лише для того, що виконується в операційній системі (як в останньому пункті вище):
* Конкретна програма, коли вона **виконується** в операційній системі.
* Це не про файл і не про код — це **саме** те, що **виконується** та керується операційною системою.
* Будь-яка програма, будь-який код **може щось робити** лише тоді, коли він **виконується**. Тобто коли **працює процес**.
* Процес може бути **завершений** (або «вбитий») вами або операційною системою. Після цього він перестає виконуватися і **більше не може нічого робити**.
* Кожен застосунок, який у вас запущений на комп’ютері, має свій процес: кожна запущена програма, кожне вікно тощо. І зазвичай одночасно працює багато процесів, доки комп’ютер увімкнений.
* Може бути **кілька процесів** **однієї й тієї самої програми**, запущених одночасно.
Якщо у вашій операційній системі відкрити «task manager» або «system monitor» (чи подібні інструменти), ви зможете побачити багато таких процесів.
І, наприклад, ви, ймовірно, побачите кілька процесів одного браузера (Firefox, Chrome, Edge тощо). Зазвичай він запускає один процес на вкладку плюс деякі додаткові процеси.
<img class="shadow" src="/img/deployment/concepts/image01.png">
---
Тепер, коли ми знаємо різницю між термінами **процес** і **програма**, продовжимо говорити про розгортання.
## Запуск під час старту { #running-on-startup }
У більшості випадків, коли ви створюєте веб-API, ви хочете, щоб воно **працювало завжди**, без перерв, аби клієнти могли постійно мати доступ. Звісно, якщо у вас немає конкретної причини запускати його лише в певних ситуаціях, найчастіше ви хочете, щоб воно було постійно запущене й **доступне**.
### На віддаленому сервері { #in-a-remote-server }
Коли ви налаштовуєте віддалений сервер (хмарний сервер, віртуальну машину тощо), найпростіше — запускати `fastapi run` (який використовує Uvicorn) або щось подібне вручну, так само як під час локальної розробки.
Це працюватиме й буде корисним **під час розробки**.
Але якщо ваше з’єднання із сервером буде втрачено, **запущений процес**, імовірно, завершиться.
І якщо сервер перезапуститься (наприклад, після оновлень або міграцій у хмарного провайдера), ви, ймовірно, **цього не помітите**. Через це ви навіть не знатимете, що процес треба перезапустити вручну. Тож ваш API просто залишиться «мертвим». 😱
### Автоматичний запуск під час старту { #run-automatically-on-startup }
Загалом, вам, ймовірно, потрібно, щоб серверна програма (наприклад, Uvicorn) запускалася автоматично під час старту сервера, без будь-якого **втручання людини**, і щоб процес із вашим API завжди працював (наприклад, Uvicorn, який запускає ваш застосунок FastAPI).
### Окрема програма { #separate-program }
Щоб цього досягти, зазвичай використовують **окрему програму**, яка гарантуватиме запуск вашого застосунку під час старту. У багатьох випадках вона також подбає про запуск інших компонентів або застосунків, наприклад бази даних.
### Приклади інструментів для запуску під час старту { #example-tools-to-run-at-startup }
Ось кілька прикладів інструментів, які можуть виконувати цю роль:
* Docker
* Kubernetes
* Docker Compose
* Docker у режимі Swarm Mode
* 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 Mode
* Systemd
* Supervisor
* Внутрішньо обробляється хмарним провайдером як частина їхніх сервісів
* Інші...
## Реплікація — процеси та пам’ять { #replication-processes-and-memory }
Для застосунку FastAPI, якщо запускати його через серверну програму на кшталт команди `fastapi`, що запускає Uvicorn, одного запуску **в одному процесі** достатньо, щоб обслуговувати кількох клієнтів одночасно.
Але в багатьох випадках ви захочете запускати одразу кілька worker-процесів.
### Кілька процесів — workers { #multiple-processes-workers }
Якщо клієнтів більше, ніж може обробити один процес (наприклад, якщо віртуальна машина не надто потужна) і на сервері є **кілька ядер** CPU, тоді ви можете запустити **кілька процесів** з одним і тим самим застосунком і розподіляти між ними всі запити.
Коли ви запускаєте **кілька процесів** однієї програми API, їх зазвичай називають **workers**.
### Worker-процеси та порти { #worker-processes-and-ports }
Пам’ятаєте з документації [Про HTTPS](https.md){.internal-link target=_blank}, що лише один процес може «слухати» одну комбінацію порту й IP-адреси на сервері?
Це все ще так.
Отже, щоб мати **кілька процесів** одночасно, має бути **один процес, що слухає порт**, і який потім якимось чином передає комунікацію кожному worker-процесу.
### Пам’ять на процес { #memory-per-process }
Коли програма завантажує дані в пам’ять, наприклад модель машинного навчання у змінну або вміст великого файлу в змінну, усе це **споживає частину пам’яті (RAM)** сервера.
А кілька процесів зазвичай **не ділять пам’ять**. Це означає, що кожен запущений процес має свої дані, змінні та пам’ять. І якщо ваш код споживає багато пам’яті, **кожен процес** споживатиме приблизно таку саму кількість пам’яті.
### Пам’ять сервера { #server-memory }
Наприклад, якщо ваш код завантажує модель машинного навчання розміром **1 GB**, то під час запуску одного процесу з вашим API він споживатиме щонайменше 1 GB RAM. А якщо ви запустите **4 процеси** (4 workers), кожен споживатиме 1 GB RAM. Тобто загалом ваш API споживатиме **4 GB RAM**.
І якщо на вашому віддаленому сервері або віртуальній машині є лише 3 GB RAM, спроба завантажити понад 4 GB RAM спричинить проблеми. 🚨
### Кілька процесів — приклад { #multiple-processes-an-example }
У цьому прикладі є **керівний процес (Manager Process)**, який запускає та контролює два **worker-процеси (Worker Processes)**.
Цей керівний процес, імовірно, буде тим, хто «слухає» **порт** на IP. І він передаватиме всю комунікацію worker-процесам.
А worker-процеси запускатимуть ваш застосунок, виконуватимуть основні обчислення для отримання **запиту** й повернення **відповіді**, та завантажуватимуть у RAM усе, що ви зберігаєте у змінних.
<img src="/img/deployment/concepts/process-ram.drawio.svg">
І, звісно, та сама машина, ймовірно, матиме **інші процеси**, окрім вашого застосунку.
Цікавий нюанс: відсоток **використання CPU** кожним процесом може сильно **змінюватися** з часом, тоді як **пам’ять (RAM)** зазвичай лишається більш-менш **стабільною**.
Якщо у вас API, яке щоразу виконує порівнянний обсяг обчислень, і у вас багато клієнтів, то **завантаження CPU**, ймовірно, *також буде стабільним* (замість того, щоб постійно швидко зростати й падати).
### Приклади інструментів і стратегій реплікації { #examples-of-replication-tools-and-strategies }
Є кілька підходів, як цього досягти. Про конкретні стратегії я розповім у наступних розділах, наприклад коли говоритимемо про Docker і контейнери.
Головне обмеження, яке слід врахувати: має бути **єдиний** компонент, що керує **портом** на **публічній IP-адресі**. А потім він має мати спосіб **передавати** комунікацію до реплікованих **процесів/workers**.
Ось кілька можливих комбінацій і стратегій:
* **Uvicorn** з `--workers`
* Один **менеджер процесів** Uvicorn слухатиме **IP** і **порт** та запускатиме **кілька worker-процесів** Uvicorn.
* **Kubernetes** та інші розподілені **контейнерні системи**
* Щось на рівні **Kubernetes** слухатиме **IP** і **порт**. Реплікація відбуватиметься через **кілька контейнерів**, у кожному з яких запущено **один процес Uvicorn**.
* **Хмарні сервіси**, які роблять це за вас
* Хмарний сервіс, імовірно, **виконає реплікацію за вас**. Він може дозволити вам визначити **процес для запуску** або **образ контейнера**. У будь-якому разі, найімовірніше, це буде **один процес Uvicorn**, а хмарний сервіс відповідатиме за його реплікацію.
/// tip | Порада
Не хвилюйтеся, якщо деякі пункти про **контейнери**, Docker або Kubernetes поки що не дуже зрозумілі.
Докладніше про образи контейнерів, Docker, Kubernetes тощо буде в майбутньому розділі: [FastAPI у контейнерах — Docker](docker.md){.internal-link target=_blank}.
///
## Попередні кроки перед запуском { #previous-steps-before-starting }
Є багато випадків, коли ви хочете виконати певні кроки **перед запуском** застосунку.
Наприклад, ви можете захотіти виконати **міграції бази даних**.
Але в більшості випадків ці кроки потрібно виконувати лише **один раз**.
Тож вам потрібен **єдиний процес**, який виконає ці **попередні кроки** перед запуском застосунку.
І вам доведеться гарантувати, що це справді один процес, який виконує попередні кроки, *навіть якщо* після цього ви запускаєте **кілька процесів** (кілька workers) для самого застосунку. Якби ці кроки виконували **кілька процесів**, вони **дублювали** б роботу, запускаючи її **паралельно**. А якщо ці кроки делікатні, як-от міграція бази даних, це може спричинити конфлікти між ними.
Звісно, є випадки, коли багаторазове виконання попередніх кроків не є проблемою — тоді це значно простіше.
/// 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
* Запуск під час старту
* Перезапуски
* Реплікація (кількість запущених процесів)
* Пам’ять
* Попередні кроки перед запуском
Розуміння цих ідей і вміння застосовувати їх має дати вам потрібну інтуїцію для ухвалення рішень під час налаштування й тонкого підкручування ваших розгортань. 🤓
У наступних розділах я наведу більш конкретні приклади можливих стратегій, яких ви можете дотримуватися. 🚀

View File

@@ -0,0 +1,620 @@
# 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.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
# If running behind a proxy like Nginx or Traefik add --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 typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
### Dockerfile { #dockerfile }
Тепер у тій самій директорії проєкту створіть файл `Dockerfile` з таким вмістом:
```{ .dockerfile .annotate }
# (1)!
FROM python:3.9
# (2)!
WORKDIR /code
# (3)!
COPY ./requirements.txt /code/requirements.txt
# (4)!
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (5)!
COPY ./app /code/app
# (6)!
CMD ["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 cache**, цей крок також **використає Docker cache**, коли він доступний.
Використання кешу на цьому кроці **заощадить** вам багато **часу** під час повторного збирання образу знову і знову в процесі розробки — замість **завантаження та встановлення** всіх залежностей **кожного разу**.
5. Скопіюйте директорію `./app` всередину директорії `/code`.
Оскільки тут міститься весь код, який **змінюється найчастіше**, **Docker cache** для цього або будь-яких **наступних кроків** не використовуватиметься легко.
Тому важливо розмістити це **ближче до кінця** `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
# ✅ Do this
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
```
⛔️ **Shell** form:
```Dockerfile
# ⛔️ Don't do this
CMD fastapi run app/main.py --port 80
```
Завжди використовуйте **exec** form, щоб гарантувати коректне завершення роботи FastAPI та запуск [lifespan events](../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">Why do my services take 10 seconds to recreate or stop?</a>.
#### Структура директорій { #directory-structure }
Тепер у вас має бути така структура директорій:
```
.
├── app
│   ├── __init__.py
│ └── main.py
├── Dockerfile
└── requirements.txt
```
#### За TLS termination proxy { #behind-a-tls-termination-proxy }
Якщо ви запускаєте контейнер за TLS Termination Proxy (балансувальником навантаження), таким як Nginx або Traefik, додайте опцію `--proxy-headers`. Вона скаже Uvicorn (через FastAPI CLI) довіряти заголовкам, які надсилає цей проксі, і які повідомляють, що застосунок працює за HTTPS тощо.
```Dockerfile
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]
```
#### Docker cache { #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>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
## Альтернативна документація 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>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Зібрати 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.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (1)!
COPY ./main.py /code/
# (2)!
CMD ["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 }
Якщо у вас є <abbr title="A group of machines that are configured to be connected and work together in some way.">cluster</abbr> машин із **Kubernetes**, Docker Swarm Mode, Nomad або іншою подібною складною системою керування розподіленими контейнерами на багатьох машинах, тоді вам, імовірно, варто **керувати реплікацією** на **рівні кластера**, замість використання **менеджера процесів** (як-от Uvicorn із workers) у кожному контейнері.
Одна з таких систем керування розподіленими контейнерами, як Kubernetes, зазвичай має вбудований спосіб обробки **реплікації контейнерів** із підтримкою **балансування навантаження** для вхідних запитів — усе на **рівні кластера**.
У таких випадках вам, імовірно, варто зібрати **Docker-образ з нуля**, як [пояснено вище](#dockerfile): встановити залежності та запускати **один процес Uvicorn**, замість кількох Uvicorn workers.
### Балансувальник навантаження { #load-balancer }
Під час роботи з контейнерами зазвичай є компонент, який **слухає основний порт**. Це може бути інший контейнер, який також є **TLS Termination Proxy** для обробки **HTTPS**, або подібний інструмент.
Оскільки цей компонент приймає **навантаження** запитів і розподіляє його між воркерами більш-менш **збалансовано**, його також часто називають **Load Balancer**.
/// tip | Порада
Той самий компонент **TLS Termination Proxy**, що використовується для HTTPS, імовірно, також буде **Load Balancer**.
///
А під час роботи з контейнерами та система, яку ви використовуєте для запуску й керування ними, вже матиме внутрішні інструменти для передачі **мережевої взаємодії** (наприклад, HTTP-запитів) від цього **load balancer** (який також може бути **TLS Termination Proxy**) до контейнера(ів) із вашим застосунком.
### Один балансувальник — кілька worker-контейнерів { #one-load-balancer-multiple-worker-containers }
Під час роботи з **Kubernetes** або подібними розподіленими системами керування контейнерами їхні внутрішні мережеві механізми дозволяють одному **load balancer**, який слухає основний **порт**, передавати взаємодію (запити) до потенційно **кількох контейнерів** із вашим застосунком.
Кожен із цих контейнерів зазвичай матиме **лише один процес** (наприклад, процес Uvicorn, який запускає ваш застосунок FastAPI). Усі вони будуть **ідентичними контейнерами**, запускатимуть те саме, але кожен матиме свій процес, пам’ять тощо. Так ви використаєте **паралелізацію** на **різних ядрах** CPU або навіть на **різних машинах**.
І розподілена контейнерна система з **load balancer** буде **розподіляти запити** до кожного з контейнерів із вашим застосунком **по черзі**. Тож кожен запит може бути оброблений одним із кількох **реплікованих контейнерів**, що запускають ваш застосунок.
І зазвичай цей **load balancer** зможе обробляти запити, які спрямовані до *інших* застосунків у вашому кластері (наприклад, на інший домен або під іншим префіксом шляху URL), і передаватиме взаємодію до правильних контейнерів для *того іншого* застосунку, що працює в кластері.
### Один процес на контейнер { #one-process-per-container }
У такому сценарії, імовірно, варто мати **один (Uvicorn) процес на контейнер**, адже реплікацію ви вже обробляєте на рівні кластера.
Отже, у цьому випадку вам **не** потрібно запускати кілька workers у контейнері, наприклад через опцію командного рядка `--workers`. Вам потрібен лише **один процес Uvicorn** на контейнер (але, ймовірно, багато контейнерів).
Додавати всередину контейнера ще один менеджер процесів (як це було б із кількома workers) — це зайва **непотрібна складність**, яку, скоріш за все, вже вирішує ваша кластерна система.
### Контейнери з кількома процесами та особливі випадки { #containers-with-multiple-processes-and-special-cases }
Звісно, є **особливі випадки**, коли вам може знадобитися **контейнер** із кількома **процесами воркерів Uvicorn** всередині.
У таких випадках ви можете використати опцію командного рядка `--workers`, щоб задати кількість воркерів, яких хочете запустити:
```{ .dockerfile .annotate }
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
# (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**). Так вона зможе **реплікувати контейнери** на **доступних машинах**, враховуючи потрібний обсяг пам’яті та доступний обсяг у машинах кластера.
Якщо ваш застосунок **простий**, це, імовірно, **не буде проблемою**, і вам може не знадобитися задавати жорсткі ліміти пам’яті. Але якщо ви **використовуєте багато пам’яті** (наприклад, з моделями **machine learning**), варто перевірити, скільки пам’яті ви споживаєте, і скоригувати **кількість контейнерів**, які запускаються **на кожній машині** (і, можливо, додати більше машин у кластер).
Якщо ви запускаєте **кілька процесів на контейнер**, вам потрібно переконатися, що кількість запущених процесів не **споживає більше пам’яті**, ніж доступно.
## Попередні кроки перед запуском і контейнери { #previous-steps-before-starting-and-containers }
Якщо ви використовуєте контейнери (наприклад Docker, Kubernetes), є два основні підходи, які ви можете застосувати.
### Кілька контейнерів { #multiple-containers }
Якщо у вас **кілька контейнерів**, імовірно, кожен запускає **один процес** (наприклад у кластері **Kubernetes**), тоді вам, імовірно, варто мати **окремий контейнер**, який виконує **попередні кроки** як один контейнер з одним процесом **перед** запуском реплікованих worker-контейнерів.
/// 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>. Але тепер він застарілий (deprecated). ⛔️
Імовірно, вам **не** варто використовувати цей базовий Docker-образ (або будь-який інший подібний).
Якщо ви використовуєте **Kubernetes** (або інші) і вже задаєте **реплікацію** на рівні кластера з кількома **контейнерами**, то в таких випадках краще **зібрати образ з нуля**, як описано вище: [Зібрати Docker-образ для FastAPI](#build-a-docker-image-for-fastapi).
А якщо вам потрібно кілька workers, ви можете просто використати опцію командного рядка `--workers`.
/// note | Технічні деталі
Docker-образ було створено тоді, коли Uvicorn не підтримував керування та перезапуск «мертвих» worker-процесів, тож потрібно було використовувати Gunicorn разом із Uvicorn. Це додавало чимало складності лише для того, щоб Gunicorn керував і перезапускав worker-процеси 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">uv Docker guide</a>.
## Підсумок { #recap }
Використовуючи контейнерні системи (наприклад, **Docker** і **Kubernetes**), стає доволі просто реалізувати всі **концепції розгортання**:
* HTTPS
* Запуск під час старту
* Перезапуски
* Реплікація (кількість запущених процесів)
* Пам’ять
* Попередні кроки перед запуском
У більшості випадків вам, імовірно, не варто використовувати будь-який базовий образ, а натомість **збирати образ контейнера з нуля** на основі офіційного Docker-образу Python.
Дбаючи про **порядок** інструкцій у `Dockerfile` та **Docker cache**, ви можете **мінімізувати час збирання**, щоб підвищити продуктивність (і уникати нудьги). 😎

View 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 з мінімальними зусиллями.
Він переносить той самий **developer experience** створення застосунків із FastAPI на їх **розгортання** в хмарі. 🎉
Також він подбає про більшість речей, які зазвичай потрібні під час розгортання застосунку, зокрема:
* HTTPS
* Реплікація з autoscaling на основі кількості запитів
* тощо
FastAPI Cloud — основний спонсор і джерело фінансування open source проєктів *FastAPI and friends*. ✨
## Розгортання в інших хмарних провайдерах { #deploy-to-other-cloud-providers }
FastAPI має відкритий вихідний код і базується на стандартах. Ви можете розгортати застосунки FastAPI в будь-якого хмарного провайдера на ваш вибір.
Дотримуйтеся інструкцій вашого хмарного провайдера, щоб розгорнути в них застосунки FastAPI. 🤓
## Розгортання на власному сервері { #deploy-your-own-server }
Пізніше в цьому посібнику з **Deployment** я також навчу вас усіх деталей, щоб ви розуміли, що відбувається, що потрібно зробити, і як розгортати застосунки FastAPI самостійно, зокрема на власних серверах. 🤓

View 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, **до того як стане відомо**, з яким доменом мають справу.
* **За замовчуванням** це означало б, що ви можете мати лише **один HTTPS-сертифікат на одну IP-адресу**.
* Неважливо, наскільки великий ваш сервер або наскільки малими є окремі застосунки на ньому.
* Однак для цього є **рішення**.
* Існує **розширення** протоколу **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">TLS Termination Proxy</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-сервері (можливо, у того ж хмарного провайдера).
Ймовірно, ви отримаєте хмарний сервер (віртуальну машину) або щось подібне, і він матиме <abbr title="That doesn't change - Це не змінюється">fixed</abbr> **публічну 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 handshake { #tls-handshake-start }
Далі браузер зв’яжеться з цією IP-адресою через **порт 443** (порт HTTPS).
Перша частина взаємодії — це встановити з’єднання між клієнтом і сервером та узгодити криптографічні ключі тощо.
<img src="/img/deployment/https/https02.drawio.svg">
Ця взаємодія між клієнтом і сервером для встановлення TLS-з’єднання називається **TLS handshake**.
### 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 Handshake** завершується.
Після цього клієнт і сервер мають **зашифроване 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-сертифікати безпосередньо в application server (наприклад, Uvicorn).
## Проксійовані forwarded headers { #proxy-forwarded-headers }
Коли ви використовуєте проксі для обробки HTTPS, ваш **application server** (наприклад Uvicorn через FastAPI CLI) нічого не знає про процес HTTPS і спілкується звичайним HTTP із **TLS Termination Proxy**.
Цей **proxy** зазвичай «на льоту» встановлює деякі HTTP-заголовки перед тим, як передати запит до **application server**, щоб дати application server знати, що запит **проксійовано** через проксі.
/// 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>
///
Попри це, оскільки **application server** не знає, що він стоїть за довіреним **proxy**, за замовчуванням він не довірятиме цим заголовкам.
Але ви можете налаштувати **application server** так, щоб він довіряв заголовкам *forwarded*, які надсилає **proxy**. Якщо ви використовуєте FastAPI CLI, ви можете використати *CLI Option* `--forwarded-allow-ips`, щоб вказати, з яких IP він має довіряти цим заголовкам *forwarded*.
Наприклад, якщо **application server** отримує запити лише від довіреного **proxy**, ви можете встановити `--forwarded-allow-ips="*"`, щоб довіряти всім вхідним IP, адже він отримуватиме запити лише з IP, який використовує **proxy**.
Так застосунок зможе знати свою власну публічну URL-адресу, чи використовує він HTTPS, домен тощо.
Це буде корисно, наприклад, щоб коректно обробляти перенаправлення.
/// tip | Порада
Докладніше дивіться в документації: [За проксі — увімкнення Proxy Forwarded Headers](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank}
///
## Підсумок { #recap }
Наявність **HTTPS** дуже важлива і в більшості випадків є **критичною**. Переважно зусилля, які вам як розробнику потрібно докласти щодо HTTPS, зводяться до **розуміння цих концепцій** і того, як вони працюють.
Але щойно ви знаєте базову інформацію про **HTTPS для розробників**, ви можете легко поєднувати й налаштовувати різні інструменти, щоб керувати всім у простий спосіб.
У наступних розділах я покажу кілька конкретних прикладів, як налаштувати **HTTPS** для застосунків **FastAPI**. 🔒

View File

@@ -0,0 +1,23 @@
# Розгортання { #deployment }
Розгортання застосунку **FastAPI** є відносно простим.
## Що означає розгортання { #what-does-deployment-mean }
**Розгорнути** застосунок означає виконати необхідні кроки, щоб зробити його **доступним для користувачів**.
Для **web API** це зазвичай передбачає розміщення його на **віддаленій машині** з **серверною програмою**, яка забезпечує хорошу продуктивність, стабільність тощо, щоб ваші **користувачі** могли ефективно **отримувати доступ** до застосунку без перерв або проблем.
Це відрізняється від етапів **розробки**, коли ви постійно змінюєте код, ламаєте його й виправляєте, зупиняєте та перезапускаєте сервер розробки тощо.
## Стратегії розгортання { #deployment-strategies }
Є кілька способів зробити це залежно від вашого конкретного сценарію використання та інструментів, які ви застосовуєте.
Ви можете **розгорнути сервер** самостійно, використовуючи комбінацію інструментів, можете скористатися **хмарним сервісом**, який виконує частину роботи за вас, або іншими можливими варіантами.
Наприклад, ми, команда FastAPI, створили <a href="https://fastapicloud.com" class="external-link" target="_blank">**FastAPI Cloud**</a>, щоб зробити розгортання застосунків FastAPI у хмарі максимально зручним і «streamlined», з таким самим developer experience, як і під час роботи з FastAPI.
Я покажу вам деякі основні концепції, які вам, імовірно, варто мати на увазі під час розгортання застосунку **FastAPI** (хоча більшість із цього стосується будь-якого іншого типу вебзастосунку).
У наступних розділах ви побачите більше деталей, які варто врахувати, і деякі техніки, як це робити. ✨

View 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 web framework-ів і серверів під назвою <abbr title="Asynchronous Server Gateway Interface - Асинхронний інтерфейс шлюзу сервера">ASGI</abbr>. FastAPI — це ASGI web framework.
Головне, що вам потрібно, щоб запустити застосунок **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 — легковагове та універсальне середовище виконання web-застосунків.
## Серверна машина та серверна програма { #server-machine-and-server-program }
Є невеликий нюанс із назвами, який варто пам’ятати. 💡
Слово «**server**» часто використовують і для віддаленого/хмарного комп’ютера (фізичної або віртуальної машини), і для програми, що працює на цій машині (наприклад, Uvicorn).
Просто майте на увазі: коли загалом читаєте «server», це може означати одну з цих двох речей.
Коли йдеться про віддалену машину, її часто називають **server**, а також **machine**, **VM** (virtual machine), **node**. Усе це означає певний тип віддаленої машини, зазвичай під Linux, на якій ви запускаєте програми.
## Встановіть серверну програму { #install-the-server-program }
Коли ви встановлюєте FastAPI, разом із ним встановлюється production server — 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-сервер вручну, зазвичай потрібно передати рядок імпорту (import string) у спеціальному форматі, щоб він імпортував ваш 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 «module»).
* `app`: об’єкт, створений всередині `main.py` рядком `app = FastAPI()`.
Це еквівалентно:
```Python
from main import app
```
///
Кожна альтернативна програма ASGI-сервера матиме подібну команду, докладніше дивіться у відповідній документації.
/// warning | Попередження
Uvicorn та інші сервери підтримують опцію `--reload`, яка корисна під час розробки.
Опція `--reload` споживає значно більше ресурсів, є менш стабільною тощо.
Вона дуже допомагає під час **розробки**, але **не слід** використовувати її в **production**.
///
## Концепції деплою { #deployment-concepts }
Ці приклади запускають серверну програму (наприклад, Uvicorn), стартуючи **один процес**, що слухає всі IP-адреси (`0.0.0.0`) на наперед визначеному порту (наприклад, `80`).
Це базова ідея. Але, ймовірно, вам також потрібно буде подбати про додаткові речі, як-от:
* Безпека — HTTPS
* Запуск при старті системи
* Перезапуски
* Реплікація (кількість запущених процесів)
* Пам’ять
* Попередні кроки перед запуском
У наступних розділах я розповім більше про кожне з цих понять: як про них думати та наведу конкретні приклади зі стратегіями, як із цим працювати. 🚀

View 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 }
Тут ви побачили, як використовувати кілька **воркерів**, щоб **паралелізувати** виконання застосунку, використати переваги **кількох ядер** CPU та мати змогу обслуговувати **більше запитів**.
Із наведеного вище списку концепцій розгортання використання воркерів здебільшого допоможе з частиною **реплікації**, і трохи — з **перезапусками**, але про решту все одно потрібно подбати:
* **Безпека — HTTPS**
* **Запуск під час старту системи**
* ***Перезапуски***
* Реплікація (кількість запущених процесів)
* **Пам’ять**
* **Попередні кроки перед запуском**
## Контейнери та Docker { #containers-and-docker }
У наступному розділі про [FastAPI у контейнерах — Docker](docker.md){.internal-link target=_blank} я поясню деякі стратегії, які ви можете використати, щоб опрацювати інші **концепції розгортання**.
Я покажу вам, як **зібрати власний image з нуля**, щоб запускати один процес Uvicorn. Це простий процес і, ймовірно, саме те, що вам потрібно при використанні розподіленої системи керування контейнерами на кшталт **Kubernetes**.
## Підсумок { #recap }
Ви можете використовувати кілька воркер-процесів за допомогою CLI-опції `--workers` з командами `fastapi` або `uvicorn`, щоб скористатися перевагами **багатоядерних CPU** та запускати **кілька процесів паралельно**.
Ви можете застосувати ці інструменти та ідеї, якщо налаштовуєте **власну систему розгортання**, водночас самостійно дбаючи про інші концепції розгортання.
Перегляньте наступний розділ, щоб дізнатися про **FastAPI** з контейнерами (наприклад, Docker і Kubernetes). Ви побачите, що ці інструменти також мають прості способи розв’язати інші **концепції розгортання**. ✨

View File

@@ -0,0 +1,93 @@
# Про версії FastAPI { #about-fastapi-versions }
**FastAPI** вже використовується в продакшені в багатьох застосунках і системах. А покриття тестами підтримується на рівні 100%. Водночас розробка все ще рухається швидко.
Нові можливості додаються часто, баги виправляються регулярно, а код і надалі безперервно поліпшується.
Саме тому поточні версії все ще `0.x.x` — це відображає те, що кожна версія потенційно може містити несумісні зміни. Це відповідає правилам <a href="https://semver.org/" class="external-link" target="_blank">Semantic Versioning</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 }
Згідно з правилами Semantic Versioning, будь-яка версія нижче `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
```

View File

@@ -0,0 +1,298 @@
# Змінні середовища { #environment-variables }
/// tip | Порада
Якщо ви вже знаєте, що таке «змінні середовища», і як ними користуватися, можете пропустити цей розділ.
///
Змінна середовища (також відома як «**env var**») — це змінна, що існує **поза** Python-кодом, в **операційній системі**, і може бути прочитана вашим Python-кодом (або також іншими програмами).
Змінні середовища можуть бути корисними для керування **налаштуваннями** застосунку, як частина **встановлення** Python тощо.
## Створення і використання env var { #create-and-use-env-vars }
Ви можете **створювати** та використовувати змінні середовища в **оболонці (терміналі)**, без потреби в Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// You could create an env var MY_NAME with
$ export MY_NAME="Wade Wilson"
// Then you could use it with other programs, like
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Create an env var MY_NAME
$ $Env:MY_NAME = "Wade Wilson"
// Use it with other programs, like
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
## Читання env var у 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
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ export MY_NAME="Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ $Env:MY_NAME = "Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
Оскільки змінні середовища можна задавати поза кодом, але їх може читати код, і їх не потрібно зберігати (комітити в `git`) разом з іншими файлами, їх часто використовують для конфігурації або **налаштувань**.
Ви також можете створити змінну середовища лише для **конкретного запуску програми** — вона буде доступна тільки цій програмі й лише на час її виконання.
Для цього задайте її прямо перед запуском програми, в тому самому рядку:
<div class="termy">
```console
// Create an env var MY_NAME in line for this program call
$ MY_NAME="Wade Wilson" python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
// The env var no longer exists afterwards
$ python main.py
Hello World from Python
```
</div>
/// tip | Порада
Докладніше про це можна прочитати тут: <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>.
///
## Типи та валідація { #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">Wikipedia про Environment Variable</a>.
У багатьох випадках не дуже очевидно, як змінні середовища можуть бути корисними й застосовними одразу. Але під час розробки вони з’являються в багатьох різних сценаріях, тож корисно знати про них.
Наприклад, вам знадобиться ця інформація в наступному розділі про [Віртуальні середовища](virtual-environments.md).

View 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 }
Ви можете «watch» 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 Expert](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: позначити коментар як **answer**.
* у GitHub Issues: **закрити** issue.
## Стежити за репозиторієм GitHub { #watch-the-github-repository }
Ви можете «watch» 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, наприклад щоб:
* Поставити **питання** або запитати про **проблему**.
* Запропонувати нову **функцію**.
**Примітка**: якщо ви це зробите, я також попрошу вас допомагати іншим. 😉
## Рецензувати Pull Requests { #review-pull-requests }
Ви можете допомогти мені рецензувати pull requests від інших.
Знову ж, будь ласка, намагайтеся бути доброзичливими. 🤗
---
Ось що варто пам’ятати і як рецензувати pull request:
### Зрозуміти проблему { #understand-the-problem }
* Спочатку переконайтеся, що ви **розумієте проблему**, яку pull request намагається розв’язати. Можливо, є довше обговорення в GitHub Discussion або issue.
* Також цілком можливо, що pull request насправді не потрібен, бо проблему можна розв’язати **іншим способом**. Тоді ви можете запропонувати або запитати про це.
### Не турбуйтеся про стиль { #dont-worry-about-style }
* Не хвилюйтеся надто щодо таких речей, як стиль повідомлень комітів — я зроблю squash і merge, відредагувавши коміт вручну.
* Також не турбуйтеся про правила стилю — уже є автоматизовані інструменти, що це перевіряють.
А якщо потрібні ще якісь зміни стилю або узгодженості, я попрошу про це напряму або додам коміти поверх із потрібними змінами.
### Перевірити код { #check-the-code }
* Перевірте й прочитайте код: чи він має сенс, **запустіть його локально** і подивіться, чи він справді розв’язує проблему.
* Потім залиште **коментар**, що ви це зробили — так я знатиму, що ви справді це перевірили.
/// info | Інформація
На жаль, я не можу просто довіряти PR, у яких є лише кілька схвалень.
Кілька разів траплялося, що є PR із 3, 5 або більше схваленнями, ймовірно тому, що опис виглядає привабливо, але коли я перевіряю PR, вони насправді зламані, містять баг або не розв’язують проблему, яку заявляють. 😅
Тому дуже важливо, щоб ви справді прочитали й запустили код і повідомили мені в коментарях, що ви це зробили. 🤓
///
* Якщо PR можна якось спростити, ви можете про це попросити, але не варто бути надто прискіпливими: може бути багато суб’єктивних поглядів (і в мене теж 🙈), тож краще зосередитися на основному.
### Тести { #tests }
* Допоможіть мені перевірити, що PR має **тести**.
* Перевірте, що тести **падають** до PR. 🚨
* Потім перевірте, що тести **проходять** після PR. ✅
* У багатьох PR немає тестів — ви можете **нагадати** додати тести або навіть **запропонувати** кілька тестів самі. Це одна з речей, що забирає найбільше часу, і тут ви можете дуже допомогти.
* Також прокоментуйте, що саме ви перевірили — так я знатиму, що ви це перевірили. 🤓
## Створити Pull Request { #create-a-pull-request }
Ви можете [зробити внесок](contributing.md){.internal-link target=_blank} у вихідний код через Pull Requests, наприклад:
* Виправити друкарську помилку, яку ви знайшли в документації.
* Поділитися статтею, відео або подкастом, який ви створили або знайшли про 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} вашою мовою.
* Ви також можете допомогти рецензувати переклади, створені іншими.
* Запропонувати нові розділи документації.
* Виправити наявний issue/баг.
* Обов’язково додайте тести.
* Додати нову функцію.
* Обов’язково додайте тести.
* Обов’язково додайте документацію, якщо це доречно.
## Допомагати підтримувати FastAPI { #help-maintain-fastapi }
Допоможіть мені підтримувати **FastAPI**! 🤓
Роботи дуже багато, і більшість із неї можете зробити **ВИ**.
Основні завдання, які ви можете виконувати вже зараз:
* [Допомагати іншим із питаннями в GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (див. розділ вище).
* [Рецензувати Pull Requests](#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 Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank} там значно вищий.
Використовуйте чат лише для інших загальних розмов.
///
### Не використовуйте чат для питань { #dont-use-the-chat-for-questions }
Майте на увазі, що оскільки чати дозволяють більш «вільну розмову», легко ставити питання, які надто загальні й складніші для відповіді — тож ви можете не отримати відповіді.
У GitHub шаблон допоможе вам сформулювати правильне питання так, щоб ви могли легше отримати хорошу відповідь або навіть розв’язати проблему самостійно ще до того, як запитаєте. А в GitHub я можу гарантувати, що завжди відповім на все, навіть якщо це займе деякий час. Я не можу особисто робити це в чат-системах. 😅
Розмови в чат-системах також не так легко шукати, як у GitHub, тож питання й відповіді можуть загубитися в потоці. І лише ті, що в GitHub, враховуються, щоб стати [FastAPI Expert](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>. Залежно від рівня, ви можете отримати додаткові переваги, наприклад бейдж у документації. 🎁
---
Дякую! 🚀

View 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 зі складними вимогами (Machine Learning, розподілені системи, асинхронні задачі, NoSQL бази даних тощо), керуючи кількома командами розробників.
У межах цього мені потрібно було дослідити, протестувати й використовувати багато альтернатив.
Історія **FastAPI** значною мірою — це історія його попередників.
Як сказано в розділі [Альтернативи](alternatives.md){.internal-link target=_blank}:
<blockquote markdown="1">
**FastAPI** не існував би без попередньої роботи інших людей.
До цього було створено багато інструментів, які допомогли надихнути його появу.
Я уникав створення нового framework протягом кількох років. Спочатку я намагався реалізувати всі можливості, які охоплює **FastAPI**, використовуючи багато різних frameworks, plug-ins та інструментів.
Але в певний момент не залишилося іншого варіанту, окрім як створити щось, що надає всі ці можливості: взяти найкращі ідеї з попередніх інструментів і поєднати їх найкращим можливим способом, використовуючи можливості мови, яких раніше навіть не було (type hints у Python 3.6+).
</blockquote>
## Дослідження { #investigation }
Використовуючи всі попередні альтернативи, я мав можливість повчитися на кожній із них, взяти ідеї та поєднати їх найкращим способом, який я зміг знайти для себе та команд розробників, з якими працював.
Наприклад, було очевидно, що в ідеалі все має базуватися на стандартних Python type hints.
Також найкращим підходом було використання вже наявних стандартів.
Тож ще до того, як почати писати код **FastAPI**, я витратив кілька місяців на вивчення специфікацій OpenAPI, JSON Schema, 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 Schema, підтримати різні способи визначення декларацій обмежень і покращити підтримку редакторів (перевірки типів, автодоповнення) на основі тестів у кількох редакторах.
Під час розробки я також зробив внесок у <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} буде дуже цінною.

View 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_py39.py hl[9:13] *}
/// tip | Порада
Зверніть увагу: функція повертає екземпляр винятку, а не піднімає його. Підняття виконується в іншій частині внутрішнього коду.
///

View File

@@ -0,0 +1,56 @@
# Умовний OpenAPI { #conditional-openapi }
За потреби ви можете використати налаштування та змінні середовища, щоб умовно конфігурувати OpenAPI залежно від середовища, і навіть повністю його вимкнути.
## Про безпеку, API та документацію { #about-security-apis-and-docs }
Приховування інтерфейсів документації у production *не повинно* бути способом захисту вашого API.
Це не додає жодної додаткової безпеки для вашого API — *операції шляху* усе одно будуть доступні там, де вони є.
Якщо у вашому коді є вразливість безпеки, вона все одно існуватиме.
Приховування документації лише ускладнює розуміння того, як взаємодіяти з вашим API, і може ускладнити вам налагодження в production. Це можна розглядати просто як форму <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">безпеки через неочевидність</a>.
Якщо ви хочете захистити свій API, є кілька кращих речей, які ви можете зробити, наприклад:
* Переконайтеся, що у вас є чітко визначені моделі Pydantic для тіл запитів і відповідей.
* Налаштуйте всі необхідні дозволи та ролі за допомогою залежностей.
* Ніколи не зберігайте паролі у відкритому вигляді, лише хеші паролів.
* Реалізуйте та використовуйте добре відомі криптографічні інструменти, як-от pwdlib і JWT tokens тощо.
* Додайте більш деталізований контроль доступу за допомогою OAuth2 scopes там, де це потрібно.
* ...тощо.
Втім, у вас може бути дуже специфічний випадок використання, коли вам справді потрібно вимкнути документацію API для певного середовища (наприклад, для production) або залежно від конфігурацій зі змінних середовища.
## Умовний OpenAPI з налаштувань і змінних середовища { #conditional-openapi-from-settings-and-env-vars }
Ви можете легко використати ті самі налаштування Pydantic, щоб налаштувати згенерований OpenAPI та UI документації.
Наприклад:
{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *}
Тут ми оголошуємо налаштування `openapi_url` з тим самим значенням за замовчуванням `"/openapi.json"`.
А потім використовуємо його під час створення застосунку `FastAPI`.
Далі ви можете вимкнути OpenAPI (включно з UI документації), встановивши змінну середовища `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"
}
```

View 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_py39.py hl[3] *}
...і тоді Swagger UI більше не показуватиме підсвічування синтаксису:
<img src="/img/tutorial/extending-openapi/image03.png">
## Зміна теми { #change-the-theme }
Так само ви можете встановити тему підсвічування синтаксису ключем `"syntaxHighlight.theme"` (зверніть увагу, що посередині є крапка):
{* ../../docs_src/configure_swagger_ui/tutorial002_py39.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_py39.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.

View File

@@ -0,0 +1,185 @@
# Власні статичні ресурси для UI документації (самостійне хостингування) { #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_py39.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_py39.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_py39.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/`.
Ймовірно, ви можете натиснути правою кнопкою миші на кожне посилання та вибрати щось на кшталт «Save link as...».
**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_py39.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
/*! For license information please see redoc.standalone.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
...
```
Це підтверджує, що ви можете віддавати статичні файли зі свого застосунку і що ви розмістили статичні файли для документації в правильному місці.
Тепер ми можемо налаштувати застосунок так, щоб він використовував ці статичні файли для документації.
### Вимкнути автоматичну документацію для статичних файлів { #disable-the-automatic-docs-for-static-files }
Так само, як і при використанні власного CDN, перший крок — вимкнути автоматичну документацію, адже за замовчуванням вона використовує CDN.
Щоб вимкнути її, встановіть її URL у `None` під час створення застосунку `FastAPI`:
{* ../../docs_src/custom_docs_ui/tutorial002_py39.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_py39.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_py39.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 та взаємодіяти з ним.

View File

@@ -0,0 +1,109 @@
# Власні класи Request і APIRoute { #custom-request-and-apiroute-class }
У деяких випадках ви можете захотіти перевизначити логіку, яку використовують класи `Request` і `APIRoute`.
Зокрема, це може бути хорошою альтернативою логіці в middleware.
Наприклад, якщо ви хочете прочитати або змінити тіло запиту до того, як його обробить ваш застосунок.
/// danger | Обережно
Це «просунута» можливість.
Якщо ви лише починаєте працювати з **FastAPI**, вам, імовірно, варто пропустити цей розділ.
///
## Випадки використання { #use-cases }
Деякі випадки використання:
* Перетворення не-JSON тіл запитів на JSON (наприклад, <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>).
* Розпаковування gzip-стиснених тіл запитів.
* Автоматичне логування всіх тіл запитів.
## Обробка власних кодувань тіла запиту { #handling-custom-request-body-encodings }
Розгляньмо, як використати власний підклас `Request`, щоб розпаковувати gzip-запити.
А також підклас `APIRoute`, щоб використовувати цей власний клас запиту.
### Створіть власний клас `GzipRequest` { #create-a-custom-gziprequest-class }
/// tip | Порада
Це навчальний приклад, щоб продемонструвати, як це працює. Якщо вам потрібна підтримка Gzip, ви можете використати наданий [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}.
///
Спочатку створюємо клас `GzipRequest`, який перевизначить метод `Request.body()`, щоб розпаковувати тіло за наявності відповідного заголовка.
Якщо в заголовку немає `gzip`, він не намагатиметься розпаковувати тіло.
Так один і той самий клас маршруту може обробляти як 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` — це функція для «отримання» тіла запиту.
`dict` `scope` і функція `receive` обидва є частиною специфікації ASGI.
І саме ці дві речі — `scope` та `receive` — потрібні, щоб створити новий екземпляр `Request`.
Щоб дізнатися більше про `Request`, перегляньте <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">документацію Starlette про Requests</a>.
///
Єдина відмінність у роботі функції, яку повертає `GzipRequest.get_route_handler`, — це перетворення `Request` на `GzipRequest`.
Завдяки цьому наш `GzipRequest` подбає про розпаковування даних (за потреби) перед тим, як передати їх у наші *операції шляху*.
Після цього вся логіка обробки залишається тією самою.
Але через наші зміни в `GzipRequest.body` тіло запиту буде автоматично розпаковане, коли **FastAPI** завантажуватиме його за потреби.
## Доступ до тіла запиту в обробнику винятків { #accessing-the-request-body-in-an-exception-handler }
/// tip | Порада
Щоб розв’язати цю саму задачу, ймовірно, значно простіше використати `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` у router { #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] *}

View 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_py39.py hl[1,4,7:9] *}
### Згенеруйте схему OpenAPI { #generate-the-openapi-schema }
Потім використайте ту саму утилітарну функцію, щоб згенерувати схему OpenAPI всередині функції `custom_openapi()`:
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *}
### Змініть схему OpenAPI { #modify-the-openapi-schema }
Тепер ви можете додати розширення ReDoc, додавши власний `x-logo` до «об’єкта» `info` у схемі OpenAPI:
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *}
### Кешуйте схему OpenAPI { #cache-the-openapi-schema }
Ви можете використати властивість `.openapi_schema` як «кеш», щоб зберігати згенеровану схему.
Так ваш застосунок не муситиме генерувати схему щоразу, коли користувач відкриває вашу документацію API.
Її буде згенеровано лише один раз, а потім для наступних запитів використовуватиметься та сама закешована схема.
{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *}
### Перевизначте метод { #override-the-method }
Тепер ви можете замінити метод `.openapi()` вашою новою функцією.
{* ../../docs_src/extending_openapi/tutorial001_py39.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">

View 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 }
Щоб позначити *операцію шляху* як застарілу та показувати це в інтерфейсі документації, прочитайте документацію: [Підручник — Налаштування операцій шляху — Deprecation](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}.
## Перетворення будь-яких даних на JSON-сумісні { #convert-any-data-to-json-compatible }
Щоб перетворити будь-які дані на JSON-сумісні, прочитайте документацію: [Підручник — JSON Compatible Encoder](../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}.

View File

@@ -0,0 +1,60 @@
# GraphQL { #graphql }
Оскільки **FastAPI** базується на стандарті **ASGI**, дуже легко інтегрувати будь-яку бібліотеку **GraphQL**, яка також сумісна з ASGI.
Ви можете поєднувати звичайні FastAPI *операції шляху* з GraphQL в одному застосунку.
/// tip | Порада
**GraphQL** розв’язує деякі дуже специфічні випадки використання.
Він має **переваги** та **недоліки** порівняно зі звичними **web 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_py39.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>.
Ви також можете прочитати більше про кожну з бібліотек, описаних вище, за наведеними посиланнями.

View File

@@ -0,0 +1,13 @@
# Як зробити — Рецепти { #how-to-recipes }
Тут ви знайдете різні рецепти або інструкції «how to» для **кількох тем**.
Більшість цих ідей є більш-менш **незалежними**, і в більшості випадків вам потрібно буде вивчати їх лише тоді, коли вони безпосередньо стосуються **вашого проєкту**.
Якщо щось здається цікавим і корисним для вашого проєкту — перегляньте це, а інакше, ймовірно, можете просто пропустити.
/// tip | Порада
Якщо ви хочете **вивчати FastAPI** структуровано (рекомендовано), натомість читайте розділ [Підручник — Посібник користувача](../tutorial/index.md){.internal-link target=_blank} главу за главою.
///

View 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 групами, крок за кроком. 🚶

View File

@@ -0,0 +1,102 @@
# Окремі схеми OpenAPI для введення та виведення чи ні { #separate-openapi-schemas-for-input-and-output-or-not }
Починаючи з виходу **Pydantic v2**, згенерований OpenAPI став трохи точнішим і **правильнішим**, ніж раніше. 😎
Фактично, у деяких випадках для однієї й тієї ж моделі Pydantic в OpenAPI може бути навіть **дві JSON Schema** — для введення та для виведення — залежно від того, чи мають поля **значення за замовчуванням**.
Розгляньмо, як це працює і як це змінити, якщо вам потрібно.
## Моделі 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 Schema для моделі може відрізнятися залежно від того, чи використовується вона для **введення чи виведення**:
* для **введення** `description` **не є обов’язковим**
* для **виведення** воно **є обов’язковим** (і може бути `None`, або в термінах JSON — `null`)
### Модель для виведення в документації { #model-for-output-in-docs }
Ви також можете перевірити модель для виведення в документації: **обидва** поля `name` і `description` позначені як **обов’язкові** **червоною зірочкою**:
<div class="screenshot">
<img src="/img/tutorial/separate-openapi-schemas/image03.png">
</div>
### Моделі для введення та виведення в документації { #model-for-input-and-output-in-docs }
А якщо переглянути всі доступні Schemas (JSON Schemas) в 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>

View 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>. 😎

View File

@@ -0,0 +1,28 @@
# Full Stack FastAPI Template { #full-stack-fastapi-template }
Шаблони, хоча зазвичай постачаються з певною конфігурацією, створені так, щоб бути гнучкими та налаштовуваними. Це дає вам змогу змінювати й адаптувати їх під вимоги вашого проєкту, що робить їх чудовою стартовою точкою. 🏁
Ви можете використати цей шаблон для старту, адже він вже містить значну частину початкового налаштування, безпеку, базу даних і деякі API endpoint-и.
Репозиторій GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a>
## Full Stack FastAPI Template — технологічний стек і можливості { #full-stack-fastapi-template-technology-stack-and-features }
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/uk) для Python backend API.
- 🧰 [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, hooks, 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) для розробки та production.
- 🔒 Безпечне хешування паролів за замовчуванням.
- 🔑 JWT (JSON Web Token) автентифікація.
- 📫 Відновлення пароля через email.
- ✅ Тести з [Pytest](https://pytest.org).
- 📞 [Traefik](https://traefik.io) як reverse proxy / load balancer.
- 🚢 Інструкції з розгортання з використанням Docker Compose, включно з тим, як налаштувати фронтенд Traefik proxy для обробки автоматичних HTTPS сертифікатів.
- 🏭 CI (continuous integration) і CD (continuous deployment) на основі GitHub Actions.

View File

@@ -0,0 +1,3 @@
# Ресурси { #resources }
Додаткові ресурси, зовнішні посилання та інше. ✈️

View File

@@ -0,0 +1,504 @@
# Великі застосунки — кілька файлів { #bigger-applications-multiple-files }
Якщо ви створюєте застосунок або web API, рідко буває так, що все можна розмістити в одному файлі.
**FastAPI** надає зручний інструмент для структурування застосунку, зберігаючи всю гнучкість.
/// info | Інформація
Якщо ви переходите з Flask, це буде еквівалентом Flask Blueprints.
///
## Приклад структури файлів { #an-example-file-structure }
Припустімо, у вас є така структура файлів:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │ ├── __init__.py
│   │ ├── items.py
│   │ └── users.py
│   └── internal
│   ├── __init__.py
│   └── admin.py
```
/// tip | Порада
Є кілька файлів `__init__.py`: по одному в кожному каталозі або підкаталозі.
Саме це дозволяє імпортувати код з одного файлу в інший.
Наприклад, у `app/main.py` у вас може бути рядок на кшталт:
```
from app.routers import items
```
///
* Каталог `app` містить усе. І в ньому є порожній файл `app/__init__.py`, тому це «Python package» (колекція «Python modules»): `app`.
* Він містить файл `app/main.py`. Оскільки він знаходиться всередині Python package (каталог із файлом `__init__.py`), це «module» цього пакета: `app.main`.
* Є також файл `app/dependencies.py`; так само як `app/main.py`, це «module»: `app.dependencies`.
* Є підкаталог `app/routers/` з іншим файлом `__init__.py`, тож це «Python subpackage»: `app.routers`.
* Файл `app/routers/items.py` знаходиться всередині пакета `app/routers/`, отже це підмодуль: `app.routers.items`.
* Так само з `app/routers/users.py` — це інший підмодуль: `app.routers.users`.
* Є також підкаталог `app/internal/` з іншим файлом `__init__.py`, тож це ще один «Python subpackage»: `app.internal`.
* А файл `app/internal/admin.py` — ще один підмодуль: `app.internal.admin`.
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
Та сама структура файлів із коментарями:
```bash
.
├── app # "app" is a Python package
│   ├── __init__.py # this file makes "app" a "Python package"
│   ├── main.py # "main" module, e.g. import app.main
│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
│   └── routers # "routers" is a "Python subpackage"
│   │ ├── __init__.py # makes "routers" a "Python subpackage"
│   │ ├── items.py # "items" submodule, e.g. import app.routers.items
│   │ └── users.py # "users" submodule, e.g. import app.routers.users
│   └── internal # "internal" is a "Python subpackage"
│   ├── __init__.py # makes "internal" a "Python subpackage"
│   └── admin.py # "admin" submodule, e.g. import app.internal.admin
```
## `APIRouter` { #apirouter }
Припустімо, файл, призначений для обробки лише користувачів, — це підмодуль у `/app/routers/users.py`.
Ви хочете, щоб *операції шляху*, пов’язані з вашими користувачами, були відокремлені від решти коду, аби зберігати порядок.
Але це все одно частина того самого застосунку/web API **FastAPI** (тобто частина того самого «Python Package»).
Ви можете створити *операції шляху* для цього модуля за допомогою `APIRouter`.
### Імпорт `APIRouter` { #import-apirouter }
Ви імпортуєте його та створюєте «instance» так само, як і для класу `FastAPI`:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
### *Операції шляху* з `APIRouter` { #path-operations-with-apirouter }
А потім використовуєте його, щоб оголошувати ваші *операції шляху*.
Використовуйте так само, як використали б клас `FastAPI`:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
Ви можете сприймати `APIRouter` як клас «міні `FastAPI`».
Підтримуються ті самі опції.
Ті самі `parameters`, `responses`, `dependencies`, `tags` тощо.
/// tip | Порада
У цьому прикладі змінна називається `router`, але ви можете назвати її як завгодно.
///
Ми включимо цей `APIRouter` у головний застосунок `FastAPI`, але спершу перевіримо dependencies та інший `APIRouter`.
## Dependencies { #dependencies }
Ми бачимо, що нам знадобляться деякі dependencies, які використовуються в кількох місцях застосунку.
Тож ми поміщаємо їх в окремий модуль `dependencies` (`app/dependencies.py`).
Зараз ми використаємо просту dependency, щоб прочитати кастомний заголовок `X-Token`:
{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | Порада
Ми використовуємо вигаданий заголовок, щоб спростити цей приклад.
Але в реальних випадках ви отримаєте кращі результати, використовуючи вбудовані [утиліти Security](security/index.md){.internal-link target=_blank}.
///
## Інший модуль з `APIRouter` { #another-module-with-apirouter }
Припустімо, у вас також є endpoints, призначені для обробки «items» у вашому застосунку, в модулі `app/routers/items.py`.
У вас є *операції шляху* для:
* `/items/`
* `/items/{item_id}`
Структура та сама, що й у `app/routers/users.py`.
Але ми хочемо бути розумнішими й трохи спростити код.
Ми знаємо, що всі *операції шляху* в цьому модулі мають однакові:
* Path `prefix`: `/items`.
* `tags`: (лише один тег: `items`).
* Додаткові `responses`.
* `dependencies`: усім потрібна dependency `X-Token`, яку ми створили.
Тож замість того, щоб додавати все це до кожної *операції шляху*, ми можемо додати це до `APIRouter`.
{* ../../docs_src/bigger_applications/app_an_py39/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):
...
```
...prefix не повинен містити кінцевий `/`.
Отже, prefix у цьому випадку — `/items`.
Ми також можемо додати список `tags` і додаткові `responses`, які будуть застосовані до всіх *операцій шляху*, включених у цей router.
І ми можемо додати список `dependencies`, які будуть додані до всіх *операцій шляху* в router і будуть виконані/розв’язані для кожного запиту, зробленого до них.
/// tip | Порада
Зверніть увагу, що, так само як і у випадку [dependencies в *декораторах операцій шляху*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, жодне значення не буде передано у вашу *функцію операції шляху*.
///
Кінцевий результат: шляхи для items тепер:
* `/items/`
* `/items/{item_id}`
...як ми й планували.
* Вони будуть позначені списком тегів, що містить один рядок `"items"`.
* Ці «tags» особливо корисні для автоматичних інтерактивних систем документації (з використанням OpenAPI).
* Усі вони включатимуть наперед визначені `responses`.
* Усі ці *операції шляху* матимуть список `dependencies`, обчислений/виконаний перед ними.
* Якщо ви також оголошуєте dependencies у конкретній *операції шляху*, **їх також буде виконано**.
* Dependencies router виконуються першими, потім [`dependencies` у декораторі](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, а потім звичайні dependencies параметрів.
* Ви також можете додати [`Security` dependencies зі `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
/// tip | Порада
Наявність `dependencies` в `APIRouter` можна використати, наприклад, щоб вимагати автентифікацію для цілого набору *операцій шляху*, навіть якщо dependencies не додані окремо до кожної з них.
///
/// check
Параметри `prefix`, `tags`, `responses` і `dependencies` (як і в багатьох інших випадках) — це просто можливість **FastAPI**, яка допомагає уникати дублювання коду.
///
### Імпорт dependencies { #import-the-dependencies }
Цей код знаходиться в модулі `app.routers.items`, файлі `app/routers/items.py`.
І нам потрібно отримати функцію dependency з модуля `app.dependencies`, файлу `app/dependencies.py`.
Тож для dependencies ми використовуємо відносний імпорт з `..`:
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
#### Як працюють відносні імпорти { #how-relative-imports-work }
/// tip | Порада
Якщо ви досконало знаєте, як працюють імпорти, переходьте до наступного розділу нижче.
///
Одна крапка `.`, як у:
```Python
from .dependencies import get_token_header
```
означає:
* Починаємо в тому самому package, де знаходиться цей модуль (файл `app/routers/items.py`), тобто в каталозі `app/routers/`...
* знаходимо модуль `dependencies` (уявний файл `app/routers/dependencies.py`)...
* і з нього імпортуємо функцію `get_token_header`.
Але такого файлу не існує — наші dependencies знаходяться у файлі `app/dependencies.py`.
Згадайте, як виглядає структура нашого застосунку/файлів:
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
---
Дві крапки `..`, як у:
```Python
from ..dependencies import get_token_header
```
означають:
* Починаємо в тому самому package, де знаходиться цей модуль (файл `app/routers/items.py`), тобто в каталозі `app/routers/`...
* переходимо до батьківського package (каталог `app/`)...
* і там знаходимо модуль `dependencies` (файл `app/dependencies.py`)...
* і з нього імпортуємо функцію `get_token_header`.
Це працює правильно! 🎉
---
Так само, якби ми використали три крапки `...`, як у:
```Python
from ...dependencies import get_token_header
```
це означало б:
* Починаємо в тому самому package, де знаходиться цей модуль (файл `app/routers/items.py`), тобто в каталозі `app/routers/`)...
* переходимо до батьківського package (каталог `app/`)...
* потім переходимо до батьківського package для нього (а його немає, `app` — верхній рівень 😱)...
* і там знаходимо модуль `dependencies` (файл `app/dependencies.py`)...
* і з нього імпортуємо функцію `get_token_header`.
Це посилалося б на якийсь package над `app/`, зі своїм файлом `__init__.py` тощо. Але в нас такого немає. Тож у нашому прикладі це викличе помилку. 🚨
Але тепер ви знаєте, як це працює, тож можете використовувати відносні імпорти у власних застосунках незалежно від того, наскільки вони складні. 🤓
### Додайте власні `tags`, `responses` і `dependencies` { #add-some-custom-tags-responses-and-dependencies }
Ми не додаємо prefix `/items` ані `tags=["items"]` до кожної *операції шляху*, тому що додали їх до `APIRouter`.
Але ми все одно можемо додати _ще_ `tags`, які будуть застосовані до конкретної *операції шляху*, і також додати додаткові `responses`, специфічні для цієї *операції шляху*:
{* ../../docs_src/bigger_applications/app_an_py39/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](dependencies/global-dependencies.md){.internal-link target=_blank}, які будуть поєднані з dependencies для кожного `APIRouter`:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
### Імпорт `APIRouter` { #import-the-apirouter }
Тепер імпортуємо інші підмодулі, які мають `APIRouter`:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
Оскільки файли `app/routers/users.py` і `app/routers/items.py` є підмодулями, що належать до того самого Python package `app`, ми можемо використати одну крапку `.` для імпорту за допомогою «relative imports».
### Як працює імпорт { #how-the-importing-works }
Секція:
```Python
from .routers import items, users
```
означає:
* Починаємо в тому самому package, де знаходиться цей модуль (файл `app/main.py`), тобто в каталозі `app/`)...
* шукаємо subpackage `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 | Інформація
Перша версія — це «relative import»:
```Python
from .routers import items, users
```
Друга версія — це «absolute import»:
```Python
from app.routers import items, users
```
Щоб дізнатися більше про Python Packages і Modules, прочитайте <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">офіційну документацію Python про Modules</a>.
///
### Уникайте колізій імен { #avoid-name-collisions }
Ми імпортуємо підмодуль `items` безпосередньо, замість імпорту лише його змінної `router`.
Це тому, що в підмодулі `users` у нас також є змінна з назвою `router`.
Якби ми імпортували одну за одною, наприклад так:
```Python
from .routers.items import router
from .routers.users import router
```
то `router` з `users` перезаписав би `router` з `items`, і ми не змогли б використовувати їх одночасно.
Тож, щоб мати змогу використовувати обидва в одному файлі, ми імпортуємо підмодулі напряму:
{* ../../docs_src/bigger_applications/app_an_py39/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_py39/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_py39/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_py39/main.py hl[14:17] title["app/main.py"] *}
Таким чином оригінальний `APIRouter` залишиться незмінним, тож ми й надалі зможемо ділитися тим самим файлом `app/internal/admin.py` з іншими проєктами в організації.
Результат: у нашому застосунку кожна *операція шляху* з модуля `admin` матиме:
* Prefix `/admin`.
* Тег `admin`.
* Dependency `get_token_header`.
* Відповідь `418`. 🍵
Але це вплине лише на цей `APIRouter` у нашому застосунку, а не на будь-який інший код, який його використовує.
Тож, наприклад, інші проєкти можуть використати той самий `APIRouter` з іншим методом автентифікації.
### Додайте *операцію шляху* { #include-a-path-operation }
Ми також можемо додавати *операції шляху* безпосередньо до застосунку `FastAPI`.
Тут ми зробимо це... просто щоб показати, що можемо 🤷:
{* ../../docs_src/bigger_applications/app_an_py39/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, включно зі шляхами з усіх підмодулів, з правильними шляхами (і prefix) та правильними тегами:
<img src="/img/tutorial/bigger-applications/image01.png">
## Підключайте той самий router кілька разів з різними `prefix` { #include-the-same-router-multiple-times-with-different-prefix }
Ви також можете викликати `.include_router()` кілька разів для *того самого* router, використовуючи різні prefix.
Це може бути корисно, наприклад, щоб надавати той самий API під різними prefix, наприклад `/api/v1` і `/api/latest`.
Це просунуте використання, яке вам, можливо, не знадобиться, але воно доступне, якщо потрібно.
## Підключіть `APIRouter` в інший { #include-an-apirouter-in-another }
Так само, як ви можете підключити `APIRouter` у застосунок `FastAPI`, ви можете підключити `APIRouter` в інший `APIRouter`, використовуючи:
```Python
router.include_router(other_router)
```
Переконайтеся, що ви зробили це перед підключенням `router` у застосунок `FastAPI`, щоб *операції шляху* з `other_router` також були включені.

View File

@@ -0,0 +1,288 @@
# Класи як залежності { #classes-as-dependencies }
Перш ніж занурюватися глибше в систему **Dependency Injection**, покращімо попередній приклад.
## `dict` з попереднього прикладу { #a-dict-from-the-previous-example }
У попередньому прикладі ми повертали `dict` з нашої залежності («dependable»):
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
Але потім ми отримуємо `dict` у параметрі `commons` *функції операції шляху*.
І ми знаємо, що редактори не можуть надати багато підтримки (як-от автодоповнення) для `dict`, бо вони не знають його ключів і типів значень.
Можна зробити краще...
## Що робить щось залежністю { #what-makes-a-dependency }
Досі ви бачили залежності, оголошені як функції.
Але це не єдиний спосіб оголошувати залежності (хоча, ймовірно, найпоширеніший).
Ключовий фактор — залежність має бути «callable».
«**callable**» у Python — це будь-що, що Python може «викликати» як функцію.
Тобто, якщо у вас є об’єкт `something` (який може _не_ бути функцією) і ви можете «викликати» його (виконати) так:
```Python
something()
```
або
```Python
something(some_argument, some_keyword_argument="foo")
```
то це «callable».
## Класи як залежності { #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 також є **callable**.
Тоді у **FastAPI** ви можете використати клас Python як залежність.
Насправді FastAPI перевіряє, що це «callable» (функція, клас або щось інше) і які параметри визначені.
Якщо ви передасте «callable» як залежність у **FastAPI**, він проаналізує параметри цього «callable» і обробить їх так само, як параметри *функції операції шляху*. Включно з підзалежностями.
Це також стосується callables взагалі без параметрів. Так само, як і для *функцій операції шляху* без параметрів.
Тож ми можемо змінити залежність «dependable» `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** використає, щоб «розв’язати» залежність.
В обох випадках буде:
* Необов’язковий query-параметр `q`, який є `str`.
* Query-параметр `skip`, який є `int`, зі значенням за замовчуванням `0`.
* Query-параметр `limit`, який є `int`, зі значенням за замовчуванням `100`.
В обох випадках дані буде перетворено, валідовано, задокументовано в схемі OpenAPI тощо.
## Використайте це { #use-it }
Тепер ви можете оголосити вашу залежність, використовуючи цей клас.
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
**FastAPI** викликає клас `CommonQueryParams`. Це створює «екземпляр» цього класу, і екземпляр буде передано як параметр `commons` у вашу функцію.
## Анотація типу vs `Depends` { #type-annotation-vs-depends }
Зверніть увагу, що в коді вище ми пишемо `CommonQueryParams` двічі:
//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.9+ без Annotated
/// tip | Порада
За можливості надавайте перевагу варіанту з `Annotated`.
///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
Останній `CommonQueryParams` у:
```Python
... Depends(CommonQueryParams)
```
...це те, що **FastAPI** фактично використає, щоб зрозуміти, що є залежністю.
Саме з нього FastAPI витягне оголошені параметри і саме його FastAPI реально викличе.
---
У цьому випадку перший `CommonQueryParams` у:
//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, ...
```
////
//// tab | Python 3.9+ без Annotated
/// tip | Порада
За можливості надавайте перевагу варіанту з `Annotated`.
///
```Python
commons: CommonQueryParams ...
```
////
...не має жодного спеціального значення для **FastAPI**. FastAPI не використовуватиме його для перетворення даних, валідації тощо (оскільки для цього він використовує `Depends(CommonQueryParams)`).
Ви фактично могли б написати просто:
//// tab | Python 3.9+
```Python
commons: Annotated[Any, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.9+ без 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.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.9+ без Annotated
/// tip | Порада
За можливості надавайте перевагу варіанту з `Annotated`.
///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
**FastAPI** надає скорочення для таких випадків, коли залежність *саме* є класом, який **FastAPI** «викликає», щоб створити екземпляр цього класу.
Для цих конкретних випадків ви можете зробити таке:
Замість того, щоб писати:
//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
```
////
//// tab | Python 3.9+ без Annotated
/// tip | Порада
За можливості надавайте перевагу варіанту з `Annotated`.
///
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
////
...ви пишете:
//// tab | Python 3.9+
```Python
commons: Annotated[CommonQueryParams, Depends()]
```
////
//// tab | Python 3.9+ без Annotated
/// tip | Порада
За можливості надавайте перевагу варіанту з `Annotated`.
///
```Python
commons: CommonQueryParams = Depends()
```
////
Ви оголошуєте залежність як тип параметра і використовуєте `Depends()` без жодного параметра, замість того щоб писати повний клас *ще раз* всередині `Depends(CommonQueryParams)`.
Тоді цей самий приклад виглядатиме так:
{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *}
...і **FastAPI** знатиме, що робити.
/// tip | Порада
Якщо це здається вам більш заплутаним, ніж корисним, просто ігноруйте це — вам це *не* потрібно.
Це лише скорочення. Адже **FastAPI** прагне допомогти вам мінімізувати повторення коду.
///

View File

@@ -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_py39.py hl[19] *}
Ці залежності буде виконано/розв’язано так само, як і звичайні залежності. Але їхнє значення (якщо вони щось повертають) не буде передано у вашу *функцію операції шляху*.
/// tip | Порада
Деякі редактори перевіряють наявність невикористаних параметрів функцій і показують їх як помилки.
Використовуючи ці `dependencies` у *декораторі операції шляху*, ви можете переконатися, що їх буде виконано, уникаючи при цьому помилок редактора/інструментів.
Це також може допомогти уникнути плутанини для нових розробників, які бачать невикористаний параметр у вашому коді й можуть подумати, що він зайвий.
///
/// info | Інформація
У цьому прикладі ми використовуємо вигадані користувацькі заголовки `X-Key` і `X-Token`.
Але в реальних випадках, під час реалізації безпеки, ви отримаєте більше переваг, використовуючи вбудовані [утиліти Security (наступний розділ)](../security/index.md){.internal-link target=_blank}.
///
## Помилки залежностей і значення, що повертаються { #dependencies-errors-and-return-values }
Ви можете використовувати ті самі *функції* залежностей, які зазвичай використовуєте.
### Вимоги залежності { #dependency-requirements }
Вони можуть оголошувати вимоги до запиту (наприклад, заголовки) або інші підзалежності:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
### Підіймання винятків { #raise-exceptions }
Ці залежності можуть `raise` винятки так само, як і звичайні залежності:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
### Значення, що повертаються { #return-values }
І вони можуть повертати значення або ні — ці значення не використовуватимуться.
Тож ви можете повторно використати звичайну залежність (яка повертає значення), яку вже десь застосовуєте, і навіть якщо значення не буде використано, залежність буде виконано:
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
## Залежності для групи *операцій шляху* { #dependencies-for-a-group-of-path-operations }
Пізніше, читаючи про те, як структурувати більші застосунки ([Більші застосунки — кілька файлів](../../tutorial/bigger-applications.md){.internal-link target=_blank}), можливо з кількома файлами, ви дізнаєтеся, як оголосити один параметр `dependencies` для групи *операцій шляху*.
## Глобальні залежності { #global-dependencies }
Далі ми побачимо, як додати залежності до всього застосунку `FastAPI`, щоб вони застосовувалися до кожної *операції шляху*.

View File

@@ -0,0 +1,290 @@
# Залежності з `yield` { #dependencies-with-yield }
FastAPI підтримує залежності, які виконують деякі <abbr title='інколи також називають «exit code», «cleanup code», «teardown code», «closing code», «context manager exit code» тощо'>додаткові кроки після завершення</abbr>.
Щоб зробити це, використовуйте `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_py39.py hl[2:4] *}
Значення, повернуте через `yield`, — це те, що буде інʼєктовано в *операції шляху* та інші залежності:
{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
Код після оператора `yield` виконується після відповіді:
{* ../../docs_src/dependencies/tutorial007_py39.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_py39.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_py39.py hl[6,14,22] *}
І всі вони можуть використовувати `yield`.
У цьому випадку `dependency_c`, щоб виконати свій код виходу, потребує, щоб значення з `dependency_b` (тут назване `dep_b`) усе ще було доступне.
А `dependency_b`, своєю чергою, потребує, щоб значення з `dependency_a` (тут назване `dep_a`) було доступне для її коду виходу.
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
Так само ви можете мати частину залежностей з `yield`, а інші — з `return`, і щоб деякі з них залежали від інших.
Також ви можете мати одну залежність, яка потребує кілька інших залежностей з `yield`, тощо.
Ви можете мати будь-які комбінації залежностей, які вам потрібні.
**FastAPI** забезпечить виконання всього в правильному порядку.
/// note | Технічні деталі
Це працює завдяки <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">Context Managers</a> у Python.
**FastAPI** використовує їх внутрішньо, щоб досягти цього.
///
## Залежності з `yield` і `HTTPException` { #dependencies-with-yield-and-httpexception }
Ви бачили, що можете використовувати залежності з `yield` і мати блоки `try`, які намагаються виконати певний код, а потім запускають код виходу після `finally`.
Ви також можете використовувати `except`, щоб перехопити виняток, який було згенеровано, і зробити з ним щось.
Наприклад, ви можете згенерувати інший виняток, як-от `HTTPException`.
/// tip | Порада
Це дещо просунута техніка, і в більшості випадків вона вам насправді не знадобиться, адже ви можете піднімати винятки (включно з `HTTPException`) з решти коду вашого застосунку, наприклад у *функції операції шляху*.
Але вона доступна, якщо вам це потрібно. 🤓
///
{* ../../docs_src/dependencies/tutorial008b_an_py39.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_py39.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_py39.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_py39.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` і Background Tasks { #dependencies-with-yield-httpexception-except-and-background-tasks }
Залежності з `yield` еволюціонували з часом, щоб покрити різні сценарії використання та виправити деякі проблеми.
Якщо ви хочете побачити, що змінювалося в різних версіях FastAPI, можете прочитати про це більше в розширеному посібнику: [Розширені залежності — залежності з `yield`, `HTTPException`, `except` і Background Tasks](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}.
## Context Managers { #context-managers }
### Що таке «Context Managers» { #what-are-context-managers }
«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")` створює об’єкт, який називають «Context Manager».
Коли блок `with` завершується, він гарантує закриття файлу, навіть якщо виникали винятки.
Коли ви створюєте залежність з `yield`, **FastAPI** внутрішньо створить для неї context manager і поєднає його з деякими іншими пов’язаними інструментами.
### Використання context managers у залежностях з `yield` { #using-context-managers-in-dependencies-with-yield }
/// warning | Попередження
Це, більш-менш, «просунута» ідея.
Якщо ви лише починаєте працювати з **FastAPI**, можливо, вам варто поки що її пропустити.
///
У Python ви можете створювати Context Managers, <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_py39.py hl[1:9,13] *}
/// tip | Порада
Інший спосіб створити context manager — це:
* <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 зробить це за вас внутрішньо.
///

View File

@@ -0,0 +1,16 @@
# Глобальні залежності { #global-dependencies }
Для деяких типів застосунків ви можете захотіти додати залежності для всього застосунку.
Подібно до того, як ви можете [додати `dependencies` до *декораторів операцій шляху*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, ви можете додати їх і до застосунку `FastAPI`.
У такому разі вони будуть застосовані до всіх *операцій шляху* в застосунку:
{* ../../docs_src/dependencies/tutorial012_an_py39.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` для групи *операцій шляху*.

View File

@@ -0,0 +1,250 @@
# Залежності { #dependencies }
**FastAPI** має дуже потужну, але інтуїтивну систему **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>**.
Її спроєктовано так, щоб вона була дуже простою у використанні та щоб будь-якому розробнику було дуже легко інтегрувати інші компоненти з **FastAPI**.
## Що таке «Dependency Injection» { #what-is-dependency-injection }
**«Dependency Injection»** у програмуванні означає, що є спосіб, за допомогою якого ваш код (у цьому випадку ваші *функції операції шляху*) може оголошувати речі, які йому потрібні для роботи та використання: «dependencies».
А потім ця система (у цьому випадку **FastAPI**) подбає про все необхідне, щоб надати вашому коду потрібні залежності (тобто «інʼєктувати» залежності).
Це дуже корисно, коли вам потрібно:
* Мати спільну логіку (одна й та сама логіка коду знову і знову).
* Спільно використовувати підключення до бази даних.
* Забезпечувати безпеку, автентифікацію, вимоги ролей тощо.
* І багато інших речей...
Усе це — мінімізуючи повторення коду.
## Перші кроки { #first-steps }
Розгляньмо дуже простий приклад. Наразі він буде настільки простим, що не надто корисним.
Але так ми зможемо зосередитися на тому, як працює система **Dependency Injection**.
### Створіть залежність, або «dependable» { #create-a-dependency-or-dependable }
Спочатку зосередьмося на залежності.
Це просто функція, яка може приймати всі ті самі параметри, що й *функція операції шляху*:
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
От і все.
**2 рядки**.
І вона має ту саму форму та структуру, що й усі ваші *функції операції шляху*.
Ви можете думати про неї як про *функцію операції шляху* без «декоратора» (без `@app.get("/some-path")`).
І вона може повертати будь-що, що ви захочете.
У цьому випадку ця залежність очікує:
* Необов’язковий query-параметр `q`, який є `str`.
* Необов’язковий query-параметр `skip`, який є `int`, і за замовчуванням дорівнює `0`.
* Необов’язковий query-параметр `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] *}
### Оголосіть залежність у «dependant» { #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** подбає про:
* Виклик вашої функції залежності («dependable») з правильними параметрами.
* Отримання результату з вашої функції.
* Присвоєння цього результату параметру у вашій *функції операції шляху*.
```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()`, вам треба написати весь параметр із type annotation та `Depends()`:
```Python
commons: Annotated[dict, Depends(common_parameters)]
```
Але оскільки ми використовуємо `Annotated`, ми можемо зберегти це значення `Annotated` у змінній і використати його в кількох місцях:
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
/// tip | Порада
Це просто стандартний Python — це називається «type alias» і насправді не є чимось специфічним для **FastAPI**.
Але оскільки **FastAPI** базується на стандартах Python, зокрема на `Annotated`, ви можете використати цей прийом у своєму коді. 😎
///
Залежності й надалі працюватимуть як очікується, а **найкраща частина** в тому, що **інформація про типи буде збережена**, тобто ваш редактор зможе й надалі надавати **автодоповнення**, **inline-помилки** тощо. Те саме стосується й інших інструментів, як-от `mypy`.
Це буде особливо корисно, коли ви використовуєте це у **великій кодовій базі**, де ви використовуєте **ті самі залежності** знову і знову в **багатьох *операціях шляху***.
## `async` чи не `async` { #to-async-or-not-to-async }
Оскільки залежності також будуть викликатися **FastAPI** (так само, як і ваші *функції операції шляху*), під час визначення ваших функцій діють ті самі правила.
Ви можете використовувати `async def` або звичайний `def`.
І ви можете оголошувати залежності з `async def` всередині звичайних `def` *функцій операції шляху*, або залежності з `def` всередині `async def` *функцій операції шляху* тощо.
Це не має значення. **FastAPI** знатиме, що робити.
/// note | Примітка
Якщо ви не знаєте, перегляньте розділ [Async: *«Поспішаєте?»*](../../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**).
Із системою Dependency Injection ви також можете сказати **FastAPI**, що ваша *функція операції шляху* також «залежить» від чогось іншого, що має бути виконано перед вашою *функцією операції шляху*, і **FastAPI** подбає про виконання цього та «інʼєкцію» результатів.
Інші поширені терміни для цієї самої ідеї «dependency injection»:
* resources
* providers
* services
* injectables
* components
## Плагіни **FastAPI** { #fastapi-plug-ins }
Інтеграції та «plug-ins» можна будувати за допомогою системи **Dependency Injection**. Але насправді **немає потреби створювати «plug-ins»**, адже, використовуючи залежності, можна оголосити нескінченну кількість інтеграцій і взаємодій, які стануть доступними вашим *функціям операції шляху*.
А залежності можна створювати дуже просто й інтуїтивно, так що ви можете просто імпортувати потрібні пакети Python і інтегрувати їх з вашими API-функціями буквально в кілька рядків коду.
Приклади цього ви побачите в наступних розділах — про реляційні та NoSQL бази даних, безпеку тощо.
## Сумісність **FastAPI** { #fastapi-compatibility }
Простота системи dependency injection робить **FastAPI** сумісним з:
* усіма реляційними базами даних
* NoSQL базами даних
* зовнішніми пакетами
* зовнішніми API
* системами автентифікації та авторизації
* системами моніторингу використання API
* системами інʼєкції даних відповіді
* тощо.
## Простота та потужність { #simple-and-powerful }
Хоча ієрархічна система dependency injection дуже проста в оголошенні та використанні, вона все одно дуже потужна.
Ви можете визначати залежності, які своєю чергою можуть визначати власні залежності.
Зрештою будується ієрархічне дерево залежностей, і система **Dependency Injection** подбає про розв’язання всіх цих залежностей для вас (і їхніх підзалежностей) та про надання (інʼєкцію) результатів на кожному кроці.
Наприклад, скажімо, у вас є 4 API endpoint-и (*операції шляху*):
* `/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, щоб це відображалося в інтерактивних системах документації.

View File

@@ -0,0 +1,105 @@
# Підзалежності { #sub-dependencies }
Ви можете створювати залежності, які мають **підзалежності**.
Вони можуть бути настільки **глибокими**, наскільки вам потрібно.
**FastAPI** подбає про їх розв’язання.
## Перша залежність «dependable» { #first-dependency-dependable }
Ви можете створити першу залежність («dependable») так:
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *}
Вона оголошує необов’язковий query-параметр `q` як `str`, а потім просто повертає його.
Це доволі просто (не надто корисно), але допоможе нам зосередитися на тому, як працюють підзалежності.
## Друга залежність, «dependable» і «dependant» { #second-dependency-dependable-and-dependant }
Далі ви можете створити іншу функцію залежності (тобто «dependable»), яка водночас оголошує власну залежність (тобто також є «dependant»):
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
Зосередьмося на оголошених параметрах:
* Хоча ця функція сама є залежністю («dependable»), вона також оголошує іншу залежність (вона «depends» від чогось іншого).
* Вона залежить від `query_extractor` і присвоює значення, повернене ним, параметру `q`.
* Вона також оголошує необов’язковий cookie `last_query` як `str`.
* Якщо користувач не надав жодного query `q`, ми використовуємо останній використаний запит, який раніше зберегли в cookie.
## Використання залежності { #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** знатиме, що цю підзалежність потрібно викликати лише один раз на запит.
І він збереже повернене значення в <abbr title="Утиліта/система для зберігання обчислених/згенерованих значень, щоб повторно використовувати їх замість повторного обчислення.">«cache»</abbr> та передасть його всім «dependants», яким воно потрібно в межах цього запиту, замість виклику залежності кілька разів для одного й того самого запиту.
У просунутому сценарії, коли ви знаєте, що залежність має викликатися на кожному кроці (можливо, кілька разів) у межах одного запиту, замість використання «cached» значення, ви можете встановити параметр `use_cache=False` під час використання `Depends`:
//// tab | Python 3.9+
```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.9+ без Annotated
/// tip | Порада
За можливості надавайте перевагу версії з `Annotated`.
///
```Python hl_lines="1"
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
return {"fresh_value": fresh_value}
```
////
## Підсумок { #recap }
Окрім усіх «модних» слів, використаних тут, система **Dependency Injection** доволі проста.
Це просто функції, що виглядають так само, як і *функції операцій шляху*.
Але водночас вона дуже потужна й дозволяє оголошувати довільно глибоко вкладені «графи» залежностей (дерева).
/// tip | Порада
Може здаватися, що з цими простими прикладами все це не надто корисно.
Але ви побачите, наскільки це корисно, у розділах про **security**.
І також побачите, скільки коду це вам зекономить.
///

View 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 `typing.List` (або просто `list` у Python 3.9 і вище):
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
## Відповідь із довільним `dict` { #response-with-arbitrary-dict }
Ви також можете оголосити відповідь, використовуючи звичайний довільний `dict`, вказавши лише тип ключів і значень, без використання Pydantic-моделі.
Це корисно, якщо ви заздалегідь не знаєте коректні назви полів/атрибутів (які були б потрібні для Pydantic-моделі).
У цьому випадку можна використати `typing.Dict` (або просто `dict` у Python 3.9 і вище):
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
## Підсумок { #recap }
Використовуйте кілька Pydantic-моделей і вільно застосовуйте успадкування для кожного випадку.
Вам не потрібно мати одну-єдину модель даних на сутність, якщо ця сутність може мати різні «стани». Як у випадку з «сутністю» користувача зі станом, що включає `password`, `password_hash` і без пароля.

View File

@@ -0,0 +1,107 @@
# Налаштування операції шляху { #path-operation-configuration }
Є кілька параметрів, які ви можете передати вашому *декоратору операції шляху*, щоб налаштувати його.
/// warning | Попередження
Зверніть увагу, що ці параметри передаються безпосередньо *декоратору операції шляху*, а не вашій *функції операції шляху*.
///
## Код статусу відповіді { #response-status-code }
Ви можете визначити (HTTP) `status_code`, який буде використано у відповіді вашої *операції шляху*.
Ви можете передати безпосередньо код `int`, наприклад `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">
### Теги з Enum { #tags-with-enums }
Якщо у вас великий застосунок, у вас може накопичитися **кілька тегів**, і ви захочете переконатися, що завжди використовуєте **той самий тег** для пов’язаних *операцій шляху*.
У таких випадках може мати сенс зберігати теги в `Enum`.
**FastAPI** підтримує це так само, як і зі звичайними рядками:
{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *}
## Підсумок і опис { #summary-and-description }
Ви можете додати `summary` і `description`:
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *}
## Опис із docstring { #description-from-docstring }
Оскільки описи зазвичай довгі та займають кілька рядків, ви можете оголосити опис *операції шляху* у <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation - багаторядковий рядок як перший вираз усередині функції (не присвоєний жодній змінній), який використовується для документації">docstring</abbr> функції, і **FastAPI** прочитає його звідти.
Ви можете писати <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> у docstring — його буде інтерпретовано та правильно показано (з урахуванням відступів docstring).
{* ../../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[19] *}
/// info | Інформація
Зверніть увагу, що `response_description` стосується конкретно відповіді, а `description`*операції шляху* загалом.
///
/// check
OpenAPI визначає, що кожна *операція шляху* потребує опису відповіді.
Тож якщо ви його не надасте, **FastAPI** автоматично згенерує «Successful response».
///
<img src="/img/tutorial/path-operation-configuration/image03.png">
## Позначення *операції шляху* як застарілої { #deprecate-a-path-operation }
Якщо вам потрібно позначити *операцію шляху* як <abbr title="obsolete, recommended not to use it - застаріле, рекомендовано не використовувати">deprecated</abbr>, але без її видалення, передайте параметр `deprecated`:
{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
В інтерактивній документації її буде чітко позначено як застарілу:
<img src="/img/tutorial/path-operation-configuration/image04.png">
Перевірте, як виглядають застарілі й не застарілі *операції шляху*:
<img src="/img/tutorial/path-operation-configuration/image05.png">
## Підсумок { #recap }
Ви можете легко налаштовувати та додавати метадані для ваших *операцій шляху*, передаючи параметри *декораторам операцій шляху*.

View File

@@ -0,0 +1,203 @@
# Безпека — перші кроки { #security-first-steps }
Уявімо, що у вас є **backend** API в якомусь домені.
І у вас є **frontend** в іншому домені або в іншому шляху того самого домену (або в мобільному застосунку).
І ви хочете мати спосіб, щоб frontend міг автентифікуватися з backend, використовуючи **username** і **password**.
Ми можемо використати **OAuth2**, щоб реалізувати це з **FastAPI**.
Але давайте заощадимо вам час на читання повної довгої специфікації лише для того, щоб знайти ті невеликі фрагменти інформації, які вам потрібні.
Скористаймося інструментами, які надає **FastAPI**, щоб обробляти безпеку.
## Як це виглядає { #how-it-looks }
Спочатку просто використаймо код і подивімося, як він працює, а потім повернемося, щоб зрозуміти, що відбувається.
## Створіть `main.py` { #create-main-py }
Скопіюйте приклад у файл `main.py`:
{* ../../docs_src/security/tutorial001_an_py39.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 (це також можете бути ви самі).
Ним можуть користуватися сторонні застосунки та системи.
І ним також можете користуватися ви самі, щоб налагоджувати, перевіряти й тестувати той самий застосунок.
## Flow `password` { #the-password-flow }
Тепер повернімося трохи назад і розберімося, що це все таке.
«Flow» `password` — це один зі способів («flows»), визначених в OAuth2, для обробки безпеки та автентифікації.
OAuth2 спроєктовано так, щоб backend або API могли бути незалежними від сервера, який автентифікує користувача.
Але в цьому випадку той самий застосунок **FastAPI** оброблятиме і API, і автентифікацію.
Тож розгляньмо це з такого спрощеного погляду:
* Користувач вводить `username` і `password` у frontend та натискає `Enter`.
* Frontend (що працює в браузері користувача) надсилає ці `username` і `password` на конкретний URL у нашому API (оголошений як `tokenUrl="token"`).
* API перевіряє `username` і `password` та відповідає «token» (ми ще нічого з цього не реалізували).
* «Token» — це просто рядок з певним вмістом, який ми зможемо використати пізніше, щоб перевірити цього користувача.
* Зазвичай token налаштовано так, щоб він ставав недійсним після певного часу.
* Тож користувачу доведеться знову увійти через деякий час.
* І якщо token буде вкрадено, ризик менший. Це не як постійний ключ, який працюватиме вічно (у більшості випадків).
* Frontend тимчасово десь зберігає token.
* Користувач у frontend клікає, щоб перейти до іншого розділу вебзастосунку.
* Frontend потрібно отримати ще дані з API.
* Але для цього конкретного endpoint потрібна автентифікація.
* Тож, щоб автентифікуватися з нашим API, він надсилає заголовок `Authorization` зі значенням `Bearer ` плюс token.
* Якщо token містить `foobar`, то вміст заголовка `Authorization` буде: `Bearer foobar`.
## `OAuth2PasswordBearer` у **FastAPI** { #fastapis-oauth2passwordbearer }
**FastAPI** надає кілька інструментів на різних рівнях абстракції для реалізації цих можливостей безпеки.
У цьому прикладі ми використаємо **OAuth2** з flow **Password**, застосовуючи **Bearer** token. Для цього використаємо клас `OAuth2PasswordBearer`.
/// info | Інформація
Bearer token — не єдиний варіант.
Але для нашого випадку це найкращий варіант.
І він може бути найкращим для більшості випадків, якщо тільки ви не експерт з OAuth2 і точно не знаєте, чому існує інший варіант, який краще підходить вашим потребам.
У такому разі **FastAPI** також надає вам інструменти, щоб це реалізувати.
///
Коли ми створюємо екземпляр класу `OAuth2PasswordBearer`, ми передаємо параметр `tokenUrl`. Цей параметр містить URL, який клієнт (frontend, що працює в браузері користувача) використовуватиме, щоб надіслати `username` і `password` та отримати token.
{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
/// tip | Порада
Тут `tokenUrl="token"` посилається на відносний URL `token`, який ми ще не створили. Оскільки це відносний URL, він еквівалентний `./token`.
Тому що ми використовуємо відносний URL, якщо ваш API розміщено за адресою `https://example.com/`, тоді це посилатиметься на `https://example.com/token`. Але якщо ваш API розміщено за адресою `https://example.com/api/v1/`, тоді це посилатиметься на `https://example.com/api/v1/token`.
Використання відносного URL важливе, щоб ваш застосунок продовжував працювати навіть у складніших сценаріях, як-от [Behind a Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}.
///
Цей параметр не створює той endpoint / *операцію шляху*, але оголошує, що URL `/token` буде тим, який клієнт має використовувати для отримання token. Ця інформація використовується в OpenAPI, а потім — в інтерактивних системах документації API.
Незабаром ми також створимо реальну операцію шляху.
/// info | Інформація
Якщо ви дуже суворий «Pythonista», вам може не подобатися стиль імені параметра `tokenUrl` замість `token_url`.
Це тому, що тут використано те саме ім’я, що й у специфікації OpenAPI. Тож якщо вам потрібно буде дослідити більше про будь-яку з цих security scheme, ви зможете просто скопіювати й вставити це, щоб знайти більше інформації.
///
Змінна `oauth2_scheme` — це екземпляр `OAuth2PasswordBearer`, але це також «callable».
Її можна викликати так:
```Python
oauth2_scheme(some, parameters)
```
Отже, її можна використовувати з `Depends`.
### Використайте це { #use-it }
Тепер ви можете передати `oauth2_scheme` у залежність через `Depends`.
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
Ця залежність надасть `str`, який буде призначено параметру `token` *функції операції шляху*.
**FastAPI** знатиме, що цю залежність можна використати для визначення «security scheme» в схемі OpenAPI (і в автоматичній документації API).
/// info | Технічні деталі
**FastAPI** знатиме, що може використати клас `OAuth2PasswordBearer` (оголошений у залежності) для визначення security scheme в OpenAPI, тому що він успадковується від `fastapi.security.oauth2.OAuth2`, який, своєю чергою, успадковується від `fastapi.security.base.SecurityBase`.
Усі утиліти безпеки, які інтегруються з OpenAPI (і з автоматичною документацією API), успадковуються від `SecurityBase` — саме так **FastAPI** знає, як інтегрувати їх в OpenAPI.
///
## Що це робить { #what-it-does }
Воно знайде в запиті заголовок `Authorization`, перевірить, чи має значення вигляд `Bearer ` плюс якийсь token, і поверне token як `str`.
Якщо заголовка `Authorization` немає, або значення не містить token з `Bearer `, воно одразу відповість помилкою зі статус-кодом 401 (`UNAUTHORIZED`).
Вам навіть не потрібно перевіряти, чи існує token, щоб повернути помилку. Ви можете бути впевнені, що якщо вашу функцію виконано, у цьому token буде `str`.
Ви вже можете спробувати це в інтерактивній документації:
<img src="/img/tutorial/security/image03.png">
Ми ще не перевіряємо валідність token, але це вже початок.
## Підсумок { #recap }
Отже, лише за 3 або 4 додаткові рядки ви вже маєте примітивну форму безпеки.

View File

@@ -0,0 +1,105 @@
# Отримання поточного користувача { #get-current-user }
У попередньому розділі система безпеки (яка базується на системі ін’єкції залежностей) передавала *функції операції шляху* `token` як `str`:
{* ../../docs_src/security/tutorial001_an_py39.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` використає (фейкову) допоміжну функцію, яку ми створили; вона приймає токен як `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
Те, як спроєктовано цю систему залежностей, дозволяє мати різні залежності (різні «dependables»), які всі повертають модель `User`.
Ми не обмежені лише однією залежністю, яка може повертати цей тип даних.
///
## Інші моделі { #other-models }
Тепер ви можете отримувати поточного користувача напряму в *функціях операцій шляху* і працювати з механізмами безпеки на рівні **Dependency Injection**, використовуючи `Depends`.
І ви можете використовувати будь-яку модель або дані для вимог безпеки (у цьому випадку — модель Pydantic `User`).
Але ви не обмежені використанням якоїсь конкретної моделі даних, класу чи типу.
Хочете мати `id` та `email` і не мати жодного `username` у вашій моделі? Звісно. Ви можете використати ці самі інструменти.
Хочете мати лише `str`? Або лише `dict`? Або напряму інстанс моделі класу бази даних? Усе працює так само.
Насправді у вас немає користувачів, які входять у ваш застосунок, а є роботи, боти чи інші системи, що мають лише токен доступу? Знову ж, усе працює так само.
Просто використовуйте будь-який тип моделі, будь-який клас, будь-яку базу даних, які потрібні для вашого застосунку. **FastAPI** покриває це системою ін’єкції залежностей.
## Розмір коду { #code-size }
Цей приклад може здатися багатослівним. Майте на увазі, що ми змішуємо безпеку, моделі даних, допоміжні функції та *операції шляху* в одному файлі.
Але ось ключовий момент.
Уся логіка безпеки та ін’єкції залежностей пишеться один раз.
І ви можете зробити її настільки складною, наскільки захочете. І при цьому вона все одно буде написана лише один раз, в одному місці. З усією гнучкістю.
Але ви можете мати тисячі endpointів (*операцій шляху*), що використовують ту саму систему безпеки.
І всі вони (або будь-яка їх частина, яку ви захочете) можуть скористатися повторним використанням цих залежностей або будь-яких інших залежностей, які ви створите.
І всі ці тисячі *операцій шляху* можуть бути такими короткими, як 3 рядки:
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
## Підсумок { #recap }
Тепер ви можете отримувати поточного користувача безпосередньо у вашій *функції операції шляху*.
Ми вже на пів шляху.
Нам лише потрібно додати *операцію шляху*, щоб користувач/клієнт міг насправді надіслати `username` і `password`.
Це буде далі.

View File

@@ -0,0 +1,273 @@
# OAuth2 із паролем (і хешуванням), Bearer з JWT-токенами { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
Тепер, коли в нас є весь security flow, зробімо застосунок справді безпечним, використовуючи токени <abbr title="JSON Web Tokens">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, вам слід встановити залежність бібліотеки cryptography: `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 }
Якщо вашу базу даних украдуть, зловмисник не отримає відкриті паролі користувачів (plaintext), лише хеші.
Тож він не зможе спробувати використати цей пароль в іншій системі (а багато користувачів використовують один і той самий пароль всюди, і це було б небезпечно).
## Встановлення `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**, security-плагіном для **Flask** чи багатьма іншими.
Тож ви зможете, наприклад, спільно використовувати одні й ті самі дані з Django-застосунку в базі даних із FastAPI-застосунком. Або поступово мігрувати Django-застосунок, використовуючи ту саму базу даних.
І ваші користувачі зможуть входити як із вашого Django-застосунку, так і з вашого застосунку **FastAPI** — одночасно.
///
## Хешування та перевірка паролів { #hash-and-verify-the-passwords }
Імпортуйте інструменти, які нам потрібні, з `pwdlib`.
Створіть екземпляр PasswordHash із рекомендованими налаштуваннями — він використовуватиметься для хешування та перевірки паролів.
/// tip | Порада
pwdlib також підтримує алгоритм хешування bcrypt, але не включає legacy-алгоритми — для роботи із застарілими хешами рекомендується використовувати бібліотеку passlib.
Наприклад, ви можете використати її, щоб читати й перевіряти паролі, згенеровані іншою системою (як-от Django), але хешувати всі нові паролі іншим алгоритмом, як-от Argon2 або Bcrypt.
І бути сумісними з усіма ними одночасно.
///
Створіть утилітну функцію для хешування пароля, що надходить від користувача.
І ще одну утиліту, щоб перевіряти, чи збігається отриманий пароль із збереженим хешем.
І ще одну — щоб автентифікувати й повертати користувача.
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
/// 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 Model, який буде використано в endpoint токена для відповіді.
Створіть утилітну функцію для генерації нового access token.
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
## Оновлення залежностей { #update-the-dependencies }
Оновіть `get_current_user`, щоб отримувати той самий токен, що й раніше, але цього разу — використовуючи JWT-токени.
Розкодуйте отриманий токен, перевірте його та поверніть поточного користувача.
Якщо токен недійсний — одразу поверніть HTTP-помилку.
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
## Оновлення *операції шляху* `/token` { #update-the-token-path-operation }
Створіть `timedelta` із часом дії токена.
Створіть реальний JWT access token і поверніть його.
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
### Технічні деталі про «subject» JWT `sub` { #technical-details-about-the-jwt-subject-sub }
Специфікація JWT каже, що існує ключ `sub`, із темою (subject) токена.
Використовувати його необов’язково, але саме туди ви зазвичай поміщаєте ідентифікацію користувача, тож ми використовуємо його тут.
JWT можна використовувати й для інших речей, окрім ідентифікації користувача та надання йому можливості виконувати операції напряму на вашому API.
Наприклад, ви можете ідентифікувати «автомобіль» або «допис у блозі».
Тоді ви можете додати дозволи щодо цієї сутності, наприклад «drive» (для автомобіля) або «edit» (для блогу).
А потім ви можете видати цей JWT-токен користувачу (або боту), і він зможе використовувати його, щоб виконувати ці дії (керувати автомобілем або редагувати допис) навіть без акаунта — лише з JWT-токеном, який ваше API згенерувало для цього.
Використовуючи ці ідеї, JWT можна застосовувати в набагато більш складних сценаріях.
У таких випадках кілька з цих сутностей можуть мати однаковий ID, скажімо `foo` (користувач `foo`, автомобіль `foo` і допис `foo`).
Тож, щоб уникнути колізій ID, під час створення 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">
Авторизуйте застосунок так само, як і раніше.
Використовуючи облікові дані:
Ім’я користувача: `johndoe`
Пароль: `secret`
/// check | Перевірка
Зверніть увагу, що в коді ніде немає відкритого пароля "`secret`" — у нас є лише його хешована версія.
///
<img src="/img/tutorial/security/image08.png">
Викличте endpoint `/users/me/`, ви отримаєте відповідь:
```JSON
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false
}
```
<img src="/img/tutorial/security/image09.png">
Якщо ви відкриєте інструменти розробника, ви зможете побачити, що надіслані дані містять лише токен; пароль надсилається лише в першому запиті, щоб автентифікувати користувача й отримати access token, але не надсилається після цього:
<img src="/img/tutorial/security/image10.png">
/// note | Примітка
Зверніть увагу на заголовок `Authorization`, зі значенням, яке починається з `Bearer `.
///
## Розширене використання зі `scopes` { #advanced-usage-with-scopes }
OAuth2 має поняття «scopes».
Ви можете використати їх, щоб додати до JWT-токена конкретний набір дозволів.
Потім ви можете надати цей токен користувачу напряму або третій стороні, щоб взаємодіяти з вашим API з певним набором обмежень.
Пізніше ви дізнаєтеся, як їх використовувати та як вони інтегруються у **FastAPI**, у **Розширеному посібнику користувача**.
## Підсумок { #recap }
На основі того, що ви побачили до цього моменту, ви можете налаштувати безпечний застосунок **FastAPI**, використовуючи стандарти на кшталт OAuth2 і JWT.
Майже в будь-якому framework робота з безпекою досить швидко стає складною темою.
Багато пакетів, що значно її спрощують, змушені робити багато компромісів щодо моделі даних, бази даних і доступних можливостей. А деякі пакети, що надмірно спрощують речі, насправді мають приховані вразливості.
---
**FastAPI** не йде на компроміси з жодною базою даних, моделлю даних чи інструментом.
Він дає вам повну гнучкість, щоб обрати те, що найкраще підходить вашому проєкту.
І ви можете напряму використовувати багато добре підтримуваних і широко застосовуваних пакетів, як-от `pwdlib` і `PyJWT`, бо **FastAPI** не потребує жодних складних механізмів для інтеграції зовнішніх пакетів.
Водночас він надає вам інструменти, щоб максимально спростити процес без втрати гнучкості, надійності чи безпеки.
І ви можете використовувати та реалізовувати безпечні стандартні протоколи, як-от OAuth2, відносно простим способом.
У **Розширеному посібнику користувача** ви можете дізнатися більше про те, як використовувати OAuth2 «scopes» для більш тонко налаштованої системи дозволів, дотримуючись цих самих стандартів. OAuth2 зі scopes — це механізм, який використовують багато великих провайдерів автентифікації, як-от Facebook, Google, GitHub, Microsoft, X (Twitter) тощо, щоб авторизувати сторонні застосунки для взаємодії з їхніми API від імені користувачів.

View File

@@ -0,0 +1,289 @@
# Простий OAuth2 з Password і Bearer { #simple-oauth2-with-password-and-bearer }
Тепер продовжимо з попереднього розділу й додамо відсутні частини, щоб отримати повний потік безпеки.
## Отримайте `username` і `password` { #get-the-username-and-password }
Ми використаємо утиліти безпеки **FastAPI**, щоб отримати `username` і `password`.
OAuth2 визначає, що під час використання «password flow» (який ми використовуємо) клієнт/користувач має надіслати поля `username` і `password` як form data.
І специфікація каже, що поля мають називатися саме так. Тож `user-name` або `email` не спрацюють.
Але не хвилюйтеся — у frontend ви можете показувати це кінцевим користувачам як завгодно.
І ваші моделі бази даних можуть використовувати будь-які інші назви, які вам потрібні.
Але для *операції шляху* входу (login) нам потрібно використовувати ці назви, щоб бути сумісними зі специфікацією (і мати змогу, наприклад, використовувати інтегровану систему документації API).
Специфікація також вказує, що `username` і `password` мають надсилатися як form data (тобто без 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` — це клас-залежність, який оголошує form body з:
* `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 }
«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 }
Відповідь endpoint `token` має бути JSON-об’єктом.
Вона має містити `token_type`. У нашому випадку, оскільки ми використовуємо токени «Bearer», тип токена має бути `bearer`.
Також вона має містити `access_token` — рядок, що містить наш токен доступу.
Для цього простого прикладу ми просто зробимо все повністю небезпечно і повернемо той самий `username` як токен.
/// tip | Порада
У наступному розділі ви побачите справді безпечну реалізацію з хешуванням паролів і токенами <abbr title="JSON Web Tokens">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-помилку, якщо користувача не існує або якщо він неактивний.
Тож у нашому endpoint ми отримаємо користувача, лише якщо користувач існує, був коректно автентифікований і є активним:
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
/// info | Інформація
Додатковий заголовок `WWW-Authenticate` зі значенням `Bearer`, який ми тут повертаємо, також є частиною специфікації.
Будь-який HTTP-код (помилки) 401 «UNAUTHORIZED» також має повертати заголовок `WWW-Authenticate`.
У випадку bearer-токенів (наш випадок) значення цього заголовка має бути `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">
Якщо ви натиснете на іконку замка й вийдете (logout), а потім спробуєте ту саму операцію знову, ви отримаєте 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">JWT</abbr>.

View 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**, який включає frontend і більше інструментів: <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 базі даних (детальніше про первинні ключі можна дізнатися в документації 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`, залежно від бази даних).
### Створення Engine { #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 *session* на запит** — саме цього й намагається досягти `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] *}
### Створення залежності Session { #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>.
///
### Створення Hero { #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`, а потім повернути його.
### Читання Heroes { #read-heroes }
Ми можемо **читати** `Hero` з бази даних, використовуючи `select()`. Можна додати `limit` і `offset`, щоб робити пагінацію результатів.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
### Читання одного Hero { #read-one-hero }
Ми можемо **прочитати** одного `Hero`.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
### Видалення Hero { #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>
Потім перейдіть до UI `/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` — *модель даних* для створення hero { #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` — *модель даних* для оновлення hero { #heroupdate-the-data-model-to-update-a-hero }
У попередній версії застосунку у нас не було способу **оновити 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`, ваш редактор і linter скаржилися б (і цілком справедливо), що ви повертаєте `Hero` замість `HeroPublic`.
Оголошуючи це в `response_model`, ми кажемо **FastAPI** робити свою роботу, не втручаючись в анотації типів і не заважаючи підказкам від редактора та інших інструментів.
///
### Читання Heroes з `HeroPublic` { #read-heroes-with-heropublic }
Ми можемо зробити так само, як і раніше, щоб **читати** `Hero`, але знову ж таки використаємо `response_model=list[HeroPublic]`, щоб гарантувати коректну валідацію та серіалізацію даних.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
### Читання одного Hero з `HeroPublic` { #read-one-hero-with-heropublic }
Ми можемо **прочитати** одного героя:
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
### Оновлення Hero з `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] *}
### Повторне видалення Hero { #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 API `/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>. 🚀

View 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
// Go to the home directory
$ cd
// Create a directory for all your code projects
$ mkdir code
// Enter into that code directory
$ cd code
// Create a directory for this project
$ mkdir awesome-project
// Enter into that project directory
$ cd awesome-project
```
</div>
## Створіть віртуальне середовище { #create-a-virtual-environment }
Коли ви починаєте працювати над Python-проєктом **уперше**, створіть віртуальне середовище **<abbr title="there are other options, this is a simple guideline - є й інші варіанти, це проста рекомендація">всередині вашого проєкту</abbr>**.
/// 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 | Порада
Іноді під час спроби оновити pip ви можете отримати помилку **`No module named 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 означає «усе». Отже, 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`, але йому потрібна **версія `3`** пакунка **`harry`**.
```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>
І тоді у вашому глобальному середовищі Python буде встановлено `harry` версії `1`.
```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>
І тоді у вашому глобальному середовищі Python буде встановлено `harry` версії `3`.
І якщо ви спробуєте знову запустити `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
// Don't run this now, it's just an example 🤓
$ 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` (наприклад, `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`, яку буде використано, знаходиться **у віртуальному середовищі**.
Ви використовуєте `which` у Linux і macOS та `Get-Command` у Windows PowerShell.
Принцип роботи цієї команди: вона перевіряє змінну середовища `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
// Error importing sirius, it's not installed 😱
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
// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎
$ deactivate
// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀
$ source .venv/bin/activate
// Now when you run python, it will find the package sirius installed in this virtual environment ✨
$ python main.py
I solemnly swear 🐺
```
</div>
## Альтернативи { #alternatives }
Це проста інструкція, щоб допомогти вам почати й пояснити, як усе працює «під капотом».
Існує багато **альтернатив** для керування віртуальними середовищами, залежностями пакунків (requirements), проєктами.
Коли ви будете готові й захочете використовувати інструмент для **керування всім проєктом** — залежностями пакунків, віртуальними середовищами тощо — я б порадив(ла) спробувати <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
`uv` може робити багато речей, зокрема:
* **Встановлювати Python** для вас, включно з різними версіями
* Керувати **віртуальним середовищем** для ваших проєктів
* Встановлювати **пакунки**
* Керувати **залежностями та версіями** пакунків для вашого проєкту
* Гарантувати, що у вас є **точний** набір пакунків і версій для встановлення, включно з їхніми залежностями, щоб ви могли бути впевнені, що запускаєте проєкт у продакшені так само, як і на своєму комп’ютері під час розробки — це називається **locking**
* Та багато іншого
## Висновок { #conclusion }
Якщо ви прочитали та зрозуміли все це, тепер **ви знаєте набагато більше** про віртуальні середовища, ніж багато розробників. 🤓
Ці деталі, найімовірніше, стануть у пригоді в майбутньому, коли ви відлагоджуватимете щось, що здається складним, але ви знатимете, **як це все працює всередині**. 😎